from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query from sqlmodel import Session, select, func from ..database import get_session from ..models import Novel, Outline from ..schemas import ( OutlineCreate, OutlineUpdate, OutlineRead, OutlineList, OutlineReorder, ) router = APIRouter(prefix="/api/novels/{novel_id}/outlines", tags=["outlines"]) def _get_novel_or_404(novel_id: int, session: Session) -> Novel: novel = session.get(Novel, novel_id) if not novel: raise HTTPException(status_code=404, detail="小说不存在") return novel def _get_outline_or_404( outline_id: int, novel_id: int, session: Session ) -> Outline: outline = session.get(Outline, outline_id) if not outline or outline.novel_id != novel_id: raise HTTPException(status_code=404, detail="大纲不存在") return outline @router.get("", response_model=OutlineList) def list_outlines( novel_id: int, session: Session = Depends(get_session), ): _get_novel_or_404(novel_id, session) total = session.exec( select(func.count(Outline.id)).where(Outline.novel_id == novel_id) ).one() items = session.exec( select(Outline) .where(Outline.novel_id == novel_id) .order_by(Outline.sort_order) ).all() return OutlineList( items=[OutlineRead.model_validate(o, from_attributes=True) for o in items], total=total, ) @router.post("", response_model=OutlineRead, status_code=201) def create_outline( novel_id: int, data: OutlineCreate, session: Session = Depends(get_session), ): _get_novel_or_404(novel_id, session) # 自动设置 sort_order 为当前最大值 + 1 max_order = session.exec( select(func.max(Outline.sort_order)).where(Outline.novel_id == novel_id) ).one() next_order = (max_order or 0) + 1 outline = Outline(novel_id=novel_id, sort_order=next_order, **data.model_dump()) session.add(outline) session.commit() session.refresh(outline) return outline @router.put("/reorder", response_model=OutlineList) def reorder_outlines( novel_id: int, data: OutlineReorder, session: Session = Depends(get_session), ): _get_novel_or_404(novel_id, session) now = datetime.now(timezone.utc) for idx, oid in enumerate(data.ordered_ids): outline = _get_outline_or_404(oid, novel_id, session) outline.sort_order = idx + 1 outline.updated_at = now session.add(outline) session.commit() items = session.exec( select(Outline) .where(Outline.novel_id == novel_id) .order_by(Outline.sort_order) ).all() total = len(items) return OutlineList( items=[OutlineRead.model_validate(o, from_attributes=True) for o in items], total=total, ) @router.get("/{outline_id}", response_model=OutlineRead) def get_outline( novel_id: int, outline_id: int, session: Session = Depends(get_session), ): return _get_outline_or_404(outline_id, novel_id, session) @router.patch("/{outline_id}", response_model=OutlineRead) def update_outline( novel_id: int, outline_id: int, data: OutlineUpdate, session: Session = Depends(get_session), ): outline = _get_outline_or_404(outline_id, novel_id, session) update_data = data.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(outline, key, value) outline.updated_at = datetime.now(timezone.utc) session.add(outline) session.commit() session.refresh(outline) return outline @router.delete("/{outline_id}", status_code=204) def delete_outline( novel_id: int, outline_id: int, session: Session = Depends(get_session), ): outline = _get_outline_or_404(outline_id, novel_id, session) session.delete(outline) session.commit()