Jarian Cottingham 5c283eceef chore: remove test artifacts, fix hardcoded paths, ruff clean, license
- Remove committed .coverage and test-results/ (196K screenshots); gitignore them
- Fix hardcoded  /home/userpath + venv/bin/python in test_endpoints.py
  (relative backend dir + sys.executable)
- Fix concatenated 'import json' in settings.py; bare excepts -> Exception;
  SQLAlchemy-safe is_active.is_(True); __all__ on models/schemas barrels
- ruff clean (93 fixes), MIT LICENSE, PLAN.md -> docs/, README Tests section
- 300 tests pass, 93.7% coverage
2026-08-20 21:34:15 +00:00

146 lines
5.6 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from ..db.database import get_db
from ..models.playlist import Playlist, PlaylistSong
from ..models.song import Song
from ..schemas.playlist import PlaylistCreate, PlaylistResponse, PlaylistWithSongs, PlaylistBase
from ..schemas.song import SongResponse
import uuid
router = APIRouter(prefix="/api/playlists", tags=["playlists"])
@router.get("", response_model=List[PlaylistResponse])
def list_playlists(db: Session = Depends(get_db)):
playlists = db.query(Playlist).order_by(Playlist.updated_at.desc()).all()
result = []
for p in playlists:
song_count = db.query(PlaylistSong).filter(PlaylistSong.playlist_id == p.id).count()
data = PlaylistResponse.model_validate(p)
data.song_count = song_count
result.append(data)
return result
@router.post("", response_model=PlaylistResponse)
def create_playlist(playlist: PlaylistCreate, db: Session = Depends(get_db)):
playlist_id = str(uuid.uuid4())
p = Playlist(
id=playlist_id,
name=playlist.name,
description=playlist.description or "",
mood_category=playlist.mood_category,
)
db.add(p)
if playlist.song_ids:
for i, song_id in enumerate(playlist.song_ids):
ps = PlaylistSong(playlist_id=playlist_id, song_id=song_id, position=i)
db.add(ps)
db.commit()
db.refresh(p)
return PlaylistResponse.model_validate(p)
@router.get("/{playlist_id}", response_model=PlaylistWithSongs)
def get_playlist(playlist_id: str, db: Session = Depends(get_db)):
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
if not playlist:
raise HTTPException(status_code=404, detail="Playlist not found")
songs_query = db.query(Song, PlaylistSong.position).join(PlaylistSong, PlaylistSong.song_id == Song.id).filter(PlaylistSong.playlist_id == playlist_id).order_by(PlaylistSong.position).all()
data = PlaylistWithSongs.model_validate(playlist)
data.songs = [SongResponse.model_validate(s) for s, _ in songs_query]
data.song_count = len(songs_query)
return data
@router.put("/{playlist_id}", response_model=PlaylistResponse)
def update_playlist(playlist_id: str, data: PlaylistBase, db: Session = Depends(get_db)):
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
if not playlist:
raise HTTPException(status_code=404, detail="Playlist not found")
playlist.name = data.name
playlist.description = data.description or ""
db.commit()
db.refresh(playlist)
return PlaylistResponse.model_validate(playlist)
@router.delete("/{playlist_id}")
def delete_playlist(playlist_id: str, db: Session = Depends(get_db)):
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
if not playlist:
raise HTTPException(status_code=404, detail="Playlist not found")
db.query(PlaylistSong).filter(PlaylistSong.playlist_id == playlist_id).delete()
db.delete(playlist)
db.commit()
return {"message": "Playlist deleted"}
@router.post("/{playlist_id}/songs")
def add_song_to_playlist(playlist_id: str, song_id: str, db: Session = Depends(get_db)):
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
if not playlist:
raise HTTPException(status_code=404, detail="Playlist not found")
song = db.query(Song).filter(Song.id == song_id).first()
if not song:
raise HTTPException(status_code=404, detail="Song not found")
existing = db.query(PlaylistSong).filter(PlaylistSong.playlist_id == playlist_id, PlaylistSong.song_id == song_id).first()
if existing:
raise HTTPException(status_code=400, detail="Song already in playlist")
max_pos = db.query(PlaylistSong).filter(PlaylistSong.playlist_id == playlist_id).count()
ps = PlaylistSong(playlist_id=playlist_id, song_id=song_id, position=max_pos)
db.add(ps)
db.commit()
return {"message": "Song added"}
@router.delete("/{playlist_id}/songs/{song_id}")
def remove_song_from_playlist(playlist_id: str, song_id: str, db: Session = Depends(get_db)):
ps = db.query(PlaylistSong).filter(PlaylistSong.playlist_id == playlist_id, PlaylistSong.song_id == song_id).first()
if not ps:
raise HTTPException(status_code=404, detail="Song not in playlist")
db.delete(ps)
db.commit()
return {"message": "Song removed"}
@router.post("/{playlist_id}/share")
def share_playlist(playlist_id: str, db: Session = Depends(get_db)):
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
if not playlist:
raise HTTPException(status_code=404, detail="Playlist not found")
token = str(uuid.uuid4())[:12]
playlist.share_token = token
playlist.is_shared = True
db.commit()
return {"token": token, "url": f"/playlists/shared/{token}"}
@router.get("/shared/{token}", response_model=PlaylistWithSongs)
def get_shared_playlist(token: str, db: Session = Depends(get_db)):
playlist = db.query(Playlist).filter(Playlist.share_token == token).first()
if not playlist:
raise HTTPException(status_code=404, detail="Shared playlist not found")
songs_query = db.query(Song, PlaylistSong.position).join(PlaylistSong, PlaylistSong.song_id == Song.id).filter(PlaylistSong.playlist_id == playlist.id).order_by(PlaylistSong.position).all()
data = PlaylistWithSongs.model_validate(playlist)
data.songs = [SongResponse.model_validate(s) for s, _ in songs_query]
data.song_count = len(songs_query)
return data