feat(S02/T01): Added CoverArtGroupURL for release-group cover art, conc…

- backend/explore/coverart.go
- backend/explore/explore.go
- backend/explore/coverart_test.go
- frontend/wailsjs/go/explore/Service.js
- frontend/wailsjs/go/explore/Service.d.ts
This commit is contained in:
2026-03-23 17:31:01 -04:00
parent 1ff53d8084
commit 677ed01d89
5 changed files with 316 additions and 1 deletions
+19 -1
View File
@@ -2,7 +2,10 @@ package explore
import "fmt"
const coverArtBaseURL = "https://coverartarchive.org/release"
const (
coverArtBaseURL = "https://coverartarchive.org/release"
coverArtGroupBaseURL = "https://coverartarchive.org/release-group"
)
// CoverArtURL returns the Cover Art Archive URL for the 250px
// front cover of the given release MBID.
@@ -16,3 +19,18 @@ func CoverArtURL(releaseMBID string) string {
func CoverArtURLSize(releaseMBID string, size int) string {
return fmt.Sprintf("%s/%s/front-%d", coverArtBaseURL, releaseMBID, size)
}
// CoverArtGroupURL returns the Cover Art Archive URL for the 250px
// front cover of the given release group MBID. Search results
// return release group MBIDs (not release MBIDs), so this is the
// correct endpoint for displaying cover art in search results.
func CoverArtGroupURL(releaseGroupMBID string) string {
return fmt.Sprintf("%s/%s/front-250", coverArtGroupBaseURL, releaseGroupMBID)
}
// CoverArtGroupURLSize returns the Cover Art Archive URL for the
// front cover of the given release group MBID at the specified
// pixel size. Common sizes are 250, 500, and 1200.
func CoverArtGroupURLSize(releaseGroupMBID string, size int) string {
return fmt.Sprintf("%s/%s/front-%d", coverArtGroupBaseURL, releaseGroupMBID, size)
}
+45
View File
@@ -50,3 +50,48 @@ func TestCoverArtURLSize(t *testing.T) {
}
}
}
func TestCoverArtGroupURL(t *testing.T) {
t.Parallel()
mbid := "abc-123"
got := explore.CoverArtGroupURL(mbid)
want := "https://coverartarchive.org/release-group/abc-123/front-250"
if got != want {
t.Errorf("CoverArtGroupURL(%q) = %q, want %q", mbid, got, want)
}
}
func TestCoverArtGroupURLSize(t *testing.T) {
t.Parallel()
mbid := "abc-123"
tests := []struct {
size int
want string
}{
{
250,
"https://coverartarchive.org/release-group/abc-123/front-250",
},
{
500,
"https://coverartarchive.org/release-group/abc-123/front-500",
},
{
1200,
"https://coverartarchive.org/release-group/abc-123/front-1200",
},
}
for _, tt := range tests {
got := explore.CoverArtGroupURLSize(mbid, tt.size)
if got != tt.want {
t.Errorf("CoverArtGroupURLSize(%q, %d) = %q, want %q",
mbid, tt.size, got, tt.want)
}
}
}
+109
View File
@@ -3,6 +3,7 @@ package explore
import (
"context"
"log/slog"
"sync"
"yellowjacket/backend/database"
)
@@ -116,3 +117,111 @@ func (e *Service) SimilarArtists(artistMBID string) ([]LBSimilarArtist, error) {
func (e *Service) CoverArtURL(releaseMBID string) string {
return CoverArtURL(releaseMBID)
}
// CoverArtGroupURL returns the Cover Art Archive URL for a release
// group's front cover at the default 250px size. This is the
// correct endpoint for search results, which return release group
// MBIDs rather than individual release MBIDs.
func (e *Service) CoverArtGroupURL(releaseGroupMBID string) string {
return CoverArtGroupURL(releaseGroupMBID)
}
// Search concurrently queries MusicBrainz for artists, release
// groups, and recordings matching the query, returning aggregated
// results in a single round-trip. If any sub-search fails the
// error is logged and the remaining results are still returned.
func (e *Service) Search(query string) (*MBSearchResult, error) {
e.logger.Info("search started", "query", query)
var (
result MBSearchResult
mu sync.Mutex
wg sync.WaitGroup
)
type searchFunc struct {
name string
fn func()
}
searches := []searchFunc{
{
name: "artists",
fn: func() {
artists, err := e.mb.SearchArtists(e.ctx, query, 0)
if err != nil {
e.logger.Warn("search sub-call failed",
"entity", "artists",
"query", query,
"error", err,
)
return
}
mu.Lock()
result.Artists = artists
mu.Unlock()
},
},
{
name: "releaseGroups",
fn: func() {
rgs, err := e.mb.SearchReleaseGroups(e.ctx, query, 0)
if err != nil {
e.logger.Warn("search sub-call failed",
"entity", "releaseGroups",
"query", query,
"error", err,
)
return
}
mu.Lock()
result.ReleaseGroups = rgs
mu.Unlock()
},
},
{
name: "recordings",
fn: func() {
recs, err := e.mb.SearchRecordings(e.ctx, query, 0)
if err != nil {
e.logger.Warn("search sub-call failed",
"entity", "recordings",
"query", query,
"error", err,
)
return
}
mu.Lock()
result.Recordings = recs
mu.Unlock()
},
},
}
wg.Add(len(searches))
for _, s := range searches {
go func() {
defer wg.Done()
s.fn()
}()
}
wg.Wait()
e.logger.Info("search completed",
"query", query,
"artists", len(result.Artists),
"releaseGroups", len(result.ReleaseGroups),
"recordings", len(result.Recordings),
)
return &result, nil
}