import type { Kysely } from "kysely"; import { entityTable, execDdl, oneOf } from "../src/migration-helpers.ts"; // 集数解析与映射(docs/plan/episode-resolution.md 批次 2): // - episode_mapping_rule:作品 + 来源季(可空 = 任意)+ 发布组(可空 = 任意)的映射规则; // 同一作用域(发布组忽略大小写,空值也算一种取值)同时只能有一条 active // - source_release.episode_resolution:集数解析结果(原始季集、规范季集、方式、规则、证据) // - decision.kind 增加 episode_mapping(资源已绑定作品但集数待确认) const OLD_DECISION_KINDS = [ "identity_ambiguous", "download_stalled", "download_failed", "path_conflict", "space_insufficient", "approval_required", "metadata_conflict", "source_unreachable", "other", "identity_unresolved", ]; const NEW_DECISION_KINDS = [...OLD_DECISION_KINDS, "episode_mapping"]; const swapDecisionKinds = (values: readonly string[]) => ` alter table decision drop constraint decision_kind_check; alter table decision add constraint decision_kind_check ${oneOf("kind", values)}; `; // biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型 export async function up(db: Kysely): Promise { await execDdl( db, `${entityTable( "episode_mapping_rule", `media_entity_id uuid not null references media_entity (id) on delete cascade, season integer, release_group text, kind text not null ${oneOf("kind", ["expression", "table", "special"])}, expression text, target_season integer, table_entries jsonb, special text ${oneOf("special", ["special", "extra", "ignore"])}, status text not null default 'active' ${oneOf("status", ["active", "disabled"])}, evidence_level text not null default 'manual' ${oneOf("evidence_level", ["manual", "rule_confirmed", "multi_evidence", "needs_review"])}, evidence jsonb not null default '{}'::jsonb, disabled_reason text, created_by_type text not null ${oneOf("created_by_type", ["user", "agent", "system", "api_key"])}, created_by_id text`, )} create unique index episode_mapping_rule_active_scope on episode_mapping_rule (media_entity_id, coalesce(season, -1), coalesce(lower(release_group), '')) where status = 'active'; create index episode_mapping_rule_entity on episode_mapping_rule (media_entity_id); alter table source_release add column episode_resolution jsonb; ${swapDecisionKinds(NEW_DECISION_KINDS)}`, ); } // biome-ignore lint/suspicious/noExplicitAny: 同上 export async function down(db: Kysely): Promise { await execDdl( db, `update decision set kind = 'other' where kind = 'episode_mapping'; ${swapDecisionKinds(OLD_DECISION_KINDS)} alter table source_release drop column if exists episode_resolution; drop table if exists episode_mapping_rule;`, ); }