-- M7 #352 slice 2: system-generated playlist queries. -- name: ListActiveUsersForSystemPlaylists :many -- Active = had a play in the last 7 days. The cron iterates this list. SELECT u.id FROM users u WHERE EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = u.id AND pe.started_at > now() - INTERVAL '7 days' ); -- name: GetSystemPlaylistRun :one SELECT user_id, last_run_at, last_run_date, in_flight, last_error FROM system_playlist_runs WHERE user_id = $1; -- name: TryClaimSystemPlaylistRun :one -- Atomic test-and-set on in_flight. Returns the row if we won the claim, -- or pgx.ErrNoRows if someone else is already building. INSERT INTO system_playlist_runs (user_id, in_flight) VALUES ($1, true) ON CONFLICT (user_id) DO UPDATE SET in_flight = true WHERE system_playlist_runs.in_flight = false RETURNING user_id, last_run_at, last_run_date, in_flight, last_error; -- name: FinishSystemPlaylistRun :exec -- Mark a successful build complete. UPDATE system_playlist_runs SET last_run_at = $2, last_run_date = $3, in_flight = false, last_error = NULL WHERE user_id = $1; -- name: FailSystemPlaylistRun :exec -- Mark a failed build complete (in_flight=false, error captured). UPDATE system_playlist_runs SET in_flight = false, last_error = $2 WHERE user_id = $1; -- name: ClearStaleSystemPlaylistInFlight :exec -- Cron startup recovery: clear any in_flight=true rows from a previously -- crashed process. Safe because at startup, by definition, nothing is -- still building. UPDATE system_playlist_runs SET in_flight = false WHERE in_flight = true; -- name: PickSeedArtists :many -- Top-5 most-engaged distinct artist candidates in the user's last 7 -- days. The Go-side picker (pickSeedArtistsForDay) shuffles these -- daily-deterministically and takes the first 3 so the set of -- "Songs like X" mixes rotates day-to-day. -- Score = unskipped-play count + 5 if user has liked the artist. WITH plays AS ( SELECT t.artist_id, COUNT(*) FILTER (WHERE pe.was_skipped = false) AS play_count FROM play_events pe JOIN tracks t ON t.id = pe.track_id WHERE pe.user_id = $1 AND pe.started_at > now() - INTERVAL '7 days' AND t.artist_id IS NOT NULL GROUP BY t.artist_id ), liked AS ( SELECT artist_id FROM general_likes_artists WHERE user_id = $1 ) SELECT p.artist_id, (p.play_count + CASE WHEN l.artist_id IS NOT NULL THEN 5 ELSE 0 END)::bigint AS score FROM plays p LEFT JOIN liked l ON l.artist_id = p.artist_id ORDER BY score DESC, p.artist_id LIMIT 5; -- name: PickTopPlayedTracksForUser :many -- For-You candidate seeds, tiered so For-You never silently vanishes: -- tier 0 top non-skip plays in the last 30 days -- tier 1 (only if tier 0 empty) all-time top non-skip plays -- tier 2 (only if tiers 0+1 empty) liked tracks -- Returns up to 5 ids; tie-break by track_id for determinism. The -- Go-side picker (pickForYouSeedForDay) rotates one per day via -- userIDHash. Widened from a hard 7-day window, which made For-You -- disappear after a week of not listening and never recover on a -- self-hosted library with sparse history. WITH recent AS ( SELECT t.id, COUNT(*) AS c, 0 AS tier FROM play_events pe JOIN tracks t ON t.id = pe.track_id WHERE pe.user_id = $1 AND pe.started_at > now() - INTERVAL '30 days' AND pe.was_skipped = false GROUP BY t.id ), alltime AS ( SELECT t.id, COUNT(*) AS c, 1 AS tier FROM play_events pe JOIN tracks t ON t.id = pe.track_id WHERE pe.user_id = $1 AND pe.was_skipped = false GROUP BY t.id ), liked AS ( SELECT gl.track_id AS id, 0::bigint AS c, 2 AS tier FROM general_likes gl WHERE gl.user_id = $1 ), chosen AS ( SELECT id, c, tier FROM recent UNION ALL SELECT id, c, tier FROM alltime WHERE NOT EXISTS (SELECT 1 FROM recent) UNION ALL SELECT id, c, tier FROM liked WHERE NOT EXISTS (SELECT 1 FROM recent) AND NOT EXISTS (SELECT 1 FROM alltime) ) SELECT id FROM chosen ORDER BY tier, c DESC, id LIMIT 5; -- name: PickTopPlayedTrackForArtistByUser :one -- "Songs like X" seed selection. Returns the user's most-played non-skipped -- track by artist X in the last 7 days. Falls back to the artist's most- -- recent album's first track if no plays exist. -- Cast to uuid so sqlc infers pgtype.UUID rather than interface{}. SELECT COALESCE( (SELECT t.id FROM play_events pe JOIN tracks t ON t.id = pe.track_id WHERE pe.user_id = $1 AND t.artist_id = $2 AND pe.started_at > now() - INTERVAL '7 days' AND pe.was_skipped = false GROUP BY t.id ORDER BY COUNT(*) DESC, t.id LIMIT 1), (SELECT t.id FROM tracks t JOIN albums a ON a.id = t.album_id WHERE t.artist_id = $2 ORDER BY a.release_date DESC NULLS LAST, t.disc_number NULLS LAST, t.track_number NULLS LAST, t.id LIMIT 1) )::uuid AS id; -- name: PickTopAlbumCoverForArtistByUser :one -- "Songs like X" cover. Most-played album by user; tie-break by most-recent -- release. NULL if the artist has no albums or all covers are NULL. -- Note: albums uses cover_art_path (not cover_path). SELECT a.cover_art_path FROM albums a LEFT JOIN tracks t ON t.album_id = a.id LEFT JOIN play_events pe ON pe.track_id = t.id AND pe.user_id = $1 AND pe.was_skipped = false WHERE a.artist_id = $2 AND a.cover_art_path IS NOT NULL GROUP BY a.id, a.release_date, a.cover_art_path ORDER BY COUNT(pe.id) DESC, a.release_date DESC NULLS LAST, a.id LIMIT 1; -- name: DeleteSystemPlaylistsForUser :exec -- Atomic-replace step. CASCADE on playlist_tracks.playlist_id handles -- the join rows. DELETE FROM playlists WHERE user_id = $1 AND kind = 'system'; -- name: ListPlaylistsByUserAndKind :many -- Used in integration tests; production filter applied in Go -- (api.handleListPlaylists fetches via Service.List and filters by Kind in -- memory so it can also surface other users' public playlists alongside -- the caller's own kind-filtered ones). -- Sorted by updated_at DESC. SELECT id, user_id, name, description, is_public, kind, system_variant, seed_artist_id, cover_path, track_count, duration_sec, created_at, updated_at FROM playlists WHERE user_id = $1 AND ($2::text = 'all' OR kind = $2) ORDER BY updated_at DESC; -- name: CreateSystemPlaylist :one -- Inserts a system-generated playlist. Used by BuildSystemPlaylists. -- For 'for_you' variant, pass seed_artist_id as NULL (zero-value pgtype.UUID -- with Valid=false). INSERT INTO playlists ( user_id, name, description, is_public, kind, system_variant, seed_artist_id, cover_path ) VALUES ($1, $2, '', false, 'system', $3, $4, $5) RETURNING *; -- name: GetSystemPlaylistByVariantForUser :one -- Returns the user's system playlist for the given variant -- ('for_you' / 'songs_like_artist' / 'discover'), or pgx.ErrNoRows -- when the user hasn't built that variant yet. For -- 'songs_like_artist' (which can have multiple rows per user, one -- per seed), this returns whichever LIMIT 1 picks — callers that -- need all instances should use a different query. SELECT p.id, p.user_id, p.name, p.kind, p.system_variant, p.seed_artist_id, p.cover_path, p.track_count FROM playlists p WHERE p.user_id = $1 AND p.kind = 'system' AND p.system_variant = $2 ORDER BY p.created_at DESC LIMIT 1;