refactor(server/coverart): provider interface takes ArtistRef/AlbumRef

Changes the AlbumCoverProvider and ArtistArtProvider interfaces from
MBID-only string parameters to ref-struct signatures so name-based
providers (Deezer, Last.fm in upcoming tasks) can act on rows
without MBIDs.

Today, 53 of 213 artists in the dev DB have NULL MBIDs — featured
artists, remixers, and compilation contributors created from
track-tag splits where the primary-artist MBID is in the file but
collaborator IDs aren't. These rows are unreachable by the current
MBID-only chain. Refactoring the interface unblocks future
name-based providers from acting on them.

TheAudioDB and MBCAA keep MBID-only behavior internally: they return
ErrNotFound when ref.MBID is empty rather than attempting a
name-based fallback. The MBID guard inside EnrichAlbum/EnrichArtist
is removed; providers receive the ref unconditionally and decide
whether they can act on it.

GetAlbumWithFirstTrackPath now JOINs artists to return artist_name
alongside the existing fields, supporting AlbumRef construction.

No behavioral change today (only TheAudioDB + MBCAA registered, both
keep MBID-only behavior). The change unblocks T3 (Deezer) and T4
(Last.fm) which can now register and act on every artist/album
regardless of MBID presence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 07:44:34 -04:00
parent 9a705d0ba4
commit 425f12db38
13 changed files with 99 additions and 58 deletions
+8 -5
View File
@@ -56,18 +56,21 @@ func (e *Enricher) EnrichArtist(ctx context.Context, artistID pgtype.UUID, dataD
}
}
// MBID guard.
if row.Mbid == nil || *row.Mbid == "" {
return nil
// Build the ref. Every provider receives it and decides whether it
// can act on the fields available (MBID-only providers return
// ErrNotFound when MBID is empty; name-based providers fall back to
// Name).
ref := ArtistRef{Name: row.Name}
if row.Mbid != nil {
ref.MBID = *row.Mbid
}
mbid := *row.Mbid
// Provider chain. allWere404 tracks whether every provider returned
// ErrNotFound — only then do we settle to 'none'. Any transient
// failure leaves the row NULL for next-pass retry.
allWere404 := true
for _, provider := range e.settings.EnabledArtistProviders() {
thumb, fanart, perr := provider.FetchArtistArt(ctx, mbid)
thumb, fanart, perr := provider.FetchArtistArt(ctx, ref)
if perr == nil {
// Atomic write — at least one of (thumb, fanart) is non-nil
// per the ArtistArtProvider contract; persist whichever