Files
UHD_Media_Tool/packages/db/migrations/0031_site_center.ts
T
rootandClaude Opus 5.5 e2d1e21968 feat(contracts): site center schemas, endpoints and migration 0031
Site health/proxy, SiteAccount login state (challenge, lastLoginAt, lastError,
userData), SiteUserData history table, SiteCredentialsBody by kind,
refresh-login/health-check/user-data/delete endpoints, SitesSettings defaults.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 14:40:07 +08:00

89 lines
3.2 KiB
TypeScript

import type { Kysely } from "kysely";
import { entityTable, execDdl, oneOf } from "../src/migration-helpers.ts";
// W9-A 站点中心(docs/design/site-center.md §5):
// - site_account:登录状态加 challenge;账密登录名、TOTP / 会话 Cookie / passkey 的 secret 引用、UA、
// 上次登录、最近错误、最近一次用户数据;user_info.username 迁到 login_name。
// - site_user_data:用户数据按天一行(同日 upsert 取最新),随账号删除。
// - site:最近健康检测(结果 + 时间)、使用代理;deleted_at 软删除(source_release 外键 restrict,资源保留)。
const OLD_STATUS = ["unknown", "active", "invalid", "expired"];
const NEW_STATUS = [...OLD_STATUS, "challenge"];
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
export async function up(db: Kysely<any>): Promise<void> {
await execDdl(
db,
`
alter table site_account drop constraint site_account_status_check;
alter table site_account add constraint site_account_status_check ${oneOf("status", NEW_STATUS)};
alter table site_account
add column login_name text,
add column totp_secret_ref uuid references secret (id) on delete set null,
add column session_secret_ref uuid references secret (id) on delete set null,
add column passkey_secret_ref uuid references secret (id) on delete set null,
add column user_agent text,
add column last_login_at timestamptz,
add column last_error jsonb,
add column user_data jsonb,
add column user_data_at timestamptz;
update site_account set login_name = user_info ->> 'username' where user_info ? 'username';
alter table site
add column last_health jsonb,
add column last_health_at timestamptz,
add column proxy boolean not null default false,
add column deleted_at timestamptz;
${entityTable(
"site_user_data",
`
site_account_id uuid not null references site_account (id) on delete cascade,
day date not null,
upload bigint not null default 0,
download bigint not null default 0,
ratio double precision,
bonus double precision,
seeding integer not null default 0,
seeding_size bigint not null default 0,
leeching integer not null default 0,
user_level text,
vip boolean,
message_unread integer not null default 0,
join_at text,
raw jsonb not null default '{}'::jsonb,
captured_at timestamptz not null default now(),
constraint site_user_data_day_key unique (site_account_id, day)`,
)}
`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: 同上
export async function down(db: Kysely<any>): Promise<void> {
await execDdl(
db,
`
drop table if exists site_user_data;
alter table site
drop column last_health,
drop column last_health_at,
drop column proxy,
drop column deleted_at;
alter table site_account
drop column login_name,
drop column totp_secret_ref,
drop column session_secret_ref,
drop column passkey_secret_ref,
drop column user_agent,
drop column last_login_at,
drop column last_error,
drop column user_data,
drop column user_data_at;
update site_account set status = 'expired' where status = 'challenge';
alter table site_account drop constraint site_account_status_check;
alter table site_account add constraint site_account_status_check ${oneOf("status", OLD_STATUS)};
`,
);
}