fix(notification): some small changes

This commit is contained in:
YuehuCao 2025-08-09 11:56:00 +08:00
parent 7cb5ba4a78
commit b6f8b70f69
4 changed files with 113 additions and 31 deletions

View File

@ -1,3 +1,4 @@
import re
from backend.models.models import MessageTemplateDoc
from common.log.module_logger import ModuleLogger
from datetime import datetime
@ -355,9 +356,60 @@ class TemplateMessageHandler:
)
raise
async def validate_template_parameters(self, template: MessageTemplateDoc, properties: dict) -> list:
"""validate template parameters"""
try:
subject_placeholders = re.findall(r'\{(\w+)\}', template.subject)
body_placeholders = re.findall(r'\{(\w+)\}', template.body)
all_placeholders = list(set(subject_placeholders + body_placeholders))
missing_params = set(all_placeholders) - set(properties.keys())
if missing_params:
raise ValueError(f"Missing required parameters: {missing_params}. "
f"Template requires: {all_placeholders}")
extra_params = set(properties.keys()) - set(all_placeholders)
if extra_params:
await self.module_logger.log_warning(
f"Extra parameters provided: {extra_params}",
properties={
"template_id": template.template_id,
"tenant_id": template.tenant_id,
"extra_params": list(extra_params)
}
)
for param, value in properties.items():
if param in all_placeholders:
if value is None:
raise ValueError(f"Parameter '{param}' cannot be None")
if isinstance(value, str) and not value.strip():
await self.module_logger.log_warning(
f"Parameter '{param}' is empty string",
properties={
"template_id": template.template_id,
"parameter": param
}
)
return all_placeholders
except Exception as e:
await self.module_logger.log_error(
error="Template parameter validation failed",
properties={
"template_id": template.template_id,
"tenant_id": template.tenant_id,
"error": str(e)
}
)
raise
async def render_template(self, template: MessageTemplateDoc, properties: dict) -> dict:
"""render template"""
try:
required_params = await self.validate_template_parameters(template, properties)
subject = template.subject.format(**properties)
body = template.body.format(**properties)
@ -367,11 +419,30 @@ class TemplateMessageHandler:
"template_id": template.template_id,
"tenant_id": template.tenant_id,
"region": template.region,
"properties_count": len(properties)
"required_params": required_params,
"provided_params": list(properties.keys())
}
)
return {"subject": subject, "body": body}
return {
"subject": subject,
"body": body,
"required_params": required_params,
"template_id": template.template_id,
"region": template.region
}
except ValueError as e:
await self.module_logger.log_error(
error="Template validation failed",
properties={
"template_id": template.template_id,
"tenant_id": template.tenant_id,
"region": template.region,
"error": str(e)
}
)
raise
except KeyError as e:
await self.module_logger.log_error(
error="Missing template parameter",
@ -382,7 +453,7 @@ class TemplateMessageHandler:
"missing_parameter": str(e)
}
)
raise ValueError(f"Missing template parameter: {e}")
raise ValueError(f"Missing template parameter: {str(e)}")
except Exception as e:
await self.module_logger.log_error(
error="Template rendering error",

View File

@ -3,7 +3,7 @@ from datetime import datetime
from typing import Optional, List
from common.constants.region import UserRegion
from common.constants.email import EmailSenderStatus, BounceType
from common.constants.email import EmailSendStatus, BounceType
class MessageTemplateDoc(Document):
template_id: str
@ -32,7 +32,7 @@ class EmailSenderDoc(Document):
name = "email_sender_doc"
indexes = ["tenant_id"]
class EmailSenderStatusDoc(Document):
class EmailSendStatusDoc(Document):
email_id: str
tenant_id: str
email_senders: List[str]
@ -40,7 +40,7 @@ class EmailSenderStatusDoc(Document):
template_id: Optional[str] = None
subject: str
body: str
status: EmailSenderStatus = EmailSenderStatus.PENDING
status: EmailSendStatus = EmailSendStatus.PENDING
sent_at: Optional[datetime] = None
failed_at: Optional[datetime] = None
error_message: Optional[str] = None
@ -88,6 +88,7 @@ class EmailTrackingDoc(Document):
class EmailBounceDoc(Document):
email: str
tenant_id: str
email_id: Optional[str] = None
template_id: Optional[str] = None
bounce_type: BounceType
reason: str

View File

@ -40,11 +40,6 @@ class TemplateMessageService:
async def create_global_template(self, template: MessageTemplateDoc):
"""create global template"""
try:
# Check if template already exists with same template_id and region
existing_template = await self.template_message_handler.find_global_template(template.template_id, template.region)
if existing_template:
raise ValueError(f"Global template with template_id '{template.template_id}' and region '{template.region}' already exists")
result = await self.template_message_handler.create_global_template(template)
await self.module_logger.log_info(
info="Global template created",
@ -54,9 +49,6 @@ class TemplateMessageService:
}
)
return result
except ValueError as e:
# Re-raise ValueError for duplicate template
raise
except Exception as e:
await self.module_logger.log_error(
error="Failed to create global template",
@ -104,8 +96,7 @@ class TemplateMessageService:
await self.module_logger.log_info(
info="Global template deleted",
properties={
"template_id": template_id,
"deleted_count": result.get("deleted_count", 0) if result else 0
"template_id": template_id
}
)
return result
@ -243,12 +234,6 @@ class TemplateMessageService:
"""create tenant template"""
try:
template.tenant_id = tenant_id
# Check if template already exists with same template_id, tenant_id and region
existing_template = await self.template_message_handler.find_tenant_template(tenant_id, template.template_id, template.region)
if existing_template:
raise ValueError(f"Tenant template with template_id '{template.template_id}', tenant_id '{tenant_id}' and region '{template.region}' already exists")
result = await self.template_message_handler.create_tenant_template(tenant_id, template)
await self.module_logger.log_info(
info="Tenant template created",
@ -259,9 +244,6 @@ class TemplateMessageService:
}
)
return result
except ValueError as e:
# Re-raise ValueError for duplicate template
raise
except Exception as e:
await self.module_logger.log_error(
error="Failed to create tenant template",
@ -360,4 +342,33 @@ class TemplateMessageService:
}
)
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 for email",
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

