e76fa586f1
Ship dual-track learning (daily accumulation vs textbook),沪教/商务词书 APIs and UI, native iOS wrapper with bundled H5, and production book import on deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
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,
|
|
)
|