/** * Seed:初始化默认管理员(用户名 + 密码登录)。 * 用法:`yarn db:seed`(幂等:已存在同名用户则跳过)。 * 密码哈希格式与 `@app/trpc` 的 `password.ts` 一致(scrypt `salt:hash` hex)。 */ import { randomBytes, scryptSync } from "node:crypto"; import { readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { eq } from "drizzle-orm"; import { createClient } from "@libsql/client"; import { drizzle } from "drizzle-orm/libsql"; import * as schema from "@app/models"; // monorepo 根 .env(tsx/node 不会自动加载) try { const rootEnv = resolve(dirname(fileURLToPath(import.meta.url)), "../../../.env"); const env = readFileSync(rootEnv, "utf-8"); for (const line of env.split("\n")) { const [key, ...vals] = line.split("="); if (key && !key.startsWith("#")) { process.env[key.trim()] ??= vals.join("=").trim(); } } } catch {} const url = process.env["TURSO_DATABASE_URL"]; if (!url) throw new Error("TURSO_DATABASE_URL is required"); const clientConfig: { url: string; authToken?: string } = { url }; const token = process.env["TURSO_AUTH_TOKEN"]; if (token) clientConfig.authToken = token; const client = createClient(clientConfig); const db = drizzle(client, { schema }); function hashPassword(password: string): string { const salt = randomBytes(16).toString("hex"); const hash = scryptSync(password, salt, 64).toString("hex"); return `${salt}:${hash}`; } const ADMIN_USERNAME = (process.env["SEED_ADMIN_USERNAME"] ?? "admin").toLowerCase(); const ADMIN_PASSWORD = process.env["SEED_ADMIN_PASSWORD"] ?? "admin123"; const ADMIN_NAME = process.env["SEED_ADMIN_NAME"] ?? "Admin"; const ADMIN_EMAIL = process.env["SEED_ADMIN_EMAIL"] ?? "admin@example.com"; async function seed(): Promise { const { users } = schema; const existing = await db .select({ id: users.id, username: users.username }) .from(users) .where(eq(users.username, ADMIN_USERNAME)) .limit(1); if (existing.length > 0) { console.log(`[seed] admin 已存在(username=${ADMIN_USERNAME}),跳过创建`); return; } await db.insert(users).values({ username: ADMIN_USERNAME, name: ADMIN_NAME, email: ADMIN_EMAIL.toLowerCase(), passwordHash: hashPassword(ADMIN_PASSWORD), }); console.log(`[seed] 已创建管理员用户 username=${ADMIN_USERNAME} password=${ADMIN_PASSWORD}`); console.log("[seed] 生产环境请通过 SEED_ADMIN_PASSWORD 覆盖默认密码,并尽快登录后修改"); } seed() .then(() => process.exit(0)) .catch((err) => { console.error("[seed] 失败:", err); process.exit(1); });