376 lines
11 KiB
Python
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
|
|
} |