78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
/**
|
||
* 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);
|
||
});
|