""" Prompt Template Management API. [AC-AISVC-52, AC-AISVC-57, AC-AISVC-58, AC-AISVC-54, AC-AISVC-55] Prompt template CRUD and publish/rollback endpoints. """ import logging import uuid from typing import Any from fastapi import APIRouter, Depends, Header, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_session from app.models.entities import PromptTemplateCreate, PromptTemplateUpdate from app.services.prompt.template_service import PromptTemplateService logger = logging.getLogger(__name__) router = APIRouter(prefix="/admin/prompt-templates", tags=["Prompt Management"]) def get_tenant_id(x_tenant_id: str = Header(..., alias="X-Tenant-Id")) -> str: """Extract tenant ID from header.""" if not x_tenant_id: raise HTTPException(status_code=400, detail="X-Tenant-Id header is required") return x_tenant_id @router.get("") async def list_templates( tenant_id: str = Depends(get_tenant_id), scene: str | None = None, session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-57] List all prompt templates for a tenant. """ logger.info(f"[AC-AISVC-57] Listing prompt templates for tenant={tenant_id}, scene={scene}") service = PromptTemplateService(session) templates = await service.list_templates(tenant_id, scene) data = [] for t in templates: published_version = await service.get_published_version_info(tenant_id, t.id) data.append({ "id": str(t.id), "name": t.name, "scene": t.scene, "description": t.description, "is_default": t.is_default, "published_version": published_version, "created_at": t.created_at.isoformat(), "updated_at": t.updated_at.isoformat(), }) return {"data": data} @router.post("", status_code=201) async def create_template( body: PromptTemplateCreate, tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-52] Create a new prompt template. """ logger.info(f"[AC-AISVC-52] Creating prompt template for tenant={tenant_id}, name={body.name}") service = PromptTemplateService(session) template = await service.create_template(tenant_id, body) return { "id": str(template.id), "name": template.name, "scene": template.scene, "description": template.description, "is_default": template.is_default, "created_at": template.created_at.isoformat(), "updated_at": template.updated_at.isoformat(), } @router.get("/{tpl_id}") async def get_template_detail( tpl_id: uuid.UUID, tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-58] Get prompt template detail with version history. """ logger.info(f"[AC-AISVC-58] Getting template detail for tenant={tenant_id}, id={tpl_id}") service = PromptTemplateService(session) detail = await service.get_template_detail(tenant_id, tpl_id) if not detail: raise HTTPException(status_code=404, detail="Template not found") return detail @router.put("/{tpl_id}") async def update_template( tpl_id: uuid.UUID, body: PromptTemplateUpdate, tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-53] Update prompt template (creates a new version). """ logger.info(f"[AC-AISVC-53] Updating template for tenant={tenant_id}, id={tpl_id}") service = PromptTemplateService(session) template = await service.update_template(tenant_id, tpl_id, body) if not template: raise HTTPException(status_code=404, detail="Template not found") published_version = await service.get_published_version_info(tenant_id, template.id) return { "id": str(template.id), "name": template.name, "scene": template.scene, "description": template.description, "is_default": template.is_default, "published_version": published_version, "created_at": template.created_at.isoformat(), "updated_at": template.updated_at.isoformat(), } @router.post("/{tpl_id}/publish") async def publish_template( tpl_id: uuid.UUID, body: dict[str, int], tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-54] Publish a specific version of the template. """ version = body.get("version") if version is None: raise HTTPException(status_code=400, detail="version is required") logger.info( f"[AC-AISVC-54] Publishing template version for tenant={tenant_id}, " f"id={tpl_id}, version={version}" ) service = PromptTemplateService(session) success = await service.publish_version(tenant_id, tpl_id, version) if not success: raise HTTPException(status_code=404, detail="Template or version not found") return {"success": True, "message": f"Version {version} published successfully"} @router.post("/{tpl_id}/rollback") async def rollback_template( tpl_id: uuid.UUID, body: dict[str, int], tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-55] Rollback to a specific historical version. """ version = body.get("version") if version is None: raise HTTPException(status_code=400, detail="version is required") logger.info( f"[AC-AISVC-55] Rolling back template for tenant={tenant_id}, " f"id={tpl_id}, version={version}" ) service = PromptTemplateService(session) success = await service.rollback_version(tenant_id, tpl_id, version) if not success: raise HTTPException(status_code=404, detail="Template or version not found") return {"success": True, "message": f"Rolled back to version {version} successfully"} @router.delete("/{tpl_id}", status_code=204) async def delete_template( tpl_id: uuid.UUID, tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> None: """ Delete a prompt template and all its versions. """ logger.info(f"Deleting template for tenant={tenant_id}, id={tpl_id}") service = PromptTemplateService(session) success = await service.delete_template(tenant_id, tpl_id) if not success: raise HTTPException(status_code=404, detail="Template not found")