Files
minstrel/internal/db/queries/system_playlists.sql
T
bvandeusen 2b286a9bfd docs(db/m7-352): note ListPlaylistsByUserAndKind is test-only
Production list-playlists handler reuses Service.List + in-memory kind
filter so other users' public playlists can surface alongside the
caller's filtered own. Comment clarifies the divergence so future
readers don't expect this query to be wired into the API path.
2026-05-04 10:31:09 -04:00

157 lines
5.4 KiB
SQL

-- 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-3 most-engaged distinct artists in the user's last 7 days.
-- 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 3;
-- name: PickTopPlayedTrackForUser :one
-- For-You seed selection. Returns the user's most-played non-skipped
-- track in the last 7 days; tie-break by track_id for determinism.
SELECT t.id
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 pe.was_skipped = false
GROUP BY t.id
ORDER BY COUNT(*) DESC, t.id
LIMIT 1;
-- 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 *;