Provenance archive linkage, post reconciliation close-out, and the cleanup/admin DRY pass #127

Merged
bvandeusen merged 8 commits from dev into main 2026-06-22 20:22:56 -04:00
2 changed files with 56 additions and 0 deletions
Showing only changes of commit 77d02f57ae - Show all commits
+23
View File
@@ -573,6 +573,29 @@ def _repoint_post_links(session: Session, loser_id: int, keeper_id: int) -> None
dup_imgs = select(ImageProvenance.image_record_id).where(
ImageProvenance.post_id == keeper_id
)
# Before dropping the colliding loser rows, carry their from_attachment_id
# (which archive the file came out of, milestone #87) onto the keeper's
# surviving row when the keeper didn't record one. For the gallery-dl→native
# case this very milestone targets, the keeper is the native stub (no
# archive) and the loser is the gallery-dl row that extracted the member, so
# a blind delete would silently lose the containing-archive linkage.
for img_id, att_id in session.execute(
select(ImageProvenance.image_record_id, ImageProvenance.from_attachment_id)
.where(
ImageProvenance.post_id == loser_id,
ImageProvenance.image_record_id.in_(dup_imgs),
ImageProvenance.from_attachment_id.is_not(None),
)
).all():
session.execute(
update(ImageProvenance)
.where(
ImageProvenance.post_id == keeper_id,
ImageProvenance.image_record_id == img_id,
ImageProvenance.from_attachment_id.is_(None),
)
.values(from_attachment_id=att_id)
)
session.execute(
delete(ImageProvenance).where(
ImageProvenance.post_id == loser_id,
+33
View File
@@ -853,3 +853,36 @@ def test_reconcile_dedups_provenance_collision(db_sync, tmp_path):
select(ImageProvenance.post_id).where(ImageProvenance.image_record_id == img_id)
).scalars().all()
assert prov == [native_id]
def test_reconcile_preserves_from_attachment_on_provenance_collision(db_sync, tmp_path):
# The gallery-dl loser row recorded which archive the image came out of
# (#87); the native keeper row didn't. Merging drops the loser provenance row
# on the (image, post) collision — but must first carry its from_attachment_id
# onto the keeper so the containing-archive linkage survives.
a = _make_artist(db_sync, slug="rc4")
img = _make_image(db_sync, artist=a, path=str(tmp_path / "z.jpg"), sha256="e" * 64)
legacy = Post(artist_id=a.id, external_post_id="9", raw_metadata={"post_id": 77})
native = Post(artist_id=a.id, external_post_id="77", raw_metadata={"post_id": 77})
db_sync.add_all([legacy, native])
db_sync.flush()
att = PostAttachment(
post_id=legacy.id, artist_id=a.id, sha256="f" * 64,
path=str(tmp_path / "bundle.cbz"), original_filename="bundle.cbz",
ext=".cbz", size_bytes=9,
)
db_sync.add(att)
db_sync.flush()
db_sync.add(ImageProvenance(
image_record_id=img.id, post_id=legacy.id, from_attachment_id=att.id,
))
db_sync.add(ImageProvenance(image_record_id=img.id, post_id=native.id))
img_id, native_id, att_id = img.id, native.id, att.id
db_sync.commit()
cleanup_service.reconcile_duplicate_posts(db_sync, dry_run=False)
rows = db_sync.execute(
select(ImageProvenance.post_id, ImageProvenance.from_attachment_id)
.where(ImageProvenance.image_record_id == img_id)
).all()
assert rows == [(native_id, att_id)]