youtube-summarizer/backend/api/templates.py

376 lines
11 KiB
Python

"""
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
}