diff --git a/apps/notification/backend/infra/template_message_handler.py b/apps/notification/backend/infra/template_message_handler.py index 6c12048..27fad96 100644 --- a/apps/notification/backend/infra/template_message_handler.py +++ b/apps/notification/backend/infra/template_message_handler.py @@ -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", diff --git a/apps/notification/backend/models/models.py b/apps/notification/backend/models/models.py index ab9d258..ce143cd 100644 --- a/apps/notification/backend/models/models.py +++ b/apps/notification/backend/models/models.py @@ -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 diff --git a/apps/notification/backend/services/template_message_service.py b/apps/notification/backend/services/template_message_service.py index c2fba18..187f973 100644 --- a/apps/notification/backend/services/template_message_service.py +++ b/apps/notification/backend/services/template_message_service.py @@ -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 \ No newline at end of file diff --git a/apps/notification/webapi/routes/template_massege.py b/apps/notification/webapi/routes/template_massege.py index d756126..e084506 100644 --- a/apps/notification/webapi/routes/template_massege.py +++ b/apps/notification/webapi/routes/template_massege.py @@ -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(