import type { Kysely } from "kysely"; import { dropTables, entityTable, execDdl, oneOf } from "../src/migration-helpers.ts"; // W5-C 订阅增强(docs/porting/moviepilot-subscribe.md): // - subscription:pending 状态(TMDB 未就绪:可搜索/评估,不可完成);主动搜索的上次/下次时间; // - content_unit(季):总集数账本 episode_total(只升;回退不低于已覆盖最高集;可人工锁定); // - acquisition_plan:unit_keys 记录计划预留的内容单元(Slot×单元预留,等价 MP note);file_selection 记录季包选文件; // 去掉"同 Slot 只允许一个在途计划"的唯一索引,改为服务端在 Slot 行锁内按单元不相交校验; // - slot_deferral:Slot 忙(单元在途冲突 / 电影 Slot 在途)时到达的发布,计划结束后重评; // - slot_unit:分集质量账本(Slot × 内容单元 → 当前资产 + 质量摘要 + 阶梯 pri),升级按单元替换; // - subscription_history:订阅完成快照(完成后保留订阅行,可重新激活)。 // biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型 export async function up(db: Kysely): Promise { await execDdl( db, ` alter table subscription drop constraint subscription_status_check; alter table subscription add constraint subscription_status_check ${oneOf("status", ["active", "paused", "pending", "completed", "cancelled"])}; alter table subscription add column last_searched_at timestamptz; alter table subscription add column next_search_at timestamptz; create index subscription_next_search_idx on subscription (next_search_at) where next_search_at is not null; alter table content_unit add column episode_total integer check (episode_total is null or episode_total >= 0); alter table content_unit add column episode_total_source text ${oneOf("episode_total_source", ["tmdb", "injected", "manual", "covered_floor", "previous"])}; alter table content_unit add column episode_total_locked boolean not null default false; alter table content_unit add column episode_total_at timestamptz; alter table acquisition_plan add column unit_keys text[] not null default '{}'::text[]; alter table acquisition_plan add column file_selection jsonb; drop index if exists acquisition_plan_slot_active_key; create index acquisition_plan_slot_active_idx on acquisition_plan (slot_id) where status in ('reserved', 'submitted'); ${entityTable( "slot_deferral", ` slot_id uuid not null references acquisition_slot (id) on delete cascade, source_release_id uuid not null references source_release (id) on delete cascade, reason text not null, unit_keys text[] not null default '{}'::text[], attempts integer not null default 1, constraint slot_deferral_key unique (slot_id, source_release_id)`, )} ${entityTable( "slot_unit", ` slot_id uuid not null references acquisition_slot (id) on delete cascade, content_unit_id uuid not null references content_unit (id) on delete cascade, media_asset_id uuid references media_asset (id) on delete set null, plan_id uuid references acquisition_plan (id) on delete set null, quality jsonb not null default '{}'::jsonb, pri integer, constraint slot_unit_key unique (slot_id, content_unit_id)`, )} create index slot_unit_asset_idx on slot_unit (media_asset_id) where media_asset_id is not null; ${entityTable( "subscription_history", ` subscription_id uuid references subscription (id) on delete set null, media_entity_id uuid references media_entity (id) on delete set null, season_unit_id uuid references content_unit (id) on delete set null, media_kind text not null, mode text not null, reason text not null, snapshot jsonb not null default '{}'::jsonb, progress jsonb not null default '{}'::jsonb, completed_at timestamptz not null default now()`, )} create index subscription_history_entity_idx on subscription_history (media_entity_id, completed_at desc); create index subscription_history_subscription_idx on subscription_history (subscription_id); `, ); } // biome-ignore lint/suspicious/noExplicitAny: 同上 export async function down(db: Kysely): Promise { await execDdl( db, ` ${dropTables(["subscription_history", "slot_unit", "slot_deferral"])} drop index if exists acquisition_plan_slot_active_idx; -- 回到"每 Slot 一个在途":同 Slot 多个在途时只保留最早的一个,其余取消 update acquisition_plan p set status = 'cancelled', status_reason = 'migration_down:multi_inflight' where p.status in ('reserved', 'submitted') and exists (select 1 from acquisition_plan q where q.slot_id = p.slot_id and q.status in ('reserved', 'submitted') and (q.created_at, q.id) < (p.created_at, p.id)); create unique index acquisition_plan_slot_active_key on acquisition_plan (slot_id) where status in ('reserved', 'submitted'); alter table acquisition_plan drop column if exists file_selection; alter table acquisition_plan drop column if exists unit_keys; alter table content_unit drop column if exists episode_total_at; alter table content_unit drop column if exists episode_total_locked; alter table content_unit drop column if exists episode_total_source; alter table content_unit drop column if exists episode_total; drop index if exists subscription_next_search_idx; alter table subscription drop column if exists next_search_at; alter table subscription drop column if exists last_searched_at; update subscription set status = 'active' where status = 'pending'; alter table subscription drop constraint subscription_status_check; alter table subscription add constraint subscription_status_check ${oneOf("status", ["active", "paused", "completed", "cancelled"])}; `, ); }