trymeapp/app/routers/favourites.py

152 lines
4.7 KiB
Python
Raw Normal View History

2026-11-11 22:06:26 +00:00
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)