- 伏笔账本:纯函数状态机(OPEN/PARTIAL/CLOSED/OVERDUE) + ForeshadowLedger repo;验收后到期扫描(BackgroundTask 自建 session 置 OVERDUE);登记/状态变更端点 - 节奏 + 三审齐:foreshadow-analyst + pace-checker 并入 LangGraph 并行审(REVIEW_SPECS),collect 分列落 chapter_reviews(conflicts/foreshadow_sug/pace),review SSE 加 foreshadow/pace 事件 - 大纲:outliner Agent 产 OutlineResult(含 foreshadow_windows),POST /outline 逐章 upsert outline 表;GET /foreshadow?status= 看板 - 前端:伏笔四泳道看板(OVERDUE 琥珀) + 大纲编辑器(窗口徽标) + 节奏节拍图(▁▃▅) + 审稿页消费 foreshadow/pace SSE - bugfix(T3.8):并行三审共用请求 session 记账触发 'Session is already flushing' → foreshadow/pace 静默丢失;SqlAlchemyLedgerSink.record 改 add-only(靠端点/事务 commit),加并发回归测试 - M3 E2E:真实 DB + mock 网关零 token 走通 埋设→进展→验收后扫描 OVERDUE→看板 + 大纲含窗口 + 三审齐 SSE/留痕;E2E 暴露并钉住上述 bug - 门禁绿:mypy 111 / pytest 228(0 xfailed) / alembic 无漂移;前端 gen:api/lint/tsc/vitest 69/build
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""FastAPI 应用入口(ARCH §7)。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from collections.abc import AsyncIterator
|
||
from contextlib import asynccontextmanager
|
||
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.responses import JSONResponse
|
||
from ww_db import get_sessionmaker
|
||
from ww_shared import AppError, ErrorBody, ErrorEnvelope
|
||
|
||
from ww_api.logging_config import configure_logging, get_logger
|
||
from ww_api.middleware import request_id_middleware
|
||
from ww_api.routers import (
|
||
foreshadow,
|
||
health,
|
||
jobs,
|
||
outline,
|
||
projects,
|
||
settings_providers,
|
||
)
|
||
from ww_api.services.project_deps import seed_stub_user
|
||
|
||
configure_logging()
|
||
log = get_logger("ww.api")
|
||
|
||
|
||
@asynccontextmanager
|
||
async def _lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||
# 幂等 seed 单用户 stub——所有 owner_id FK 依赖它(见 memory/gotchas)。
|
||
async with get_sessionmaker()() as session:
|
||
await seed_stub_user(session)
|
||
yield
|
||
|
||
|
||
def create_app() -> FastAPI:
|
||
app = FastAPI(title="网文创作工作流 API", version="0.0.0", lifespan=_lifespan)
|
||
app.middleware("http")(request_id_middleware)
|
||
|
||
@app.exception_handler(AppError)
|
||
async def _app_error_handler(request: Request, exc: AppError) -> JSONResponse:
|
||
request_id = getattr(request.state, "request_id", None)
|
||
log.warning("app_error", code=exc.code, message=exc.message)
|
||
envelope = ErrorEnvelope(
|
||
error=ErrorBody(
|
||
code=exc.code,
|
||
message=exc.message,
|
||
details=exc.details,
|
||
request_id=request_id,
|
||
)
|
||
)
|
||
return JSONResponse(status_code=exc.http_status, content=envelope.model_dump())
|
||
|
||
app.include_router(health.router)
|
||
app.include_router(jobs.router)
|
||
app.include_router(projects.router)
|
||
app.include_router(foreshadow.router)
|
||
app.include_router(outline.router)
|
||
app.include_router(settings_providers.router)
|
||
return app
|
||
|
||
|
||
app = create_app()
|