"""Playlists API routes.""" from typing import List from fastapi import APIRouter, HTTPException, Query from app.api.dependencies import CurrentUser, DBSession from app.schemas.playlist import ( AddTrackRequest, PlaylistCreate, PlaylistResponse, PlaylistWithTracks, PlaylistUpdate, PlaylistTrackResponse, ReorderTracksRequest, ) from app.services.playlist_service import PlaylistService router = APIRouter(prefix="/playlists", tags=["playlists"]) @router.get("", response_model=List[PlaylistResponse]) async def get_playlists( current_user: CurrentUser, db: DBSession, limit: int = Query(50, ge=1, le=100), offset: int = Query(0, ge=0), ): """ Get all playlists for current user. - **limit**: Maximum results (1-100) - **offset**: Pagination offset """ playlist_service = PlaylistService(db) playlists = await playlist_service.get_user_playlists( user_id=current_user.id, limit=limit, offset=offset, ) return [PlaylistResponse.model_validate(p) for p in playlists] @router.post("", response_model=PlaylistResponse, status_code=status.HTTP_201_CREATED) async def create_playlist( playlist_data: PlaylistCreate, current_user: CurrentUser, db: DBSession, ): """ Create a new playlist. - **name**: Playlist name (required) - **description**: Optional description - **image_url**: Optional cover image URL - **is_public**: Whether playlist is public (default: false) - **is_collaborative**: Whether playlist is collaborative (default: false) """ playlist_service = PlaylistService(db) playlist = await playlist_service.create_playlist( user_id=current_user.id, name=playlist_data.name, description=playlist_data.description, image_url=playlist_data.image_url, is_public=playlist_data.is_public, is_collaborative=playlist_data.is_collaborative, ) return PlaylistResponse.model_validate(playlist) @router.get("/{playlist_id}", response_model=PlaylistWithTracks) async def get_playlist( playlist_id: str, current_user: CurrentUser, db: DBSession, include_tracks: bool = Query(True, description="Include tracks in response"), ): """ Get a playlist by ID. - **include_tracks**: Whether to include tracks (default: true) """ from uuid import UUID playlist_service = PlaylistService(db) try: playlist = await playlist_service.get_playlist( UUID(playlist_id), include_tracks=include_tracks, ) if not playlist: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Playlist not found", ) # Check access permissions if not playlist.is_public and playlist.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to view this playlist", ) response = PlaylistWithTracks.model_validate(playlist) if include_tracks and playlist.playlist_tracks: # Load tracks with details from sqlalchemy import select from app.models.track import Track track_ids = [pt.track_id for pt in playlist.playlist_tracks] stmt = ( select(Track) .where(Track.id.in_(track_ids)) ) result = await db.execute(stmt) tracks = {t.id: t for t in result.scalars().all()} # Build response with track details response.tracks = [ { "id": str(pt.track_id), "position": pt.position, "added_at": pt.added_at.isoformat(), "added_by": str(pt.added_by) if pt.added_by else None, "track": { "id": str(tracks[pt.track_id].id), "title": tracks[pt.track_id].title, "duration": tracks[pt.track_id].duration, "artist": tracks[pt.track_id].artist.name if tracks[pt.track_id].artist else None, "image_url": tracks[pt.track_id].image_url, } if pt.track_id in tracks else None, } for pt in sorted(playlist.playlist_tracks, key=lambda x: x.position) ] return response except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid playlist ID", ) @router.put("/{playlist_id}", response_model=PlaylistResponse) async def update_playlist( playlist_id: str, playlist_data: PlaylistUpdate, current_user: CurrentUser, db: DBSession, ): """ Update a playlist. Only the owner can update a playlist. """ from uuid import UUID playlist_service = PlaylistService(db) try: playlist = await playlist_service.update_playlist( playlist_id=UUID(playlist_id), user_id=current_user.id, name=playlist_data.name, description=playlist_data.description, image_url=playlist_data.image_url, is_public=playlist_data.is_public, ) return PlaylistResponse.model_validate(playlist) except ValueError as e: if "not found" in str(e).lower(): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(e), ) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e), ) except Exception: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid playlist ID", ) @router.delete("/{playlist_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_playlist( playlist_id: str, current_user: CurrentUser, db: DBSession, ): """ Delete a playlist. Only the owner can delete a playlist. """ from uuid import UUID playlist_service = PlaylistService(db) try: await playlist_service.delete_playlist( playlist_id=UUID(playlist_id), user_id=current_user.id, ) except ValueError as e: if "not found" in str(e).lower(): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(e), ) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e), ) except Exception: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid playlist ID", ) @router.post("/{playlist_id}/tracks", response_model=PlaylistResponse) async def add_tracks( playlist_id: str, track_data: AddTrackRequest, current_user: CurrentUser, db: DBSession, ): """ Add tracks to a playlist. - **track_ids**: List of track UUIDs to add (1-100 tracks) - **position**: Optional starting position (default: end of playlist) """ from uuid import UUID playlist_service = PlaylistService(db) try: playlist = await playlist_service.add_tracks( playlist_id=UUID(playlist_id), track_ids=track_data.track_ids, user_id=current_user.id, position=track_data.position, ) return PlaylistResponse.model_validate(playlist) except ValueError as e: if "not found" in str(e).lower(): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(e), ) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid playlist or track ID", ) @router.delete("/{playlist_id}/tracks/{track_id}", response_model=PlaylistResponse) async def remove_track( playlist_id: str, track_id: str, current_user: CurrentUser, db: DBSession, ): """ Remove a track from a playlist. Only the owner can remove tracks. """ from uuid import UUID playlist_service = PlaylistService(db) try: playlist = await playlist_service.remove_track( playlist_id=UUID(playlist_id), track_id=UUID(track_id), user_id=current_user.id, ) return PlaylistResponse.model_validate(playlist) except ValueError as e: if "not found" in str(e).lower(): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(e), ) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e), ) except Exception: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid playlist or track ID", ) @router.put("/{playlist_id}/tracks/reorder", response_model=PlaylistResponse) async def reorder_track( playlist_id: str, reorder_data: ReorderTracksRequest, current_user: CurrentUser, db: DBSession, ): """ Reorder a track within a playlist. - **track_id**: Track UUID to reorder - **new_position**: New position (0-indexed) Only the owner can reorder tracks. """ from uuid import UUID playlist_service = PlaylistService(db) try: playlist = await playlist_service.reorder_track( playlist_id=UUID(playlist_id), track_id=reorder_data.track_id, new_position=reorder_data.new_position, user_id=current_user.id, ) return PlaylistResponse.model_validate(playlist) except ValueError as e: if "not found" in str(e).lower(): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(e), ) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e), ) except Exception: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid playlist or track ID", )