diff --git a/backend/app/services/importer.py b/backend/app/services/importer.py index c51a9fc..fa81129 100644 --- a/backend/app/services/importer.py +++ b/backend/app/services/importer.py @@ -682,6 +682,15 @@ class Importer: error=f"{pct:.2%} transparent", ) + # Artist anchored to the attribution path (folder→artist), resolved + # UP-FRONT so the enrich-on-duplicate branches link provenance with the + # right artist even when the sidecar carries none — which is now the norm + # for archive members under post-first (the per-media sidecar is minimal). + # Without this a cross-posted / re-packed archive image deduped and was + # left UNLINKED from the new post = a post showing "no images" (#718). + _artist_name = derive_top_level_artist(attribution_path, self.import_root) + path_artist = self._upsert_artist(_artist_name) if _artist_name else None + # Hash dedup (exact). sha = _sha256_of(source) existing_stmt = select(ImageRecord).where(ImageRecord.sha256 == sha) @@ -691,9 +700,9 @@ class Importer: # Enrich-on-duplicate (parity with attach_in_place): a re-scanned # file that is a byte-dup of an existing image still links its # post via _apply_sidecar, so a cross-posted image shows on every - # post. Artist is derived from the sidecar here (not yet resolved). + # post. self._apply_sidecar( - existing, attribution_path, None, + existing, attribution_path, path_artist, explicit_source=explicit_source, ) self.session.commit() @@ -727,7 +736,7 @@ class Importer: larger = self.session.get(ImageRecord, match_id) if larger is not None: self._apply_sidecar( - larger, attribution_path, None, + larger, attribution_path, path_artist, explicit_source=explicit_source, ) self.session.commit() @@ -761,13 +770,11 @@ class Importer: self.session.flush() self._phash_cache_append(phash, width, height, record.id) - # Folder→artist (anchored to attribution_path). - artist = None - artist_name = derive_top_level_artist( - attribution_path, self.import_root - ) - if artist_name: - artist = self._attach_artist(record, artist_name) + # Folder→artist (path_artist resolved up-front above); bind it to the + # new record so it carries the canonical Artist. + if path_artist is not None and record.artist_id is None: + record.artist_id = path_artist.id + self.session.flush() # Sidecar provenance (best-effort; never fails the import). # explicit_source lets the FC-3c download path bind the new @@ -777,7 +784,7 @@ class Importer: # subscription-downloaded zip previously lost subscription # linkage if the on-disk layout didn't match assumptions. self._apply_sidecar( - record, attribution_path, artist, explicit_source=explicit_source, + record, attribution_path, path_artist, explicit_source=explicit_source, ) # Thumbnail is queued separately by the calling task; the importer @@ -1076,17 +1083,6 @@ class Importer: self.session.flush() return artist - def _attach_artist(self, record: ImageRecord, artist_name: str) -> Artist: - """Upsert the Artist and set it as the image's canonical artist. - Artist-kind tags were retired in FC-2d-vii-c — the Artist row is - the single source of truth. Returns the Artist (sidecar - provenance attaches its Source to it).""" - artist = self._upsert_artist(artist_name) - if record.artist_id is None: - record.artist_id = artist.id - self.session.flush() - return artist - @staticmethod def _sidecar_artist_name(data: dict) -> str | None: for k in ("artist", "author_name"):