From 58c77ce0022dfd33979b16a716fdd01233d8eb73 Mon Sep 17 00:00:00 2001 From: YuehuCao Date: Tue, 12 Aug 2025 13:58:08 +0800 Subject: [PATCH] feat(templates): add duplicate creation prevention --- .../business/template_message_manager.py | 12 +- .../backend/infra/template_message_handler.py | 103 +++++++++++++----- apps/notification/backend/models/models.py | 5 +- .../webapi/routes/template_massege.py | 27 +++-- 4 files changed, 108 insertions(+), 39 deletions(-) diff --git a/apps/notification/backend/business/template_message_manager.py b/apps/notification/backend/business/template_message_manager.py index a0db479..017de66 100644 --- a/apps/notification/backend/business/template_message_manager.py +++ b/apps/notification/backend/business/template_message_manager.py @@ -44,10 +44,10 @@ class TemplateMessageManager: ) result = await self.template_message_service.create_global_template(template) - + action = "skipped" if hasattr(result, "_is_existing") and result._is_existing else "created" await self.module_logger.log_info( - info="Global template created", - properties={"template_id": template_id, "region": region} + info=f"Global template {action}", + properties={"template_id": template_id, "region": region, "action": action} ) return result @@ -157,12 +157,14 @@ class TemplateMessageManager: result = await self.template_message_service.create_tenant_template(tenant_id, template) + action = "skipped" if hasattr(result, "_is_existing") and result._is_existing else "created" await self.module_logger.log_info( - info="Tenant template created", + info=f"Tenant template {action}", properties={ "tenant_id": tenant_id, "template_id": template_id, - "region": region + "region": region, + "action": action } ) diff --git a/apps/notification/backend/infra/template_message_handler.py b/apps/notification/backend/infra/template_message_handler.py index 27fad96..44fa4d3 100644 --- a/apps/notification/backend/infra/template_message_handler.py +++ b/apps/notification/backend/infra/template_message_handler.py @@ -42,21 +42,47 @@ class TemplateMessageHandler: # ==================== global templates ==================== async def create_global_template(self, template: MessageTemplateDoc) -> MessageTemplateDoc: - """create global template""" + """create global template with upsert support""" try: - await template.create() - await self.module_logger.log_info( - info="Template created in database", - properties={ - "template_id": template.template_id, - "tenant_id": template.tenant_id, - "region": template.region - } - ) - return template + # check if template already exists + existing_template = await MessageTemplateDoc.find_one({ + "template_id": template.template_id, + "tenant_id": template.tenant_id, + "region": template.region + }) + + if existing_template: + # if template already exists, skip creation + await self.module_logger.log_info( + info="Global template already exists, skipping creation", + properties={ + "template_id": template.template_id, + "tenant_id": template.tenant_id, + "region": template.region + } + ) + + # mark as existing template + existing_template._is_existing = True + return existing_template + else: + # if template does not exist, create new template + await template.create() + await self.module_logger.log_info( + info="Template created in database", + properties={ + "template_id": template.template_id, + "tenant_id": template.tenant_id, + "region": template.region + } + ) + # mark as new template + template._is_existing = False + return template + except Exception as e: await self.module_logger.log_error( - error="Failed to create template in database", + error="Failed to create/update template in database", properties={ "template_id": template.template_id, "error": str(e) @@ -258,23 +284,50 @@ class TemplateMessageHandler: raise async def create_tenant_template(self, tenant_id: str, template: MessageTemplateDoc) -> MessageTemplateDoc: - """create tenant template""" + """create tenant template with upsert support""" try: - await template.create() - await self.module_logger.log_info( - info="Template created in database", + # check if template already exists + existing_template = await MessageTemplateDoc.find_one({ + "template_id": template.template_id, + "tenant_id": template.tenant_id, + "region": template.region + }) + + if existing_template: + # if template already exists, skip creation + await self.module_logger.log_info( + info="Tenant template already exists, skipping creation", + properties={ + "template_id": template.template_id, + "tenant_id": template.tenant_id, + "region": template.region + } + ) + + # mark as existing template + existing_template._is_existing = True + return existing_template + else: + # if template does not exist, create new template + await template.create() + await self.module_logger.log_info( + info="Template created in database", + properties={ + "template_id": template.template_id, + "tenant_id": template.tenant_id, + "region": template.region + } + ) + # mark as new template + template._is_existing = False + return template + + except Exception as e: + await self.module_logger.log_error( + error="Failed to create/update tenant template in database", properties={ "template_id": template.template_id, "tenant_id": template.tenant_id, - "region": template.region - } - ) - return template - except Exception as e: - await self.module_logger.log_error( - error="Failed to create template in database", - properties={ - "template_id": template.template_id, "error": str(e) } ) diff --git a/apps/notification/backend/models/models.py b/apps/notification/backend/models/models.py index ce143cd..e619e71 100644 --- a/apps/notification/backend/models/models.py +++ b/apps/notification/backend/models/models.py @@ -12,6 +12,7 @@ class MessageTemplateDoc(Document): subject: str body: str is_active: bool = True + is_existing: bool = False created_at: datetime = datetime.utcnow() updated_at: Optional[datetime] = None @@ -20,8 +21,8 @@ class MessageTemplateDoc(Document): indexes = [ "template_id", "tenant_id", - "region" - ] + "region" + ] class EmailSenderDoc(Document): tenant_id: str diff --git a/apps/notification/webapi/routes/template_massege.py b/apps/notification/webapi/routes/template_massege.py index e084506..202de43 100644 --- a/apps/notification/webapi/routes/template_massege.py +++ b/apps/notification/webapi/routes/template_massege.py @@ -7,7 +7,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from pydantic import BaseModel from typing import Dict, List import json -from datetime import datetime +from datetime import datetime, timedelta router = APIRouter() @@ -127,7 +127,7 @@ def verify_tenant_access(template_id: str, user_tenant_id: str, template_message dependencies=[Depends(admin_only)], operation_id="create_global_template", summary="Create a new global template (Admin Only)", - description="Create a new global template that all tenants can use", + description="Create a new global template that all tenants can use. If template already exists, it will be skipped.", response_description="Success/failure response in creating the global template" ) async def create_global_template(request: TemplateCreateRequest): @@ -139,10 +139,16 @@ async def create_global_template(request: TemplateCreateRequest): body=request.body, is_active=request.is_active ) + # check if the template is created or skipped (already exists) + is_skipped = hasattr(result, '_is_existing') and result._is_existing return JSONResponse( - content={"message": "Global template created successfully", "template_id": request.template_id}, - status_code=201 + content={ + "message": f"Global template {'skipped (already exists)' if is_skipped else 'created'} successfully", + "template_id": request.template_id, + "action": "skipped" if is_skipped else "created" + }, + status_code=200 if is_skipped else 201 ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @@ -265,7 +271,7 @@ async def assign_templates_to_tenant(request: TemplateAssignRequest, payload: di dependencies=[Depends(tenant_only)], operation_id="create_tenant_template", summary="Create a tenant template (Tenant Only)", - description="Create a new template for the current tenant", + description="Create a new template for the current tenant. If template already exists, it will be skipped.", response_description="Success/failure response in creating the tenant template" ) async def create_tenant_template(request: TemplateCreateRequest, payload: dict = Depends(tenant_only)): @@ -281,6 +287,9 @@ async def create_tenant_template(request: TemplateCreateRequest, payload: dict = is_active=request.is_active ) + # check if the template is created or skipped (already exists) + is_skipped = hasattr(result, '_is_existing') and result._is_existing + if hasattr(result, 'dict'): result_dict = result.dict() for key, value in result_dict.items(): @@ -299,8 +308,12 @@ async def create_tenant_template(request: TemplateCreateRequest, payload: dict = } return JSONResponse( - content={"message": "Tenant template created successfully", "result": result_dict}, - status_code=201, + content={ + "message": f"Tenant template {'skipped (already exists)' if is_skipped else 'created'} successfully", + "result": result_dict, + "action": "skipped" if is_skipped else "created" + }, + status_code=200 if is_skipped else 201, media_type="application/json" ) except ValueError as e: