feat(templates): F3b 模板库 repo + 端点(GET/POST/DELETE /templates)

- TemplateRepo/SqlTemplateRepo(list/create/delete,owner stub 过滤,只 flush 端点 commit)
- schemas/templates.py(title/body min_length=1→422)+ routers/templates.py(删不存在→404)+ main 注册
- 单测:repo CRUD/owner 隔离/frozen + 端点 201/列出/204/422×2/404
This commit is contained in:
Yaojia Wang
2026-06-23 20:17:32 +02:00
parent 9bb453acc8
commit cfe6a535b0
8 changed files with 480 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
"""模板库端点F3 / 契约 §F3
单用户本地版:作者保存/复用提示词模板,可一键填入生成器的 brief/text。
**不做分享/市场**需多租户。owner_id 全程 stub单用户原型
- GET /templates 列出当前用户的模板。
- POST /templates 新建模板201title/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.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) -> list[TemplateResponse]:
"""列出当前用户stub的模板。"""
views = await repo.list_for_owner(STUB_OWNER_ID)
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 422schema `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)