fix(recapture): link on-disk images to their post (#1288)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 36s
CI / integration (push) Successful in 3m32s

Recapture disk-skips already-downloaded media, and upsert_post_record only
writes Post fields — so a pre-existing image (e.g. one pulled under the old
gallery-dl path, imported bare with no post) stays orphaned even after its post
record is (re)written. Confirmed on the operator's instance: 329 pixiv images
with primary_post_id NULL, 694 pixiv posts with content but no linked images, 0
duplicate posts.

Fix: the recapture relink channel now carries the media's post_id (2- → 3-tuple
path/url/post_id), and phase 3 calls importer.link_existing_image_to_post — match
the on-disk image by path, find its Post by (source, external_post_id), upsert
image_provenance + primary_post_id. Factored the provenance-linking out of
_apply_sidecar into a shared _attach_provenance so the fresh-import and
recapture-backlink paths can't diverge. Idempotent; generic across native
platforms (no-op for already-linked Patreon/SubscribeStar). Re-running recapture
now repairs orphaned images; future walks never orphan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
2026-07-04 20:13:53 -04:00
parent 437bf4d37a
commit 8838b325fb
6 changed files with 205 additions and 76 deletions
+4 -2
View File
@@ -667,10 +667,12 @@ async def test_backfill_skips_already_captured_post_but_recapture_forces_it(
assert downloader2.post_records == 1
assert res_recap.files_downloaded == 0 # no media re-download
assert downloader2.download_calls == 0
# The on-disk media is surfaced as a (path, CDN url) relink pair.
# The on-disk media is surfaced as a (path, CDN url, post_id) relink triple
# (post_id drives the #1288 image→post back-link in phase 3).
assert len(res_recap.relink_source_paths) == 1
rel_path, rel_url = res_recap.relink_source_paths[0]
rel_path, rel_url, rel_post_id = res_recap.relink_source_paths[0]
assert rel_url == m1.url
assert rel_post_id == m1.post_id
# Diagnostic summary surfaces the post-record + relink counts (operator reads
# this off the event stdout to see what a recapture actually did).
assert "1 post-record(s), 1 relinked" in res_recap.stdout