Files
john e76fa586f1 Add word-book practice, iOS app shell, and fix embedded WebView blank screen.
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>
2026-06-06 21:09:27 +08:00

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,
)