Commit Graph

22 Commits

Author SHA1 Message Date
bvandeusen b6632136b7 feat(server/m7-cover-sources): admin cover-sources HTTP handlers
Three endpoints for the admin Settings UI:
- GET /api/admin/cover-sources — lists registered providers with
  current settings + capability badges + sources_version
- PATCH /api/admin/cover-sources/{provider_id} — updates enabled
  and/or api_key; returns version_bumped: true only when the
  enabled set changed
- POST /api/admin/cover-sources/{provider_id}/test — invokes
  TestableProvider.TestConnection; returns ok:bool + duration_ms +
  error string. Returns 200 in both success and failure cases (the
  operation completed; the test result is data, not an error).

api_key is never echoed in GET — replaced with api_key_set: bool
to follow the standard credential-display convention. PATCH
api_key: "" clears; omitting the field leaves it unchanged. PATCH
on an unknown provider_id returns 404.

All under existing RequireAdmin middleware. Routes registered
alongside the library/coverage endpoint in the /api/admin/ tree.

handlers struct gains a coverSettings *coverart.SettingsService
field; server.New + cmd/minstrel/main.go plumb the existing
coverSettings instance through.

coverart.ResetRegistryForTests() exported wrapper added so api
package tests can reset the registry without importing internals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:08:45 -04:00
bvandeusen 00cd28e79b feat(server,web/m7-cover-sources): scan-orchestrator 4th stage
Adds an artist-art-enrich stage after cover-enrich in RunScan. The
stage drains EnrichArtistBatch with a configurable cap and the
operator's data_dir, persists processed/succeeded/failed tallies to
the scan_runs.artist_art_enrich jsonb column added in migration
0018.

The admin /admin Library Scan card grid expands from 3 to 4 columns
with the new "Artist art" card mirroring the cover-enrichment one
(Processed / Succeeded / Failed). The TS ScanStatus type gains an
optional artist_art_enrich field. The Go scanStatusResp gains the
matching ArtistArtEnrich json.RawMessage field.

cmd/minstrel/main.go (and admin scan-trigger handler) wire the new
RunScanConfig fields: ArtistEnrichCap reuses cfg.Library.CoverArtBackfillCap
for now (can be split if separate budgets are useful), DataDir is
cfg.Storage.DataDir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:03:36 -04:00
bvandeusen 385eb3b163 feat(server/m7-cover-sources): album enricher uses provider chain
Rewires EnrichAlbum to iterate EnabledAlbumProviders from the
SettingsService instead of a single hardcoded MBCAA fetcher. Sidecar
layer is unchanged (always tried first). Per-row eligibility check
honors cover_art_sources_version: terminal 'found' values skip;
'none' flips to NULL via ClearAlbumCoverNone and proceeds (the DB-
level ListAlbumsMissingCover query guards the version check for batch
callers; RetryAlbum clears via ClearAlbumCover first); NULL is eligible.

The provider chain uses an inline allWere404 accumulator to settle
the row to 'none' only when every provider returned ErrNotFound.
Any provider returning ErrTransient leaves the row NULL for next-
pass retry — even if other providers returned ErrNotFound — since
the transient call's MBID might succeed on a future attempt.

Boot wiring (cmd/minstrel/main.go) replaces the
NewFetcher+NewEnricher(fetcher, mbcaaOn) shape with
NewMBCAAProviderFromConfig (swaps in production User-Agent on the
registered provider) + NewSettingsService + NewEnricher(settings).
The old cfg.Library.CoverArtFromMBCAA flag is no-op'd; DB-backed
settings replace it. Field stays in the config struct for backward
compat with deployed config files.

Consolidates fetcher.go's HTTP logic into provider_mbcaa.go (no more
wrapper indirection): mbcaaProvider now holds cfg/mu/lastCall/client
directly. Deletes fetcher.go and fetcher_test.go. ErrNotFound and
ErrTransient move into provider.go alongside the other sentinels.

