Files
life-echo/api/routers/books.py
penghanyuan 39736a2ae2 feat: 添加章节管理功能以支持清除回忆
- 在数据库模型中新增 is_active 字段,用于标记章节是否启用。
- 添加数据库迁移脚本以更新现有章节,确保默认值为 TRUE。
- 更新章节相关的 API 以仅返回 active 章节,并实现清除章节的功能。
- 在 Android 客户端中实现清除章节的确认弹窗和相应的 API 调用,提升用户体验。
2026-02-14 10:57:51 +01:00

132 lines
4.1 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.
"""
回忆录相关 API 路由
"""
from fastapi import APIRouter, Depends, HTTPException, Query, Body
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_async_db
from database.models import Book as BookModel
from database.models import User as UserModel
from services.pdf_service import pdf_service
from middleware.auth import get_current_user
router = APIRouter(prefix="/api/books", tags=["books"])
@router.get("/current")
async def get_current_book(
current_user: UserModel = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""获取当前回忆录(需要认证)"""
stmt = select(BookModel).where(BookModel.user_id == current_user.id).order_by(BookModel.updated_at.desc()).limit(1)
result = await db.execute(stmt)
book = result.scalar_one_or_none()
if not book:
return {"message": "No book found"}
return {
"id": book.id,
"title": book.title,
"total_pages": book.total_pages,
"total_words": book.total_words,
"cover_image_url": book.cover_image_url,
"has_update": book.has_update,
"last_update_chapter_id": book.last_update_chapter_id,
}
@router.post("/clear-update")
async def clear_book_update(
current_user: UserModel = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db),
):
"""清除回忆录更新标记"""
stmt = select(BookModel).where(BookModel.user_id == current_user.id).order_by(BookModel.updated_at.desc()).limit(1)
result = await db.execute(stmt)
book = result.scalar_one_or_none()
if not book:
return {"status": "ok", "message": "No book found"}
book.has_update = False
await db.commit()
return {"status": "ok"}
class UpdateBookRequest(BaseModel):
title: str
subtitle: str | None = None # 目前数据库不支持subtitle但保留字段以便将来扩展
@router.put("/{book_id}")
async def update_book(
book_id: str,
request: UpdateBookRequest = Body(...),
current_user: UserModel = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""更新书籍标题(需要认证,只能更新自己的回忆录)"""
book = await db.get(BookModel, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
# 验证用户权限
if book.user_id != current_user.id:
raise HTTPException(status_code=403, detail="无权更新此回忆录")
# 更新标题
book.title = request.title
# subtitle字段目前数据库不支持暂时忽略
await db.commit()
await db.refresh(book)
return {
"id": book.id,
"title": book.title,
"total_pages": book.total_pages,
"total_words": book.total_words,
"cover_image_url": book.cover_image_url,
"has_update": book.has_update,
"last_update_chapter_id": book.last_update_chapter_id,
}
class ExportPdfRequest(BaseModel):
book_id: str
@router.post("/export-pdf")
async def export_pdf(
request: ExportPdfRequest = Body(...),
current_user: UserModel = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""导出 PDF需要认证只能导出自己的回忆录"""
book = await db.get(BookModel, request.book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
# 验证用户权限
if book.user_id != current_user.id:
raise HTTPException(status_code=403, detail="无权导出此回忆录")
# 获取所有 active 章节
from database.models import Chapter
stmt = select(Chapter).where(
Chapter.user_id == current_user.id,
Chapter.is_active == True
).order_by(Chapter.order_index)
result = await db.execute(stmt)
chapters = result.scalars().all()
# 生成 PDF
pdf_bytes = await pdf_service.generate_pdf(book, chapters)
return {
"pdf_base64": pdf_bytes.decode('latin1'), # 简化处理,实际应该用 base64
"filename": f"{book.title}.pdf"
}