refactor(tags): shared tag_query for fandom self-join + serialization (DRY sweep)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 24s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 3m17s

The fandom self-join (resolve a character's fandom NAME via Tag.fandom_id->Tag)
and the {id,name,kind,fandom_id,fandom_name} dict were hand-written in
TagService.autocomplete/.list_for_image, GalleryService.get_image_with_tags and
the api/tags handlers — the last few grown by this session's fandom-on-chip
feature. Consolidate to services/tag_query: fandom_join_alias() + tag_columns()
build the select; serialize_tag(row) builds the dict. Now a new tag field is
added in one place.

Over-DRY guard: TagDirectoryService selects the full Tag ORM + an image-count
aggregate (a different select shape) — left as its own variant. §8b: the
fandom_lookup alias lives only in tag_query; gallery + both api/tags handlers
serialize via serialize_tag. Test: serialize_tag handles enum + string kind.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 00:19:17 -04:00
parent 074c5868fb
commit 14c244bd3d
5 changed files with 86 additions and 56 deletions
+5 -19
View File
@@ -24,6 +24,7 @@ from sqlalchemy.orm import aliased
from ..models import Artist, ImageProvenance, ImageRecord, Post, Source, Tag
from ..models.tag import image_tag
from .pagination import decode_cursor, encode_cursor
from .tag_query import fandom_join_alias, serialize_tag, tag_columns
# Reserved `platform` filter value selecting images with NO platformed
# provenance (filesystem imports). Returned by facets() as a null-valued
@@ -543,16 +544,10 @@ class GalleryService:
if record is None:
return None
# Self-join Tag to resolve a character's fandom NAME (not just id) so the
# modal chip can label it without an N+1 — mirrors list_for_image.
fandom_alias = Tag.__table__.alias("fandom_lookup")
# modal chip can label it without an N+1 (shared tag_query helpers).
fandom_alias = fandom_join_alias()
tag_stmt = (
select(
Tag.id,
Tag.name,
Tag.kind,
Tag.fandom_id,
fandom_alias.c.name.label("fandom_name"),
)
select(*tag_columns(fandom_alias))
.select_from(
Tag.__table__
.join(image_tag, image_tag.c.tag_id == Tag.id)
@@ -599,16 +594,7 @@ class GalleryService:
{"id": artist.id, "name": artist.name, "slug": artist.slug}
if artist is not None else None
),
"tags": [
{
"id": t.id,
"name": t.name,
"kind": t.kind.value if hasattr(t.kind, "value") else t.kind,
"fandom_id": t.fandom_id,
"fandom_name": t.fandom_name,
}
for t in tags
],
"tags": [serialize_tag(t) for t in tags],
"neighbors": neighbors,
}