Repository navigation
feat(author): add safe catalogue reconciliation (#2208) - #2247
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
54d7662 to
f3fa718
Compare
|
Read the whole thing and ran it locally. This is careful work, and the deletion boundary in particular is built the way I'd want it built: ownership checked before anything else, apply recomputes the preview and intersects rather than trusting the client's ids, and every predicate repeated inside the SQL so an import that lands between preview and apply wins the race. The route sits next to Two things I want changed, then one limitation I think needs saying out loud rather than fixing. 1. An empty
|
Signed-off-by: kevinatlee <kevin.atlee@live.ca>
Ignore blank provider and local identifiers so unrelated books cannot match through an empty key. Batch-load identifier rows by author to avoid serialized per-book queries, and document OpenLibrary's 200-work partial-catalogue limitation. Signed-off-by: kevinatlee <kevin.atlee@live.ca>
f3fa718 to
b0d43b5
Compare
|
Addressed in b0d43b5 after rebasing onto current
The branch is rebased onto current upstream |
vavallee
left a comment
There was a problem hiding this comment.
All three land. Checked locally rather than taking CI's word for it.
Empty and whitespace ids are now out of acceptedIDs, rejectedIDs and indeterminateIDs, and out of the per book ids slice. I reverted just those four guards on a scratch copy and both new tests fail there, so they are real regressions rather than tests that would have passed before the fix.
ListBookIdentifiersByAuthor is scope equivalent to the loop it replaces: ListByAuthorIncludingExcluded selects on WHERE author_id = ?, and the new query joins books b ON b.id = bi.book_id WHERE b.author_id = ?. Same book set, same columns, same per book ordering, one query instead of one per book. Moving the error context from the book id to the author id is the right call given there is no longer a per book call to attribute it to.
The 200 work ceiling being written down is what I wanted. That is the honest description of the behaviour until the OpenLibrary side paginates.
Branch builds and vets clean here, and go test ./internal/api/... ./internal/db/... is green.
Approving. The regression tests that actually fail without the fix are the part most PRs skip, so thanks for doing it properly.
…2352) Foreign-ID prefix routing is written out nine times. Seven agree on six branches; providerNameForForeignID and metadataProviderFromForeignID had drifted to four and were missing "calibre:" and "abs:", so an importer's synthetic ID fell through to the openlibrary default. For the metadata one that mattered: providerForForeignID routes on the name and falls back to the primary for "openlibrary", so calibre:author:N was handed to the OpenLibrary client, which 404s on it. It now returns nil, which every one of the six call sites already handles. Two paths improve as a result: canonicalPrimaryBookAuthorWorks stops treating a Calibre author as an OpenLibrary one, and GetPrimaryAuthorWorksSnapshot returns "metadata provider is not available for this author" instead of a snapshot built from a 404, which matters because that snapshot feeds the catalogue reconciliation delete decision (#2247). For the api one: a Calibre or Audiobookshelf sourced book was stamped "openlibrary" in books.metadata_provider, disagreeing with the value migration 078 backfills for the same id. No hand-written prefix guard is removed. All three in internal/api/authors.go (:1820, :2428, :3579) do more than route: the first drives relinkCalibreAuthor and the single-work early return, the other two rewrite a Calibre stub's foreign id during dedup. The one in internal/scheduler/scheduler.go:1172 does become redundant, since the updated == nil check ten lines below now catches the same case, but it is left in place to keep this off another branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SUzQ8XBez4cD68eS2FWsXP Signed-off-by: vavallee <vavallee@protonmail.com>
Eight entries from the code audit, plus the catalogue reconciliation fragment that has been sitting in changelog.d since #2247 landed after v1.33.3 was tagged. Three entries carry a behaviour change worth reading before upgrading: restore now applies at the next start rather than immediately, the Google Books key field loads empty because the key is write-only now, and books that want both formats will start searching for the format they had silently stopped looking for. changelog.d is cleared, per its README. Claude-Session: https://claude.ai/code/session_01SUzQ8XBez4cD68eS2FWsXP Signed-off-by: vavallee <vavallee@protonmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…2352) (#2392) Foreign-ID prefix routing is written out nine times. Seven agree on six branches; providerNameForForeignID and metadataProviderFromForeignID had drifted to four and were missing "calibre:" and "abs:", so an importer's synthetic ID fell through to the openlibrary default. For the metadata one that mattered: providerForForeignID routes on the name and falls back to the primary for "openlibrary", so calibre:author:N was handed to the OpenLibrary client, which 404s on it. It now returns nil, which every one of the six call sites already handles. Two paths improve as a result: canonicalPrimaryBookAuthorWorks stops treating a Calibre author as an OpenLibrary one, and GetPrimaryAuthorWorksSnapshot returns "metadata provider is not available for this author" instead of a snapshot built from a 404, which matters because that snapshot feeds the catalogue reconciliation delete decision (#2247). For the api one: a Calibre or Audiobookshelf sourced book was stamped "openlibrary" in books.metadata_provider, disagreeing with the value migration 078 backfills for the same id. No hand-written prefix guard is removed. All three in internal/api/authors.go (:1820, :2428, :3579) do more than route: the first drives relinkCalibreAuthor and the single-work early return, the other two rewrite a Calibre stub's foreign id during dedup. The one in internal/scheduler/scheduler.go:1172 does become redundant, since the updated == nil check ten lines below now catches the same case, but it is left in place to keep this off another branch. Claude-Session: https://claude.ai/code/session_01SUzQ8XBez4cD68eS2FWsXP Signed-off-by: vavallee <vavallee@protonmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…and profile filters (#3048, #2208) Series page marks split edition parts, leaves them out of the missing count, offers a one click unmonitor and reports what a fill skipped. Drops the profile level retroactive endpoints from the first commit: per author Reconcile catalogue (#2247) already applies every filter to stored rows. Adds the db test for the covered split part query, docs and changelog fragments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
…ta profile filters to series fill (#3048, #2208) (#3109) * fix(series): skip split edition parts and apply profile filters in series fill (#3048, #2208) Fill and the wanted sweep skip a stored book that is a split edition part of a whole already in the library, using the same two signal rule as #2915. A new series endpoint unmonitors those parts. Series fill now screens the books it creates and re queues through the author's metadata profile, and two admin endpoints preview and unmonitor wanted books a profile filter turned on later would have kept out. Nothing is deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com> * fix(series): series page cleanup, docs and fragments for split parts and profile filters (#3048, #2208) Series page marks split edition parts, leaves them out of the missing count, offers a one click unmonitor and reports what a fill skipped. Drops the profile level retroactive endpoints from the first commit: per author Reconcile catalogue (#2247) already applies every filter to stored rows. Adds the db test for the covered split part query, docs and changelog fragments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com> * fix(series): let unknown languages through a series fill and stop counting hand monitored books as skipped (#2208) Review follow up. Hardcover series entries carry no language, so a profile with unknown languages set to fail rejected every book of an add all whenever OpenLibrary was primary. A blank language now passes during a fill, and a failed language evidence lookup passes too, the same unknown passes rule MinPages and SkipMissingISBN use. Only a definitive "not allowed" rejects. The re-queue loop now screens only unmonitored stored rows. A monitored book is already being sought, often because it was added by hand, so it is queued as before and no longer reported as skipped on every fill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com> --------- Signed-off-by: vavallee <vavallee@protonmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
filePath,ebookFilePath,audiobookFilePath, or abook_filesrecord; no filesystem deletion is involvedThe branch is rebased onto current
main, including the merged Hardcover language fix from #2246. Reconciliation remains a separate explicit operation and does not make refresh destructive. Refs #2208; the separate series-fill behavior described there is intentionally outside this PR.Suggested review order
internal/db/books.goandinternal/api/catalogue_reconciliation.go— deletion boundary and apply-time safeguardsinternal/metadata/aggregator_author_works.goand OpenLibrary completeness reporting — conservative catalogue evidenceinternal/api/catalogue_reconciliation_test.goandinternal/db/books_reconciliation_test.go— destructive/error-path coverageweb/src/components/CatalogueReconciliationModal.tsx— explicit preview and per-row selectionChecklist
git commit -s— see Sign your workdocs/DEPLOYMENT.mdupdated if env vars, config, or upgrade path changed (not applicable: no deployment/config change)changelog.d/(not an edit toCHANGELOG.md) — see changelog.d/README.mdTest plan
go test -timeout=30m ./cmd/... ./internal/...-count=10-count=10catalogue_reconciliation.goand forDeleteMetadataOnlyWantedByIDsgo vet ./...go build ./...golangci-lint run ./...(0 issues)govulncheck ./...(no called vulnerabilities)go test -count=1 -timeout=60s ./tests/smoke/...)The full backend run used
TMPDIR=/private/tmpon macOS so the existing rTorrent deletion safety test sees a direct path instead of the/var→/private/varsystem symlink.