Files
UHD_Media_Tool/packages/db/migrations/0001_init.ts
T
umtandClaude Fable 5.1 b22c440139 chore: scaffold monorepo and migrate uhd_web design system (W0-A)
- pnpm workspace: apps/{web,server}, packages/{contracts,db,domain,adapters,tasks,agent,testkit}
- server: Hono + zod-openapi, API Key auth with scopes, redacting pino logger, SSE event bus with replay, worker heartbeat
- db: Kysely + pg, file migrator, 0001_init (settings/api_key/audit_event/outbox_event)
- web: React Router 7 SPA console shell with 12 workspace placeholders, uhd_web tokens/ui/guard scripts
- docs: ADR-001 (Node over Deno), dev runbook, design system rules

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-27 17:36:26 +08:00

68 lines
2.8 KiB
TypeScript

import { type Kysely, sql } from "kysely";
// 首个迁移:底座四张表。gen_random_uuid() 为 PG13+ 内置
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable("settings")
.addColumn("key", "text", (c) => c.primaryKey())
.addColumn("value", "jsonb", (c) => c.notNull())
.addColumn("updated_at", "timestamptz", (c) => c.notNull().defaultTo(sql`now()`))
.execute();
await db.schema
.createTable("api_key")
.addColumn("id", "uuid", (c) => c.primaryKey().defaultTo(sql`gen_random_uuid()`))
.addColumn("name", "text", (c) => c.notNull())
.addColumn("key_hash", "text", (c) => c.notNull().unique())
.addColumn("scopes", sql`text[]`, (c) => c.notNull().defaultTo(sql`'{}'::text[]`))
.addColumn("created_at", "timestamptz", (c) => c.notNull().defaultTo(sql`now()`))
.addColumn("revoked_at", "timestamptz")
.addColumn("last_used_at", "timestamptz")
.execute();
await db.schema
.createTable("audit_event")
.addColumn("id", "uuid", (c) => c.primaryKey().defaultTo(sql`gen_random_uuid()`))
.addColumn("at", "timestamptz", (c) => c.notNull().defaultTo(sql`now()`))
.addColumn("actor_type", "text", (c) => c.notNull())
.addColumn("actor_id", "text")
.addColumn("action", "text", (c) => c.notNull())
.addColumn("target_type", "text")
.addColumn("target_id", "text")
.addColumn("payload", "jsonb", (c) => c.notNull().defaultTo(sql`'{}'::jsonb`))
.addColumn("trace_id", "text")
.execute();
await db.schema.createIndex("audit_event_at_idx").on("audit_event").column("at").execute();
await db.schema
.createIndex("audit_event_target_idx")
.on("audit_event")
.columns(["target_type", "target_id"])
.execute();
await db.schema
.createTable("outbox_event")
.addColumn("id", "uuid", (c) => c.primaryKey().defaultTo(sql`gen_random_uuid()`))
.addColumn("at", "timestamptz", (c) => c.notNull().defaultTo(sql`now()`))
.addColumn("type", "text", (c) => c.notNull())
.addColumn("payload", "jsonb", (c) => c.notNull().defaultTo(sql`'{}'::jsonb`))
.addColumn("published_at", "timestamptz")
.execute();
// 只索引未投递的事件,投递器按时间顺序扫描
await db.schema
.createIndex("outbox_event_pending_idx")
.on("outbox_event")
.column("at")
.where(sql.ref("published_at"), "is", null)
.execute();
}
// biome-ignore lint/suspicious/noExplicitAny: 同上
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable("outbox_event").ifExists().execute();
await db.schema.dropTable("audit_event").ifExists().execute();
await db.schema.dropTable("api_key").ifExists().execute();
await db.schema.dropTable("settings").ifExists().execute();
}