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 backend.models.models import MessageTemplateDoc
from common.log.module_logger import ModuleLogger from common.log.module_logger import ModuleLogger
from datetime import datetime from datetime import datetime
@ -355,9 +356,60 @@ class TemplateMessageHandler:
) )
raise 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: async def render_template(self, template: MessageTemplateDoc, properties: dict) -> dict:
"""render template""" """render template"""
try: try:
required_params = await self.validate_template_parameters(template, properties)
subject = template.subject.format(**properties) subject = template.subject.format(**properties)
body = template.body.format(**properties) body = template.body.format(**properties)
@ -367,11 +419,30 @@ class TemplateMessageHandler:
"template_id": template.template_id, "template_id": template.template_id,
"tenant_id": template.tenant_id, "tenant_id": template.tenant_id,
"region": template.region, "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: except KeyError as e:
await self.module_logger.log_error( await self.module_logger.log_error(
error="Missing template parameter", error="Missing template parameter",
@ -382,7 +453,7 @@ class TemplateMessageHandler:
"missing_parameter": str(e) "missing_parameter": str(e)
} }
) )
raise ValueError(f"Missing template parameter: {e}") raise ValueError(f"Missing template parameter: {str(e)}")
except Exception as e: except Exception as e:
await self.module_logger.log_error( await self.module_logger.log_error(
error="Template rendering error", error="Template rendering error",

View File

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

View File

@ -40,11 +40,6 @@ class TemplateMessageService:
async def create_global_template(self, template: MessageTemplateDoc): async def create_global_template(self, template: MessageTemplateDoc):
"""create global template""" """create global template"""
try: 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) result = await self.template_message_handler.create_global_template(template)
await self.module_logger.log_info( await self.module_logger.log_info(
info="Global template created", info="Global template created",
@ -54,9 +49,6 @@ class TemplateMessageService:
} }
) )
return result return result
except ValueError as e:
# Re-raise ValueError for duplicate template
raise
except Exception as e: except Exception as e:
await self.module_logger.log_error( await self.module_logger.log_error(
error="Failed to create global template", error="Failed to create global template",
@ -104,8 +96,7 @@ class TemplateMessageService:
await self.module_logger.log_info( await self.module_logger.log_info(
info="Global template deleted", info="Global template deleted",
properties={ properties={
"template_id": template_id, "template_id": template_id
"deleted_count": result.get("deleted_count", 0) if result else 0
} }
) )
return result return result
@ -243,12 +234,6 @@ class TemplateMessageService:
"""create tenant template""" """create tenant template"""
try: try:
template.tenant_id = tenant_id 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) result = await self.template_message_handler.create_tenant_template(tenant_id, template)
await self.module_logger.log_info( await self.module_logger.log_info(
info="Tenant template created", info="Tenant template created",
@ -259,9 +244,6 @@ class TemplateMessageService:
} }
) )
return result return result
except ValueError as e:
# Re-raise ValueError for duplicate template
raise
except Exception as e: except Exception as e:
await self.module_logger.log_error( await self.module_logger.log_error(
error="Failed to create tenant template", error="Failed to create tenant template",
@ -361,3 +343,32 @@ class TemplateMessageService:
) )
raise 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 fastapi.responses import JSONResponse
from backend.models.models import MessageTemplateDoc from backend.models.models import MessageTemplateDoc
from backend.application.template_message_hub import TemplateMessageHub 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", description="Delete an existing template for the current tenant",
response_description="Success/failure response in deleting the tenant template" 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: try:
tenant_id = payload.get("tenant_id") tenant_id = payload.get("tenant_id")
await verify_tenant_access(template_id, tenant_id, template_message_hub, allow_global_template=False, region=region) 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)], dependencies=[Depends(tenant_only)],
operation_id="render_template", operation_id="render_template",
summary="Render a template (Tenant Only)", 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" 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: try:
tenant_id = payload.get("tenant_id") 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=False, region=region)
await verify_tenant_access(template_id, tenant_id, template_message_hub, allow_global_template=True, region=region)
result = await template_message_hub.render_template(tenant_id, template_id, request.properties, region) result = await template_message_hub.render_template(tenant_id, template_id, request.properties, region)
return JSONResponse( return JSONResponse(