""" Email Templates API Router Management of system and customer-specific email templates """ from typing import List, Optional, Dict, Any from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel, Json from app.core.database import execute_query import json import logging from datetime import datetime logger = logging.getLogger(__name__) router = APIRouter(prefix="/email-templates", tags=["Email Templates"]) # --- Models --- class EmailTemplateBase(BaseModel): name: str slug: str subject: str body: str category: str = "general" description: Optional[str] = None variables: Dict[str, str] = {} customer_id: Optional[int] = None class EmailTemplateCreate(EmailTemplateBase): pass class EmailTemplateUpdate(BaseModel): name: Optional[str] = None subject: Optional[str] = None body: Optional[str] = None category: Optional[str] = None description: Optional[str] = None variables: Optional[Dict[str, str]] = None customer_id: Optional[int] = None class EmailTemplate(BaseModel): id: int name: str slug: str subject: str body: str category: str description: Optional[str] = None variables: Optional[Dict[str, str]] = {} is_system: bool customer_id: Optional[int] = None created_at: datetime updated_at: datetime # --- Endpoints --- @router.get("/", response_model=List[EmailTemplate]) async def get_email_templates( category: Optional[str] = None, customer_id: Optional[int] = None ): """ Get all email templates. Optionally filter by category or specific customer. """ sql = """ SELECT id, name, slug, subject, body, category, description, variables, is_system, customer_id, created_at, updated_at FROM email_templates WHERE 1=1 """ params = [] if category: sql += " AND category = %s" params.append(category) if customer_id is not None: # Fetch global templates (customer_id IS NULL) AND this specific customer's templates sql += " AND (customer_id IS NULL OR customer_id = %s)" params.append(customer_id) sql += " ORDER BY category, name" rows = execute_query(sql, tuple(params)) return rows @router.get("/{template_id}", response_model=EmailTemplate) async def get_email_template(template_id: int): """Get a single template by ID""" sql = """ SELECT id, name, slug, subject, body, category, description, variables, is_system, customer_id, created_at, updated_at FROM email_templates WHERE id = %s """ rows = execute_query(sql, (template_id,)) if not rows: raise HTTPException(status_code=404, detail="Template not found") return rows[0] @router.post("/", response_model=EmailTemplate) async def create_email_template(template: EmailTemplateCreate): """Create a new email template""" # Check for slug uniqueness check_sql = "SELECT id FROM email_templates WHERE slug = %s AND (customer_id IS NULL OR customer_id = %s)" check_val = (template.customer_id,) if template.customer_id else (None,) # If customer_id is None, we check where customer_id IS NULL. # Actually, SQL `slug = %s AND customer_id = %s` works if we handle NULL correctly in python -> SQL # Simpler uniqueness check in python logic tailored to the constraint if template.customer_id: existing = execute_query( "SELECT id FROM email_templates WHERE slug = %s AND customer_id = %s", (template.slug, template.customer_id) ) else: existing = execute_query( "SELECT id FROM email_templates WHERE slug = %s AND customer_id IS NULL", (template.slug,) ) if existing: raise HTTPException(status_code=400, detail=f"A template with slug '{template.slug}' already exists for this context.") sql = """ INSERT INTO email_templates (name, slug, subject, body, category, description, variables, customer_id, is_system) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, FALSE) RETURNING id, name, slug, subject, body, category, description, variables, is_system, customer_id, created_at, updated_at """ params = ( template.name, template.slug, template.subject, template.body, template.category, template.description, json.dumps(template.variables), template.customer_id ) try: rows = execute_query(sql, params) return rows[0] except Exception as e: logger.error(f"Error creating template: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.put("/{template_id}", response_model=EmailTemplate) async def update_email_template(template_id: int, update: EmailTemplateUpdate): """Update an existing template""" # Verify existence current = execute_query("SELECT is_system FROM email_templates WHERE id = %s", (template_id,)) if not current: raise HTTPException(status_code=404, detail="Template not found") is_system = current[0]['is_system'] # Prevent changing critical fields on system templates? # Usually we allow editing subject/body, but maybe not slug. # For now, we allow everything except slug changes on system templates if defined so, # but the logic below updates whatever is provided. fields = [] params = [] if update.name is not None: fields.append("name = %s") params.append(update.name) if update.subject is not None: fields.append("subject = %s") params.append(update.subject) if update.body is not None: fields.append("body = %s") params.append(update.body) if update.category is not None: fields.append("category = %s") params.append(update.category) if update.description is not None: fields.append("description = %s") params.append(update.description) if update.variables is not None: fields.append("variables = %s") params.append(json.dumps(update.variables)) if update.customer_id is not None: fields.append("customer_id = %s") params.append(update.customer_id) # Don't change slug if it's a system template? # Usually slugs are fixed for system templates so the code can find them. # But for now we don't expose slug update in the provided model if we want to be strict. # Wait, the frontend might need to update other things. # Let's assume we don't update slug for now via this endpoint as per my minimalist implementation. if not fields: raise HTTPException(status_code=400, detail="No fields to update") fields.append("updated_at = NOW()") sql = f"UPDATE email_templates SET {', '.join(fields)} WHERE id = %s RETURNING *" params.append(template_id) rows = execute_query(sql, tuple(params)) return rows[0] @router.delete("/{template_id}") async def delete_email_template(template_id: int): """Delete a template (Non-system only)""" current = execute_query("SELECT is_system FROM email_templates WHERE id = %s", (template_id,)) if not current: raise HTTPException(status_code=404, detail="Template not found") if current[0]['is_system']: raise HTTPException(status_code=403, detail="Cannot delete system templates") execute_query("DELETE FROM email_templates WHERE id = %s", (template_id,)) return {"message": "Template deleted successfully"}