diff --git a/alembic/versions/0023_drop_meta_rating_tag_kinds.py b/alembic/versions/0023_drop_meta_rating_tag_kinds.py new file mode 100644 index 0000000..1d2b052 --- /dev/null +++ b/alembic/versions/0023_drop_meta_rating_tag_kinds.py @@ -0,0 +1,73 @@ +"""drop meta + rating tag kinds — operator-retired 2026-05-26 + +Revision ID: 0023 +Revises: 0022 +Create Date: 2026-05-26 + +Operator decided meta + rating aren't valid tag kinds for FC. Per-row +behavior: DELETE existing rows (operator chose "clean break" over +"convert to general"). All cascading FKs (image_tag, tag_alias, +tag_allowlist, tag_reference_embedding, tag_suggestion_rejection, +series_page) use ondelete="CASCADE" so a single DELETE on tag cleans +the related rows in one go. + +After the data cleanup, recreate the tag_kind ENUM without 'meta' / +'rating' (Postgres has no `ALTER TYPE ... DROP VALUE`; standard +rename-create-cast-drop dance). The server default 'general' is +dropped before the type swap and restored after. +""" +from typing import Sequence, Union + +from alembic import op + +revision: str = "0023" +down_revision: Union[str, None] = "0022" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # 1. Delete tags of the retired kinds. CASCADE handles related tables. + op.execute("DELETE FROM tag WHERE kind IN ('meta', 'rating')") + + # 2. Drop the server default — ALTER COLUMN TYPE can't carry it + # across the type swap below. + op.execute("ALTER TABLE tag ALTER COLUMN kind DROP DEFAULT") + + # 3. Recreate the tag_kind enum without meta/rating. + op.execute("ALTER TYPE tag_kind RENAME TO tag_kind_old") + op.execute( + "CREATE TYPE tag_kind AS ENUM (" + "'artist', 'character', 'fandom', 'general', " + "'series', 'archive', 'post'" + ")" + ) + op.execute( + "ALTER TABLE tag " + "ALTER COLUMN kind TYPE tag_kind " + "USING kind::text::tag_kind" + ) + op.execute("DROP TYPE tag_kind_old") + + # 4. Restore the server default. + op.execute("ALTER TABLE tag ALTER COLUMN kind SET DEFAULT 'general'") + + +def downgrade() -> None: + # Add the values back to the enum so old code can boot. The deleted + # tag rows are gone permanently — no safe restore. + op.execute("ALTER TABLE tag ALTER COLUMN kind DROP DEFAULT") + op.execute("ALTER TYPE tag_kind RENAME TO tag_kind_old") + op.execute( + "CREATE TYPE tag_kind AS ENUM (" + "'artist', 'character', 'fandom', 'general', " + "'series', 'archive', 'post', 'meta', 'rating'" + ")" + ) + op.execute( + "ALTER TABLE tag " + "ALTER COLUMN kind TYPE tag_kind " + "USING kind::text::tag_kind" + ) + op.execute("DROP TYPE tag_kind_old") + op.execute("ALTER TABLE tag ALTER COLUMN kind SET DEFAULT 'general'") diff --git a/backend/app/models/tag.py b/backend/app/models/tag.py index c99fc06..a74575b 100644 --- a/backend/app/models/tag.py +++ b/backend/app/models/tag.py @@ -35,8 +35,10 @@ class TagKind(StrEnum): series = "series" archive = "archive" post = "post" - meta = "meta" - rating = "rating" + # `meta` and `rating` retired by operator 2026-05-26 (alembic 0023). + # `artist` retired in FC-2d-vii-c — artists are first-class entities + # via Artist/Source rows now, not tags — but the enum value stays + # to keep historic tag rows queryable. image_tag = Table( diff --git a/frontend/src/stores/tags.js b/frontend/src/stores/tags.js index fbb871a..5bf47d1 100644 --- a/frontend/src/stores/tags.js +++ b/frontend/src/stores/tags.js @@ -2,24 +2,22 @@ import { defineStore } from 'pinia' import { ref } from 'vue' import { useApi } from '../composables/useApi.js' +// `artist` retired in FC-2d-vii-c (provenance is its own axis), `meta` + +// `rating` retired by operator 2026-05-26 (alembic 0023 drops them from +// the enum). KIND_COLOR keeps `archive` + `post` so any legacy +// system-managed tag still renders with a neutral color. const KIND_OPTIONS = [ { value: 'general', label: 'General', icon: 'mdi-tag' }, - { value: 'artist', label: 'Artist', icon: 'mdi-palette' }, { value: 'character', label: 'Character', icon: 'mdi-account-circle' }, { value: 'fandom', label: 'Fandom', icon: 'mdi-book-open-page-variant' }, - { value: 'series', label: 'Series', icon: 'mdi-bookshelf' }, - { value: 'meta', label: 'Meta', icon: 'mdi-cog-outline' }, - { value: 'rating', label: 'Rating', icon: 'mdi-shield-check-outline' } + { value: 'series', label: 'Series', icon: 'mdi-bookshelf' } ] const KIND_COLOR = { - artist: 'accent', character: 'info', fandom: 'secondary', series: 'warning', general: 'on-surface', - meta: 'on-surface', - rating: 'on-surface', archive: 'on-surface', post: 'on-surface' } diff --git a/frontend/src/views/TagsView.vue b/frontend/src/views/TagsView.vue index d973b20..b79b6f8 100644 --- a/frontend/src/views/TagsView.vue +++ b/frontend/src/views/TagsView.vue @@ -99,10 +99,11 @@ import MergeConfirmDialog from '../components/discovery/MergeConfirmDialog.vue' import DestructiveConfirmModal from '../components/modal/DestructiveConfirmModal.vue' // Must stay a subset of the backend TagKind enum (character, fandom, -// general, series, archive, post, meta, rating). 'fandom' is this -// model's copyright/franchise concept (characters link via fandom_id). -// 'artist' retired in FC-2d-vii-c — artists are the Artist row, not a tag. -const KINDS = ['character', 'fandom', 'general', 'series', 'meta'] +// general, series, archive, post). 'fandom' is this model's +// copyright/franchise concept (characters link via fandom_id). +// 'artist' retired in FC-2d-vii-c — artists are the Artist row, not +// a tag. 'meta' + 'rating' retired by operator 2026-05-26 (alembic 0023). +const KINDS = ['character', 'fandom', 'general', 'series'] const store = useTagDirectoryStore() const router = useRouter()