Files
UHD_Media_Tool/packages/db/migrations/0066_episode_mapping.ts
T

68 lines
2.9 KiB
TypeScript

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<any>): Promise<void> {
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<any>): Promise<void> {
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;`,
);
}