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:
@@ -27,6 +27,7 @@ from ww_api.routers import (
|
||||
rules,
|
||||
settings_providers,
|
||||
style,
|
||||
templates,
|
||||
toolbox,
|
||||
)
|
||||
from ww_api.security.credentials import _fernet
|
||||
@@ -115,6 +116,7 @@ def create_app() -> FastAPI:
|
||||
app.include_router(outline.router)
|
||||
app.include_router(rules.router)
|
||||
app.include_router(style.router)
|
||||
app.include_router(templates.router)
|
||||
app.include_router(generation.router)
|
||||
app.include_router(generation.skills_router)
|
||||
app.include_router(toolbox.router)
|
||||
|
||||
90
apps/api/ww_api/routers/templates.py
Normal file
90
apps/api/ww_api/routers/templates.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""模板库端点(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.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 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)
|
||||
31
apps/api/ww_api/schemas/templates.py
Normal file
31
apps/api/ww_api/schemas/templates.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""模板库端点的请求/响应 schema(F3 / 契约 §F3)。
|
||||
|
||||
snake_case;前端经 OpenAPI 生成 TS 类型消费。改字段 → 前端必须 `pnpm gen:api`。
|
||||
单用户本地版:作者保存/复用提示词模板,可一键填入生成器的 brief/text。
|
||||
`title`/`body` 非空(`min_length=1`,空 → FastAPI 422)。`category`/`tool_key` 可选。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TemplateCreateRequest(BaseModel):
|
||||
"""POST /templates:新建一条提示词模板。"""
|
||||
|
||||
title: str = Field(min_length=1, description="模板标题")
|
||||
body: str = Field(min_length=1, description="模板正文(一键填入生成器的 brief/text)")
|
||||
category: str | None = Field(default=None, description="可选分类")
|
||||
tool_key: str | None = Field(default=None, description="可选关联生成器(NULL=通用)")
|
||||
|
||||
|
||||
class TemplateResponse(BaseModel):
|
||||
"""模板视图(列出/创建后回显;snake_case)。"""
|
||||
|
||||
id: uuid.UUID
|
||||
title: str
|
||||
body: str
|
||||
category: str | None = None
|
||||
tool_key: str | None = None
|
||||
@@ -29,6 +29,7 @@ from ww_core.domain.repositories import MemoryRepos, OutlineRepo, RulesRepo
|
||||
from ww_core.domain.review_repo import ReviewRepo, SqlReviewRepo
|
||||
from ww_core.domain.rule_repo import RuleWriteRepo, SqlRuleWriteRepo
|
||||
from ww_core.domain.style_repo import SqlStyleFingerprintWriteRepo, StyleFingerprintWriteRepo
|
||||
from ww_core.domain.template_repo import SqlTemplateRepo, TemplateRepo
|
||||
from ww_core.domain.world_entity_repo import SqlWorldEntityWriteRepo, WorldEntityWriteRepo
|
||||
from ww_core.memory.sql_repositories import SqlOutlineRepo, SqlRulesRepo, sql_memory_repos
|
||||
from ww_db import get_session, get_sessionmaker
|
||||
@@ -159,6 +160,16 @@ def get_rule_write_repo(
|
||||
return SqlRuleWriteRepo(session)
|
||||
|
||||
|
||||
async def get_template_repo(
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
) -> TemplateRepo:
|
||||
"""模板库 repo(GET/POST/DELETE /templates;create/delete 只 flush,端点提交)。
|
||||
|
||||
测试经 `app.dependency_overrides` 注入内存 fake。
|
||||
"""
|
||||
return SqlTemplateRepo(session)
|
||||
|
||||
|
||||
async def get_skill_registry(
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
) -> SkillRegistry:
|
||||
|
||||
Reference in New Issue
Block a user