app-template/packages/db/scripts/seed.ts

78 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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<void> {
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);
});