You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

54 lines
1.7 KiB
TypeScript

import "dotenv/config";
import { eq } from "drizzle-orm";
import { db } from "../db";
import { user } from "../db/schema";
import { auth } from "../lib/auth";
function requireEnv(name: string): string {
const value = process.env[name]?.trim();
if (!value) throw new Error(`${name} is not set in .env`);
return value;
}
async function seedAdmin() {
// The username plugin stores usernames lowercased, so lookups must match that.
const displayUsername = requireEnv("SEED_ADMIN_USERNAME");
const username = displayUsername.toLowerCase();
const password = requireEnv("SEED_ADMIN_PASSWORD");
const name = process.env.SEED_ADMIN_NAME?.trim() || "Administrator";
const email = process.env.SEED_ADMIN_EMAIL?.trim() || `${username}@cleo.local`;
const [existing] = await db
.select({ id: user.id })
.from(user)
.where(eq(user.username, username))
.limit(1);
if (existing) {
console.log(`Admin "${username}" already exists, skipped.`);
return;
}
// Goes through Better Auth's internal adapter instead of the sign-up endpoint, which is
// closed by `disableSignUp`. Hashing with ctx.password keeps the format login expects.
const ctx = await auth.$context;
const created = await ctx.internalAdapter.createUser(
{ name, email, username, displayUsername },
{ method: "admin" },
);
await ctx.internalAdapter.linkAccount({
userId: created.id,
providerId: "credential",
accountId: created.id,
password: await ctx.password.hash(password),
});
console.log(`Admin "${username}" created.`);
}
seedAdmin()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(() => db.$client.end());