409 lines
17 KiB
Python
409 lines
17 KiB
Python
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
from fastapi.responses import JSONResponse
|
|
from backend.models.models import MessageTemplateDoc
|
|
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_message_hub = TemplateMessageHub()
|
|
token_manager = TokenManager()
|
|
security = HTTPBearer()
|
|
|
|
# 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
|
|
|
|
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)
|
|
|
|
# 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=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")
|
|
|
|
# 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()
|
|
|
|
# ==================== 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:
|
|
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))
|
|
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")
|
|
|