GET /projects 与 GET /templates 原一次返回全部 owner 行,行数随数据线性增长。 新增可复用分页依赖 ww_api.pagination.get_page(limit∈[1,200] 默认 50、offset≥0, 越界 → 422),两端点接入,Repository.list_for_owner 加 keyword-only limit/offset 把 LIMIT/OFFSET 下推 DB。向后兼容:不传参返回第一页。补 projects 分页 + 边界 422 测试。
92 lines
3.3 KiB
Python
92 lines
3.3 KiB
Python
"""模板库端点(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)
|