from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.orm import Session, selectinload from app.auth import get_active_user from app.database import get_db from app.models import Favourite, Quiz, User from app.schemas import FavouriteAddIn, FavouriteItemOut, FavouriteReorderIn from app.services.quiz_service import enrich_quizzes router = APIRouter(prefix="/api/v1/favourites", tags=["Favourites"]) def _load_ordered_favourites(db: Session, user_id: int) -> list[Favourite]: rows = list( db.scalars( select(Favourite) .options( selectinload(Favourite.quiz).selectinload(Quiz.author), selectinload(Favourite.quiz).selectinload(Quiz.questions), ) .where(Favourite.user_id == user_id) .order_by(Favourite.position.asc(), Favourite.id.asc()) ).all() ) # Filter out any removed quizzes cleanly active: list[Favourite] = [] dirty = False for row in rows: if row.quiz is None or row.quiz.is_removed: db.delete(row) dirty = True else: active.append(row) for idx, row in enumerate(active): if row.position != idx: row.position = idx dirty = True if dirty: db.commit() return active def _serialize_favourites( db: Session, favs: list[Favourite], user: User ) -> list[FavouriteItemOut]: if not favs: return [] quizzes = [f.quiz for f in favs] summaries = {s.id: s for s in enrich_quizzes(db, quizzes, user)} return [ FavouriteItemOut( favourite_id=f.id, position=f.position, quiz=summaries[f.quiz_id], ) for f in favs if f.quiz_id in summaries ] @router.get("", response_model=list[FavouriteItemOut]) def list_favourites( db: Session = Depends(get_db), user: User = Depends(get_active_user), ) -> list[FavouriteItemOut]: favs = _load_ordered_favourites(db, user.id) return _serialize_favourites(db, favs, user) @router.post("", response_model=list[FavouriteItemOut], status_code=status.HTTP_201_CREATED) def add_favourite( payload: FavouriteAddIn, db: Session = Depends(get_db), user: User = Depends(get_active_user), ) -> list[FavouriteItemOut]: quiz = db.get(Quiz, payload.quiz_id) if quiz is None or quiz.is_removed: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found.") if not quiz.is_public and quiz.author_id != user.id and not user.is_admin: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found.") favs = _load_ordered_favourites(db, user.id) existing = next((f for f in favs if f.quiz_id == payload.quiz_id), None) if existing is not None: favs.remove(existing) target = existing else: target = Favourite(user_id=user.id, quiz_id=payload.quiz_id, position=len(favs)) db.add(target) insert_at = ( min(max(payload.position, 0), len(favs)) if payload.position is not None else len(favs) ) favs.insert(insert_at, target) for idx, item in enumerate(favs): item.position = idx db.commit() refreshed = _load_ordered_favourites(db, user.id) return _serialize_favourites(db, refreshed, user) @router.put("/reorder", response_model=list[FavouriteItemOut]) def reorder_favourites( payload: FavouriteReorderIn, db: Session = Depends(get_db), user: User = Depends(get_active_user), ) -> list[FavouriteItemOut]: favs = _load_ordered_favourites(db, user.id) by_quiz_id = {f.quiz_id: f for f in favs} ordered: list[Favourite] = [] seen: set[int] = set() for qid in payload.quiz_ids: if qid in by_quiz_id and qid not in seen: ordered.append(by_quiz_id[qid]) seen.add(qid) for f in favs: if f.quiz_id not in seen: ordered.append(f) for idx, f in enumerate(ordered): f.position = idx db.commit() return _serialize_favourites(db, ordered, user) @router.delete("/{quiz_id}", response_model=list[FavouriteItemOut]) def remove_favourite( quiz_id: int, db: Session = Depends(get_db), user: User = Depends(get_active_user), ) -> list[FavouriteItemOut]: existing = db.scalar( select(Favourite).where(Favourite.user_id == user.id, Favourite.quiz_id == quiz_id) ) if existing is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz is not in your favourites.", ) db.delete(existing) db.commit() favs = _load_ordered_favourites(db, user.id) return _serialize_favourites(db, favs, user)