- Add golang.org/x/time/rate dependency for token-bucket rate limiting - Replace custom channel-based rate limiter with rate.NewLimiter(1, 1) - Add context.Context support to doGet for cancellation - Add cached_at column to external_releases via migration 005 - Implement cache hit/miss queries with TTL-based filtering - Add CacheStats type for tracking cached RGIDs - Update ExternalRelease struct with CachedAt field - Add rate limiting tests (1 req/sec enforcement, burst behavior) - Add cache tests (hit, miss, expired, mixed, empty artist) - Update migration count test for new migration
7.3 KiB
7.3 KiB
2026-05-21-musicbrainz-provider
Overview
Implement a MusicBrainz API provider with strict 1 request/second rate limiting, 24-hour caching of Release Group data, and filtering capabilities as per specification. The provider will fetch artist discographies from MusicBrainz, normalize the data, and store it in the external_releases table for use by the scanner engine.
Context (from discovery)
- Files/components involved: internal/musicbrainz/ package (new), database schema updates, main.go integration
- Related patterns found: Follows the internal/navidrome/ pattern with client.go, sync.go, and model separation
- Dependencies identified: Will add golang.org/x/time/rate for rate limiting, use net/http for API calls
Development Approach
- Testing approach: Regular (code first, then tests)
- Complete each task fully before moving to the next
- Make small, focused changes
- CRITICAL: every task MUST include new/updated tests for code changes in that task
- tests are not optional - they are a required part of the checklist
- write unit tests for new functions/methods
- write unit tests for modified functions/methods
- add new test cases for new code paths
- update existing test cases if behavior changes
- tests cover both success and error scenarios
- CRITICAL: all tests must pass before starting next task - no exceptions
- CRITICAL: update this plan file when scope changes during implementation
- Run tests after each change
- Maintain backward compatibility
Testing Strategy
- Unit tests: required for every task (see Development Approach above)
- E2E tests: if project has UI-based e2e tests (Playwright, Cypress, etc.):
- UI changes → add/update e2e tests in same task as UI code
- Backend changes supporting UI → add/update e2e tests in same task
- Treat e2e tests with same rigor as unit tests (must pass before next task)
- Store e2e tests alongside unit tests (or in designated e2e directory)
Progress Tracking
- Mark completed items with
[x]immediately when done - Add newly discovered tasks with ➕ prefix
- Document issues/blockers with ⚠️ prefix
- Update plan if implementation deviates from original scope
- Keep plan in sync with actual work done
What Goes Where
- Implementation Steps (
[ ]checkboxes): tasks achievable within this codebase - code changes, tests, documentation updates - Post-Completion (no checkboxes): items requiring external action - manual testing, changes in consuming projects, deployment configs, third-party verifications
- Checkbox placement: Checkboxes belong only in Task sections (
### Task N:or### Iteration N:). Do not put checkboxes in Success criteria, Overview, or Context — they cause extra loop iterations.
Implementation Steps
Task 1: Create MusicBrainz client and data models
- create
internal/musicbrainz/client.gowith MusicBrainzClient struct wrapping net/http.Client - implement constructor taking config and rate limiter
- create
internal/musicbrainz/model.gowith structs for MusicBrainz API responses (ReleaseGroup, Artist, etc.) - implement XML parsing functions for MusicBrainz responses
- write tests for XML parsing (success + error cases)
- write tests for client constructor and basic API call structure
- run tests - must pass before next task
Task 2: Implement rate limiting and caching layer
- add golang.org/x/time/rate dependency to go.mod
- implement rate limiter using golang.org/x/time/rate.NewLimiter(1, 1) for 1 req/sec
- create wrapper method for rate-limited HTTP GET requests
- implement caching check: query database for existing Release Group data within TTL
- write tests for rate limiting behavior (timing tests)
- write tests for cache hit/miss logic
- run tests - must pass before next task
Task 3: Implement MusicBrainz API endpoints and filtering
- implement GetArtistReleaseGroups(artistMBID string) method
- apply filters: exclude Bootleg/Promotion/Pseudo-Release status
- apply type filters: include Album/Single/EP/Compilation only
- implement per-artist type filtering hooks (placeholder for Web UI integration)
- normalize artist names and titles (remove special characters, years, brackets)
- write tests for filtering logic (table-driven test cases)
- write tests for normalization functions
- run tests - must pass before next task
Task 4: Implement database integration and sync orchestration
- create
internal/musicbrainz/sync.gowith SyncArtistDiscography function - implement upsert logic: INSERT OR REPLACE into external_releases table
- add cached_at column to external_releases table via migration
- implement context.Context support for cancellation
- write tests for database upsert operations
- write integration tests with in-memory SQLite
- run tests - must pass before next task
Task 5: Wire up provider in application entry point
- update
cmd/naviwatcher/main.goto initialize MusicBrainz client - add MusicBrainz client to application context/dependencies
- ensure graceful shutdown includes closing HTTP client connections
- update config validation to ensure MusicBrainz.UserAgent is set
- write tests for main.go integration (startup/shutdown)
- run tests - must pass before next task
Task 6: Verify acceptance criteria and run full test suite
- verify all requirements from Overview are implemented
- verify edge cases are handled (network errors, invalid responses, rate limit blocking)
- run full test suite (unit tests)
- run linter - all issues must be fixed
- verify test coverage meets project standard (80%+)
Technical Details
Data Structures
- MusicBrainzClient: wraps *http.Client with rate limiter and config
- ExternalRelease: matches existing database struct with addition of CachedAt time.Time
- MusicBrainz API Response Models: ReleaseGroup, Artist, etc. based on XML schema
Parameters and Formats
- Rate Limiter: 1 request per second burst size of 1 (strict limit)
- Cache TTL: configurable via MusicBrainzConfig.CacheTTL (default 24h)
- API Endpoint: https://musicbrainz.org/ws/2/ with proper User-Agent header
- Response Format: XML parsing of MusicBrainz Web Service responses
Processing Flow
- SyncArtistDiscography called with MusicBrainz Artist ID
- Check cache: query external_releases for RGIDs with cached_at within TTL
- If cache miss or expired: call MusicBrainz API with rate limiting
- Parse XML response into ReleaseGroup models
- Apply status/type filtering (Bootleg/Promotion/Pseudo-Release excluded)
- Apply per-artist type filtering ( Singles/Compilations toggle via Web UI)
- Normalize strings (remove special chars, years, brackets for fuzzy matching)
- Upsert each Release Group to external_releases with current timestamp
- Return list of Release Groups for scanner consumption
Post-Completion
Items requiring manual intervention or external systems - no checkboxes, informational only
Manual verification (if applicable):
- Manual testing of rate limiting under load
- Verify cache expiration behavior over time
- Test with real MusicBrainz API to ensure compliance with their usage policy
- Performance testing of XML parsing and filtering logic
External system updates (if applicable):
- None - this is a standalone provider implementation