feat(metadata): wire provider IDs end-to-end to populate hardcover_book_id, google_id, audible_id, asin (bookshelf-yco0j) #1075
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-yco0j"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
book_metadata.hardcover_book_id(orgoogle_id,audible_id,asinfrom background fetches). The Hardcover sync (bookshelf-oi1l2, PR #1070) filters onhardcover_book_id IS NOT NULL, so sync was a no-op for all existing books.HardcoverBookID,GoogleBooksID,AudibleID,AmazonASINfields tometadata.MetadataDTOHardcoverBookID, Google Books →GoogleBooksID, Audnexus →AudibleID+AmazonASINSetHardcoverBookID,SetAudibleID,SetAsinSQL functions inmetadata_extra.go(all honour per-field*_lockedflags viaIF(COALESCE(...))guard); extendUpdateBookProviderIDsto also writehardcover_book_idPersistMetadataDeps,SaveMetadataDeps, and both call sites (persistInTxWithComicviapersistProviderIDshelper, interactivepersistMatchedProviderIDmulti-setter for Hardcover)applyProviderIDs,persistProviderIDs,callSetter,nullF64Ptrhelpers to keep funlen/gocyclo under gates — no new.golangci.ymlexclusionsinternal/coverage, no schema migrations, no Grimmory-table columns addedTest plan
make buildpassesmake coveragepasses (100% coverage gate)make lintpasses (no new exclusions; pre-existing foreign-worktree errcheck errors only)AllFieldsLocked, error paths propagatedGetMetadataForShowtest verifiesHardcoverBookIDpopulated from provider IDs rowhardcover_idandhardcover_book_idCloses bead bookshelf-yco0j on merge.
Security Review — PR #1075 (bookshelf-yco0j)
Persist provider IDs (
hardcover_book_id/google_id/audible_id/asin) from metadata fetch + manual save. Read-only review of the diff (origin/main...origin/bd-bookshelf-yco0j).Findings
1. SQL injection — CLEAN. All new writes are parameterized.
UpdateBookProviderIDsuses?placeholders for every value (internal/db/sqlc/metadata_extra.go:147-171), and the newSetHardcoverBookID/SetAudibleID/SetAsinhelpers (metadata_extra.go:236-280) each doSET bm.<col> = ?withExecContext(ctx, query, <id>, bookID). No string interpolation of provider- or user-supplied ids into SQL anywhere.2. Lock bypass / integrity — CLEAN. The
IF(COALESCE(all_fields_locked,0)=0 AND COALESCE(hardcover_book_id_locked,0)=0, ?, bm.hardcover_book_id)guard is correct (AND of both unlocked → write, else keep existing). The three newSet*helpers replicate the same guard as WHERE predicates (AND all_fields_locked=0 AND <field>_locked=0 AND b.deleted=0) — a fetch cannot clobber a user-locked id.persistProviderIDsadditionally short-circuits whenexisting.AllFieldsLockedin Go (metadata_store.go:1097); per-field locks are enforced at the SQL layer, no bypass. The form-mirrorp.HardcoverBookID = nullStr(req.HardcoverID)(metadata_store.go:672) writes both columns from one field but each column's own lock still gates its write — correct.3. Manual form input — CLEAN. No new user-input surface:
dto.gois unchanged (thehardcover_id/audible_id/asinform fields pre-exist) and there are zero changes tohandler.go/routes.go— the metadata-edit endpoint's existing auth gating is untouched. Values flow to varchar(100) columns via parameterized writes; over-length input yields a DB error, not injection (no security impact).4. Secrets/PII — CLEAN. Persisted values are opaque provider book IDs (Hardcover numeric id, Google volume id, Audible/Amazon ASIN) — not secrets or PII. The non-fatal Warn log (
metadata_service.go:632) carries onlybook_id/provider/error/trace_id. No API keys/tokens persisted or logged.Notes
[MINOR] internal/db/sqlc/metadata_extra.go:122 —
UpdateBookProviderIDsParamsstruct fields are not gofmt-aligned (HardcoverBookIDis longer than its neighbors, which were not re-aligned). Cosmetic;gofmt/lint in CI will flag it. Rungofmt -w.REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Security Review — PR #1075 (bookshelf-yco0j)
Persist provider IDs (
hardcover_book_id/google_id/audible_id/asin) from metadata fetch + manual save. Read-only review of the diff (origin/main...origin/bd-bookshelf-yco0j).Findings
1. SQL injection — CLEAN. All new writes are parameterized.
UpdateBookProviderIDsuses?placeholders for every value (internal/db/sqlc/metadata_extra.go:147-171), and the newSetHardcoverBookID/SetAudibleID/SetAsinhelpers (metadata_extra.go:236-280) each doSET bm.<col> = ?withExecContext(ctx, query, <id>, bookID). No string interpolation of provider- or user-supplied ids into SQL anywhere.2. Lock bypass / integrity — CLEAN. The
IF(COALESCE(all_fields_locked,0)=0 AND COALESCE(hardcover_book_id_locked,0)=0, ?, bm.hardcover_book_id)guard is correct (AND of both unlocked → write, else keep existing). The three newSet*helpers replicate the same guard as WHERE predicates (AND all_fields_locked=0 AND <field>_locked=0 AND b.deleted=0) — a fetch cannot clobber a user-locked id.persistProviderIDsadditionally short-circuits whenexisting.AllFieldsLockedin Go (metadata_store.go:1097); per-field locks are enforced at the SQL layer, no bypass. The form-mirrorp.HardcoverBookID = nullStr(req.HardcoverID)(metadata_store.go:672) writes both columns from one field but each column's own lock still gates its write — correct.3. Manual form input — CLEAN. No new user-input surface:
dto.gois unchanged (thehardcover_id/audible_id/asinform fields pre-exist) and there are zero changes tohandler.go/routes.go— the metadata-edit endpoint's existing auth gating is untouched. Values flow to varchar(100) columns via parameterized writes; over-length input yields a DB error, not injection (no security impact).4. Secrets/PII — CLEAN. Persisted values are opaque provider book IDs (Hardcover numeric id, Google volume id, Audible/Amazon ASIN) — not secrets or PII. The non-fatal Warn log (
metadata_service.go:632) carries onlybook_id/provider/error/trace_id. No API keys/tokens persisted or logged.Notes
[MINOR] internal/db/sqlc/metadata_extra.go:122 —
UpdateBookProviderIDsParamsstruct fields are not gofmt-aligned (HardcoverBookIDis longer than its neighbors, which were not re-aligned). Cosmetic;gofmt/lint in CI will flag it. Rungofmt -w.REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Security Review — PR #1075 (bookshelf-yco0j)
Persist provider IDs (
hardcover_book_id/google_id/audible_id/asin) from metadata fetch + manual save. Read-only review of the diff (origin/main...origin/bd-bookshelf-yco0j).Findings
1. SQL injection — CLEAN. All new writes are parameterized.
UpdateBookProviderIDsuses?placeholders for every value (internal/db/sqlc/metadata_extra.go:147-171), and the newSetHardcoverBookID/SetAudibleID/SetAsinhelpers (metadata_extra.go:236-280) each doSET bm.<col> = ?withExecContext(ctx, query, <id>, bookID). No string interpolation of provider- or user-supplied ids into SQL anywhere.2. Lock bypass / integrity — CLEAN. The
IF(COALESCE(all_fields_locked,0)=0 AND COALESCE(hardcover_book_id_locked,0)=0, ?, bm.hardcover_book_id)guard is correct (AND of both unlocked → write, else keep existing). The three newSet*helpers replicate the same guard as WHERE predicates (AND all_fields_locked=0 AND <field>_locked=0 AND b.deleted=0) — a fetch cannot clobber a user-locked id.persistProviderIDsadditionally short-circuits whenexisting.AllFieldsLockedin Go (metadata_store.go:1097); per-field locks are enforced at the SQL layer, no bypass. The form-mirrorp.HardcoverBookID = nullStr(req.HardcoverID)(metadata_store.go:672) writes both columns from one field but each column's own lock still gates its write — correct.3. Manual form input — CLEAN. No new user-input surface:
dto.gois unchanged (thehardcover_id/audible_id/asinform fields pre-exist) and there are zero changes tohandler.go/routes.go— the metadata-edit endpoint's existing auth gating is untouched. Values flow to varchar(100) columns via parameterized writes; over-length input yields a DB error, not injection (no security impact).4. Secrets/PII — CLEAN. Persisted values are opaque provider book IDs (Hardcover numeric id, Google volume id, Audible/Amazon ASIN) — not secrets or PII. The non-fatal Warn log (
metadata_service.go:632) carries onlybook_id/provider/error/trace_id. No API keys/tokens persisted or logged.Notes
[MINOR] internal/db/sqlc/metadata_extra.go:122 —
UpdateBookProviderIDsParamsstruct fields are not gofmt-aligned (HardcoverBookIDis longer than its neighbors, which were not re-aligned). Cosmetic;gofmt/lint in CI will flag it. Rungofmt -w.REVIEW VERDICT: 0 blocker, 0 major, 1 minor
c9b740f6351d11d9baed