import { fileURLToPath } from "node:url" import { UserStatus } from "@/generated/prisma/client" import { normalizeEmail } from "@/lib/email" import { getPasswordHash } from "@/lib/security" import prisma from "../src/lib/prisma" type BootstrapAdminInput = { email: string name: string password: string } function splitName(name: string) { const [firstName = "Administrator", ...rest] = name.trim().split(/\s+/) return { firstName, lastName: rest.join(" "), } } function getBootstrapAdminInput(): BootstrapAdminInput { const isProduction = process.env.NODE_ENV === "production" const email = process.env.ADMIN_EMAIL ?? "admin@local.host" const name = process.env.ADMIN_NAME ?? "Administrator" const password = process.env.ADMIN_PASSWORD if (isProduction && !password) { throw new Error("ADMIN_PASSWORD is required to bootstrap an admin user") } return { email, name, password: password ?? "admin", } } export async function bootstrapAdmin(client: typeof prisma) { const enabled = process.env.ADMIN_BOOTSTRAP_ENABLED !== "false" if (!enabled) return const admin = getBootstrapAdminInput() const email = normalizeEmail(admin.email) const { firstName, lastName } = splitName(admin.name) const existingUser = await client.user.findUnique({ where: { emailNormalized: email, }, select: { id: true, passwordHash: true, activatedAt: true, person: { select: { id: true, }, }, }, }) const user = existingUser ? await client.user.update({ where: { id: existingUser.id, }, data: { name: admin.name, email: admin.email, emailNormalized: email, role: "ADMIN", status: UserStatus.ACTIVE, ...(existingUser.passwordHash ? {} : { passwordHash: await getPasswordHash(admin.password), passwordChangedAt: new Date(), }), ...(existingUser.activatedAt ? {} : { activatedAt: new Date() }), }, select: { id: true, person: { select: { id: true, }, }, }, }) : await client.user.create({ data: { name: admin.name, email: admin.email, emailNormalized: email, role: "ADMIN", status: UserStatus.ACTIVE, passwordHash: await getPasswordHash(admin.password), activatedAt: new Date(), passwordChangedAt: new Date(), }, select: { id: true, person: { select: { id: true, }, }, }, }) if (!user.person) { await client.person.upsert({ where: { userId: user.id, }, update: { firstName, lastName, email: admin.email, }, create: { firstName, lastName, email: admin.email, user: { connect: { id: user.id, }, }, }, }) } } async function main() { try { await bootstrapAdmin(prisma) } finally { await prisma.$disconnect() } } if (process.argv[1] === fileURLToPath(import.meta.url)) { main().catch((error) => { console.error(error) process.exit(1) }) }