"""Template library endpoints.""" from __future__ import annotations import os from fastapi import APIRouter, Depends, HTTPException, Request from sqlmodel import Session from auth import get_current_user, require_admin from database import get_session from models.agent import Agent from models.stack import Stack from models.template import TemplateInstantiateRequest, TemplateSaveRequest from models.user import User from services import agent_service, audit_service, compose_service, template_service from services.agent_service import AgentError router = APIRouter(prefix="/api/templates", tags=["templates"]) def _ip(request: Request) -> str: return request.client.host if request.client else "unknown" @router.get("") def list_templates( session: Session = Depends(get_session), _user: User = Depends(get_current_user), ) -> list[dict]: return template_service.list_templates(session) @router.get("/{template_id}") def get_template( template_id: str, session: Session = Depends(get_session), _user: User = Depends(get_current_user), ) -> dict: tpl = template_service.get_template(session, template_id) if not tpl: raise HTTPException(status_code=404, detail="Template not found") return tpl @router.post("") def save_template( body: TemplateSaveRequest, request: Request, session: Session = Depends(get_session), user: User = Depends(require_admin), ) -> dict: tpl = template_service.save_custom( session, body.name, body.yaml, body.description or "", body.tags ) audit_service.record( session, user=user.username, action="template.save", target=tpl.slug, ip=_ip(request) ) return {"id": f"custom:{tpl.slug}", "name": tpl.name} @router.delete("/custom/{slug}") def delete_template( slug: str, request: Request, session: Session = Depends(get_session), user: User = Depends(require_admin), ) -> dict: if not template_service.delete_custom(session, slug): raise HTTPException(status_code=404, detail="Custom template not found") audit_service.record( session, user=user.username, action="template.delete", target=slug, ip=_ip(request) ) return {"ok": True} @router.post("/{template_id}/instantiate", status_code=201) async def instantiate( template_id: str, body: TemplateInstantiateRequest, request: Request, session: Session = Depends(get_session), user: User = Depends(require_admin), ) -> dict: tpl = template_service.get_template(session, template_id) if not tpl: raise HTTPException(status_code=404, detail="Template not found") rendered = template_service.render(tpl["yaml"], body.values) if body.agent_id is not None: agent = session.get(Agent, body.agent_id) if not agent: raise HTTPException(status_code=404, detail=f"Agent {body.agent_id} not found") try: result = await agent_service.call( session, agent, "POST", "/agent/stacks", json={"name": body.name, "yaml": rendered, "env": None}, ) except AgentError as exc: raise HTTPException( status_code=exc.status if exc.status >= 400 else 502, detail={"error": exc.error, "detail": exc.detail}, ) audit_service.record( session, user=user.username, action="template.instantiate", target=f"{agent.name}/{result.get('id')}", detail=template_id, ip=_ip(request), ) return {"id": result.get("id"), "name": body.name, "agent_id": agent.id} stack_id = compose_service.slugify(body.name) if session.get(Stack, stack_id) or os.path.isdir(compose_service.stack_dir(stack_id)): raise HTTPException(status_code=409, detail=f"Stack '{stack_id}' already exists") compose_service.write_compose(stack_id, rendered) stack = Stack(id=stack_id, name=body.name, description=tpl.get("description")) session.add(stack) session.commit() audit_service.record( session, user=user.username, action="template.instantiate", target=stack_id, detail=template_id, ip=_ip(request), ) return {"id": stack_id, "name": body.name, "agent_id": None}