View File

@ -1,4 +1,4 @@
from fastapi import APIRouter, HTTPException, Depends, Query
from fastapi import APIRouter, HTTPException, Depends
from fastapi.responses import JSONResponse
from backend.models.models import MessageTemplateDoc
from backend.application.template_message_hub import TemplateMessageHub
@ -360,7 +360,7 @@ async def update_tenant_template(template_id: str, request: TemplateUpdateReques
description="Delete an existing template for the current tenant",
response_description="Success/failure response in deleting the tenant template"
)
async def delete_tenant_template(template_id: str, region: int = Query(..., description="Template region"), payload: dict = Depends(tenant_only)):
async def delete_tenant_template(template_id: str, region: int, payload: dict = Depends(tenant_only)):
try:
tenant_id = payload.get("tenant_id")
await verify_tenant_access(template_id, tenant_id, template_message_hub, allow_global_template=False, region=region)
@ -386,15 +386,14 @@ async def delete_tenant_template(template_id: str, region: int = Query(..., desc
dependencies=[Depends(tenant_only)],
operation_id="render_template",
summary="Render a template (Tenant Only)",
description="Render a template with provided properties (can render own templates or assigned global templates)",
description="Render a template with provided properties (can only render own templates)",
response_description="Rendered template content"
)
async def render_template(template_id: str, request: TemplateRenderRequest, region: int = Query(..., description="Template region"), payload: dict = Depends(tenant_only)):
async def render_template(template_id: str, region: int, request: TemplateRenderRequest, payload: dict = Depends(tenant_only)):
try:
tenant_id = payload.get("tenant_id")
# Allow access to both tenant templates and assigned global templates
await verify_tenant_access(template_id, tenant_id, template_message_hub, allow_global_template=True, region=region)
await verify_tenant_access(template_id, tenant_id, template_message_hub, allow_global_template=False, region=region)
result = await template_message_hub.render_template(tenant_id, template_id, request.properties, region)
return JSONResponse(