"""模板库端点(F3 / 契约 §F3)。 单用户本地版:作者保存/复用提示词模板,可一键填入生成器的 brief/text。 **不做分享/市场**(需多租户)。owner_id 全程 stub(单用户原型)。 - GET /templates 列出当前用户的模板。 - POST /templates 新建模板(201;title/body 空 → 422)。 - DELETE /templates/:id 删除模板(204;不存在 → 404)。 提交边界:`TemplateRepo.create`/`delete` 只 `flush()`,端点写后 `await session.commit()` (仿 rules/foreshadow 写侧,见 memory/gotchas)。 """ from __future__ import annotations import uuid from typing import Annotated from fastapi import APIRouter, Depends, Request, Response from sqlalchemy.ext.asyncio import AsyncSession from ww_core.domain import TemplateCreate, TemplateRepo from ww_db import get_session from ww_shared import AppError, ErrorCode from ww_api.logging_config import get_logger from ww_api.pagination import PageDep from ww_api.schemas.templates import TemplateCreateRequest, TemplateResponse from ww_api.services.credentials import STUB_OWNER_ID from ww_api.services.project_deps import get_template_repo log = get_logger("ww.api.templates") router = APIRouter(prefix="/templates", tags=["templates"]) TemplateRepoDep = Annotated[TemplateRepo, Depends(get_template_repo)] SessionDep = Annotated[AsyncSession, Depends(get_session)] @router.get("") async def list_templates(repo: TemplateRepoDep, page: PageDep) -> list[TemplateResponse]: """列出当前用户(stub)的模板(分页;`?limit=&offset=`,默认第一页)。""" views = await repo.list_for_owner(STUB_OWNER_ID, limit=page.limit, offset=page.offset) return [ TemplateResponse( id=v.id, title=v.title, body=v.body, category=v.category, tool_key=v.tool_key ) for v in views ] @router.post("", status_code=201) async def create_template( body: TemplateCreateRequest, request: Request, repo: TemplateRepoDep, session: SessionDep, ) -> TemplateResponse: """新建模板(201)。title/body 空 → FastAPI 422(schema `min_length=1`)。""" request_id = getattr(request.state, "request_id", None) view = await repo.create( STUB_OWNER_ID, TemplateCreate( title=body.title, body=body.body, category=body.category, tool_key=body.tool_key ), ) await session.commit() log.info("template_created", template_id=str(view.id), request_id=request_id) return TemplateResponse( id=view.id, title=view.title, body=view.body, category=view.category, tool_key=view.tool_key, ) @router.delete("/{template_id}", status_code=204) async def delete_template( template_id: uuid.UUID, request: Request, repo: TemplateRepoDep, session: SessionDep, ) -> Response: """删除模板(204)。不存在 → 404 NOT_FOUND。""" request_id = getattr(request.state, "request_id", None) deleted = await repo.delete(STUB_OWNER_ID, template_id) if not deleted: raise AppError(ErrorCode.NOT_FOUND, f"template not found: {template_id}") await session.commit() log.info("template_deleted", template_id=str(template_id), request_id=request_id) return Response(status_code=204)