|
| 1 | +"""Game endpoints (§6.10). List + detail; games are unscored.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +from typing import Annotated, Any |
| 6 | + |
| 7 | +from fastapi import APIRouter, Query |
| 8 | +from sqlalchemy import func |
| 9 | +from sqlmodel import select |
| 10 | +from sqlmodel.sql.expression import SelectOfScalar |
| 11 | + |
| 12 | +from app.dependencies import PaginationDep, SessionDep |
| 13 | +from app.errors import APIError, not_found |
| 14 | +from app.models.game import Game |
| 15 | +from app.routers.utils import build_ref_page |
| 16 | +from app.schemas.common import Page, ResourceRef |
| 17 | +from app.schemas.game import GameRead |
| 18 | +from app.schemas.serializers import game_read, resource_ref |
| 19 | + |
| 20 | +router = APIRouter(prefix="/games", tags=["games"]) |
| 21 | + |
| 22 | +_SORT_FIELDS: dict[str, Any] = { |
| 23 | + "name": Game.name, |
| 24 | + "release_date": Game.release_date, |
| 25 | + "rating": Game.rating, |
| 26 | + "metacritic": Game.metacritic, |
| 27 | +} |
| 28 | + |
| 29 | + |
| 30 | +def _apply_sort(stmt: SelectOfScalar[Any], sort: str | None) -> SelectOfScalar[Any]: |
| 31 | + if not sort: |
| 32 | + return stmt.order_by(Game.name) |
| 33 | + descending = sort.startswith("-") |
| 34 | + field = sort[1:] if descending else sort |
| 35 | + column = _SORT_FIELDS.get(field) |
| 36 | + if column is None: |
| 37 | + raise APIError(400, "INVALID_REQUEST", f"Cannot sort by '{field}'") |
| 38 | + return stmt.order_by(column.desc() if descending else column.asc()) |
| 39 | + |
| 40 | + |
| 41 | +@router.get("", summary="List games") |
| 42 | +def list_games( |
| 43 | + session: SessionDep, |
| 44 | + pagination: PaginationDep, |
| 45 | + sort: Annotated[str | None, Query()] = None, |
| 46 | +) -> Page[ResourceRef]: |
| 47 | + count = session.exec(select(func.count()).select_from(Game)).one() |
| 48 | + list_stmt = _apply_sort(select(Game), sort).offset(pagination.offset).limit(pagination.limit) |
| 49 | + rows = session.exec(list_stmt).all() |
| 50 | + |
| 51 | + refs = [resource_ref("games", row.slug, row.name) for row in rows] |
| 52 | + applied = {k: v for k, v in (("sort", sort),) if v} |
| 53 | + return build_ref_page( |
| 54 | + refs, count=count, path="/v1/games", pagination=pagination, filters=applied |
| 55 | + ) |
| 56 | + |
| 57 | + |
| 58 | +@router.get("/{slug}", summary="Get a game") |
| 59 | +def get_game(slug: str, session: SessionDep) -> GameRead: |
| 60 | + game = session.exec(select(Game).where(Game.slug == slug)).first() |
| 61 | + if game is None: |
| 62 | + raise not_found("Game", slug) |
| 63 | + return game_read(game) |
0 commit comments