Commit Graph

10 Commits

Author SHA1 Message Date
Jonathan 6ca39859fa feat(library): surface albums the scan couldn't match
The library scan silently skipped album folders with no MusicBrainz match
(or an unreadable file). Persist them so they're visible and fixable.

- New UnmatchedAlbum model (migration add_unmatched_album), keyed by path.
- scan_chunk records a skipped album as unmatched (reason "no MusicBrainz
  match" or "unreadable: <err>"), clears the row when an album later
  resolves, and prunes stale rows (a prior scan's, or a removed folder)
  when a scan completes.
- GET /api/library/unmatched lists them; DELETE ?id= dismisses one.
- /library shows a "Couldn't match" section (path + reason + Dismiss).

Worker + web tests added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 20:56:31 +02:00
Jonathan 034ae40084 feat(worker): share the MusicBrainz cache from scans/discovery
The worker's MB browser (discography + artist search, used by monitor sweeps,
library scans, and Last.fm discovery) re-fetched every time. Route it through
the same ApiCache table + logical keys the web app uses, so a discography one
process fetched warms the other, and repeat lookups stop hitting MB.

- apicache.py cached_api(): read-through, serve-stale-on-outage, never caches
  null, best-effort (a cache-DB error degrades to a live fetch, never breaks
  scan/sweep/discovery). Its own DEDICATED autocommit connection so it can't
  commit the worker's in-flight transaction, and autocommit avoids freezing
  Postgres now() (which would break TTL math).
- CachingMbBrowser decorates MusicBrainzBrowser, serializing to the SAME
  camelCase JSON shape the web writes (round-trip tested). registry.build_browser
  wraps it when a DSN is present; the Last.fm source's browser rides it too.
- Hourly prune drops ApiCache rows > 90d so the table stays bounded.
- worker 213 tests / 7-skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:17:51 +02:00
Jonathan bdd8fe72e9 feat: add DiscoverySeedContribution + ScanWorkItem tables 2026-07-13 15:14:58 +02:00
Jonathan 0ddc9db611 feat(discovery): DiscoverySuggestion schema + WatchedArtist.lastDiscoveredAt
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:58:45 +02:00
Jonathan af67d01aa2 feat: LibraryItem.requestId nullable (scanned haves need no request) + test cleanup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 16:56:59 +02:00
Jonathan f59f972c1b fix: guard test cleanup to a dedicated lyra_test DB (never wipe live data)
Test suites were running DELETE FROM.../deleteMany() against whatever
DATABASE_URL pointed to, which was the live app database, wiping user
credentials and library. Add a guard in both the worker conftest and
web test setup that raises before any cleanup unless the target DB
name ends in '_test'. Default DATABASE_URL to lyra_test when unset in
the worker, and make `npm test` target lyra_test explicitly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 16:16:12 +02:00
Jonathan ec85016d88 feat: monitoring data model (WatchedArtist, MonitoredRelease, Request link) 2026-07-11 12:07:09 +02:00
Jonathan feaff42010 feat: add worker config reader with secret decryption 2026-07-10 20:07:49 +02:00
Jonathan fc1229b885 test: clean worker test DB before each test to prevent flakiness
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 17:02:38 +02:00
Jonathan 49b69c592d feat: add worker job-claim logic 2026-07-10 16:58:28 +02:00