|
| 1 | +"""Website endpoints (§6.12). List + detail; websites 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.website import Website |
| 15 | +from app.routers.utils import build_ref_page |
| 16 | +from app.schemas.common import Page, ResourceRef |
| 17 | +from app.schemas.serializers import resource_ref, website_read |
| 18 | +from app.schemas.website import WebsiteRead |
| 19 | + |
| 20 | +router = APIRouter(prefix="/websites", tags=["websites"]) |
| 21 | + |
| 22 | +_SORT_FIELDS: dict[str, Any] = { |
| 23 | + "name": Website.name, |
| 24 | + "launch_date": Website.launch_date, |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +def _apply_sort(stmt: SelectOfScalar[Any], sort: str | None) -> SelectOfScalar[Any]: |
| 29 | + if not sort: |
| 30 | + return stmt.order_by(Website.name) |
| 31 | + descending = sort.startswith("-") |
| 32 | + field = sort[1:] if descending else sort |
| 33 | + column = _SORT_FIELDS.get(field) |
| 34 | + if column is None: |
| 35 | + raise APIError(400, "INVALID_REQUEST", f"Cannot sort by '{field}'") |
| 36 | + return stmt.order_by(column.desc() if descending else column.asc()) |
| 37 | + |
| 38 | + |
| 39 | +@router.get("", summary="List websites") |
| 40 | +def list_websites( |
| 41 | + session: SessionDep, |
| 42 | + pagination: PaginationDep, |
| 43 | + sort: Annotated[str | None, Query()] = None, |
| 44 | +) -> Page[ResourceRef]: |
| 45 | + count = session.exec(select(func.count()).select_from(Website)).one() |
| 46 | + list_stmt = _apply_sort(select(Website), sort) |
| 47 | + list_stmt = list_stmt.offset(pagination.offset).limit(pagination.limit) |
| 48 | + rows = session.exec(list_stmt).all() |
| 49 | + |
| 50 | + refs = [resource_ref("websites", row.slug, row.name) for row in rows] |
| 51 | + applied = {k: v for k, v in (("sort", sort),) if v} |
| 52 | + return build_ref_page( |
| 53 | + refs, count=count, path="/v1/websites", pagination=pagination, filters=applied |
| 54 | + ) |
| 55 | + |
| 56 | + |
| 57 | +@router.get("/{slug}", summary="Get a website") |
| 58 | +def get_website(slug: str, session: SessionDep) -> WebsiteRead: |
| 59 | + website = session.exec(select(Website).where(Website.slug == slug)).first() |
| 60 | + if website is None: |
| 61 | + raise not_found("Website", slug) |
| 62 | + return website_read(website) |
0 commit comments