import type { Kysely } from "kysely"; import { dropTables, entityTable, execDdl, oneOf } from "../src/migration-helpers.ts"; // W2-B 字段级元数据协调(REQ §7.3): // - metadata_field:各来源的候选字段值(来源/语言/时间/来源修订),刷新只写这里; // - field_override:用户(或 Agent 生成并确认)的字段覆盖与锁定,优先于任何自动来源; // 最终展示值投影到 media_entity.metadata / content_unit.metadata。路径绑定不受元数据刷新影响。 const SOURCE = ["tmdb", "user", "agent", "nfo", "site", "import"] as const; const target = "constraint {t}_target_check check (num_nonnulls(media_entity_id, content_unit_id) = 1)"; // biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型 export async function up(db: Kysely): Promise { await execDdl( db, ` ${entityTable( "metadata_field", ` media_entity_id uuid references media_entity (id) on delete cascade, content_unit_id uuid references content_unit (id) on delete cascade, field text not null, value jsonb, language text, source text not null ${oneOf("source", SOURCE)}, source_revision text, fetched_at timestamptz not null default now(), ${target.replace("{t}", "metadata_field")}`, )} create unique index metadata_field_key on metadata_field (media_entity_id, content_unit_id, field, source, language) nulls not distinct; ${entityTable( "field_override", ` media_entity_id uuid references media_entity (id) on delete cascade, content_unit_id uuid references content_unit (id) on delete cascade, field text not null, value jsonb, locked boolean not null default true, source text not null default 'user' ${oneOf("source", SOURCE)}, reason text, actor_type text not null ${oneOf("actor_type", ["user", "agent", "system", "api_key"])}, actor_id text, ${target.replace("{t}", "field_override")}`, )} create unique index field_override_key on field_override (media_entity_id, content_unit_id, field) nulls not distinct; `, ); } // biome-ignore lint/suspicious/noExplicitAny: 同上 export async function down(db: Kysely): Promise { await execDdl(db, dropTables(["field_override", "metadata_field"])); }