// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: scrobble.sql package dbq import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const enqueueScrobble = `-- name: EnqueueScrobble :execrows INSERT INTO scrobble_queue (user_id, play_event_id, status) VALUES ($1, $2, 'pending') ON CONFLICT (play_event_id) DO NOTHING ` type EnqueueScrobbleParams struct { UserID pgtype.UUID PlayEventID pgtype.UUID } // Idempotent: if a row exists for this play_event_id, ON CONFLICT does nothing // and the affected-rows count is 0. Caller (MaybeEnqueue) doesn't treat 0 as // an error. func (q *Queries) EnqueueScrobble(ctx context.Context, arg EnqueueScrobbleParams) (int64, error) { result, err := q.db.Exec(ctx, enqueueScrobble, arg.UserID, arg.PlayEventID) if err != nil { return 0, err } return result.RowsAffected(), nil } const listPendingScrobbles = `-- name: ListPendingScrobbles :many SELECT sq.id AS queue_id, sq.user_id AS user_id, sq.play_event_id AS play_event_id, sq.attempts AS attempts, u.listenbrainz_token AS lb_token, u.listenbrainz_enabled AS lb_enabled, pe.started_at AS started_at, t.title AS track_title, t.duration_ms AS track_duration_ms, t.mbid AS track_mbid, al.title AS album_title, al.mbid AS album_mbid, ar.name AS artist_name, ar.mbid AS artist_mbid FROM scrobble_queue sq JOIN users u ON u.id = sq.user_id JOIN play_events pe ON pe.id = sq.play_event_id JOIN tracks t ON t.id = pe.track_id JOIN albums al ON al.id = t.album_id JOIN artists ar ON ar.id = t.artist_id WHERE sq.status = 'pending' AND sq.next_attempt_at <= now() ORDER BY sq.next_attempt_at LIMIT $1 ` type ListPendingScrobblesRow struct { QueueID pgtype.UUID UserID pgtype.UUID PlayEventID pgtype.UUID Attempts int32 LbToken *string LbEnabled bool StartedAt pgtype.Timestamptz TrackTitle string TrackDurationMs int32 TrackMbid *string AlbumTitle string AlbumMbid *string ArtistName string ArtistMbid *string } // Worker pulls up to N pending rows whose next_attempt_at has passed. // Joins play_events + tracks/albums/artists + users so the worker can // assemble the LB Listen payload in one round-trip. func (q *Queries) ListPendingScrobbles(ctx context.Context, limit int32) ([]ListPendingScrobblesRow, error) { rows, err := q.db.Query(ctx, listPendingScrobbles, limit) if err != nil { return nil, err } defer rows.Close() var items []ListPendingScrobblesRow for rows.Next() { var i ListPendingScrobblesRow if err := rows.Scan( &i.QueueID, &i.UserID, &i.PlayEventID, &i.Attempts, &i.LbToken, &i.LbEnabled, &i.StartedAt, &i.TrackTitle, &i.TrackDurationMs, &i.TrackMbid, &i.AlbumTitle, &i.AlbumMbid, &i.ArtistName, &i.ArtistMbid, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const markScrobbleFailed = `-- name: MarkScrobbleFailed :exec UPDATE scrobble_queue SET status = 'failed', last_error = $2 WHERE id = $1 ` type MarkScrobbleFailedParams struct { ID pgtype.UUID LastError *string } // After a permanent failure (4xx, exhausted retries, auth) — mark failed // and keep the row for diagnostics. func (q *Queries) MarkScrobbleFailed(ctx context.Context, arg MarkScrobbleFailedParams) error { _, err := q.db.Exec(ctx, markScrobbleFailed, arg.ID, arg.LastError) return err } const markScrobbleRetryAfter = `-- name: MarkScrobbleRetryAfter :exec UPDATE scrobble_queue SET next_attempt_at = $2 WHERE id = $1 ` type MarkScrobbleRetryAfterParams struct { ID pgtype.UUID NextAttemptAt pgtype.Timestamptz } // After a 429: schedule next attempt per LB's Retry-After header, but do NOT // increment attempts (the server told us to wait, not that we failed). func (q *Queries) MarkScrobbleRetryAfter(ctx context.Context, arg MarkScrobbleRetryAfterParams) error { _, err := q.db.Exec(ctx, markScrobbleRetryAfter, arg.ID, arg.NextAttemptAt) return err } const markScrobbleSent = `-- name: MarkScrobbleSent :exec WITH del AS ( DELETE FROM scrobble_queue WHERE scrobble_queue.id = $1 RETURNING play_event_id ) UPDATE play_events SET scrobbled_at = now() WHERE play_events.id = (SELECT play_event_id FROM del) ` // Successful submit. Delete the queue row AND stamp play_events.scrobbled_at // in one statement via a CTE so partial failure is impossible. func (q *Queries) MarkScrobbleSent(ctx context.Context, id pgtype.UUID) error { _, err := q.db.Exec(ctx, markScrobbleSent, id) return err } const rescheduleScrobble = `-- name: RescheduleScrobble :exec UPDATE scrobble_queue SET attempts = attempts + 1, next_attempt_at = $2, last_error = $3 WHERE id = $1 ` type RescheduleScrobbleParams struct { ID pgtype.UUID NextAttemptAt pgtype.Timestamptz LastError *string } // After a transient failure: increment attempts, schedule next attempt, // record the error. func (q *Queries) RescheduleScrobble(ctx context.Context, arg RescheduleScrobbleParams) error { _, err := q.db.Exec(ctx, rescheduleScrobble, arg.ID, arg.NextAttemptAt, arg.LastError) return err }