Files
vibesonic/internal/database/external_metadata_cache.go
owenandClaude Sonnet 5 a921f9e2bc Add optional MusicBrainz/Cover Art Archive metadata enrichment
getArtistInfo/2 and getAlbumInfo/2 now return a real MusicBrainz
disambiguation/cover-art lookup for entities that carry a locally
tagged MusicBrainz ID, gated behind the new
VIBESONIC_ENABLE_EXTERNAL_METADATA flag (default off). Responses are
cached in the existing external_metadata_cache table for 30 days to
avoid hammering the free MusicBrainz/CAA services.

VibeSonic still works completely offline: with the flag off, or when
an entity has no tagged MusicBrainz ID, or on any network/lookup
failure, the handlers fall back to local-only fields exactly as
before — nothing is ever fabricated, and a failed external lookup
never fails the request.

internal/metadataenrich is a small, independently-testable HTTP client
for the two real APIs (base URLs overridable for tests, exercised
against httptest servers rather than the live network).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014r98AtcBbevpscWLsfwVvv
2026-08-23 08:35:40 -05:00

40 lines
1.5 KiB
Go

package database
import (
"database/sql"
"time"
"git.noroutes.net/NoRoutes/vibetunes/internal/idgen"
)
// GetCachedExternalMetadata returns a previously fetched MusicBrainz/
// Cover Art Archive response for (entityType, mbid, source), if any.
// found is false when nothing has been cached yet — callers decide
// whether/how to refresh, this layer never guesses an age-based expiry.
func GetCachedExternalMetadata(db *sql.DB, entityType, mbid, source string) (dataJSON string, fetchedAt time.Time, found bool, err error) {
var fetchedAtStr string
err = db.QueryRow(`SELECT data_json, fetched_at FROM external_metadata_cache WHERE entity_type = ? AND mbid = ? AND source = ?`,
entityType, mbid, source).Scan(&dataJSON, &fetchedAtStr)
if err == sql.ErrNoRows {
return "", time.Time{}, false, nil
}
if err != nil {
return "", time.Time{}, false, err
}
fetchedAt, _ = time.Parse("2006-01-02T15:04:05.000Z", fetchedAtStr)
return dataJSON, fetchedAt, true, nil
}
// PutCachedExternalMetadata stores (or replaces) the cached response
// for (entityType, mbid, source).
func PutCachedExternalMetadata(db *sql.DB, entityType, mbid, source, dataJSON string) error {
_, err := db.Exec(`
INSERT INTO external_metadata_cache (id, entity_type, mbid, source, data_json, fetched_at)
VALUES (?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))
ON CONFLICT(entity_type, mbid, source) DO UPDATE SET
data_json = excluded.data_json,
fetched_at = excluded.fetched_at`,
idgen.New("emc"), entityType, mbid, source, dataJSON)
return err
}