152 lines
4.7 KiB
Python
152 lines
4.7 KiB
Python
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)
|