feat(db/m7-cover-sources): SQL queries for provider settings + recheck
- Extends GetAlbumCoverageRollup's with_art FILTER to include 'theaudiodb' so F's coverage gauge surfaces TheAudioDB-found rows as with_art rather than silently undercounting. - Modifies ListAlbumsMissingCover to take the current sources_version as a parameter; eligibility now includes stale-'none' rows. - Adds SetAlbumCoverWithVersion (atomic write of path + source + version stamp) and the parallel SetArtistArtWithVersion + ClearArtistArtNone + ListArtistsMissingArt for the artist-art enricher. - New coverart_settings.sql with ListProviderSettings, UpsertProviderSettings (boot reconciliation, preserves operator values on conflict), UpdateProviderSettings (admin PATCH; tri- state api_key handling), GetCurrentSourcesVersion, BumpSourcesVersion (RETURNING the new value).
This commit is contained in:
@@ -72,7 +72,7 @@ func (q *Queries) DeleteAlbumIfEmpty(ctx context.Context, id pgtype.UUID) (Delet
|
||||
}
|
||||
|
||||
const getAlbumByArtistAndTitle = `-- name: GetAlbumByArtistAndTitle :one
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source FROM albums WHERE artist_id = $1 AND title = $2 LIMIT 1
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version FROM albums WHERE artist_id = $1 AND title = $2 LIMIT 1
|
||||
`
|
||||
|
||||
type GetAlbumByArtistAndTitleParams struct {
|
||||
@@ -95,12 +95,13 @@ func (q *Queries) GetAlbumByArtistAndTitle(ctx context.Context, arg GetAlbumByAr
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getAlbumByID = `-- name: GetAlbumByID :one
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source FROM albums WHERE id = $1
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version FROM albums WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetAlbumByID(ctx context.Context, id pgtype.UUID) (Album, error) {
|
||||
@@ -117,6 +118,7 @@ func (q *Queries) GetAlbumByID(ctx context.Context, id pgtype.UUID) (Album, erro
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -124,7 +126,7 @@ func (q *Queries) GetAlbumByID(ctx context.Context, id pgtype.UUID) (Album, erro
|
||||
const getAlbumCoverageRollup = `-- name: GetAlbumCoverageRollup :one
|
||||
SELECT
|
||||
COUNT(*) AS total,
|
||||
COUNT(*) FILTER (WHERE cover_art_source IN ('sidecar','embedded','mbcaa')) AS with_art,
|
||||
COUNT(*) FILTER (WHERE cover_art_source IN ('sidecar','embedded','mbcaa','theaudiodb')) AS with_art,
|
||||
COUNT(*) FILTER (WHERE cover_art_source IS NULL) AS pending,
|
||||
COUNT(*) FILTER (WHERE cover_art_source = 'none') AS settled,
|
||||
COUNT(*) FILTER (WHERE cover_art_source IS NULL AND mbid IS NULL) AS pending_no_mbid
|
||||
@@ -149,7 +151,7 @@ type GetAlbumCoverageRollupRow struct {
|
||||
// Invariant: with_art + pending + settled = total. (pending_no_mbid is
|
||||
// not part of the sum — it's a subset of pending, surfaced separately.)
|
||||
//
|
||||
// The IN list below ('sidecar','embedded','mbcaa') must stay in sync
|
||||
// The IN list below ('sidecar','embedded','mbcaa','theaudiodb') must stay in sync
|
||||
// with the cover_art_source CHECK constraint in migration
|
||||
// 0016_album_cover_source.up.sql. If a new source value is added there
|
||||
// without updating this query, with_art will silently undercount.
|
||||
@@ -167,7 +169,7 @@ func (q *Queries) GetAlbumCoverageRollup(ctx context.Context) (GetAlbumCoverageR
|
||||
}
|
||||
|
||||
const listAlbumsAlphaByArtist = `-- name: ListAlbumsAlphaByArtist :many
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, artists.sort_name AS artist_sort_name
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, artists.sort_name AS artist_sort_name
|
||||
FROM albums
|
||||
JOIN artists ON artists.id = albums.artist_id
|
||||
ORDER BY artists.sort_name, albums.sort_title
|
||||
@@ -206,6 +208,7 @@ func (q *Queries) ListAlbumsAlphaByArtist(ctx context.Context, arg ListAlbumsAlp
|
||||
&i.Album.CreatedAt,
|
||||
&i.Album.UpdatedAt,
|
||||
&i.Album.CoverArtSource,
|
||||
&i.Album.CoverArtSourcesVersion,
|
||||
&i.ArtistSortName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -219,7 +222,7 @@ func (q *Queries) ListAlbumsAlphaByArtist(ctx context.Context, arg ListAlbumsAlp
|
||||
}
|
||||
|
||||
const listAlbumsAlphaByName = `-- name: ListAlbumsAlphaByName :many
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source FROM albums ORDER BY sort_title LIMIT $1 OFFSET $2
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version FROM albums ORDER BY sort_title LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ListAlbumsAlphaByNameParams struct {
|
||||
@@ -247,6 +250,7 @@ func (q *Queries) ListAlbumsAlphaByName(ctx context.Context, arg ListAlbumsAlpha
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -259,7 +263,7 @@ func (q *Queries) ListAlbumsAlphaByName(ctx context.Context, arg ListAlbumsAlpha
|
||||
}
|
||||
|
||||
const listAlbumsAlphaWithArtist = `-- name: ListAlbumsAlphaWithArtist :many
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, artists.name AS artist_name
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, artists.name AS artist_name
|
||||
FROM albums
|
||||
JOIN artists ON artists.id = albums.artist_id
|
||||
ORDER BY albums.sort_title, albums.id
|
||||
@@ -298,6 +302,7 @@ func (q *Queries) ListAlbumsAlphaWithArtist(ctx context.Context, arg ListAlbumsA
|
||||
&i.Album.CreatedAt,
|
||||
&i.Album.UpdatedAt,
|
||||
&i.Album.CoverArtSource,
|
||||
&i.Album.CoverArtSourcesVersion,
|
||||
&i.ArtistName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -311,7 +316,7 @@ func (q *Queries) ListAlbumsAlphaWithArtist(ctx context.Context, arg ListAlbumsA
|
||||
}
|
||||
|
||||
const listAlbumsByArtist = `-- name: ListAlbumsByArtist :many
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source FROM albums WHERE artist_id = $1 ORDER BY release_date NULLS LAST, sort_title
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version FROM albums WHERE artist_id = $1 ORDER BY release_date NULLS LAST, sort_title
|
||||
`
|
||||
|
||||
func (q *Queries) ListAlbumsByArtist(ctx context.Context, artistID pgtype.UUID) ([]Album, error) {
|
||||
@@ -334,6 +339,7 @@ func (q *Queries) ListAlbumsByArtist(ctx context.Context, artistID pgtype.UUID)
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -346,7 +352,7 @@ func (q *Queries) ListAlbumsByArtist(ctx context.Context, artistID pgtype.UUID)
|
||||
}
|
||||
|
||||
const listAlbumsByGenre = `-- name: ListAlbumsByGenre :many
|
||||
SELECT DISTINCT ON (albums.id) albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source
|
||||
SELECT DISTINCT ON (albums.id) albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version
|
||||
FROM albums
|
||||
JOIN tracks ON tracks.album_id = albums.id
|
||||
WHERE tracks.genre = $1
|
||||
@@ -381,6 +387,7 @@ func (q *Queries) ListAlbumsByGenre(ctx context.Context, arg ListAlbumsByGenrePa
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -446,7 +453,7 @@ func (q *Queries) ListAlbumsMissingMbidWithTrack(ctx context.Context, limit int3
|
||||
}
|
||||
|
||||
const listAlbumsNewest = `-- name: ListAlbumsNewest :many
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source FROM albums ORDER BY created_at DESC LIMIT $1 OFFSET $2
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version FROM albums ORDER BY created_at DESC LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ListAlbumsNewestParams struct {
|
||||
@@ -474,6 +481,7 @@ func (q *Queries) ListAlbumsNewest(ctx context.Context, arg ListAlbumsNewestPara
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -486,7 +494,7 @@ func (q *Queries) ListAlbumsNewest(ctx context.Context, arg ListAlbumsNewestPara
|
||||
}
|
||||
|
||||
const listAlbumsRandom = `-- name: ListAlbumsRandom :many
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source FROM albums ORDER BY random() LIMIT $1
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version FROM albums ORDER BY random() LIMIT $1
|
||||
`
|
||||
|
||||
func (q *Queries) ListAlbumsRandom(ctx context.Context, limit int32) ([]Album, error) {
|
||||
@@ -509,6 +517,7 @@ func (q *Queries) ListAlbumsRandom(ctx context.Context, limit int32) ([]Album, e
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -521,7 +530,7 @@ func (q *Queries) ListAlbumsRandom(ctx context.Context, limit int32) ([]Album, e
|
||||
}
|
||||
|
||||
const listRecentlyAddedAlbumsWithArtist = `-- name: ListRecentlyAddedAlbumsWithArtist :many
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, artists.name AS artist_name
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, artists.name AS artist_name
|
||||
FROM albums
|
||||
JOIN artists ON artists.id = albums.artist_id
|
||||
ORDER BY albums.created_at DESC, albums.id
|
||||
@@ -557,6 +566,7 @@ func (q *Queries) ListRecentlyAddedAlbumsWithArtist(ctx context.Context, limit i
|
||||
&i.Album.CreatedAt,
|
||||
&i.Album.UpdatedAt,
|
||||
&i.Album.CoverArtSource,
|
||||
&i.Album.CoverArtSourcesVersion,
|
||||
&i.ArtistName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -570,7 +580,7 @@ func (q *Queries) ListRecentlyAddedAlbumsWithArtist(ctx context.Context, limit i
|
||||
}
|
||||
|
||||
const searchAlbums = `-- name: SearchAlbums :many
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source FROM albums
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version FROM albums
|
||||
WHERE title ILIKE '%' || $1 || '%'
|
||||
ORDER BY sort_title
|
||||
LIMIT $2 OFFSET $3
|
||||
@@ -602,6 +612,7 @@ func (q *Queries) SearchAlbums(ctx context.Context, arg SearchAlbumsParams) ([]A
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -613,6 +624,35 @@ func (q *Queries) SearchAlbums(ctx context.Context, arg SearchAlbumsParams) ([]A
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const setAlbumCoverWithVersion = `-- name: SetAlbumCoverWithVersion :exec
|
||||
UPDATE albums
|
||||
SET cover_art_path = $2,
|
||||
cover_art_source = $3,
|
||||
cover_art_sources_version = $4,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type SetAlbumCoverWithVersionParams struct {
|
||||
ID pgtype.UUID
|
||||
CoverArtPath *string
|
||||
CoverArtSource *string
|
||||
CoverArtSourcesVersion int32
|
||||
}
|
||||
|
||||
// M7 cover-sources: records a successful or settled enrichment with the
|
||||
// current sources_version stamp. cover_art_path is NULL when source is
|
||||
// 'none' (no file written). Atomic; called per album per pass.
|
||||
func (q *Queries) SetAlbumCoverWithVersion(ctx context.Context, arg SetAlbumCoverWithVersionParams) error {
|
||||
_, err := q.db.Exec(ctx, setAlbumCoverWithVersion,
|
||||
arg.ID,
|
||||
arg.CoverArtPath,
|
||||
arg.CoverArtSource,
|
||||
arg.CoverArtSourcesVersion,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const setAlbumMbidIfNull = `-- name: SetAlbumMbidIfNull :exec
|
||||
UPDATE albums
|
||||
SET mbid = $2,
|
||||
@@ -643,7 +683,7 @@ DO UPDATE SET
|
||||
release_date = EXCLUDED.release_date,
|
||||
cover_art_path = EXCLUDED.cover_art_path,
|
||||
updated_at = now()
|
||||
RETURNING id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source
|
||||
RETURNING id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version
|
||||
`
|
||||
|
||||
type UpsertAlbumParams struct {
|
||||
@@ -676,6 +716,7 @@ func (q *Queries) UpsertAlbum(ctx context.Context, arg UpsertAlbumParams) (Album
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
+153
-13
@@ -11,6 +11,21 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const clearArtistArtNone = `-- name: ClearArtistArtNone :exec
|
||||
UPDATE artists
|
||||
SET artist_art_source = NULL,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
AND artist_art_source = 'none'
|
||||
`
|
||||
|
||||
// M7 cover-sources: parallel of ClearAlbumCoverNone. Used by the
|
||||
// eligibility loop to flip stale 'none' rows back to NULL.
|
||||
func (q *Queries) ClearArtistArtNone(ctx context.Context, id pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, clearArtistArtNone, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const countArtists = `-- name: CountArtists :one
|
||||
SELECT COUNT(*) FROM artists
|
||||
`
|
||||
@@ -52,25 +67,46 @@ func (q *Queries) DeleteArtistIfEmpty(ctx context.Context, id pgtype.UUID) (pgty
|
||||
}
|
||||
|
||||
const getArtistByID = `-- name: GetArtistByID :one
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists WHERE id = $1
|
||||
SELECT a.id, a.name, a.sort_name, a.mbid,
|
||||
a.artist_thumb_path, a.artist_fanart_path,
|
||||
a.artist_art_source, a.artist_art_sources_version
|
||||
FROM artists a
|
||||
WHERE a.id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetArtistByID(ctx context.Context, id pgtype.UUID) (Artist, error) {
|
||||
type GetArtistByIDRow struct {
|
||||
ID pgtype.UUID
|
||||
Name string
|
||||
SortName string
|
||||
Mbid *string
|
||||
ArtistThumbPath *string
|
||||
ArtistFanartPath *string
|
||||
ArtistArtSource *string
|
||||
ArtistArtSourcesVersion int32
|
||||
}
|
||||
|
||||
// M7 cover-sources: enricher reads the artist's mbid + current art
|
||||
// columns to drive eligibility check. (Modified from SELECT * to
|
||||
// explicitly include the new artist_art_* columns added in migration
|
||||
// 0018.)
|
||||
func (q *Queries) GetArtistByID(ctx context.Context, id pgtype.UUID) (GetArtistByIDRow, error) {
|
||||
row := q.db.QueryRow(ctx, getArtistByID, id)
|
||||
var i Artist
|
||||
var i GetArtistByIDRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.SortName,
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getArtistByName = `-- name: GetArtistByName :one
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists WHERE name = $1 LIMIT 1
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists WHERE name = $1 LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetArtistByName(ctx context.Context, name string) (Artist, error) {
|
||||
@@ -83,12 +119,16 @@ func (q *Queries) GetArtistByName(ctx context.Context, name string) (Artist, err
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getArtistsByIDs = `-- name: GetArtistsByIDs :many
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists WHERE id = ANY($1::uuid[])
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists WHERE id = ANY($1::uuid[])
|
||||
`
|
||||
|
||||
// Batched lookup used by M5c suggestion attribution to resolve top-3
|
||||
@@ -109,6 +149,10 @@ func (q *Queries) GetArtistsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) (
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -121,7 +165,7 @@ func (q *Queries) GetArtistsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) (
|
||||
}
|
||||
|
||||
const listArtists = `-- name: ListArtists :many
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists ORDER BY sort_name
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists ORDER BY sort_name
|
||||
`
|
||||
|
||||
func (q *Queries) ListArtists(ctx context.Context) ([]Artist, error) {
|
||||
@@ -140,6 +184,10 @@ func (q *Queries) ListArtists(ctx context.Context) ([]Artist, error) {
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -152,7 +200,7 @@ func (q *Queries) ListArtists(ctx context.Context) ([]Artist, error) {
|
||||
}
|
||||
|
||||
const listArtistsAlpha = `-- name: ListArtistsAlpha :many
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists ORDER BY sort_name, name, id LIMIT $1 OFFSET $2
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists ORDER BY sort_name, name, id LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ListArtistsAlphaParams struct {
|
||||
@@ -176,6 +224,10 @@ func (q *Queries) ListArtistsAlpha(ctx context.Context, arg ListArtistsAlphaPara
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -188,7 +240,7 @@ func (q *Queries) ListArtistsAlpha(ctx context.Context, arg ListArtistsAlphaPara
|
||||
}
|
||||
|
||||
const listArtistsAlphaWithCovers = `-- name: ListArtistsAlphaWithCovers :many
|
||||
SELECT artists.id, artists.name, artists.sort_name, artists.mbid, artists.created_at, artists.updated_at,
|
||||
SELECT artists.id, artists.name, artists.sort_name, artists.mbid, artists.created_at, artists.updated_at, artists.artist_thumb_path, artists.artist_fanart_path, artists.artist_art_source, artists.artist_art_sources_version,
|
||||
cov.id AS cover_album_id,
|
||||
cnt.album_count::bigint AS album_count
|
||||
FROM artists
|
||||
@@ -237,6 +289,10 @@ func (q *Queries) ListArtistsAlphaWithCovers(ctx context.Context, arg ListArtist
|
||||
&i.Artist.Mbid,
|
||||
&i.Artist.CreatedAt,
|
||||
&i.Artist.UpdatedAt,
|
||||
&i.Artist.ArtistThumbPath,
|
||||
&i.Artist.ArtistFanartPath,
|
||||
&i.Artist.ArtistArtSource,
|
||||
&i.Artist.ArtistArtSourcesVersion,
|
||||
&i.CoverAlbumID,
|
||||
&i.AlbumCount,
|
||||
); err != nil {
|
||||
@@ -250,8 +306,46 @@ func (q *Queries) ListArtistsAlphaWithCovers(ctx context.Context, arg ListArtist
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listArtistsMissingArt = `-- name: ListArtistsMissingArt :many
|
||||
SELECT a.id
|
||||
FROM artists a
|
||||
WHERE a.artist_art_source IS NULL
|
||||
OR (a.artist_art_source = 'none' AND a.artist_art_sources_version != $1)
|
||||
ORDER BY a.created_at
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type ListArtistsMissingArtParams struct {
|
||||
ArtistArtSourcesVersion int32
|
||||
Limit int32
|
||||
}
|
||||
|
||||
// M7 cover-sources: returns artist rows the artist-art enricher should
|
||||
// attempt. Eligible: artist_art_source IS NULL OR (= 'none' AND stale
|
||||
// artist_art_sources_version). MBID-less artists are returned but the
|
||||
// enricher skips them at the row.mbid == nil guard inside the chain.
|
||||
func (q *Queries) ListArtistsMissingArt(ctx context.Context, arg ListArtistsMissingArtParams) ([]pgtype.UUID, error) {
|
||||
rows, err := q.db.Query(ctx, listArtistsMissingArt, arg.ArtistArtSourcesVersion, arg.Limit)
|
||||
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 listArtistsNewest = `-- name: ListArtistsNewest :many
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists ORDER BY created_at DESC, id DESC LIMIT $1 OFFSET $2
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists ORDER BY created_at DESC, id DESC LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ListArtistsNewestParams struct {
|
||||
@@ -276,6 +370,10 @@ func (q *Queries) ListArtistsNewest(ctx context.Context, arg ListArtistsNewestPa
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -288,7 +386,7 @@ func (q *Queries) ListArtistsNewest(ctx context.Context, arg ListArtistsNewestPa
|
||||
}
|
||||
|
||||
const searchArtists = `-- name: SearchArtists :many
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists
|
||||
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists
|
||||
WHERE name ILIKE '%' || $1 || '%'
|
||||
ORDER BY sort_name
|
||||
LIMIT $2 OFFSET $3
|
||||
@@ -316,6 +414,10 @@ func (q *Queries) SearchArtists(ctx context.Context, arg SearchArtistsParams) ([
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -327,6 +429,40 @@ func (q *Queries) SearchArtists(ctx context.Context, arg SearchArtistsParams) ([
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const setArtistArtWithVersion = `-- name: SetArtistArtWithVersion :exec
|
||||
UPDATE artists
|
||||
SET artist_thumb_path = $2,
|
||||
artist_fanart_path = $3,
|
||||
artist_art_source = $4,
|
||||
artist_art_sources_version = $5,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type SetArtistArtWithVersionParams struct {
|
||||
ID pgtype.UUID
|
||||
ArtistThumbPath *string
|
||||
ArtistFanartPath *string
|
||||
ArtistArtSource *string
|
||||
ArtistArtSourcesVersion int32
|
||||
}
|
||||
|
||||
// M7 cover-sources: records a successful or settled artist-art
|
||||
// enrichment with the current sources_version stamp. thumb_path /
|
||||
// fanart_path are NULL on a 'none' result, or independently NULL if
|
||||
// the provider returned only one of the two image types (e.g.
|
||||
// TheAudioDB had a thumb but no fanart for this artist).
|
||||
func (q *Queries) SetArtistArtWithVersion(ctx context.Context, arg SetArtistArtWithVersionParams) error {
|
||||
_, err := q.db.Exec(ctx, setArtistArtWithVersion,
|
||||
arg.ID,
|
||||
arg.ArtistThumbPath,
|
||||
arg.ArtistFanartPath,
|
||||
arg.ArtistArtSource,
|
||||
arg.ArtistArtSourcesVersion,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const setArtistMbidIfNull = `-- name: SetArtistMbidIfNull :exec
|
||||
UPDATE artists
|
||||
SET mbid = $2,
|
||||
@@ -355,7 +491,7 @@ DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
sort_name = EXCLUDED.sort_name,
|
||||
updated_at = now()
|
||||
RETURNING id, name, sort_name, mbid, created_at, updated_at
|
||||
RETURNING id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version
|
||||
`
|
||||
|
||||
type UpsertArtistParams struct {
|
||||
@@ -376,6 +512,10 @@ func (q *Queries) UpsertArtist(ctx context.Context, arg UpsertArtistParams) (Art
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: coverart_settings.sql
|
||||
|
||||
package dbq
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const bumpSourcesVersion = `-- name: BumpSourcesVersion :one
|
||||
UPDATE cover_art_sources_meta
|
||||
SET current_version = current_version + 1
|
||||
WHERE id = true
|
||||
RETURNING current_version
|
||||
`
|
||||
|
||||
// Increments and returns the new version. Called by SettingsService
|
||||
// only when the *enabled set* changes (not on key/display_order
|
||||
// changes). RETURNING gives the new value back so the handler can
|
||||
// include it in the response without a follow-up SELECT.
|
||||
func (q *Queries) BumpSourcesVersion(ctx context.Context) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, bumpSourcesVersion)
|
||||
var current_version int32
|
||||
err := row.Scan(¤t_version)
|
||||
return current_version, err
|
||||
}
|
||||
|
||||
const getCurrentSourcesVersion = `-- name: GetCurrentSourcesVersion :one
|
||||
SELECT current_version FROM cover_art_sources_meta WHERE id = true
|
||||
`
|
||||
|
||||
func (q *Queries) GetCurrentSourcesVersion(ctx context.Context) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, getCurrentSourcesVersion)
|
||||
var current_version int32
|
||||
err := row.Scan(¤t_version)
|
||||
return current_version, err
|
||||
}
|
||||
|
||||
const listProviderSettings = `-- name: ListProviderSettings :many
|
||||
|
||||
SELECT provider_id, enabled, api_key, display_order, created_at, updated_at
|
||||
FROM cover_art_provider_settings
|
||||
ORDER BY display_order, provider_id
|
||||
`
|
||||
|
||||
// M7 cover-sources: queries for the cover_art_provider_settings table
|
||||
// and the cover_art_sources_meta singleton. The SettingsService
|
||||
// consumes these.
|
||||
// Returns all rows from cover_art_provider_settings. Used by the
|
||||
// SettingsService at boot for reconciliation and by the admin GET
|
||||
// handler.
|
||||
func (q *Queries) ListProviderSettings(ctx context.Context) ([]CoverArtProviderSetting, error) {
|
||||
rows, err := q.db.Query(ctx, listProviderSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []CoverArtProviderSetting
|
||||
for rows.Next() {
|
||||
var i CoverArtProviderSetting
|
||||
if err := rows.Scan(
|
||||
&i.ProviderID,
|
||||
&i.Enabled,
|
||||
&i.ApiKey,
|
||||
&i.DisplayOrder,
|
||||
&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 updateProviderSettings = `-- name: UpdateProviderSettings :exec
|
||||
UPDATE cover_art_provider_settings
|
||||
SET enabled = COALESCE($2, enabled),
|
||||
api_key = CASE WHEN $3::boolean THEN $4 ELSE api_key END,
|
||||
updated_at = now()
|
||||
WHERE provider_id = $1
|
||||
`
|
||||
|
||||
type UpdateProviderSettingsParams struct {
|
||||
ProviderID string
|
||||
Enabled bool
|
||||
Column3 bool
|
||||
ApiKey *string
|
||||
}
|
||||
|
||||
// Admin PATCH applies one or both fields. Pass NULL for enabled to
|
||||
// leave it unchanged. Pass FALSE for the apiKeyChanged flag to leave
|
||||
// the api_key unchanged regardless of apiKey value; pass TRUE with
|
||||
// empty string to clear, or TRUE with non-empty to set.
|
||||
func (q *Queries) UpdateProviderSettings(ctx context.Context, arg UpdateProviderSettingsParams) error {
|
||||
_, err := q.db.Exec(ctx, updateProviderSettings,
|
||||
arg.ProviderID,
|
||||
arg.Enabled,
|
||||
arg.Column3,
|
||||
arg.ApiKey,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertProviderSettings = `-- name: UpsertProviderSettings :exec
|
||||
INSERT INTO cover_art_provider_settings (provider_id, enabled, display_order)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (provider_id) DO UPDATE
|
||||
SET display_order = EXCLUDED.display_order,
|
||||
updated_at = now()
|
||||
`
|
||||
|
||||
type UpsertProviderSettingsParams struct {
|
||||
ProviderID string
|
||||
Enabled bool
|
||||
DisplayOrder int32
|
||||
}
|
||||
|
||||
// INSERT a default row for a newly-registered provider, or update an
|
||||
// existing row's display_order on boot reconciliation. Does NOT
|
||||
// overwrite enabled / api_key on conflict — the operator's settings
|
||||
// persist across restarts.
|
||||
func (q *Queries) UpsertProviderSettings(ctx context.Context, arg UpsertProviderSettingsParams) error {
|
||||
_, err := q.db.Exec(ctx, upsertProviderSettings, arg.ProviderID, arg.Enabled, arg.DisplayOrder)
|
||||
return err
|
||||
}
|
||||
@@ -109,41 +109,38 @@ func (q *Queries) GetAlbumWithFirstTrackPath(ctx context.Context, id pgtype.UUID
|
||||
|
||||
const listAlbumsMissingCover = `-- name: ListAlbumsMissingCover :many
|
||||
|
||||
SELECT a.id, a.mbid, a.title, a.artist_id
|
||||
SELECT a.id
|
||||
FROM albums a
|
||||
WHERE a.cover_art_source IS NULL
|
||||
ORDER BY a.created_at ASC
|
||||
LIMIT $1
|
||||
OR (a.cover_art_source = 'none' AND a.cover_art_sources_version != $1)
|
||||
ORDER BY a.created_at
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type ListAlbumsMissingCoverRow struct {
|
||||
ID pgtype.UUID
|
||||
Mbid *string
|
||||
Title string
|
||||
ArtistID pgtype.UUID
|
||||
type ListAlbumsMissingCoverParams struct {
|
||||
CoverArtSourcesVersion int32
|
||||
Limit int32
|
||||
}
|
||||
|
||||
// M7 #353: album cover enrichment queries.
|
||||
// Albums where cover_art_source has never been set. Used by the boot
|
||||
// backfill and post-scan enrichment passes. LIMIT supplied by caller.
|
||||
func (q *Queries) ListAlbumsMissingCover(ctx context.Context, limit int32) ([]ListAlbumsMissingCoverRow, error) {
|
||||
rows, err := q.db.Query(ctx, listAlbumsMissingCover, limit)
|
||||
// M7 cover-sources: returns rows the enricher should attempt. Eligible:
|
||||
// cover_art_source IS NULL (never tried), OR cover_art_source = 'none'
|
||||
// AND cover_art_sources_version != current (settled under an older
|
||||
// source set; recheck against the current chain). LIMIT supplied by
|
||||
// caller for batching.
|
||||
func (q *Queries) ListAlbumsMissingCover(ctx context.Context, arg ListAlbumsMissingCoverParams) ([]pgtype.UUID, error) {
|
||||
rows, err := q.db.Query(ctx, listAlbumsMissingCover, arg.CoverArtSourcesVersion, arg.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListAlbumsMissingCoverRow
|
||||
var items []pgtype.UUID
|
||||
for rows.Next() {
|
||||
var i ListAlbumsMissingCoverRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Mbid,
|
||||
&i.Title,
|
||||
&i.ArtistID,
|
||||
); err != nil {
|
||||
var id pgtype.UUID
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
items = append(items, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -120,7 +120,7 @@ func (q *Queries) ListLikedAlbumIDs(ctx context.Context, userID pgtype.UUID) ([]
|
||||
}
|
||||
|
||||
const listLikedAlbumRows = `-- name: ListLikedAlbumRows :many
|
||||
SELECT a.id, a.title, a.sort_title, a.artist_id, a.release_date, a.mbid, a.cover_art_path, a.created_at, a.updated_at, a.cover_art_source FROM albums a
|
||||
SELECT a.id, a.title, a.sort_title, a.artist_id, a.release_date, a.mbid, a.cover_art_path, a.created_at, a.updated_at, a.cover_art_source, a.cover_art_sources_version FROM albums a
|
||||
JOIN general_likes_albums l ON l.album_id = a.id
|
||||
WHERE l.user_id = $1
|
||||
ORDER BY l.liked_at DESC
|
||||
@@ -153,6 +153,7 @@ func (q *Queries) ListLikedAlbumRows(ctx context.Context, arg ListLikedAlbumRows
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CoverArtSource,
|
||||
&i.CoverArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -189,7 +190,7 @@ func (q *Queries) ListLikedArtistIDs(ctx context.Context, userID pgtype.UUID) ([
|
||||
}
|
||||
|
||||
const listLikedArtistRows = `-- name: ListLikedArtistRows :many
|
||||
SELECT a.id, a.name, a.sort_name, a.mbid, a.created_at, a.updated_at FROM artists a
|
||||
SELECT a.id, a.name, a.sort_name, a.mbid, a.created_at, a.updated_at, a.artist_thumb_path, a.artist_fanart_path, a.artist_art_source, a.artist_art_sources_version FROM artists a
|
||||
JOIN general_likes_artists l ON l.artist_id = a.id
|
||||
WHERE l.user_id = $1
|
||||
ORDER BY l.liked_at DESC
|
||||
@@ -218,6 +219,10 @@ func (q *Queries) ListLikedArtistRows(ctx context.Context, arg ListLikedArtistRo
|
||||
&i.Mbid,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ArtistThumbPath,
|
||||
&i.ArtistFanartPath,
|
||||
&i.ArtistArtSource,
|
||||
&i.ArtistArtSourcesVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+43
-23
@@ -188,25 +188,30 @@ func (ns NullLidarrRequestStatus) Value() (driver.Value, error) {
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
ID pgtype.UUID
|
||||
Title string
|
||||
SortTitle string
|
||||
ArtistID pgtype.UUID
|
||||
ReleaseDate pgtype.Date
|
||||
Mbid *string
|
||||
CoverArtPath *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
CoverArtSource *string
|
||||
ID pgtype.UUID
|
||||
Title string
|
||||
SortTitle string
|
||||
ArtistID pgtype.UUID
|
||||
ReleaseDate pgtype.Date
|
||||
Mbid *string
|
||||
CoverArtPath *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
CoverArtSource *string
|
||||
CoverArtSourcesVersion int32
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
ID pgtype.UUID
|
||||
Name string
|
||||
SortName string
|
||||
Mbid *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
ID pgtype.UUID
|
||||
Name string
|
||||
SortName string
|
||||
Mbid *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
ArtistThumbPath *string
|
||||
ArtistFanartPath *string
|
||||
ArtistArtSource *string
|
||||
ArtistArtSourcesVersion int32
|
||||
}
|
||||
|
||||
type ArtistSimilarity struct {
|
||||
@@ -236,6 +241,20 @@ type ContextualLike struct {
|
||||
SessionID pgtype.UUID
|
||||
}
|
||||
|
||||
type CoverArtProviderSetting struct {
|
||||
ProviderID string
|
||||
Enabled bool
|
||||
ApiKey *string
|
||||
DisplayOrder int32
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type CoverArtSourcesMetum struct {
|
||||
ID bool
|
||||
CurrentVersion int32
|
||||
}
|
||||
|
||||
type GeneralLike struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
@@ -364,13 +383,14 @@ type PlaylistTrack struct {
|
||||
}
|
||||
|
||||
type ScanRun struct {
|
||||
ID pgtype.UUID
|
||||
StartedAt pgtype.Timestamptz
|
||||
FinishedAt pgtype.Timestamptz
|
||||
Library []byte
|
||||
MbidBackfill []byte
|
||||
CoverEnrich []byte
|
||||
ErrorMessage *string
|
||||
ID pgtype.UUID
|
||||
StartedAt pgtype.Timestamptz
|
||||
FinishedAt pgtype.Timestamptz
|
||||
Library []byte
|
||||
MbidBackfill []byte
|
||||
CoverEnrich []byte
|
||||
ErrorMessage *string
|
||||
ArtistArtEnrich []byte
|
||||
}
|
||||
|
||||
type ScrobbleQueue struct {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const listLastPlayedArtistsForUser = `-- name: ListLastPlayedArtistsForUser :many
|
||||
SELECT a.id, a.name, a.sort_name, a.mbid, a.created_at, a.updated_at,
|
||||
SELECT a.id, a.name, a.sort_name, a.mbid, a.created_at, a.updated_at, a.artist_thumb_path, a.artist_fanart_path, a.artist_art_source, a.artist_art_sources_version,
|
||||
cov.id AS cover_album_id,
|
||||
cnt.album_count::bigint AS album_count,
|
||||
max_started.started_at::timestamptz AS last_played_at
|
||||
@@ -68,6 +68,10 @@ func (q *Queries) ListLastPlayedArtistsForUser(ctx context.Context, arg ListLast
|
||||
&i.Artist.Mbid,
|
||||
&i.Artist.CreatedAt,
|
||||
&i.Artist.UpdatedAt,
|
||||
&i.Artist.ArtistThumbPath,
|
||||
&i.Artist.ArtistFanartPath,
|
||||
&i.Artist.ArtistArtSource,
|
||||
&i.Artist.ArtistArtSourcesVersion,
|
||||
&i.CoverAlbumID,
|
||||
&i.AlbumCount,
|
||||
&i.LastPlayedAt,
|
||||
@@ -157,7 +161,7 @@ func (q *Queries) ListMostPlayedTracksForUser(ctx context.Context, arg ListMostP
|
||||
}
|
||||
|
||||
const listRediscoverAlbumsFallbackForUser = `-- name: ListRediscoverAlbumsFallbackForUser :many
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, artists.name AS artist_name
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, artists.name AS artist_name
|
||||
FROM general_likes_albums gla
|
||||
JOIN albums ON albums.id = gla.album_id
|
||||
JOIN artists ON artists.id = albums.artist_id
|
||||
@@ -199,6 +203,7 @@ func (q *Queries) ListRediscoverAlbumsFallbackForUser(ctx context.Context, arg L
|
||||
&i.Album.CreatedAt,
|
||||
&i.Album.UpdatedAt,
|
||||
&i.Album.CoverArtSource,
|
||||
&i.Album.CoverArtSourcesVersion,
|
||||
&i.ArtistName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -226,7 +231,7 @@ WITH eligible AS (
|
||||
HAVING COALESCE(max(pe.started_at), '1970-01-01'::timestamptz)
|
||||
< now() - interval '14 days'
|
||||
)
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, artists.name AS artist_name
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, artists.name AS artist_name
|
||||
FROM eligible e
|
||||
JOIN albums ON albums.id = e.album_id
|
||||
JOIN artists ON artists.id = albums.artist_id
|
||||
@@ -268,6 +273,7 @@ func (q *Queries) ListRediscoverAlbumsForUser(ctx context.Context, arg ListRedis
|
||||
&i.Album.CreatedAt,
|
||||
&i.Album.UpdatedAt,
|
||||
&i.Album.CoverArtSource,
|
||||
&i.Album.CoverArtSourcesVersion,
|
||||
&i.ArtistName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -281,7 +287,7 @@ func (q *Queries) ListRediscoverAlbumsForUser(ctx context.Context, arg ListRedis
|
||||
}
|
||||
|
||||
const listRediscoverArtistsFallbackForUser = `-- name: ListRediscoverArtistsFallbackForUser :many
|
||||
SELECT artists.id, artists.name, artists.sort_name, artists.mbid, artists.created_at, artists.updated_at,
|
||||
SELECT artists.id, artists.name, artists.sort_name, artists.mbid, artists.created_at, artists.updated_at, artists.artist_thumb_path, artists.artist_fanart_path, artists.artist_art_source, artists.artist_art_sources_version,
|
||||
cov.id AS cover_album_id,
|
||||
cnt.album_count::bigint AS album_count
|
||||
FROM general_likes_artists gla
|
||||
@@ -327,6 +333,10 @@ func (q *Queries) ListRediscoverArtistsFallbackForUser(ctx context.Context, arg
|
||||
&i.Artist.Mbid,
|
||||
&i.Artist.CreatedAt,
|
||||
&i.Artist.UpdatedAt,
|
||||
&i.Artist.ArtistThumbPath,
|
||||
&i.Artist.ArtistFanartPath,
|
||||
&i.Artist.ArtistArtSource,
|
||||
&i.Artist.ArtistArtSourcesVersion,
|
||||
&i.CoverAlbumID,
|
||||
&i.AlbumCount,
|
||||
); err != nil {
|
||||
@@ -355,7 +365,7 @@ WITH eligible AS (
|
||||
HAVING COALESCE(max(pe.started_at), '1970-01-01'::timestamptz)
|
||||
< now() - interval '14 days'
|
||||
)
|
||||
SELECT artists.id, artists.name, artists.sort_name, artists.mbid, artists.created_at, artists.updated_at,
|
||||
SELECT artists.id, artists.name, artists.sort_name, artists.mbid, artists.created_at, artists.updated_at, artists.artist_thumb_path, artists.artist_fanart_path, artists.artist_art_source, artists.artist_art_sources_version,
|
||||
cov.id AS cover_album_id,
|
||||
cnt.album_count::bigint AS album_count
|
||||
FROM eligible e
|
||||
@@ -403,6 +413,10 @@ func (q *Queries) ListRediscoverArtistsForUser(ctx context.Context, arg ListRedi
|
||||
&i.Artist.Mbid,
|
||||
&i.Artist.CreatedAt,
|
||||
&i.Artist.UpdatedAt,
|
||||
&i.Artist.ArtistThumbPath,
|
||||
&i.Artist.ArtistFanartPath,
|
||||
&i.Artist.ArtistArtSource,
|
||||
&i.Artist.ArtistArtSourcesVersion,
|
||||
&i.CoverAlbumID,
|
||||
&i.AlbumCount,
|
||||
); err != nil {
|
||||
|
||||
@@ -59,11 +59,21 @@ SELECT id, started_at, finished_at, library, mbid_backfill, cover_enrich, error_
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
type GetLatestScanRunRow struct {
|
||||
ID pgtype.UUID
|
||||
StartedAt pgtype.Timestamptz
|
||||
FinishedAt pgtype.Timestamptz
|
||||
Library []byte
|
||||
MbidBackfill []byte
|
||||
CoverEnrich []byte
|
||||
ErrorMessage *string
|
||||
}
|
||||
|
||||
// Powers GET /api/admin/scan/status. Returns NoRows when no scan has
|
||||
// ever run; the API layer maps that to a 200 with empty payload.
|
||||
func (q *Queries) GetLatestScanRun(ctx context.Context) (ScanRun, error) {
|
||||
func (q *Queries) GetLatestScanRun(ctx context.Context) (GetLatestScanRunRow, error) {
|
||||
row := q.db.QueryRow(ctx, getLatestScanRun)
|
||||
var i ScanRun
|
||||
var i GetLatestScanRunRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.StartedAt,
|
||||
|
||||
@@ -136,15 +136,26 @@ SELECT a.id AS album_id,
|
||||
-- Invariant: with_art + pending + settled = total. (pending_no_mbid is
|
||||
-- not part of the sum — it's a subset of pending, surfaced separately.)
|
||||
--
|
||||
-- The IN list below ('sidecar','embedded','mbcaa') must stay in sync
|
||||
-- The IN list below ('sidecar','embedded','mbcaa','theaudiodb') must stay in sync
|
||||
-- with the cover_art_source CHECK constraint in migration
|
||||
-- 0016_album_cover_source.up.sql. If a new source value is added there
|
||||
-- without updating this query, with_art will silently undercount.
|
||||
SELECT
|
||||
COUNT(*) AS total,
|
||||
COUNT(*) FILTER (WHERE cover_art_source IN ('sidecar','embedded','mbcaa')) AS with_art,
|
||||
COUNT(*) FILTER (WHERE cover_art_source IN ('sidecar','embedded','mbcaa','theaudiodb')) AS with_art,
|
||||
COUNT(*) FILTER (WHERE cover_art_source IS NULL) AS pending,
|
||||
COUNT(*) FILTER (WHERE cover_art_source = 'none') AS settled,
|
||||
COUNT(*) FILTER (WHERE cover_art_source IS NULL AND mbid IS NULL) AS pending_no_mbid
|
||||
FROM albums;
|
||||
|
||||
-- name: SetAlbumCoverWithVersion :exec
|
||||
-- M7 cover-sources: records a successful or settled enrichment with the
|
||||
-- current sources_version stamp. cover_art_path is NULL when source is
|
||||
-- 'none' (no file written). Atomic; called per album per pass.
|
||||
UPDATE albums
|
||||
SET cover_art_path = $2,
|
||||
cover_art_source = $3,
|
||||
cover_art_sources_version = $4,
|
||||
updated_at = now()
|
||||
WHERE id = $1;
|
||||
|
||||
|
||||
@@ -11,7 +11,15 @@ DO UPDATE SET
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetArtistByID :one
|
||||
SELECT * FROM artists WHERE id = $1;
|
||||
-- M7 cover-sources: enricher reads the artist's mbid + current art
|
||||
-- columns to drive eligibility check. (Modified from SELECT * to
|
||||
-- explicitly include the new artist_art_* columns added in migration
|
||||
-- 0018.)
|
||||
SELECT a.id, a.name, a.sort_name, a.mbid,
|
||||
a.artist_thumb_path, a.artist_fanart_path,
|
||||
a.artist_art_source, a.artist_art_sources_version
|
||||
FROM artists a
|
||||
WHERE a.id = $1;
|
||||
|
||||
-- name: GetArtistByName :one
|
||||
SELECT * FROM artists WHERE name = $1 LIMIT 1;
|
||||
@@ -83,3 +91,38 @@ UPDATE artists
|
||||
SET mbid = $2,
|
||||
updated_at = now()
|
||||
WHERE id = $1 AND mbid IS NULL;
|
||||
|
||||
-- name: ListArtistsMissingArt :many
|
||||
-- M7 cover-sources: returns artist rows the artist-art enricher should
|
||||
-- attempt. Eligible: artist_art_source IS NULL OR (= 'none' AND stale
|
||||
-- artist_art_sources_version). MBID-less artists are returned but the
|
||||
-- enricher skips them at the row.mbid == nil guard inside the chain.
|
||||
SELECT a.id
|
||||
FROM artists a
|
||||
WHERE a.artist_art_source IS NULL
|
||||
OR (a.artist_art_source = 'none' AND a.artist_art_sources_version != $1)
|
||||
ORDER BY a.created_at
|
||||
LIMIT $2;
|
||||
|
||||
-- name: SetArtistArtWithVersion :exec
|
||||
-- M7 cover-sources: records a successful or settled artist-art
|
||||
-- enrichment with the current sources_version stamp. thumb_path /
|
||||
-- fanart_path are NULL on a 'none' result, or independently NULL if
|
||||
-- the provider returned only one of the two image types (e.g.
|
||||
-- TheAudioDB had a thumb but no fanart for this artist).
|
||||
UPDATE artists
|
||||
SET artist_thumb_path = $2,
|
||||
artist_fanart_path = $3,
|
||||
artist_art_source = $4,
|
||||
artist_art_sources_version = $5,
|
||||
updated_at = now()
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: ClearArtistArtNone :exec
|
||||
-- M7 cover-sources: parallel of ClearAlbumCoverNone. Used by the
|
||||
-- eligibility loop to flip stale 'none' rows back to NULL.
|
||||
UPDATE artists
|
||||
SET artist_art_source = NULL,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
AND artist_art_source = 'none';
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
-- M7 cover-sources: queries for the cover_art_provider_settings table
|
||||
-- and the cover_art_sources_meta singleton. The SettingsService
|
||||
-- consumes these.
|
||||
|
||||
-- name: ListProviderSettings :many
|
||||
-- Returns all rows from cover_art_provider_settings. Used by the
|
||||
-- SettingsService at boot for reconciliation and by the admin GET
|
||||
-- handler.
|
||||
SELECT provider_id, enabled, api_key, display_order, created_at, updated_at
|
||||
FROM cover_art_provider_settings
|
||||
ORDER BY display_order, provider_id;
|
||||
|
||||
-- name: UpsertProviderSettings :exec
|
||||
-- INSERT a default row for a newly-registered provider, or update an
|
||||
-- existing row's display_order on boot reconciliation. Does NOT
|
||||
-- overwrite enabled / api_key on conflict — the operator's settings
|
||||
-- persist across restarts.
|
||||
INSERT INTO cover_art_provider_settings (provider_id, enabled, display_order)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (provider_id) DO UPDATE
|
||||
SET display_order = EXCLUDED.display_order,
|
||||
updated_at = now();
|
||||
|
||||
-- name: UpdateProviderSettings :exec
|
||||
-- Admin PATCH applies one or both fields. Pass NULL for enabled to
|
||||
-- leave it unchanged. Pass FALSE for the apiKeyChanged flag to leave
|
||||
-- the api_key unchanged regardless of apiKey value; pass TRUE with
|
||||
-- empty string to clear, or TRUE with non-empty to set.
|
||||
UPDATE cover_art_provider_settings
|
||||
SET enabled = COALESCE($2, enabled),
|
||||
api_key = CASE WHEN $3::boolean THEN $4 ELSE api_key END,
|
||||
updated_at = now()
|
||||
WHERE provider_id = $1;
|
||||
|
||||
-- name: GetCurrentSourcesVersion :one
|
||||
SELECT current_version FROM cover_art_sources_meta WHERE id = true;
|
||||
|
||||
-- name: BumpSourcesVersion :one
|
||||
-- Increments and returns the new version. Called by SettingsService
|
||||
-- only when the *enabled set* changes (not on key/display_order
|
||||
-- changes). RETURNING gives the new value back so the handler can
|
||||
-- include it in the response without a follow-up SELECT.
|
||||
UPDATE cover_art_sources_meta
|
||||
SET current_version = current_version + 1
|
||||
WHERE id = true
|
||||
RETURNING current_version;
|
||||
@@ -1,13 +1,17 @@
|
||||
-- M7 #353: album cover enrichment queries.
|
||||
|
||||
-- name: ListAlbumsMissingCover :many
|
||||
-- Albums where cover_art_source has never been set. Used by the boot
|
||||
-- backfill and post-scan enrichment passes. LIMIT supplied by caller.
|
||||
SELECT a.id, a.mbid, a.title, a.artist_id
|
||||
-- M7 cover-sources: returns rows the enricher should attempt. Eligible:
|
||||
-- cover_art_source IS NULL (never tried), OR cover_art_source = 'none'
|
||||
-- AND cover_art_sources_version != current (settled under an older
|
||||
-- source set; recheck against the current chain). LIMIT supplied by
|
||||
-- caller for batching.
|
||||
SELECT a.id
|
||||
FROM albums a
|
||||
WHERE a.cover_art_source IS NULL
|
||||
ORDER BY a.created_at ASC
|
||||
LIMIT $1;
|
||||
OR (a.cover_art_source = 'none' AND a.cover_art_sources_version != $1)
|
||||
ORDER BY a.created_at
|
||||
LIMIT $2;
|
||||
|
||||
-- name: ListAlbumsRetryMissing :many
|
||||
-- Bulk-retry candidates: NULL (never tried) plus 'none' (tried, failed).
|
||||
|
||||
Reference in New Issue
Block a user