feat(templates): add duplicate creation prevention

This commit is contained in:
YuehuCao 2025-08-12 13:58:08 +08:00
parent f5206175da
commit 58c77ce002
4 changed files with 108 additions and 39 deletions

View File

@ -44,10 +44,10 @@ class TemplateMessageManager:
) )
result = await self.template_message_service.create_global_template(template) 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( await self.module_logger.log_info(
info="Global template created", info=f"Global template {action}",
properties={"template_id": template_id, "region": region} properties={"template_id": template_id, "region": region, "action": action}
) )
return result return result
@ -157,12 +157,14 @@ class TemplateMessageManager:
result = await self.template_message_service.create_tenant_template(tenant_id, template) 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( await self.module_logger.log_info(
info="Tenant template created", info=f"Tenant template {action}",
properties={ properties={
"tenant_id": tenant_id, "tenant_id": tenant_id,
"template_id": template_id, "template_id": template_id,
"region": region "region": region,
"action": action
} }
) )

View File

@ -42,21 +42,47 @@ class TemplateMessageHandler:
# ==================== global templates ==================== # ==================== global templates ====================
async def create_global_template(self, template: MessageTemplateDoc) -> MessageTemplateDoc: async def create_global_template(self, template: MessageTemplateDoc) -> MessageTemplateDoc:
"""create global template""" """create global template with upsert support"""
try: try:
await template.create() # check if template already exists
await self.module_logger.log_info( existing_template = await MessageTemplateDoc.find_one({
info="Template created in database", "template_id": template.template_id,
properties={ "tenant_id": template.tenant_id,
"template_id": template.template_id, "region": template.region
"tenant_id": template.tenant_id, })
"region": template.region
} if existing_template:
) # if template already exists, skip creation
return template 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: except Exception as e:
await self.module_logger.log_error( await self.module_logger.log_error(
error="Failed to create template in database", error="Failed to create/update template in database",
properties={ properties={
"template_id": template.template_id, "template_id": template.template_id,
"error": str(e) "error": str(e)
@ -258,23 +284,50 @@ class TemplateMessageHandler:
raise raise
async def create_tenant_template(self, tenant_id: str, template: MessageTemplateDoc) -> MessageTemplateDoc: async def create_tenant_template(self, tenant_id: str, template: MessageTemplateDoc) -> MessageTemplateDoc:
"""create tenant template""" """create tenant template with upsert support"""
try: try:
await template.create() # check if template already exists
await self.module_logger.log_info( existing_template = await MessageTemplateDoc.find_one({
info="Template created in database", "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={ properties={
"template_id": template.template_id, "template_id": template.template_id,
"tenant_id": template.tenant_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) "error": str(e)
} }
) )

View File

@ -12,6 +12,7 @@ class MessageTemplateDoc(Document):
subject: str subject: str
body: str body: str
is_active: bool = True is_active: bool = True
is_existing: bool = False
created_at: datetime = datetime.utcnow() created_at: datetime = datetime.utcnow()
updated_at: Optional[datetime] = None updated_at: Optional[datetime] = None
@ -20,8 +21,8 @@ class MessageTemplateDoc(Document):
indexes = [ indexes = [
"template_id", "template_id",
"tenant_id", "tenant_id",
"region" "region"
] ]
class EmailSenderDoc(Document): class EmailSenderDoc(Document):
tenant_id: str tenant_id: str

View File

@ -7,7 +7,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel from pydantic import BaseModel
from typing import Dict, List from typing import Dict, List
import json import json
from datetime import datetime from datetime import datetime, timedelta
router = APIRouter() router = APIRouter()
@ -127,7 +127,7 @@ def verify_tenant_access(template_id: str, user_tenant_id: str, template_message
dependencies=[Depends(admin_only)], dependencies=[Depends(admin_only)],
operation_id="create_global_template", operation_id="create_global_template",
summary="Create a new global template (Admin Only)", 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" response_description="Success/failure response in creating the global template"
) )
async def create_global_template(request: TemplateCreateRequest): async def create_global_template(request: TemplateCreateRequest):
@ -139,10 +139,16 @@ async def create_global_template(request: TemplateCreateRequest):
body=request.body, body=request.body,
is_active=request.is_active 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( return JSONResponse(
content={"message": "Global template created successfully", "template_id": request.template_id}, content={
status_code=201 "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: except ValueError as e:
raise HTTPException(status_code=400, detail=str(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)], dependencies=[Depends(tenant_only)],
operation_id="create_tenant_template", operation_id="create_tenant_template",
summary="Create a tenant template (Tenant Only)", 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" response_description="Success/failure response in creating the tenant template"
) )
async def create_tenant_template(request: TemplateCreateRequest, payload: dict = Depends(tenant_only)): 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 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'): if hasattr(result, 'dict'):
result_dict = result.dict() result_dict = result.dict()
for key, value in result_dict.items(): for key, value in result_dict.items():
@ -299,8 +308,12 @@ async def create_tenant_template(request: TemplateCreateRequest, payload: dict =
} }
return JSONResponse( return JSONResponse(
content={"message": "Tenant template created successfully", "result": result_dict}, content={
status_code=201, "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" media_type="application/json"
) )
except ValueError as e: except ValueError as e: