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>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "UnmatchedAlbum" (
|
||||
"id" TEXT NOT NULL,
|
||||
"artist" TEXT NOT NULL,
|
||||
"album" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"reason" TEXT NOT NULL,
|
||||
"scanId" TEXT NOT NULL,
|
||||
"scannedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "UnmatchedAlbum_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UnmatchedAlbum_path_key" ON "UnmatchedAlbum"("path");
|
||||
@@ -191,6 +191,21 @@ model ScanWorkItem {
|
||||
@@index([scanId, done, artist, album])
|
||||
}
|
||||
|
||||
// Album folders on disk that the library scan could not resolve to a MusicBrainz release
|
||||
// (no match, or an unreadable/corrupt file). Surfaced on /library so the user can fix the
|
||||
// metadata or add them manually instead of them being silently skipped. Keyed by path; a
|
||||
// row is removed when the album later matches, and stale rows (a prior scan's, or a folder
|
||||
// since removed) are pruned when a scan completes.
|
||||
model UnmatchedAlbum {
|
||||
id String @id @default(cuid())
|
||||
artist String
|
||||
album String
|
||||
path String @unique
|
||||
reason String
|
||||
scanId String
|
||||
scannedAt DateTime @default(now())
|
||||
}
|
||||
|
||||
// Generic read-through cache for external API responses shared by web + worker (keyed on
|
||||
// the LOGICAL operation, not the request URL, so both processes share entries). TTL is
|
||||
// applied at read time from fetchedAt in code (no expiresAt column → tunable, no migration).
|
||||
|
||||
Reference in New Issue
Block a user