feat(db): add username + password_hash to operators

Migration 0010 widens the existing operators table for username +
password auth. Backfills 'admin' on the seed row so the NOT NULL
constraint succeeds; password_hash stays nullable so the operator is
forced to set one via scripts/set-password.sh before they can sign in.
Adds a unique index on lower(username).

seed.ts also picks up the new username field (defaults to 'admin' so
re-running scripts/db.sh seed stays idempotent against the backfilled row).
This commit is contained in:
yiekheng 2026-05-10 17:35:01 +08:00
parent 477e09f645
commit a37b36196d
5 changed files with 1092 additions and 0 deletions

View File

@ -0,0 +1,9 @@
-- Add username + password_hash to operators. Backfill the seed row to
-- 'admin' so the NOT NULL constraint succeeds; password_hash stays
-- nullable so the operator is forced to set one via the CLI before
-- they can sign in.
ALTER TABLE "operators" ADD COLUMN "username" text;--> statement-breakpoint
ALTER TABLE "operators" ADD COLUMN "password_hash" text;--> statement-breakpoint
UPDATE "operators" SET "username" = 'admin' WHERE "username" IS NULL;--> statement-breakpoint
ALTER TABLE "operators" ALTER COLUMN "username" SET NOT NULL;--> statement-breakpoint
CREATE UNIQUE INDEX "operators_username_uq" ON "operators" (lower("username"));

File diff suppressed because it is too large Load Diff

View File

@ -71,6 +71,13 @@
"when": 1778464000000,
"tag": "0009_rename_ended_to_inactive",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1778405570914,
"tag": "0010_fancy_wolf_cub",
"breakpoints": true
}
]
}

View File

@ -1,3 +1,4 @@
import { sql } from "drizzle-orm";
import {
pgTable,
uuid,
@ -17,6 +18,8 @@ export const operators = pgTable(
{
id: uuid("id").primaryKey().defaultRandom(),
telegramUserId: bigint("telegram_user_id", { mode: "number" }).notNull(),
username: text("username").notNull(),
passwordHash: text("password_hash"),
displayName: text("display_name").notNull(),
role: text("role").notNull().default("admin"),
defaultTimezone: text("default_timezone").notNull().default("Asia/Kuala_Lumpur"),
@ -24,6 +27,7 @@ export const operators = pgTable(
},
(t) => ({
telegramUserIdUnique: uniqueIndex("operators_telegram_user_id_uq").on(t.telegramUserId),
usernameUnique: uniqueIndex("operators_username_uq").on(sql`lower(${t.username})`),
}),
);

View File

@ -19,6 +19,7 @@ await db
.insert(operators)
.values({
telegramUserId: Number(operatorTelegramId),
username: process.env.SEED_OPERATOR_USERNAME ?? "admin",
displayName: operatorName,
role: "admin",
defaultTimezone: "Asia/Kuala_Lumpur",