"""Playlist endpoints.""" import uuid from typing import Any from fastapi import APIRouter, Query, Response from app.api.deps import ( AlbumRepoDep, ArtistRepoDep, CurrentUser, PlaylistRepoDep, TrackRepoDep, ) from app.api.schemas.pagination import PagedResponse from app.api.schemas.playlist import PlaylistAddTrack, PlaylistCreate, PlaylistOut, PlaylistUpdate from app.api.schemas.track import TrackOut from app.api.v1.tracks import _build_track_out from app.domain.entities.playlist import Playlist from app.domain.errors import NotFoundError, PermissionDeniedError from app.infrastructure.db.repositories.playlist_repository import SqlAlchemyPlaylistRepository router = APIRouter(prefix="/playlists", tags=["playlists"]) async def _build_playlist_out( playlists: list[Playlist], playlist_repo: SqlAlchemyPlaylistRepository ) -> list[PlaylistOut]: ids = [p.id for p in playlists] counts = await playlist_repo.track_count_many(ids) return [ PlaylistOut( id=p.id, name=p.name, description=p.description, owner_id=p.owner_id, version=p.version, track_count=counts.get(p.id, 0), created_at=p.created_at, ) for p in playlists ] @router.get("") async def list_playlists( playlist_repo: PlaylistRepoDep, user: CurrentUser, limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0), ) -> PagedResponse[PlaylistOut]: playlists = await playlist_repo.list(owner_id=user.id, limit=limit, offset=offset) total = await playlist_repo.count(owner_id=user.id) items = await _build_playlist_out(playlists, playlist_repo) return PagedResponse(items=items, total=total, limit=limit, offset=offset) @router.post("", status_code=201) async def create_playlist( body: PlaylistCreate, playlist_repo: PlaylistRepoDep, user: CurrentUser, ) -> PlaylistOut: playlist = await playlist_repo.add( name=body.name, description=body.description, owner_id=user.id ) items = await _build_playlist_out([playlist], playlist_repo) return items[0] @router.get("/{playlist_id}") async def get_playlist( playlist_id: uuid.UUID, playlist_repo: PlaylistRepoDep, _: CurrentUser, ) -> PlaylistOut: playlist = await playlist_repo.get_by_id(playlist_id) if playlist is None: raise NotFoundError(f"Playlist {playlist_id} not found.") items = await _build_playlist_out([playlist], playlist_repo) return items[0] @router.patch("/{playlist_id}") async def update_playlist( playlist_id: uuid.UUID, body: PlaylistUpdate, playlist_repo: PlaylistRepoDep, user: CurrentUser, ) -> PlaylistOut: playlist = await playlist_repo.get_by_id(playlist_id) if playlist is None: raise NotFoundError(f"Playlist {playlist_id} not found.") if playlist.owner_id != user.id: raise PermissionDeniedError("You don't own this playlist.") updated = await playlist_repo.update(playlist_id, name=body.name, description=body.description) items = await _build_playlist_out([updated], playlist_repo) return items[0] @router.delete("/{playlist_id}", status_code=204) async def delete_playlist( playlist_id: uuid.UUID, playlist_repo: PlaylistRepoDep, user: CurrentUser, ) -> Response: playlist = await playlist_repo.get_by_id(playlist_id) if playlist is None: raise NotFoundError(f"Playlist {playlist_id} not found.") if playlist.owner_id != user.id: raise PermissionDeniedError("You don't own this playlist.") await playlist_repo.delete(playlist_id) return Response(status_code=204) @router.get("/{playlist_id}/tracks") async def get_playlist_tracks( playlist_id: uuid.UUID, playlist_repo: PlaylistRepoDep, artist_repo: ArtistRepoDep, album_repo: AlbumRepoDep, _: CurrentUser, limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0), ) -> PagedResponse[TrackOut]: playlist = await playlist_repo.get_by_id(playlist_id) if playlist is None: raise NotFoundError(f"Playlist {playlist_id} not found.") tracks = await playlist_repo.get_tracks(playlist_id, limit=limit, offset=offset) total = await playlist_repo.get_track_total(playlist_id) artist_ids = list({t.artist_id for t in tracks}) album_ids = list({t.album_id for t in tracks if t.album_id is not None}) artists_map = {a.id: a for a in await artist_repo.get_many(artist_ids)} albums_map = {a.id: a for a in await album_repo.get_many(album_ids)} items = await _build_track_out(tracks, artists_map, albums_map) return PagedResponse(items=items, total=total, limit=limit, offset=offset) @router.post("/{playlist_id}/tracks", status_code=204) async def add_playlist_track( playlist_id: uuid.UUID, body: PlaylistAddTrack, playlist_repo: PlaylistRepoDep, track_repo: TrackRepoDep, user: CurrentUser, ) -> Response: playlist = await playlist_repo.get_by_id(playlist_id) if playlist is None: raise NotFoundError(f"Playlist {playlist_id} not found.") if playlist.owner_id != user.id: raise PermissionDeniedError("You don't own this playlist.") track = await track_repo.get_by_id(body.track_id) if track is None: raise NotFoundError(f"Track {body.track_id} not found.") position = body.position if position is None: position = await playlist_repo.max_position(playlist_id) + 1.0 await playlist_repo.add_track(playlist_id, body.track_id, position=position) return Response(status_code=204) @router.delete("/{playlist_id}/tracks/{track_id}", status_code=204) async def remove_playlist_track( playlist_id: uuid.UUID, track_id: uuid.UUID, playlist_repo: PlaylistRepoDep, user: CurrentUser, ) -> Response: playlist = await playlist_repo.get_by_id(playlist_id) if playlist is None: raise NotFoundError(f"Playlist {playlist_id} not found.") if playlist.owner_id != user.id: raise PermissionDeniedError("You don't own this playlist.") await playlist_repo.remove_track(playlist_id, track_id) return Response(status_code=204) @router.put("/{playlist_id}/tracks/reorder") async def reorder_playlist_tracks(playlist_id: uuid.UUID, _: CurrentUser) -> Any: ... @router.get("/{playlist_id}/cover") async def get_playlist_cover(playlist_id: uuid.UUID, _: CurrentUser) -> Any: ...