Files
life-echo/api/app/features/memoir/router.py
Sully 53e0065e3e refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env)
统一错误契约
Auth 与事务边界
Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client
可观测性(OpenTelemetry + LGTM)
2026-05-22 13:44:50 +08:00

175 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
回忆录 feature — books / chapters / memoir-state 合并路由
"""
from typing import Optional
from fastapi import APIRouter, Body, Depends, Query
from app.core.deps_types import CurrentUserDep
from app.core.logging import get_logger
from app.core.openapi import error_responses
from app.features.memoir.deps import get_memoir_service
from app.features.memoir.schemas import (
BookResponse,
ChapterDetailResponse,
ChapterListItemResponse,
CoverGenerationResponse,
ExportPdfRequest,
ExportPdfResponse,
MemoirStateResponse,
NextQuestionContextResponse,
SetChapterStoryOrderRequest,
StatusMessageResponse,
StoryOrderResponse,
UpdateBookRequest,
)
from app.features.memoir.service import MemoirService
router = APIRouter(
prefix="/api",
tags=["memoir"],
responses=error_responses(401, 403, 404),
)
logger = get_logger(__name__)
# ===========================================================================
# Books endpoints
# ===========================================================================
@router.get("/books/current", response_model=BookResponse)
async def get_current_book(
current_user: CurrentUserDep,
service: MemoirService = Depends(get_memoir_service),
):
"""获取当前回忆录(需要认证)"""
return await service.get_current_book(current_user.id)
@router.post("/books/clear-update", response_model=StatusMessageResponse)
async def clear_book_update(
current_user: CurrentUserDep,
service: MemoirService = Depends(get_memoir_service),
):
"""清除回忆录更新标记"""
return await service.clear_book_update(current_user.id)
@router.put("/books/{book_id}", response_model=BookResponse)
async def update_book(
book_id: str,
current_user: CurrentUserDep,
request: UpdateBookRequest = Body(...),
service: MemoirService = Depends(get_memoir_service),
):
"""更新书籍标题(需要认证,只能更新自己的回忆录)"""
return await service.update_book(book_id, current_user.id, request.title)
@router.post("/books/export-pdf", response_model=ExportPdfResponse)
async def export_pdf(
current_user: CurrentUserDep,
request: ExportPdfRequest = Body(...),
service: MemoirService = Depends(get_memoir_service),
):
"""导出 PDF需要认证只能导出自己的回忆录"""
return await service.export_pdf(current_user.id, request.book_id)
# ===========================================================================
# Chapters endpoints
# ===========================================================================
@router.get("/chapters", response_model=list[ChapterListItemResponse])
async def get_chapters(
current_user: CurrentUserDep,
is_new: Optional[bool] = Query(None, description="仅返回未读章节"),
service: MemoirService = Depends(get_memoir_service),
):
"""
获取用户所有有效章节(需要认证,仅返回 active 章节)。
"""
return await service.get_chapters(current_user.id, is_new=is_new)
@router.get("/chapters/{chapter_id}", response_model=ChapterDetailResponse)
async def get_chapter(
chapter_id: str,
current_user: CurrentUserDep,
service: MemoirService = Depends(get_memoir_service),
):
"""获取章节详情(需要认证,只能访问自己的章节)"""
return await service.get_chapter(chapter_id, current_user.id)
@router.post("/chapters/check-cover-generation", response_model=CoverGenerationResponse)
async def check_cover_generation(
current_user: CurrentUserDep,
service: MemoirService = Depends(get_memoir_service),
):
"""
检查可生成封面的章节story-linked 且正文 asset 插图 > 3
若有则触发生成任务。已有封面的章节不再检查。
"""
return await service.check_and_trigger_cover_generation(current_user.id)
@router.delete("/chapters/{chapter_id}", response_model=StatusMessageResponse)
async def disable_chapter(
chapter_id: str,
current_user: CurrentUserDep,
service: MemoirService = Depends(get_memoir_service),
):
"""清除章节(将章节标记为 disabled需要认证只能操作自己的章节"""
return await service.disable_chapter(chapter_id, current_user.id)
@router.put("/chapters/{chapter_id}/story-order", response_model=StoryOrderResponse)
async def set_chapter_story_order(
chapter_id: str,
current_user: CurrentUserDep,
request: SetChapterStoryOrderRequest = Body(...),
service: MemoirService = Depends(get_memoir_service),
):
"""
设置章节收录的 stories 顺序(覆盖 chapter_story_links并立即物化 canonical_markdown。
"""
return await service.set_chapter_story_order(
chapter_id, current_user.id, request.story_ids
)
# ===========================================================================
# Memoir-state endpoints
# ===========================================================================
@router.get("/memoir-state", response_model=MemoirStateResponse)
async def get_memoir_state(
current_user: CurrentUserDep,
service: MemoirService = Depends(get_memoir_service),
):
"""获取当前用户回忆录状态"""
return await service.get_memoir_state(current_user.id)
@router.get("/memoir-state/next-question", response_model=NextQuestionContextResponse)
async def get_next_question_context(
current_user: CurrentUserDep,
service: MemoirService = Depends(get_memoir_service),
):
"""获取下一步问题的上下文(当前阶段与空 slot"""
return await service.get_next_question_context(current_user.id)
@router.post("/memoir-state/mark-read", response_model=StatusMessageResponse)
async def mark_memoir_read(
current_user: CurrentUserDep,
service: MemoirService = Depends(get_memoir_service),
):
"""标记回忆录更新已读"""
return await service.mark_memoir_read(current_user.id)