-- name: CreatePlaylist :one INSERT INTO playlists (user_id, name, description, is_public) VALUES ($1, $2, $3, $4) RETURNING *; -- name: GetPlaylist :one SELECT p.*, u.username AS owner_username FROM playlists p JOIN users u ON u.id = p.user_id WHERE p.id = $1; -- name: ListPlaylistsForUser :many -- Owner's playlists (any visibility) + other users' public playlists. -- Ordered by updated_at desc so newly-edited ones float to the top. SELECT p.*, u.username AS owner_username FROM playlists p JOIN users u ON u.id = p.user_id WHERE p.user_id = $1 OR p.is_public = true ORDER BY p.updated_at DESC; -- name: UpdatePlaylist :one -- Updates only the fields whose corresponding `updateX` flag is true. -- The flags let the service layer keep PATCH semantics (only-touch-what-the-caller-sent) -- without writing N variants. UPDATE playlists SET name = CASE WHEN sqlc.arg(update_name)::boolean THEN sqlc.arg(name)::text ELSE name END, description = CASE WHEN sqlc.arg(update_description)::boolean THEN sqlc.arg(description)::text ELSE description END, is_public = CASE WHEN sqlc.arg(update_is_public)::boolean THEN sqlc.arg(is_public)::boolean ELSE is_public END, updated_at = now() WHERE id = sqlc.arg(id) RETURNING *; -- name: UpdatePlaylistRollups :exec -- Set track_count + duration_sec from a fresh aggregate. Called after -- every mutation that touches playlist_tracks. Cheap; the table is small. UPDATE playlists SET track_count = (SELECT COUNT(*) FROM playlist_tracks pt WHERE pt.playlist_id = $1), duration_sec = (SELECT COALESCE(SUM(pt.duration_sec), 0) FROM playlist_tracks pt WHERE pt.playlist_id = $1), updated_at = now() WHERE id = $1; -- name: SetPlaylistCover :exec UPDATE playlists SET cover_path = $2, updated_at = now() WHERE id = $1; -- name: DeletePlaylist :one -- Returns cover_path so the caller can clean up the cached collage on disk. DELETE FROM playlists WHERE id = $1 RETURNING id, cover_path; -- name: ListPlaylistTracks :many -- Joined to tracks for the live track id (the service layer derives the -- stream URL from it); LEFT JOIN preserves the row when track_id is NULL -- (track was removed from the library). The denormalized snapshot fields -- on playlist_tracks remain authoritative for title/artist/album text. SELECT pt.*, t.id AS live_track_id, albums.id AS album_id, artists.id AS artist_id FROM playlist_tracks pt LEFT JOIN tracks t ON t.id = pt.track_id LEFT JOIN albums ON albums.id = t.album_id LEFT JOIN artists ON artists.id = t.artist_id WHERE pt.playlist_id = $1 ORDER BY pt.position; -- name: AppendPlaylistTrack :one -- Inserts at the next available position. Snapshot fields are copied -- from the tracks/albums/artists join at insert time. tracks.duration_ms -- is converted to seconds for the snapshot. INSERT INTO playlist_tracks (playlist_id, position, track_id, title, artist_name, album_title, duration_sec) SELECT sqlc.arg(playlist_id)::uuid, COALESCE((SELECT MAX(position) + 1 FROM playlist_tracks WHERE playlist_id = sqlc.arg(playlist_id)::uuid), 0), t.id, t.title, artists.name, albums.title, (t.duration_ms / 1000)::integer FROM tracks t JOIN albums ON albums.id = t.album_id JOIN artists ON artists.id = t.artist_id WHERE t.id = sqlc.arg(track_id)::uuid RETURNING *; -- name: DeletePlaylistTrack :exec -- Two-step: delete the row at `position`, then renumber subsequent rows -- to close the gap. The renumber is a single UPDATE; the service layer -- runs both in one transaction. DELETE FROM playlist_tracks WHERE playlist_id = $1 AND position = $2; -- name: RenumberPlaylistTracksAfter :exec -- Used after DeletePlaylistTrack to close the gap. UPDATE playlist_tracks SET position = position - 1 WHERE playlist_id = $1 AND position > $2; -- name: ListAllPlaylistTracksForCollage :many -- First N tracks for the collage. Uses LEFT JOIN on albums for the -- cover_path; rows with NULL cover_path get the glyph fallback. SELECT pt.position, albums.cover_art_path AS album_cover_path FROM playlist_tracks pt LEFT JOIN tracks t ON t.id = pt.track_id LEFT JOIN albums ON albums.id = t.album_id WHERE pt.playlist_id = $1 ORDER BY pt.position LIMIT $2; -- name: GetPlaylistsByIDs :many -- Batched lookup used by /api/library/sync to hydrate upsert payloads -- (#357). Mirror of GetArtistsByIDs. SELECT * FROM playlists WHERE id = ANY($1::uuid[]);