/** * Idempotent Super Administrator provisioning, run on every container * start (see docker/entrypoint.sh) — NOT part of the installation wizard, * per the brief's explicit requirement that this account come from * environment variables. Safe to run repeatedly: re-syncs the name from * the env vars every run. * * No password — sign-in across this entire app is OTP-only (see * src/lib/auth/authorize-credentials.ts), and a Super Administrator signs * in exactly the same way as anyone else, via a one-time code emailed to * SUPER_ADMIN_EMAIL. `User.passwordHash` is never read anywhere in the * actual sign-in path — this script used to require and set one anyway * (a leftover from before the OTP migration), which only meant an unused * value with its own, pointless minimum-length requirement. Removed * rather than kept "just in case": a field nothing checks is worse than * no field, since it invites someone to assume it's load-bearing. * * Deliberately does NOT create a StaffProfile — that's what keeps the * Super Administrator out of the Team list, the customer-facing barber * picker, and off the Appointment.staffId foreign key entirely, by * construction rather than a filter. See src/lib/auth/permissions.ts * (hasPermission's isSuperAdmin short-circuit) and * src/lib/business/get-acting-business-context.ts. * * Uses relative imports (not the "@/..." alias) and constructs its own * PrismaClient, matching prisma/seed.ts — both scripts run under `tsx` * outside Next's own module resolution/bundling. */ import "dotenv/config"; import { PrismaClient } from "../generated/prisma/client"; import { PrismaMariaDb } from "@prisma/adapter-mariadb"; import { buildMysqlPoolConfig } from "../src/lib/db/mysql-pool-config"; async function main() { const email = process.env.SUPER_ADMIN_EMAIL?.trim().toLowerCase(); if (!email) { console.log("[ensure-super-admin] SUPER_ADMIN_EMAIL not set — skipping (no Super Administrator configured)."); return; } const firstName = process.env.SUPER_ADMIN_FIRST_NAME?.trim() ?? ""; const lastName = process.env.SUPER_ADMIN_LAST_NAME?.trim() ?? ""; const name = [firstName, lastName].filter(Boolean).join(" ") || null; const adapter = new PrismaMariaDb(buildMysqlPoolConfig()); const prisma = new PrismaClient({ adapter }); try { const existing = await prisma.user.findUnique({ where: { email } }); if (existing && !existing.isSuperAdmin) { console.error( `[ensure-super-admin] A user with email ${email} already exists and is not a Super Administrator. Refusing to overwrite it — use a different SUPER_ADMIN_EMAIL or remove the conflicting account first.`, ); process.exitCode = 1; return; } if (existing) { await prisma.user.update({ where: { id: existing.id }, data: { name, emailVerified: existing.emailVerified ?? new Date() }, }); console.log(`[ensure-super-admin] Synced name for existing Super Administrator (${email}).`); } else { await prisma.user.create({ data: { email, name, isSuperAdmin: true, emailVerified: new Date() }, }); console.log(`[ensure-super-admin] Created Super Administrator (${email}).`); } } finally { await prisma.$disconnect(); } } main().catch((error) => { console.error("[ensure-super-admin] Failed:", error); process.exitCode = 1; });