# app/api/v1/admin/email_templates.py """ Admin email templates management endpoints. Provides endpoints for: - Listing all platform email templates - Viewing template details (all languages) - Updating template content - Preview and test email sending """ import logging from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api from app.core.database import get_db from app.services.email_service import EmailService from models.database.email import EmailCategory, EmailTemplate from models.database.user import User from models.schema.email import ( EmailPreviewRequest, EmailPreviewResponse, EmailTemplateResponse, EmailTemplateSummary, EmailTemplateUpdate, EmailTestRequest, EmailTestResponse, ) router = APIRouter(prefix="/email-templates") logger = logging.getLogger(__name__) @router.get("", response_model=list[EmailTemplateSummary]) def list_templates( category: str | None = Query(None, description="Filter by category"), include_inactive: bool = Query(False, description="Include inactive templates"), db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ List all platform email templates. Templates are grouped by code, showing available languages for each. """ templates = EmailTemplate.get_all_templates( db, category=category, include_inactive=include_inactive ) return EmailTemplateSummary.from_db_list(templates) @router.get("/categories") def get_categories( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Get all email template categories.""" return { "categories": [ {"code": EmailCategory.AUTH.value, "name": "Authentication", "description": "Signup, password reset, verification"}, {"code": EmailCategory.ORDERS.value, "name": "Orders", "description": "Order confirmations, shipping updates"}, {"code": EmailCategory.BILLING.value, "name": "Billing", "description": "Invoices, payment failures, subscription changes"}, {"code": EmailCategory.SYSTEM.value, "name": "System", "description": "Team invites, notifications"}, {"code": EmailCategory.MARKETING.value, "name": "Marketing", "description": "Newsletters, promotions"}, ] } @router.get("/{code}", response_model=list[EmailTemplateResponse]) def get_template( code: str, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Get a template by code with all language versions. Returns all language versions of the template. """ templates = ( db.query(EmailTemplate) .filter(EmailTemplate.code == code) .order_by(EmailTemplate.language) .all() ) if not templates: raise HTTPException(status_code=404, detail=f"Template not found: {code}") return [EmailTemplateResponse.from_db(t) for t in templates] @router.get("/{code}/{language}", response_model=EmailTemplateResponse) def get_template_language( code: str, language: str, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Get a specific language version of a template. """ template = ( db.query(EmailTemplate) .filter( EmailTemplate.code == code, EmailTemplate.language == language, ) .first() ) if not template: raise HTTPException( status_code=404, detail=f"Template not found: {code} ({language})", ) return EmailTemplateResponse.from_db(template) @router.put("/{code}/{language}", response_model=EmailTemplateResponse) def update_template( code: str, language: str, data: EmailTemplateUpdate, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Update a specific language version of a template. Only provided fields are updated. """ template = ( db.query(EmailTemplate) .filter( EmailTemplate.code == code, EmailTemplate.language == language, ) .first() ) if not template: raise HTTPException( status_code=404, detail=f"Template not found: {code} ({language})", ) # Update provided fields import json if data.name is not None: template.name = data.name if data.description is not None: template.description = data.description if data.subject is not None: template.subject = data.subject if data.body_html is not None: template.body_html = data.body_html if data.body_text is not None: template.body_text = data.body_text if data.variables is not None: template.variables = json.dumps(data.variables) if data.required_variables is not None: template.required_variables = json.dumps(data.required_variables) if data.is_active is not None: template.is_active = data.is_active db.commit() db.refresh(template) logger.info( f"Email template updated: {code} ({language}) by admin {current_admin.id}" ) return EmailTemplateResponse.from_db(template) @router.post("/{code}/preview", response_model=EmailPreviewResponse) def preview_template( code: str, data: EmailPreviewRequest, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Preview a template with sample variables. Returns rendered subject and body without sending. """ template = ( db.query(EmailTemplate) .filter( EmailTemplate.code == code, EmailTemplate.language == data.language, ) .first() ) if not template: raise HTTPException( status_code=404, detail=f"Template not found: {code} ({data.language})", ) email_service = EmailService(db) # Render with provided variables subject = email_service.render_template(template.subject, data.variables) body_html = email_service.render_template(template.body_html, data.variables) body_text = ( email_service.render_template(template.body_text, data.variables) if template.body_text else None ) return EmailPreviewResponse( subject=subject, body_html=body_html, body_text=body_text, ) @router.post("/{code}/test", response_model=EmailTestResponse) def send_test_email( code: str, data: EmailTestRequest, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Send a test email to specified address. Uses the template with provided variables. """ template = ( db.query(EmailTemplate) .filter( EmailTemplate.code == code, EmailTemplate.language == data.language, ) .first() ) if not template: raise HTTPException( status_code=404, detail=f"Template not found: {code} ({data.language})", ) email_service = EmailService(db) # Send test email log = email_service.send_template( template_code=code, to_email=data.to_email, to_name=current_admin.full_name, language=data.language, variables=data.variables, user_id=current_admin.id, related_type="email_test", include_branding=True, ) if log.status == "sent": return EmailTestResponse( success=True, message=f"Test email sent to {data.to_email}", email_log_id=log.id, ) else: return EmailTestResponse( success=False, message=f"Failed to send test email: {log.error_message}", email_log_id=log.id, ) @router.get("/{code}/logs") def get_template_logs( code: str, limit: int = Query(50, ge=1, le=200), db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Get recent email logs for a specific template. Useful for debugging and monitoring. """ from models.database.email import EmailLog logs = ( db.query(EmailLog) .filter(EmailLog.template_code == code) .order_by(EmailLog.created_at.desc()) .limit(limit) .all() ) return { "template_code": code, "logs": [ { "id": log.id, "recipient_email": log.recipient_email, "subject": log.subject, "status": log.status, "sent_at": log.sent_at, "error_message": log.error_message, "created_at": log.created_at, } for log in logs ], "total": len(logs), }