diff --git a/apps/notification/webapi/routes/__init__.py b/apps/notification/webapi/routes/__init__.py index 98424b1..3d8ef36 100644 --- a/apps/notification/webapi/routes/__init__.py +++ b/apps/notification/webapi/routes/__init__.py @@ -1,9 +1,19 @@ from fastapi import APIRouter from .send_notification import router as sn_router from .online_platform_notification import router as ws_router +from .template_massege import router as template_router +from .email_sender import router as email_sender_router api_router = APIRouter(prefix="/notification") api_router.include_router(sn_router, tags=["notification"]) +api_router.include_router(template_router, prefix="/templates", tags=["templates"]) +api_router.include_router(email_sender_router, tags=["email_sender"]) + +# WebSocket路由 +websocket_router = APIRouter() +websocket_router.include_router(ws_router, prefix="/downstream", tags=["downstream"]) +# 将WebSocket路由也注册到主路由中 +api_router.include_router(websocket_router) websocket_router = APIRouter() websocket_router.include_router(ws_router, prefix="/downstream", tags=["downstream"]) diff --git a/apps/notification/webapi/routes/email_sender.py b/apps/notification/webapi/routes/email_sender.py index 3a7e1e3..dbf3e04 100644 --- a/apps/notification/webapi/routes/email_sender.py +++ b/apps/notification/webapi/routes/email_sender.py @@ -1,56 +1,212 @@ -from fastapi import APIRouter, Depends, HTTPException -from backend.services.email_sender_service import EmailSenderService -from common.token.token_manager import TokenManager +from fastapi import APIRouter, HTTPException, Depends +import traceback +from fastapi.responses import JSONResponse +from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from backend.application.email_sender_hub import EmailSenderHub +from common.token.token_manager import TokenManager +from pydantic import BaseModel +from typing import List router = APIRouter() -email_sender_service = EmailSenderService() -token_manager = TokenManager() + security = HTTPBearer() +token_manager = TokenManager() +email_sender_hub = EmailSenderHub() -def get_current_tenant_id( - credentials: HTTPAuthorizationCredentials = Depends(security) -) -> str: +# Define the request body schema +class EmailSenderSetRequest(BaseModel): + email_senders: List[str] + +class EmailSenderAddRequest(BaseModel): + new_senders: List[str] + +class EmailSenderRemoveRequest(BaseModel): + emails_to_remove: List[str] + +# check credentials for admin and tenant +def admin_only(credentials: HTTPAuthorizationCredentials = Depends(security)): try: payload = token_manager.decode_token(credentials.credentials) - tenant_id = payload.get("tenant_id") + + # handle token generated by structure Authentication service + if "subject" in payload: + # Authentication format: payload.subject.role + role = payload.get("subject", {}).get("role") + else: + # local generated format: payload.role + role = payload.get("role") + + # according to AdministrativeRole enum defined in authentication service, ADMINISTRATOR = 8 + if role not in ["admin", 8]: + raise HTTPException(status_code=403, detail="Admin access required") + return payload + except Exception: + raise HTTPException(status_code=401, detail="Invalid token") + +def tenant_only(credentials: HTTPAuthorizationCredentials = Depends(security)): + try: + payload = token_manager.decode_token(credentials.credentials) + + # handle token generated by structure Authentication service + if "subject" in payload: + tenant_id = payload.get("subject", {}).get("id") + user_id = payload.get("subject", {}).get("id") + role = payload.get("subject", {}).get("role") + else: + tenant_id = payload.get("tenant_id") + user_id = payload.get("id") + role = payload.get("role") + if not tenant_id: - raise HTTPException(status_code=401, detail="No tenant_id in token") - return tenant_id + raise HTTPException(status_code=403, detail="Tenant access required") + + # according to AdministrativeRole enum defined in authentication service, BUSINESS = 2 + if role not in [2, "tenant"]: + raise HTTPException(status_code=403, detail="Tenant access required") + + payload["tenant_id"] = tenant_id + payload["user_id"] = user_id + payload["is_admin"] = False + return payload except Exception: raise HTTPException(status_code=401, detail="Invalid token") +# Web API +# Get email senders for tenant +@router.get( + "/email_senders/get", + dependencies=[Depends(tenant_only)], + operation_id="get_email_senders", + summary="Get email senders for tenant", + description="Retrieve the list of email senders configured for the current tenant", + response_description="List of email sender addresses" +) +async def get_email_senders(payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") + + result = await email_sender_hub.get_email_senders(tenant_id) + return JSONResponse( + content={"success": True, "email_senders": result}, + status_code=200 + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to get email senders") -@router.get("/email_senders") -async def get_email_senders(tenant_id: str = Depends(get_current_tenant_id)): - return await email_sender_service.get_email_sender(tenant_id) +# Set email senders for tenant +@router.post( + "/email_senders/set", + dependencies=[Depends(tenant_only)], + operation_id="set_email_senders", + summary="Set email senders for tenant", + description="Set the complete list of email senders for the specified tenant", + response_description="Success/failure response in setting email senders" +) +async def set_email_senders(request: EmailSenderSetRequest, payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") + result = await email_sender_hub.set_email_senders(tenant_id, request.email_senders) + return JSONResponse( + content=result, + status_code=200 + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + import traceback + traceback.print_exc() + raise HTTPException(status_code=500, detail=f"Failed to set email senders: {str(e)}") -@router.post("/email_sender/set") -async def set_email_senders( - email_senders: list[str], - tenant_id: str = Depends(get_current_tenant_id) -): - return await email_sender_service.set_email_sender(tenant_id, email_senders) +# Add email senders to tenant +@router.post( + "/email_senders/add", + dependencies=[Depends(tenant_only)], + operation_id="add_email_senders", + summary="Add email senders to tenant", + description="Add new email senders to the existing list for the specified tenant", + response_description="Success/failure response in adding email senders" +) +async def add_email_senders(request: EmailSenderAddRequest, payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") -@router.post("/email_senders/add") -async def add_email_senders( - new_senders: list[str], - tenant_id: str = Depends(get_current_tenant_id) -): - return await email_sender_service.add_email_senders(tenant_id, new_senders) + result = await email_sender_hub.add_email_senders(tenant_id, request.new_senders) + return JSONResponse( + content=result, + status_code=200 + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + import traceback + traceback.print_exc() + raise HTTPException(status_code=500, detail=f"Failed to add email senders: {str(e)}") -@router.delete("/email_senders/remove") -async def remove_email_senders( - emails_to_remove: list[str], - tenant_id: str = Depends(get_current_tenant_id) -): - return await email_sender_service.remove_email_senders(tenant_id, emails_to_remove) +# Remove email senders from tenant +@router.delete( + "/email_senders/remove", + dependencies=[Depends(tenant_only)], + operation_id="remove_email_senders", + summary="Remove email senders from tenant", + description="Remove specific email senders from the tenant's list", + response_description="Success/failure response in removing email senders" +) +async def remove_email_senders(request: EmailSenderRemoveRequest, payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") -@router.delete("/email_senders/clear") -async def clear_email_senders(tenant_id: str = Depends(get_current_tenant_id)): - return await email_sender_service.clear_email_senders(tenant_id) + result = await email_sender_hub.remove_email_senders(tenant_id, request.emails_to_remove) + return JSONResponse( + content=result, + status_code=200 + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + import traceback + traceback.print_exc() + raise HTTPException(status_code=500, detail=f"Failed to remove email senders: {str(e)}") -@router.delete("/email_senders/delete") -async def delete_email_sender(tenant_id: str = Depends(get_current_tenant_id)): - return await email_sender_service.delete_email_sender(tenant_id) \ No newline at end of file +# Clear all email senders for tenant +@router.delete( + "/email_senders/clear", + dependencies=[Depends(tenant_only)], + operation_id="clear_email_senders", + summary="Clear all email senders for tenant", + description="Remove all email senders from the current tenant's list", + response_description="Success/failure response in clearing email senders" +) +async def clear_email_senders(payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") + + result = await email_sender_hub.clear_email_senders(tenant_id) + return JSONResponse( + content=result, + status_code=200 + ) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to clear email senders") + +# Delete email sender configuration for tenant +@router.delete( + "/email_senders/delete/{tenant_id}", + dependencies=[Depends(admin_only)], + operation_id="delete_email_sender", + summary="Delete email sender configuration for tenant", + description="Completely delete the email sender configuration for the specified tenant", + response_description="Success/failure response in deleting email sender configuration" +) +async def delete_email_sender(tenant_id: str): + try: + result = await email_sender_hub.delete_email_sender(tenant_id) + return JSONResponse( + content=result, + status_code=200 + ) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to delete email sender") diff --git a/apps/notification/webapi/routes/template_massege.py b/apps/notification/webapi/routes/template_massege.py index bf4f3ba..d756126 100644 --- a/apps/notification/webapi/routes/template_massege.py +++ b/apps/notification/webapi/routes/template_massege.py @@ -1,106 +1,408 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, HTTPException, Depends, Query +from fastapi.responses import JSONResponse from backend.models.models import MessageTemplateDoc -from backend.services.template_message_service import TemplateMessageService +from backend.application.template_message_hub import TemplateMessageHub from common.token.token_manager import TokenManager from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from pydantic import BaseModel +from typing import Dict, List +import json +from datetime import datetime + router = APIRouter() -template_service = TemplateMessageService() - +template_message_hub = TemplateMessageHub() token_manager = TokenManager() security = HTTPBearer() -""" -# token payload example -{ - "user_id": "...", - "tenant_id": "...", - "role": "...", - "exp": ... -} -""" +# Define the request body schema +class TemplateCreateRequest(BaseModel): + template_id: str + tenant_id: str = None + region: int + subject: str + body: str + is_active: bool = True -def get_current_tenant_id( - credentials: HTTPAuthorizationCredentials = Depends(security) -) -> str: +class TemplateUpdateRequest(BaseModel): + subject: str = None + body: str = None + is_active: bool = None + region: int = None + +class TemplateAssignRequest(BaseModel): + template_ids: List[str] + region: int + +class TemplateRenderRequest(BaseModel): + properties: Dict + +class TemplateCopyRequest(BaseModel): + template_id: str + new_template_id: str + region: int + +# check credentials for admin and tenant +def admin_only(credentials: HTTPAuthorizationCredentials = Depends(security)): try: payload = token_manager.decode_token(credentials.credentials) - tenant_id = payload.get("tenant_id") + + # handle token generated by structure Authentication service + if "subject" in payload: + # Authentication format: payload.subject.role + role = payload.get("subject", {}).get("role") + else: + # local generated format: payload.role + role = payload.get("role") + + # according to AdministrativeRole enum defined in authentication service, ADMINISTRATOR = 8 + if role not in ["admin", 8]: + raise HTTPException(status_code=403, detail="Admin access required") + return payload + except Exception: + raise HTTPException(status_code=401, detail="Invalid token") + +def tenant_only(credentials: HTTPAuthorizationCredentials = Depends(security)): + try: + payload = token_manager.decode_token(credentials.credentials) + + # handle token generated by structure Authentication service + if "subject" in payload: + tenant_id = payload.get("subject", {}).get("id") + user_id = payload.get("subject", {}).get("id") + role = payload.get("subject", {}).get("role") + else: + tenant_id = payload.get("tenant_id") + user_id = payload.get("id") + role = payload.get("role") + if not tenant_id: - raise HTTPException(status_code=401, detail="No tenant_id in token") - return tenant_id - except Exception: - raise HTTPException(status_code=401, detail="Invalid token") - ... -def admin_only( - credentials: HTTPAuthorizationCredentials = Depends(security) -): - try: - payload = token_manager.decode_token(credentials.credentials) - role = payload.get("role") - if role != "admin": - raise HTTPException(status_code=403, detail="Not a platform admin") + raise HTTPException(status_code=403, detail="Tenant access required") + + # according to AdministrativeRole enum defined in authentication service, BUSINESS = 2 + if role not in [2, "tenant"]: + raise HTTPException(status_code=403, detail="Tenant access required") + + payload["tenant_id"] = tenant_id + payload["user_id"] = user_id + payload["is_admin"] = False + + return payload except Exception: raise HTTPException(status_code=401, detail="Invalid token") -# interface for platform admin(management of global templates) -@router.post("/global_templates", dependencies=[Depends(admin_only)]) -async def create_global_template(template: MessageTemplateDoc): - return await template_service.create_global_template(template) +# verify tenant access for template +def verify_tenant_access(template_id: str, user_tenant_id: str, template_message_hub: TemplateMessageHub, allow_global_template: bool = True, region: int = None): + async def _verify(): + try: + if not template_id: + raise HTTPException(status_code=400, detail="Template ID is required") + + if user_tenant_id: + if region is None: + raise HTTPException(status_code=400, detail="Region parameter is required for tenant template access to ensure correct template version") + + template = await template_message_hub.verify_tenant_access(template_id, user_tenant_id, region) + if not template: + raise HTTPException(status_code=404, detail=f"Tenant template not found for template_id: {template_id}, tenant_id: {user_tenant_id}, region: {region}") + + if template.tenant_id != user_tenant_id: + raise HTTPException(status_code=403, detail="Access denied: Template belongs to different tenant") + + return template + else: + raise HTTPException(status_code=403, detail="Tenant access required for template operations") + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to verify template access") + + return _verify() -@router.put("/global_templates/{id}", dependencies=[Depends(admin_only)]) -async def update_global_template(id: str, data: dict): +# ==================== ADMIN API ==================== +# Global template management API (Admin only) + +@router.post( + "/admin/global_templates/create", + 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", + response_description="Success/failure response in creating the global template" +) +async def create_global_template(request: TemplateCreateRequest): try: - return await template_service.update_global_template(id, data) - except PermissionError: - raise HTTPException(status_code=403, detail="Not a global template") - -@router.delete("/global_templates/{id}", dependencies=[Depends(admin_only)]) -async def delete_global_template(id: str): - try: - return await template_service.delete_global_template(id) - except PermissionError: - raise HTTPException(status_code=403, detail="Not a global template") - -@router.get("/global_templates") -async def list_global_templates(region: int): - return await template_service.list_global_templates(region) - - -# interface for tenant(management of tenant's templates) -@router.get("/templates") -async def list_tenant_templates(region: int, tenant_id: str = Depends(get_current_tenant_id)): - return await template_service.list_tenant_templates(tenant_id, region) - -@router.post("/templates/assign") -async def assign_templates(template_ids: list[str], region: int, tenant_id: str = Depends(get_current_tenant_id)): - return await template_service.assign_template_to_tenant(template_ids, region, tenant_id) - -@router.post("/templates") -async def create_template(template: MessageTemplateDoc, tenant_id: str = Depends(get_current_tenant_id)): - return await template_service.create_template(template, tenant_id) - -@router.put("/templates/{id}") -async def update_template(id: str, data: dict, tenant_id: str = Depends(get_current_tenant_id)): - try: - return await template_service.update_template(id, tenant_id, data) - except PermissionError: - raise HTTPException(status_code=403, detail="Forbidden") - -@router.delete("/templates/{id}") -async def delete_template(id: str, tenant_id: str = Depends(get_current_tenant_id)): - try: - return await template_service.delete_template(id, tenant_id) - except PermissionError: - raise HTTPException(status_code=403, detail="Forbidden") - -@router.post("/templates/{id}/render") -async def render_template(id: str, properties: dict, tenant_id: str = Depends(get_current_tenant_id)): - template = await template_service.get_template(id, tenant_id) - if not template: - raise HTTPException(status_code=404, detail="Template not found") - try: - subject, body = await template_service.render_template(template, properties) - return {"subject": subject, "body": body} + result = await template_message_hub.create_global_template( + template_id=request.template_id, + region=request.region, + subject=request.subject, + body=request.body, + is_active=request.is_active + ) + + return JSONResponse( + content={"message": "Global template created successfully", "template_id": request.template_id}, + status_code=201 + ) except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) \ No newline at end of file + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + import traceback + traceback.print_exc() + raise HTTPException(status_code=500, detail=f"Failed to create global template: {str(e)}") + +@router.put( + "/admin/global_templates/update/{template_id}", + dependencies=[Depends(admin_only)], + operation_id="update_global_template", + summary="Update a global template (Admin Only)", + description="Update an existing global template", + response_description="Success/failure response in updating the global template" +) +async def update_global_template(template_id: str, request: TemplateUpdateRequest): + try: + region = request.region + if region is None: + raise HTTPException(status_code=400, detail="Region parameter is required for global template update to ensure correct template version") + + update_data = {k: v for k, v in request.dict().items() if v is not None and k != "region"} + + if not update_data: + raise ValueError("At least one field must be provided for update") + result = await template_message_hub.update_global_template(template_id, update_data, region) + return JSONResponse( + content={"message": "Global template updated successfully", "result": result}, + status_code=200 + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update global template") + +@router.delete( + "/admin/global_templates/delete/{template_id}", + dependencies=[Depends(admin_only)], + operation_id="delete_global_template", + summary="Delete a global template (Admin Only)", + description="Delete an existing global template", + response_description="Success/failure response in deleting the global template" +) +async def delete_global_template(template_id: str): + try: + result = await template_message_hub.delete_global_template(template_id) + return JSONResponse( + content={"message": "Global template deleted successfully", "result": result}, + status_code=200 + ) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to delete global template") + +# ==================== SHARED API ==================== +# Global template list API (Admin and Tenant) + +@router.get( + "/global_templates/list", + operation_id="list_global_templates", + summary="List global templates (Admin and Tenant)", + description="Get a list of all global templates (read-only operation)", + response_description="List of global templates" +) +async def list_global_templates(region: int): + try: + templates = await template_message_hub.list_global_templates(region) + return JSONResponse( + content={"templates": templates}, + status_code=200 + ) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to list global templates") + +# ==================== TENANT API ==================== +# Tenant template management API (Tenant only) + +@router.get( + "/tenant/templates/list", + dependencies=[Depends(tenant_only)], + operation_id="list_tenant_templates", + summary="List tenant templates (Tenant Only)", + description="Get a list of templates belonging to the current tenant", + response_description="List of tenant templates" +) +async def list_tenant_templates(region: int, payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") + templates = await template_message_hub.list_tenant_templates(tenant_id, region) + return JSONResponse( + content={"templates": templates}, + status_code=200 + ) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to list tenant templates") + +@router.post( + "/tenant/templates/assign", + dependencies=[Depends(tenant_only)], + operation_id="assign_templates", + summary="Assign global templates to tenant (Tenant Only)", + description="Assign one or more global templates to the current tenant", + response_description="Success/failure response in assigning templates" +) +async def assign_templates_to_tenant(request: TemplateAssignRequest, payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") + results = await template_message_hub.assign_templates_to_tenant(tenant_id, request.template_ids, request.region) + return JSONResponse( + content={"message": "Templates assigned successfully", "results": results}, + status_code=200 + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to assign templates") + +@router.post( + "/tenant/templates/create", + 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", + response_description="Success/failure response in creating the tenant template" +) +async def create_tenant_template(request: TemplateCreateRequest, payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") + + result = await template_message_hub.create_tenant_template( + template_id=request.template_id, + region=request.region, + subject=request.subject, + body=request.body, + tenant_id=tenant_id, + is_active=request.is_active + ) + + if hasattr(result, 'dict'): + result_dict = result.dict() + for key, value in result_dict.items(): + if hasattr(value, 'isoformat'): + result_dict[key] = value.isoformat() + elif hasattr(value, 'value'): + result_dict[key] = value.value + else: + result_dict = { + "template_id": getattr(result, 'template_id', request.template_id), + "tenant_id": getattr(result, 'tenant_id', tenant_id), + "region": getattr(result, 'region', request.region), + "subject": getattr(result, 'subject', request.subject), + "body": getattr(result, 'body', request.body), + "is_active": getattr(result, 'is_active', request.is_active) + } + + return JSONResponse( + content={"message": "Tenant template created successfully", "result": result_dict}, + status_code=201, + media_type="application/json" + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + import traceback + traceback.print_exc() + raise HTTPException(status_code=500, detail="Failed to create tenant template") + +@router.put( + "/tenant/templates/update/{template_id}", + dependencies=[Depends(tenant_only)], + operation_id="update_tenant_template", + summary="Update a tenant template (Tenant Only)", + description="Update an existing template for the current tenant", + response_description="Success/failure response in updating the tenant template" +) +async def update_tenant_template(template_id: str, request: TemplateUpdateRequest, payload: dict = Depends(tenant_only)): + try: + tenant_id = payload.get("tenant_id") + + region = request.region + if region is None: + raise HTTPException(status_code=400, detail="Region parameter is required for tenant template update to ensure correct template version") + + await verify_tenant_access(template_id, tenant_id, template_message_hub, allow_global_template=False, region=region) + + update_data = {k: v for k, v in request.dict().items() if v is not None and k != "region"} + + if not update_data: + raise ValueError("At least one field must be provided for update") + + + result = await template_message_hub.update_tenant_template(tenant_id, template_id, update_data, region) + + if isinstance(result, dict): + result_dict = result + else: + result_dict = {"success": True, "template_id": template_id} + + return JSONResponse( + content={"message": "Tenant template updated successfully", "result": result_dict}, + status_code=200 + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except PermissionError: + raise HTTPException(status_code=403, detail="Forbidden") + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update tenant template") + +@router.delete( + "/tenant/templates/delete/{template_id}", + dependencies=[Depends(tenant_only)], + operation_id="delete_tenant_template", + summary="Delete a tenant template (Tenant Only)", + 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)): + try: + tenant_id = payload.get("tenant_id") + await verify_tenant_access(template_id, tenant_id, template_message_hub, allow_global_template=False, region=region) + + result = await template_message_hub.delete_tenant_template(tenant_id, template_id, region) + + if isinstance(result, dict): + result_dict = result + else: + result_dict = {"success": True, "template_id": template_id} + + return JSONResponse( + content={"message": "Tenant template deleted successfully", "result": result_dict}, + status_code=200 + ) + except PermissionError: + raise HTTPException(status_code=403, detail="Forbidden") + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to delete tenant template") + +@router.post( + "/tenant/templates/render/{template_id}", + 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)", + 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)): + 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) + + result = await template_message_hub.render_template(tenant_id, template_id, request.properties, region) + return JSONResponse( + content={"message": "Template rendered successfully", "result": result}, + status_code=200 + ) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to render template") +