"""Album endpoints.""" import uuid from fastapi import APIRouter, Query from fastapi.responses import StreamingResponse from app.api.covers import stream_cover from app.api.deps import ( AlbumRepoDep, ArtistRepoDep, CurrentUser, FileStorageDep, StreamUser, TrackRepoDep, ) from app.api.schemas.album import AlbumOut from app.api.schemas.pagination import PagedResponse from app.api.schemas.track import TrackOut from app.api.v1.tracks import _build_track_out from app.domain.entities.album import Album from app.domain.entities.track import Artist from app.domain.errors import NotFoundError router = APIRouter(prefix="/albums", tags=["albums"]) async def _build_album_out( albums: list[Album], artists: dict[uuid.UUID, Artist], track_counts: dict[uuid.UUID, int], ) -> list[AlbumOut]: return [ AlbumOut( id=a.id, title=a.title, artist_id=a.artist_id, artist_name=artists[a.artist_id].name if a.artist_id in artists else "Unknown Artist", year=a.year, track_count=track_counts.get(a.id, 0), has_cover=bool(a.cover_path), created_at=a.created_at, ) for a in albums ] @router.get("") async def list_albums( album_repo: AlbumRepoDep, artist_repo: ArtistRepoDep, _: CurrentUser, artist_id: uuid.UUID | None = None, q: str | None = None, limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0), ) -> PagedResponse[AlbumOut]: albums = await album_repo.list(artist_id=artist_id, q=q, limit=limit, offset=offset) total = await album_repo.count(artist_id=artist_id, q=q) artist_ids = list({a.artist_id for a in albums}) artists = {a.id: a for a in await artist_repo.get_many(artist_ids)} track_counts = await album_repo.track_count_many([a.id for a in albums]) items = await _build_album_out(albums, artists, track_counts) return PagedResponse(items=items, total=total, limit=limit, offset=offset) @router.get("/{album_id}") async def get_album( album_id: uuid.UUID, album_repo: AlbumRepoDep, artist_repo: ArtistRepoDep, _: CurrentUser, ) -> AlbumOut: album = await album_repo.get_by_id(album_id) if album is None: raise NotFoundError(f"Album {album_id} not found.") artists = {a.id: a for a in await artist_repo.get_many([album.artist_id])} track_counts = await album_repo.track_count_many([album.id]) items = await _build_album_out([album], artists, track_counts) return items[0] @router.get("/{album_id}/tracks") async def get_album_tracks( album_id: uuid.UUID, track_repo: TrackRepoDep, artist_repo: ArtistRepoDep, album_repo: AlbumRepoDep, _: CurrentUser, limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0), ) -> PagedResponse[TrackOut]: album = await album_repo.get_by_id(album_id) if album is None: raise NotFoundError(f"Album {album_id} not found.") tracks = await track_repo.list( artist_id=None, album_id=album_id, q=None, sort_by="title", order="asc", limit=limit, offset=offset, ) total = await track_repo.count(artist_id=None, album_id=album_id, q=None) artist_ids = list({t.artist_id for t in tracks}) artists = {a.id: a for a in await artist_repo.get_many(artist_ids)} albums = {album.id: album} items = await _build_track_out(tracks, artists, albums) return PagedResponse(items=items, total=total, limit=limit, offset=offset) @router.get("/{album_id}/cover") async def get_album_cover( album_id: uuid.UUID, album_repo: AlbumRepoDep, storage: FileStorageDep, _: StreamUser, ) -> StreamingResponse: # ```` can't send a bearer header → StreamUser accepts ``?token=``. album = await album_repo.get_by_id(album_id) if album is None or not album.cover_path: raise NotFoundError("Cover not found.") return await stream_cover(storage, album.cover_path)