Updates admin_covers_test.go and provider_mbcaa_test.go to the new
constructor shapes. Adds multi-provider-chain, transient-leaves-null,
and stale-none-flips-and-retries test cases in enricher_test.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:52:57 -04:00
bvandeusen 3d0e213081 feat(server/m7-381): admin scan-status + manual trigger endpoints
Adds GET /api/admin/scan/status and POST /api/admin/scan/run under the
existing RequireAdmin middleware block; plumbs *library.Scanner and
library.RunScanConfig through api.Mount, server.New, and main.go so the
manual trigger reuses the same RunScan orchestrator as startup scans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 20:13:31 -04:00
bvandeusen 65a3c4892c feat(server/m7-381): RunScan orchestrator chains library+backfill+enrich
Adds internal/library/scanrun.go with RunScan, which creates a scan_runs
row and sequences file-walk → MBID backfill → cover enrich, persisting
per-stage tallies as jsonb. Extends coverart.EnrichBatch to return
(processed, succeeded, failed, err) so the orchestrator can classify
outcomes. Replaces main.go's two separate boot goroutines with a single
RunScan call; passes nil scanner in the no-startup-scan branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 20:08:48 -04:00
bvandeusen 34615fffbd feat(server/m7-380): one-shot MBID backfill worker for existing libraries
Adds a boot-time goroutine that walks albums with NULL mbid, re-reads
tags from one track per album via dhowden/tag, and persists album + artist
MBIDs. Healed albums also get their cover_art_source='none' cleared so the
enricher's next batch retries the MBCAA fetch. Caps at 5000 albums per
boot to avoid stalling startup on huge libraries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:40:27 -04:00
bvandeusen d4a837b904 feat(server/m7-353): admin endpoints for cover refetch (per-album + bulk)
Adds POST /api/admin/albums/{id}/cover/refetch (synchronous single-album
retry) and POST /api/admin/covers/refetch-missing (async bulk drain).
Wires coverart.Enricher through server.New → api.Mount → handlers struct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:11:53 -04:00
bvandeusen 6c33acf062 feat(server/m7-353): config + main.go wiring for coverart enricher 2026-05-04 15:06:28 -04:00
bvandeusen 391f5f16a1 feat(server/m7-352): cron loop + main.go wiring for system playlist build 2026-05-04 09:16:07 -04:00
bvandeusen ca82862346 chore(config): rename SMARTMUSIC_ env prefix to MINSTREL_; default admin user
The legacy SMARTMUSIC_ prefix dates from when the project was specced
as "smart-music" before being renamed to Minstrel. Hard cutover to
MINSTREL_* across config.go, tests, config.example.yaml, README, and
docker-compose. Also renames SMARTMUSIC_CONFIG in cmd/minstrel/main.go
which was missed in the original audit.

Also seeds Auth.AdminBootstrap.Username = "admin" in Default() so
MINSTREL_DATABASE_URL is the only env var required for a fresh prod
deployment — the bootstrap auto-generates a password and prints it
to stderr. Drops the now-redundant MINSTREL_AUTH_ADMIN_USERNAME=admin
line from the README quickstart and docker-compose.

M1 follow-up from the #363 final review: guards
MINSTREL_BRANDING_APP_NAME and _DESCRIPTION against empty-string
overrides — empty would have shipped <title></title> to share-preview
crawlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 18:40:59 -04:00
bvandeusen 46ea9a761a feat(server/m7-363): thread BrandingConfig through server.New 2026-05-03 17:17:52 -04:00
bvandeusen 6d1709caff fix(config): wire DataDir from yaml/env through to playlists service
Slice 1 of M7 #352 added Server.DataDir but left it un-populated —
the playlist cover-collage writer would have used "" as the relative
path root in production, writing to the current working directory.

- Add Storage.DataDir to Config (yaml: storage.data_dir, env:
  SMARTMUSIC_STORAGE_DATA_DIR), defaulting to "./data".
- server.New takes the data dir as a parameter; main.go threads it.
- main.go MkdirAll's the directory at startup so the playlist cover
  writer doesn't fail on first use with a missing parent.
- config.example.yaml documents the new section.

Existing internal/server/server_test.go constructs Server directly
without server.New, so no test fixture changes needed for that file.
2026-05-03 11:37:41 -04:00
bvandeusen 1bde1787b9 feat(cmd): start Lidarr reconciler worker alongside HTTP server
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 17:57:42 -04:00
bvandeusen 358bfd9453 feat(cmd): start similarity worker alongside HTTP server 2026-04-28 20:25:11 -04:00
bvandeusen 46b23424c2 feat(cmd): start scrobble worker alongside HTTP server 2026-04-28 09:28:13 -04:00
bvandeusen 08591debee feat(api): rewrite /api/radio with weighted shuffle v1
Validates seed_track + optional limit (default cfg.Recommendation.RadioSize,
clamped to RadioSizeMax). Calls recommendation.LoadCandidates +
recommendation.Shuffle. Prepends seed to result. Server seeds a
math/rand source at startup; handlers package threads that as a
func() float64 so tests inject deterministic RNGs.

Mount + server.New gain a RecommendationConfig parameter.
2026-04-27 08:08:10 -04:00
bvandeusen f4e73b81b4 feat(api): add POST /api/events handler
Discriminated-union JSON over three event types: play_started,
play_ended, play_skipped. Auth via existing RequireUser. play_started
returns play_event_id + session_id; the other two return { ok: true }.
Validates ownership: play_ended/play_skipped on another user's row
returns 403. Mount signature now takes EventsConfig and constructs
the playevents.Writer; server.New propagates it through.
2026-04-26 00:12:06 -04:00
bvandeusen 9f325cf309 feat(cmd): plumb SubsonicConfig into server.New 2026-04-19 17:40:24 +00:00
bvandeusen bea0604846 M1/#293: wire scanner into startup + server 2026-04-19 15:16:03 +00:00
bvandeusen d358d7e1a0 M1/#292: wire auth.Bootstrap into startup 2026-04-19 02:31:05 +00:00
bvandeusen e54482409a M1/#291: run migrations + open pool on startup 2026-04-19 01:12:23 +00:00
bvandeusen be181f8245 feat(skel): add cmd/minstrel entrypoint
Parses --config / SMARTMUSIC_CONFIG, loads YAML config with env overlay,
initializes slog, starts the chi HTTP server, and shuts down cleanly on
SIGINT/SIGTERM.
2026-04-18 21:19:39 +00:00