freeleaps-service-hub/apps/notification/backend/services/template_message_service.py
YuehuCao 2213fa59b5 feat(templates): enforce uniqueness and improve safety
- Raise TemplateExistsError when duplicate detected
- Migrate placeholder syntax from {} to {{}}
- Add validation for:
  * Reserved keyword collisions
  * Injection attempt patterns
- Update all test cases
2025-08-18 22:15:43 +08:00

374 lines
14 KiB
Python

from typing import List, Optional
from backend.infra.template_message_handler import TemplateMessageHandler
from backend.models.models import MessageTemplateDoc
from common.log.module_logger import ModuleLogger
from datetime import datetime
from common.constants.region import UserRegion
class TemplateMessageService:
def __init__(self):
self.template_message_handler = TemplateMessageHandler()
self.module_logger = ModuleLogger(sender_id="TemplateMessageService")
async def verify_tenant_access(self, template_id: str, tenant_id: str, region: int):
"""verify tenant access"""
try:
result = await self.template_message_handler.verify_tenant_access(template_id, tenant_id, region)
await self.module_logger.log_info(
info="Tenant access verified",
properties={
"template_id": template_id,
"tenant_id": tenant_id,
"region": region
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to verify tenant access",
properties={
"template_id": template_id,
"tenant_id": tenant_id,
"region": region,
"error": str(e)
}
)
raise
# ==================== global templates ====================
async def create_global_template(self, template: MessageTemplateDoc):
"""create global template"""
try:
result = await self.template_message_handler.create_global_template(template)
await self.module_logger.log_info(
info="Global template created",
properties={
"template_id": template.template_id,
"region": template.region
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to create global template",
properties={
"template_id": template.template_id,
"error": str(e)
}
)
raise
async def update_global_template(self, template_id: str, data: dict, region: int):
"""update global template"""
try:
# check if template exists
template = await self.template_message_handler.find_global_template(template_id, region)
if not template:
raise ValueError(f"Global template not found for template_id: {template_id}, region: {region}")
result = await self.template_message_handler.update_global_template(template_id, data, region)
await self.module_logger.log_info(
info="Global template updated",
properties={
"template_id": template_id,
"region": region,
"updated_fields": list(data.keys())
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to update global template",
properties={
"template_id": template_id,
"region": region,
"error": str(e)
}
)
raise
async def delete_global_template(self, template_id: str):
"""delete global template"""
try:
result = await self.template_message_handler.delete_global_template(template_id)
await self.module_logger.log_info(
info="Global template deleted",
properties={
"template_id": template_id
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to delete global template",
properties={
"template_id": template_id,
"error": str(e)
}
)
raise
async def list_global_templates(self, region: int):
"""list global templates"""
try:
result = await self.template_message_handler.list_global_templates(region)
await self.module_logger.log_info(
info="Global templates listed",
properties={
"region": region,
"count": len(result) if result else 0
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to list global templates",
properties={
"region": region,
"error": str(e)
}
)
raise
# ==================== TENANT templates ====================
async def list_tenant_templates(self, tenant_id: str, region: int):
"""list tenant templates"""
try:
result = await self.template_message_handler.list_tenant_templates(tenant_id, region)
await self.module_logger.log_info(
info="Tenant templates listed",
properties={
"tenant_id": tenant_id,
"region": region,
"count": len(result) if result else 0
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to list tenant templates",
properties={
"tenant_id": tenant_id,
"region": region,
"error": str(e)
}
)
raise
async def assign_template_to_tenant(self, tenant_id: str, template_ids: List[str], region: int):
"""assign templates to tenant"""
try:
results = []
for template_id in template_ids:
try:
global_template = await self.template_message_handler.find_global_template(template_id, region)
if not global_template:
results.append({
"template_id": template_id,
"success": False,
"msg": "Global template not found"
})
continue
# check if tenant already has this template
existing = await self.template_message_handler.find_tenant_template(tenant_id, template_id, region)
if existing:
results.append({
"template_id": template_id,
"success": False,
"msg": "Template already assigned to tenant"
})
continue
# copy template to tenant, use unique template_id
tenant_template_id = f"{tenant_id}_{global_template.template_id}"
new_template = MessageTemplateDoc(
template_id=tenant_template_id,
tenant_id=tenant_id,
region=global_template.region,
subject=global_template.subject,
body=global_template.body,
is_active=global_template.is_active,
created_at=datetime.utcnow()
)
await self.template_message_handler.create_tenant_template(tenant_id, new_template)
results.append({
"template_id": template_id,
"success": True,
"template_db_id": str(new_template.id)
})
except Exception as e:
results.append({
"template_id": template_id,
"success": False,
"msg": f"Error: {str(e)}"
})
success_count = sum(1 for r in results if r.get("success"))
await self.module_logger.log_info(
info="Templates assigned to tenant",
properties={
"tenant_id": tenant_id,
"region": region,
"total_requested": len(template_ids),
"success_count": success_count
}
)
return results
except Exception as e:
await self.module_logger.log_error(
error="Failed to assign templates to tenant",
properties={
"tenant_id": tenant_id,
"region": region,
"error": str(e)
}
)
raise
async def create_tenant_template(self, tenant_id: str, template: MessageTemplateDoc):
"""create tenant template"""
try:
template.tenant_id = tenant_id
result = await self.template_message_handler.create_tenant_template(tenant_id, template)
await self.module_logger.log_info(
info="Tenant template created",
properties={
"tenant_id": tenant_id,
"template_id": template.template_id,
"region": template.region
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to create tenant template",
properties={
"tenant_id": tenant_id,
"template_id": template.template_id,
"error": str(e)
}
)
raise
async def update_tenant_template(self, tenant_id: str, template_id: str, data: dict, region: int):
"""update tenant template"""
try:
template = await self.template_message_handler.find_tenant_template(tenant_id, template_id, region)
if not template:
raise ValueError("Template not found")
result = await self.template_message_handler.update_tenant_template(tenant_id, template_id, data, region)
await self.module_logger.log_info(
info="Tenant template updated",
properties={
"tenant_id": tenant_id,
"template_id": template_id,
"region": region,
"updated_fields": list(data.keys())
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to update tenant template",
properties={
"tenant_id": tenant_id,
"template_id": template_id,
"region": region,
"error": str(e)
}
)
raise
async def delete_tenant_template(self, tenant_id: str, template_id: str, region: int):
"""delete tenant template"""
try:
template = await self.template_message_handler.find_tenant_template(tenant_id, template_id, region)
if not template:
raise ValueError("Template not found")
result = await self.template_message_handler.delete_tenant_template(tenant_id, template_id, region)
await self.module_logger.log_info(
info="Tenant template deleted",
properties={
"tenant_id": tenant_id,
"template_id": template_id
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to delete tenant template",
properties={
"tenant_id": tenant_id,
"template_id": template_id,
"error": str(e)
}
)
raise
async def render_template(self, tenant_id: str, template_id: str, properties: dict, region: int):
"""render template"""
try:
template = await self.template_message_handler.find_tenant_template(tenant_id, template_id, region)
if not template:
raise ValueError("Template not found")
result = await self.template_message_handler.render_template(template, properties)
await self.module_logger.log_info(
info="Template rendered successfully",
properties={
"tenant_id": tenant_id,
"template_id": template_id,
"region": region,
"properties_count": len(properties)
}
)
return result
except Exception as e:
await self.module_logger.log_error(
error="Failed to render template",
properties={
"tenant_id": tenant_id,
"template_id": template_id,
"region": region,
"error": str(e)
}
)
raise
async def get_template(self, tenant_id: str, template_id: str, region: int):
"""get template"""
try:
template = await self.template_message_handler.find_tenant_template(tenant_id, template_id, region)
if template:
await self.module_logger.log_info(
"Tenant template found",
properties={
"tenant_id": tenant_id,
"template_id": template_id,
"region": region
}
)
return template
raise ValueError(f"Template not found")
except Exception as e:
await self.module_logger.log_error(
error="Failed to get tenant template",
properties={
"tenant_id": tenant_id,
"template_id": template_id,
"region": region,
"error": str(e)
}
)
raise