from fastapi import APIRouter, Depends from sqlalchemy import func from sqlalchemy.orm import Session from auth import get_current_user from database import get_db from models import User, WordBookEntry from schemas import ( ActiveBookOut, BookPracticeSettingsOut, BookPracticeSettingsUpdate, SetActiveBookRequest, WordBookDetailOut, WordBookOut, WordBookProgressOut, WordBookUnitOut, ) from services.book_service import book_service router = APIRouter(prefix="/api/books", tags=["books"]) def _active_out(db: Session, user: User, book) -> ActiveBookOut: progress = book_service.book_progress(db, user, book.id) settings = book_service.practice_settings_dict(db, user, book) return ActiveBookOut( book=WordBookOut.model_validate(book), progress=WordBookProgressOut(**progress), settings=BookPracticeSettingsOut(**settings), ) @router.get("", response_model=list[WordBookOut]) def list_books( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): return book_service.list_books(db) @router.get("/settings", response_model=BookPracticeSettingsOut) def get_book_practice_settings( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): book = book_service.get_active_book(db, current_user) if not book: from fastapi import HTTPException raise HTTPException(status_code=400, detail="请先在词书设置中选择词书") data = book_service.practice_settings_dict(db, current_user, book) return BookPracticeSettingsOut(**data) @router.patch("/settings", response_model=ActiveBookOut) def update_book_practice_settings( data: BookPracticeSettingsUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): book = book_service.get_active_book(db, current_user) if data.book_id: book = book_service.activate_book(db, current_user, data.book_id) elif not book: from fastapi import HTTPException raise HTTPException(status_code=400, detail="请先选择词书") payload = data.model_dump(exclude_unset=True, exclude={"book_id"}) if payload: book_service.update_practice_settings(db, current_user, book.id, payload) return _active_out(db, current_user, book) @router.get("/active", response_model=ActiveBookOut) def get_active_book( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): book = book_service.get_active_book(db, current_user) if not book: return ActiveBookOut(book=None, progress=None, settings=None) return _active_out(db, current_user, book) @router.post("/active", response_model=ActiveBookOut) def set_active_book( data: SetActiveBookRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): book = book_service.activate_book(db, current_user, data.book_id) return _active_out(db, current_user, book) @router.get("/{book_id}", response_model=WordBookDetailOut) def get_book_detail( book_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): book = book_service.get_book(db, book_id) rows = ( db.query( WordBookEntry.unit, WordBookEntry.unit_title, func.count(WordBookEntry.id), ) .filter(WordBookEntry.book_id == book.id) .group_by(WordBookEntry.unit, WordBookEntry.unit_title) .order_by(WordBookEntry.unit.asc()) .all() ) units = [WordBookUnitOut(unit=r[0], title=r[1], word_count=r[2]) for r in rows] return WordBookDetailOut( **WordBookOut.model_validate(book).model_dump(), units=units, )