feat(bookdrop): comic-aware Import Metadata — route .cbr/.cbz to comic providers (bookshelf-qgd90.1) #1226
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-qgd90.1"
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
Technical changes
internal/bookdrop/meta.go:ComicMetadataJSON+Comic *ComicMetadataJSONonFileMetadataJSONinternal/bookdrop/import_metadata_service.go:isComicExtension,buildComicImportQuery,parseComicFilenameForQuery,filterProvidersForQuery,mergeComicFields,applyComicFields,comicProviderMergeChain— all at 100% coverageinternal/bookdrop/review_handler.go: explicit field mapping after addingComicbroke the direct type-castinternal/bookdrop/wire.go: capability-based provider filter replaces hardcoded[:3]sliceinternal/app/providerspec.go:bookdropProviderSpecsnow includes ComicVine, Metron, AniListinternal/app/build_extended_deps.go: comic provider URLs + creds wired for bookdrop activityinternal/wfengine/import_metadata_workflow.go:ImportMetadataComicCandidatefor JSON bridgestatic/js/controllers/bookdrop_import_metadata_controller.js: detectsc.comicnon-null, renders comic fields in modal (no inlinestyle=— CSP-safe)Test plan
make testpasses (all packages, including bookdrop)make coveragegreen — zero uncovered statement blocksmake lint— no errors in this worktree's filesIsComicExtension,ParseComicFilenameForQuery,FilterProvidersForQuery,ImportMetadataForProposals (comic routing),CandidateFromMetadata cover URL path,MergeMetadataFields CoverURL,ParseComicFilenameForQuery zero-digit normalization,BuildImportQuery scan-ID-only comic fallback,ApplyImportMetadata with comic candidateCloses bead bookshelf-qgd90.1 on merge.
Security Review — PR #1226 (bookshelf-qgd90.1): bookdrop Import Metadata → comic providers + provider cover image
Scope reviewed: provider cover URL + comic string fields rendered into the modal (XSS/scheme), multi-user/permission gating, filename→provider query encoding (SSRF/request-splitting), and the
internal/bookdrop↔ workflow-engine architecture boundary.1. Provider cover URL — XSS / scheme injection: SAFE.
candidateFromMetadata/comicMetadataToFieldsrun every cover URL throughurlutil.SafeURL(internal/urlutil/safe_url.go), which returns "" for any scheme other than http/https —javascript:/data:/file:are stripped before the value ever reaches the JSON response.static/js/controllers/bookdrop_import_metadata_controller.js:326-332): the URL is assigned via theimg.srcDOM property (not string-concatenated intoinnerHTML), and noonerror/inline handler is attached. Defense in depth on top of the Go filter.2. Comic string fields (characters/creators/story-arc/etc.) — XSS: SAFE.
_addField(line ~345) writes every value viadd.textContent(and the header viatextContent). NoinnerHTMLsink anywhere in the render path — provider-controlled strings cannot inject markup.3. Filename → provider query — SSRF / request-splitting: SAFE.
Filename-parsed Series/Issue/Year (
parseComicFilenameForQuery) populate a typedmetadata.MatchQuerystruct, not a URL. The comic provider (internal/metadata/comicvine/search.go:298-305) sets them viaurl.Values.Set(...)+Encode()against a fixed base URL — values are percent-encoded, so a crafted filename cannot split the request or redirect the outbound call.4. Multi-user / permission gating: UNCHANGED / SAFE.
The diff touches no routes, no auth middleware, and reads no userID from the request body/query. Bookdrop remains admin/permission-gated; no per-user data is exposed by the new comic fields.
5. Architecture boundary: INTACT.
internal/bookdrop/*.go(non-test) has nogo-workflowsimport (only comment references to "wfengine"). The wfengineImportMetadataComicCandidatestruct is the JSON wire bridge — correct.6. Workflow command sequence: no version gate needed.
The
import_metadata_workflow.godiff only extends wire-format struct definitions; it adds/removes/reorders noExecuteActivity/sub-workflow/ContinueAsNewcommand — replay-safe.Fan-out cap raised 3→6 (
maxConcurrentProviders) — still single-digit, compliant with the fan-out rule.[MINOR] internal/bookdrop/import_metadata_service.go — provider cover rendered as a direct external
<img>The modal loads the provider CDN cover directly (
img.src = provider URL), leaking the admin's IP/referer to ComicVine/Metron's CDN on each preview render. This is an admin-only surface so the exposure is low; consider proxying the preview image through the app (as covers are already fetched server-side elsewhere) if referer/IP leakage to third-party CDNs is a concern. No action required to merge.REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Import Metadata modal — comic fields rendered (.cbr file)
Comic file:
Army of Darkness 1979 01.cbr→ ComicVine stub returns Issue #1, Cover Date 1992-11, Story Arc, Characters, Teams, Locations, Creators.Fields shown: Issue #, Volume, Cover Date, Story Arc, Characters, Teams, Locations, Writers, Pencilers, Inkers, Colorists, Letterers, Cover Artists, Editors.
UI Review — PR #1226 (bookshelf-qgd90.1)
Screenshot reviewed:
bookdrop_comic_modal.png(Army of Darkness 1979 01.cbr, comicvine provider).What I see in the rendered screenshot
.modal-overlay→.modal-dialog→.modal-header/.modal-footer. No bespoke parallel class system.bim-field-label/bim-field-valueclasses on a two-columndt/ddgrid, identical to the ebook field layout. Muted label colour (var(--fg-muted)) and value colour (var(--fg)) consistent with the rest of the app..bim-result-provider), top-right..btn .btn-small .btn-ghost/.btn-primary.style=, no hardcoded colours.Source cross-check
[MINOR]
static/js/controllers/bookdrop_import_metadata_controller.js(cover-image block) —.bim-result-coverand.bim-result-cover-imgare new classes emitted by this PR with no CSS rules inmain.css. Whencover_urlis non-empty the image renders unsized: nomax-width, no height constraint. A typical ComicVine cover (1000x1500 px) would overflow the card and squash the fields column. The screenshot does not exercise this path so the rendered result looks correct, but the latent defect is real.Fix: add alongside the existing
.bim-result-bodyblock:REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Code Review: Fix Delta on bd-bookshelf-qgd90.1 (
981a5c20→924ea392)MAJOR Finding: CLOSED ✓
Comic metadata lost on manual edit is now correctly fixed.
Verification:
UpdateProposalMetadata (internal/bookdrop/review_service.go:89–98):
OriginalMetadatablob viaparseOriginalMetadata(f.OriginalMetadata)Comicsub-object ONLY when incomingmeta.Comic == nil(meaning user didn't edit comic fields)Regression test (internal/bookdrop/review_service_test.go:3178–3217):
Design alignment: Comment correctly notes this "mirrors the merge logic in applyOneCandidate"—the pattern is: incoming values take precedence when set; nil incoming values preserve existing data.
MINOR Finding: CLOSED ✓
Cover image CSS sizing (static/css/main.css:7431–7433):
.bim-result-cover { flex-shrink: 0; }prevents flex from squeezing the thumbnail.bim-result-cover-img { display: block; width: 4rem; height: auto; border-radius: var(--radius); }var(--radius)token ✓ (no inline styles, no hardcoded colors)No New Issues
.golangci.ymlexclusions ✓package bookdrop_test) ✓REVIEW VERDICT: 0 blocker, 0 major, 0 minor
924ea3922e53c8d9a8db