import type { Kysely } from "kysely"; import { dropTables, entityTable, execDdl } from "../src/migration-helpers.ts"; // 管理员登录门禁(任务书 §3.1-14;docs/design/auth-reference.md): // admin_user 单行(singleton 唯一)、admin_session(只存 sha256(token),软删除吊销)、 // login_attempt(IP + 账号双维度限速落表,多进程/重启一致);另补 audit_event 的 actor/action 查询索引。 // biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型 export async function up(db: Kysely): Promise { await execDdl( db, ` ${entityTable( "admin_user", ` singleton boolean not null default true check (singleton), username text, password_hash text, setup_token_hash text, password_changed_at timestamptz, totp_secret_ref uuid references secret (id) on delete set null, totp_pending_secret_ref uuid references secret (id) on delete set null, totp_enabled_at timestamptz, totp_last_counter bigint, constraint admin_user_singleton_key unique (singleton), constraint admin_user_initialized_check check ((username is null) = (password_hash is null))`, )} ${entityTable( "admin_session", ` admin_user_id uuid not null references admin_user (id) on delete cascade, session_hash text not null, ip text, user_agent text, last_seen_at timestamptz not null default now(), expires_at timestamptz not null, revoked_at timestamptz, revoke_reason text, constraint admin_session_hash_key unique (session_hash)`, )} create index admin_session_active_idx on admin_session (admin_user_id) where revoked_at is null; create table login_attempt ( id bigserial primary key, at timestamptz not null default now(), ip text not null, username text not null, success boolean not null, reason text ); create index login_attempt_ip_idx on login_attempt (ip, at) where not success; create index login_attempt_username_idx on login_attempt (username, at) where not success; create index audit_event_actor_idx on audit_event (actor_type, actor_id, at); create index audit_event_action_idx on audit_event (action, at); `, ); } // biome-ignore lint/suspicious/noExplicitAny: 同上 export async function down(db: Kysely): Promise { await execDdl( db, ` drop index if exists audit_event_action_idx; drop index if exists audit_event_actor_idx; ${dropTables(["login_attempt", "admin_session", "admin_user"])} `, ); }