Some checks failed
Build and Push Docker Image / build (pull_request) Failing after 38s
This commit includes: 1. Live/Remix Filtering Feature: - Added ignore_live and ignore_remix columns to artist_settings table (migration 010) - Updated ArtistSettings struct with IgnoreLive and IgnoreRemix fields - Modified SaveArtistSettings and UpdateArtistSettings to handle new fields - Extended FilterOptions struct with IgnoreLive and IgnoreRemix - Updated ApplyTypeToggles and ApplyTypeTogglesToReleaseGroups to filter Live/Remix types - Added toggleIgnoreLive and toggleIgnoreRemix handlers in web layer - Updated ArtistData view model and artist.html template with new toggle UI - Comprehensive test coverage for all new functionality 2. CI/CD Pipeline with Gitea Actions: - Added .gitea/workflows/docker-build.yml for automated Docker builds - Workflow triggers on pushes to main/master and tags, plus PRs - Runs Go tests before building - Builds and pushes multi-architecture Docker images to gitea.mrixs.me - Includes caching for faster subsequent builds - Proper tagging strategy (branch, semver, SHA) - CI-CD-GUIDE.md documentation 3. Cleanup: - Removed temporary build artifacts and coverage files
71 lines
2.6 KiB
Go
71 lines
2.6 KiB
Go
package musicbrainz
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
|
|
"naviwatcher/internal/normalize"
|
|
)
|
|
|
|
// mbArtistSearchResult models the JSON response of the MusicBrainz artist
|
|
// search endpoint (/ws/2/artist?query=artist:<name>&fmt=json). Only the
|
|
// fields we need for MBID resolution are decoded.
|
|
type mbArtistSearchResult struct {
|
|
Artists []struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Score int `json:"score"`
|
|
} `json:"artists"`
|
|
}
|
|
|
|
// minResolutionScore is the minimum MusicBrainz search score (0-100) we accept
|
|
// for an MBID resolution. Below this, the best hit is too weak a match to
|
|
// trust, and caching it would silently pollute an artist's discography with
|
|
// the wrong MusicBrainz data.
|
|
const minResolutionScore = 80
|
|
|
|
// ResolveArtistMBID resolves a MusicBrainz artist ID (MBID) for the given
|
|
// artist name by querying the MusicBrainz artist search endpoint. It returns
|
|
// the ID of the highest-scoring matching artist, but only when that artist's
|
|
// normalized name actually matches the requested name (and its search score is
|
|
// at or above minResolutionScore). An error is returned if the search yields
|
|
// no usable match, the response cannot be parsed, or the underlying request
|
|
// fails. Rejecting a low-confidence hit lets the caller surface the problem
|
|
// instead of caching a wrong MBID.
|
|
func (c *MusicBrainzClient) ResolveArtistMBID(ctx context.Context, name string) (string, error) {
|
|
params := url.Values{}
|
|
params.Set("query", fmt.Sprintf("artist:%s", name))
|
|
params.Set("fmt", "json")
|
|
path := "/artist?" + params.Encode()
|
|
|
|
body, err := c.doGet(ctx, path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve MBID for artist %q: %w", name, err)
|
|
}
|
|
|
|
var result mbArtistSearchResult
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return "", fmt.Errorf("parse artist search response for %q: %w", name, err)
|
|
}
|
|
|
|
if len(result.Artists) == 0 {
|
|
return "", fmt.Errorf("no MusicBrainz artist found for %q", name)
|
|
}
|
|
|
|
best := result.Artists[0]
|
|
if best.Score < minResolutionScore {
|
|
return "", fmt.Errorf("no confident MusicBrainz match for %q (best candidate %q scored %d, need >= %d)", name, best.Name, best.Score, minResolutionScore)
|
|
}
|
|
// Even with a high score, require the normalized name to match, guarding
|
|
// against score inflation on name collisions (e.g. tribute acts).
|
|
if normalize.NormalizeArtistName(best.Name) != normalize.NormalizeArtistName(name) {
|
|
log.Printf("MusicBrainz MBID resolution skipped for %q: best candidate %q did not match by name", name, best.Name)
|
|
return "", fmt.Errorf("best MusicBrainz candidate %q does not match %q by name", best.Name, name)
|
|
}
|
|
|
|
return best.ID, nil
|
|
}
|