feat(tags): folksonomy tag enricher — pluggable provider chain (#1490 Step 2)
Milestone #160 Option 1, Step 2. New internal/tags package that enriches the taste profile's tag facet beyond raw ID3 genre, built so adding a source later is "implement TrackTagProvider + Register()" — no enricher, settings, or schema change (operator directive). Mirrors the internal/coverart pattern: - Provider interface + package registry (Register/AllProviders/ByID); TrackTagProvider fetch capability + TestableProvider for the admin test. - DB-backed SettingsService over new tag_provider_settings + tag_sources_meta (migration 0043) — enable/key/version, boot reconciliation, and a provider-hash bump that re-opens 'none' rows when the compiled-in provider set changes. - Slim self-contained httpClient (rate-limit + retry + User-Agent), kept local so tag enrichment never depends on coverart internals. Providers: - MusicBrainz: keyless, default-ON, recording tags by MBID (rule #26 baseline). - Last.fm: keyed, default-OFF, track.getTopTags by artist+track — opt-in once a key is supplied. Enricher uses MERGE semantics (differs from coverart's first-success-wins for a single image): unions tags across every enabled provider, caps to top-K by weight, and stamps tag_source musicbrainz|lastfm|mixed|none. Writes are transactional (atomic replace of track_tags). Unit-tested without a DB: registry mechanics, provider JSON parsing + weight normalization via httptest, and the pure merge/top-K/source-label helpers. Wiring (startup + on-scan), integration tests, and the taste union (Step 3) + Settings UI (Step 4) come next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
-- Reverse 0043_tag_provider_settings.up.sql.
|
||||
DROP TABLE IF EXISTS tag_sources_meta;
|
||||
DROP TABLE IF EXISTS tag_provider_settings;
|
||||
@@ -0,0 +1,37 @@
|
||||
-- Tag-enrichment provider settings (milestone #160, #1490 Step 2).
|
||||
--
|
||||
-- Mirrors the cover-art provider-settings substrate (migration 0018) so
|
||||
-- adding a new tag source later is "register a provider + it auto-upserts
|
||||
-- a row here" — never a schema change. The tag enricher reads the enabled
|
||||
-- set + api keys from tag_provider_settings and stamps the current version
|
||||
-- from tag_sources_meta onto tracks.tag_sources_version (added in 0042).
|
||||
--
|
||||
-- current_version starts at 1 while tracks.tag_sources_version defaults to
|
||||
-- 0 (migration 0042), so every existing track is "stale" relative to
|
||||
-- current and gets enriched on the first pass after this migrates.
|
||||
|
||||
CREATE TABLE tag_provider_settings (
|
||||
provider_id text PRIMARY KEY,
|
||||
enabled boolean NOT NULL DEFAULT true,
|
||||
api_key text,
|
||||
display_order int NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE tag_sources_meta (
|
||||
id boolean PRIMARY KEY DEFAULT true,
|
||||
current_version int NOT NULL DEFAULT 1,
|
||||
last_registered_providers_hash text NOT NULL DEFAULT '',
|
||||
CONSTRAINT tag_sources_meta_singleton CHECK (id = true)
|
||||
);
|
||||
INSERT INTO tag_sources_meta (id, current_version) VALUES (true, 1);
|
||||
|
||||
-- Seed the v1 providers. MusicBrainz is the keyless always-on default;
|
||||
-- Last.fm ships disabled and is opt-in once the operator supplies a key
|
||||
-- (Step 4 Settings UI), per the no-coercive-settings rule (#26). The
|
||||
-- SettingsService also upserts these at boot, so the seed is belt-and-
|
||||
-- suspenders / documents the intended defaults.
|
||||
INSERT INTO tag_provider_settings (provider_id, enabled, display_order) VALUES
|
||||
('musicbrainz', true, 0),
|
||||
('lastfm', false, 1);
|
||||
Reference in New Issue
Block a user