""" Template API endpoints for YouTube Summarizer Manages custom export templates """ from typing import List, Optional from fastapi import APIRouter, HTTPException, Depends, Body from pydantic import BaseModel, Field from enum import Enum from ..services.template_manager import TemplateManager, TemplateType, ExportTemplate # Create router router = APIRouter(prefix="/api/templates", tags=["templates"]) class TemplateTypeEnum(str, Enum): """Template type enum for API""" MARKDOWN = "markdown" HTML = "html" TEXT = "text" class CreateTemplateRequest(BaseModel): """Request model for creating a template""" name: str = Field(..., description="Template name") type: TemplateTypeEnum = Field(..., description="Template type") content: str = Field(..., description="Template content with Jinja2 syntax") description: Optional[str] = Field(None, description="Template description") class UpdateTemplateRequest(BaseModel): """Request model for updating a template""" content: str = Field(..., description="Updated template content") description: Optional[str] = Field(None, description="Updated description") class TemplateResponse(BaseModel): """Response model for template""" id: str name: str description: str type: str variables: List[str] is_default: bool preview_available: bool = True class TemplateDetailResponse(TemplateResponse): """Detailed template response with content""" content: str preview: Optional[str] = None class RenderTemplateRequest(BaseModel): """Request to render a template""" template_name: str = Field(..., description="Template name") template_type: TemplateTypeEnum = Field(..., description="Template type") data: dict = Field(..., description="Data to render with template") # Initialize template manager template_manager = TemplateManager() @router.get("/list", response_model=List[TemplateResponse]) async def list_templates( template_type: Optional[TemplateTypeEnum] = None ): """ List all available templates Optionally filter by template type """ type_filter = TemplateType[template_type.value.upper()] if template_type else None templates = template_manager.list_templates(type_filter) return [ TemplateResponse( id=t.id, name=t.name, description=t.description, type=t.type.value, variables=t.variables, is_default=t.is_default ) for t in templates ] @router.get("/{template_type}/{template_name}", response_model=TemplateDetailResponse) async def get_template( template_type: TemplateTypeEnum, template_name: str, include_preview: bool = False ): """ Get a specific template with details Optionally include a preview with sample data """ t_type = TemplateType[template_type.value.upper()] template = template_manager.get_template(template_name, t_type) if not template: raise HTTPException(status_code=404, detail="Template not found") preview = None if include_preview: try: preview = template_manager.get_template_preview(template_name, t_type) except Exception as e: preview = f"Preview generation failed: {str(e)}" return TemplateDetailResponse( id=template.id, name=template.name, description=template.description, type=template.type.value, variables=template.variables, is_default=template.is_default, content=template.content, preview=preview ) @router.post("/create", response_model=TemplateResponse) async def create_template(request: CreateTemplateRequest): """ Create a new custom template Templates use Jinja2 syntax for variable substitution """ try: t_type = TemplateType[request.type.value.upper()] # Check if template with same name exists existing = template_manager.get_template(request.name, t_type) if existing: raise HTTPException( status_code=409, detail=f"Template '{request.name}' already exists for type {request.type}" ) # Create template template = template_manager.create_template( name=request.name, template_type=t_type, content=request.content, description=request.description or f"Custom {request.type} template" ) return TemplateResponse( id=template.id, name=template.name, description=template.description, type=template.type.value, variables=template.variables, is_default=template.is_default ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to create template: {str(e)}") @router.put("/{template_type}/{template_name}", response_model=TemplateResponse) async def update_template( template_type: TemplateTypeEnum, template_name: str, request: UpdateTemplateRequest ): """ Update an existing custom template Default templates cannot be modified """ if template_name == "default": raise HTTPException(status_code=403, detail="Cannot modify default templates") try: t_type = TemplateType[template_type.value.upper()] # Check if template exists existing = template_manager.get_template(template_name, t_type) if not existing: raise HTTPException(status_code=404, detail="Template not found") # Update template template = template_manager.update_template( name=template_name, template_type=t_type, content=request.content ) return TemplateResponse( id=template.id, name=template.name, description=request.description or template.description, type=template.type.value, variables=template.variables, is_default=template.is_default ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to update template: {str(e)}") @router.delete("/{template_type}/{template_name}") async def delete_template( template_type: TemplateTypeEnum, template_name: str ): """ Delete a custom template Default templates cannot be deleted """ if template_name == "default": raise HTTPException(status_code=403, detail="Cannot delete default templates") try: t_type = TemplateType[template_type.value.upper()] # Check if template exists existing = template_manager.get_template(template_name, t_type) if not existing: raise HTTPException(status_code=404, detail="Template not found") # Delete template template_manager.delete_template(template_name, t_type) return {"message": f"Template '{template_name}' deleted successfully"} except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to delete template: {str(e)}") @router.post("/render") async def render_template(request: RenderTemplateRequest): """ Render a template with provided data Returns the rendered content as plain text """ try: t_type = TemplateType[request.template_type.value.upper()] # Validate template exists template = template_manager.get_template(request.template_name, t_type) if not template: raise HTTPException(status_code=404, detail="Template not found") # Validate required variables are provided missing_vars = template_manager.validate_template_data( request.template_name, t_type, request.data ) if missing_vars: raise HTTPException( status_code=400, detail=f"Missing required template variables: {', '.join(missing_vars)}" ) # Render template rendered = template_manager.render_template( request.template_name, t_type, request.data ) return { "rendered_content": rendered, "template_name": request.template_name, "template_type": request.template_type } except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to render template: {str(e)}") @router.post("/validate") async def validate_template( content: str = Body(..., description="Template content to validate"), template_type: TemplateTypeEnum = Body(..., description="Template type") ): """ Validate template syntax without saving Returns validation result and extracted variables """ try: from jinja2 import Template, TemplateError import jinja2.meta # Try to parse template template = Template(content) env = template_manager.env ast = env.parse(content) variables = list(jinja2.meta.find_undeclared_variables(ast)) return { "valid": True, "variables": variables, "message": "Template syntax is valid" } except TemplateError as e: return { "valid": False, "variables": [], "message": f"Template syntax error: {str(e)}" } except Exception as e: return { "valid": False, "variables": [], "message": f"Validation error: {str(e)}" } @router.get("/variables/{template_type}/{template_name}") async def get_template_variables( template_type: TemplateTypeEnum, template_name: str ): """ Get list of variables required by a template Useful for building dynamic forms """ t_type = TemplateType[template_type.value.upper()] template = template_manager.get_template(template_name, t_type) if not template: raise HTTPException(status_code=404, detail="Template not found") # Categorize variables by prefix categorized = { "video_metadata": [], "export_metadata": [], "custom": [] } for var in template.variables: if var.startswith("video_metadata"): categorized["video_metadata"].append(var) elif var.startswith("export_metadata"): categorized["export_metadata"].append(var) else: categorized["custom"].append(var) return { "template_name": template_name, "template_type": template_type, "all_variables": template.variables, "categorized_variables": categorized }