Files
minstrel/internal/db/dbq/system_playlists.sql.go
T

406 lines
12 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: system_playlists.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const clearStaleSystemPlaylistInFlight = `-- name: ClearStaleSystemPlaylistInFlight :exec
UPDATE system_playlist_runs SET in_flight = false WHERE in_flight = true
`
// Cron startup recovery: clear any in_flight=true rows from a previously
// crashed process. Safe because at startup, by definition, nothing is
// still building.
func (q *Queries) ClearStaleSystemPlaylistInFlight(ctx context.Context) error {
_, err := q.db.Exec(ctx, clearStaleSystemPlaylistInFlight)
return err
}
const createSystemPlaylist = `-- name: CreateSystemPlaylist :one
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 id, user_id, name, description, is_public, cover_path, track_count, duration_sec, created_at, updated_at, kind, system_variant, seed_artist_id
`
type CreateSystemPlaylistParams struct {
UserID pgtype.UUID
Name string
SystemVariant *string
SeedArtistID pgtype.UUID
CoverPath *string
}
// 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).
func (q *Queries) CreateSystemPlaylist(ctx context.Context, arg CreateSystemPlaylistParams) (Playlist, error) {
row := q.db.QueryRow(ctx, createSystemPlaylist,
arg.UserID,
arg.Name,
arg.SystemVariant,
arg.SeedArtistID,
arg.CoverPath,
)
var i Playlist
err := row.Scan(
&i.ID,
&i.UserID,
&i.Name,
&i.Description,
&i.IsPublic,
&i.CoverPath,
&i.TrackCount,
&i.DurationSec,
&i.CreatedAt,
&i.UpdatedAt,
&i.Kind,
&i.SystemVariant,
&i.SeedArtistID,
)
return i, err
}
const deleteSystemPlaylistsForUser = `-- name: DeleteSystemPlaylistsForUser :exec
DELETE FROM playlists WHERE user_id = $1 AND kind = 'system'
`
// Atomic-replace step. CASCADE on playlist_tracks.playlist_id handles
// the join rows.
func (q *Queries) DeleteSystemPlaylistsForUser(ctx context.Context, userID pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteSystemPlaylistsForUser, userID)
return err
}
const failSystemPlaylistRun = `-- name: FailSystemPlaylistRun :exec
UPDATE system_playlist_runs
SET in_flight = false,
last_error = $2
WHERE user_id = $1
`
type FailSystemPlaylistRunParams struct {
UserID pgtype.UUID
LastError *string
}
// Mark a failed build complete (in_flight=false, error captured).
func (q *Queries) FailSystemPlaylistRun(ctx context.Context, arg FailSystemPlaylistRunParams) error {
_, err := q.db.Exec(ctx, failSystemPlaylistRun, arg.UserID, arg.LastError)
return err
}
const finishSystemPlaylistRun = `-- name: FinishSystemPlaylistRun :exec
UPDATE system_playlist_runs
SET last_run_at = $2,
last_run_date = $3,
in_flight = false,
last_error = NULL
WHERE user_id = $1
`
type FinishSystemPlaylistRunParams struct {
UserID pgtype.UUID
LastRunAt pgtype.Timestamptz
LastRunDate pgtype.Date
}
// Mark a successful build complete.
func (q *Queries) FinishSystemPlaylistRun(ctx context.Context, arg FinishSystemPlaylistRunParams) error {
_, err := q.db.Exec(ctx, finishSystemPlaylistRun, arg.UserID, arg.LastRunAt, arg.LastRunDate)
return err
}
const getSystemPlaylistRun = `-- name: GetSystemPlaylistRun :one
SELECT user_id, last_run_at, last_run_date, in_flight, last_error
FROM system_playlist_runs
WHERE user_id = $1
`
func (q *Queries) GetSystemPlaylistRun(ctx context.Context, userID pgtype.UUID) (SystemPlaylistRun, error) {
row := q.db.QueryRow(ctx, getSystemPlaylistRun, userID)
var i SystemPlaylistRun
err := row.Scan(
&i.UserID,
&i.LastRunAt,
&i.LastRunDate,
&i.InFlight,
&i.LastError,
)
return i, err
}
const listActiveUsersForSystemPlaylists = `-- name: ListActiveUsersForSystemPlaylists :many
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'
)
`
// M7 #352 slice 2: system-generated playlist queries.
// Active = had a play in the last 7 days. The cron iterates this list.
func (q *Queries) ListActiveUsersForSystemPlaylists(ctx context.Context) ([]pgtype.UUID, error) {
rows, err := q.db.Query(ctx, listActiveUsersForSystemPlaylists)
if err != nil {
return nil, err
}
defer rows.Close()
var items []pgtype.UUID
for rows.Next() {
var id pgtype.UUID
if err := rows.Scan(&id); err != nil {
return nil, err
}
items = append(items, id)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listPlaylistsByUserAndKind = `-- name: ListPlaylistsByUserAndKind :many
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
`
type ListPlaylistsByUserAndKindParams struct {
UserID pgtype.UUID
Column2 string
}
type ListPlaylistsByUserAndKindRow struct {
ID pgtype.UUID
UserID pgtype.UUID
Name string
Description string
IsPublic bool
Kind string
SystemVariant *string
SeedArtistID pgtype.UUID
CoverPath *string
TrackCount int32
DurationSec int32
CreatedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
}
// 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.
func (q *Queries) ListPlaylistsByUserAndKind(ctx context.Context, arg ListPlaylistsByUserAndKindParams) ([]ListPlaylistsByUserAndKindRow, error) {
rows, err := q.db.Query(ctx, listPlaylistsByUserAndKind, arg.UserID, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListPlaylistsByUserAndKindRow
for rows.Next() {
var i ListPlaylistsByUserAndKindRow
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.Name,
&i.Description,
&i.IsPublic,
&i.Kind,
&i.SystemVariant,
&i.SeedArtistID,
&i.CoverPath,
&i.TrackCount,
&i.DurationSec,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const pickSeedArtists = `-- name: PickSeedArtists :many
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
`
type PickSeedArtistsRow struct {
ArtistID pgtype.UUID
Score int64
}
// Top-3 most-engaged distinct artists in the user's last 7 days.
// Score = unskipped-play count + 5 if user has liked the artist.
func (q *Queries) PickSeedArtists(ctx context.Context, userID pgtype.UUID) ([]PickSeedArtistsRow, error) {
rows, err := q.db.Query(ctx, pickSeedArtists, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []PickSeedArtistsRow
for rows.Next() {
var i PickSeedArtistsRow
if err := rows.Scan(&i.ArtistID, &i.Score); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const pickTopAlbumCoverForArtistByUser = `-- name: PickTopAlbumCoverForArtistByUser :one
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
`
type PickTopAlbumCoverForArtistByUserParams struct {
UserID pgtype.UUID
ArtistID pgtype.UUID
}
// "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).
func (q *Queries) PickTopAlbumCoverForArtistByUser(ctx context.Context, arg PickTopAlbumCoverForArtistByUserParams) (*string, error) {
row := q.db.QueryRow(ctx, pickTopAlbumCoverForArtistByUser, arg.UserID, arg.ArtistID)
var cover_art_path *string
err := row.Scan(&cover_art_path)
return cover_art_path, err
}
const pickTopPlayedTrackForArtistByUser = `-- name: PickTopPlayedTrackForArtistByUser :one
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
`
type PickTopPlayedTrackForArtistByUserParams struct {
UserID pgtype.UUID
ArtistID pgtype.UUID
}
// "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{}.
func (q *Queries) PickTopPlayedTrackForArtistByUser(ctx context.Context, arg PickTopPlayedTrackForArtistByUserParams) (pgtype.UUID, error) {
row := q.db.QueryRow(ctx, pickTopPlayedTrackForArtistByUser, arg.UserID, arg.ArtistID)
var id pgtype.UUID
err := row.Scan(&id)
return id, err
}
const pickTopPlayedTrackForUser = `-- name: PickTopPlayedTrackForUser :one
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
`
// 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.
func (q *Queries) PickTopPlayedTrackForUser(ctx context.Context, userID pgtype.UUID) (pgtype.UUID, error) {
row := q.db.QueryRow(ctx, pickTopPlayedTrackForUser, userID)
var id pgtype.UUID
err := row.Scan(&id)
return id, err
}
const tryClaimSystemPlaylistRun = `-- name: TryClaimSystemPlaylistRun :one
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
`
// Atomic test-and-set on in_flight. Returns the row if we won the claim,
// or pgx.ErrNoRows if someone else is already building.
func (q *Queries) TryClaimSystemPlaylistRun(ctx context.Context, userID pgtype.UUID) (SystemPlaylistRun, error) {
row := q.db.QueryRow(ctx, tryClaimSystemPlaylistRun, userID)
var i SystemPlaylistRun
err := row.Scan(
&i.UserID,
&i.LastRunAt,
&i.LastRunDate,
&i.InFlight,
&i.LastError,
)
return i, err
}