fix(bootstrap): include missing bootstrap.service helper

The route handlers in 1a65e02 import hasAnySuperAdmin and
createInitialSuperAdmin from this file; was accidentally left
untracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 03:38:16 +02:00
parent 1a65e02885
commit b10bf9bf8e

View File

@@ -0,0 +1,82 @@
/**
* First-run bootstrap: lets the very first operator self-register as the
* super-admin on a fresh DB. Safe because the only "do it" path
* (`createInitialSuperAdmin`) refuses to run when any super-admin row
* already exists, so the window closes the moment the first account is
* created.
*/
import { eq } from 'drizzle-orm';
import { db } from '@/lib/db';
import { user, userProfiles } from '@/lib/db/schema';
import { auth } from '@/lib/auth';
import { ConflictError, ValidationError } from '@/lib/errors';
/** True when no user has `is_super_admin = true` in user_profiles. */
export async function hasAnySuperAdmin(): Promise<boolean> {
const row = await db
.select({ userId: userProfiles.userId })
.from(userProfiles)
.where(eq(userProfiles.isSuperAdmin, true))
.limit(1);
return row.length > 0;
}
export interface BootstrapInput {
name: string;
email: string;
password: string;
}
/**
* Atomically: create the better-auth user, create the user_profiles row
* with isSuperAdmin=true. Refuses to run when a super-admin already
* exists — the only safe-by-design self-registration path.
*
* Returns the new user's id on success.
*/
export async function createInitialSuperAdmin(input: BootstrapInput): Promise<string> {
if (input.password.length < 9) {
throw new ValidationError('Password must be at least 9 characters');
}
if (!input.email.includes('@')) {
throw new ValidationError('A valid email is required');
}
if (!input.name.trim()) {
throw new ValidationError('Name is required');
}
// Re-check inside the critical path so two concurrent first-run
// submissions can't both win — the first to insert the profile row
// closes the window for everyone else.
if (await hasAnySuperAdmin()) {
throw new ConflictError('A super-administrator account already exists');
}
const email = input.email.toLowerCase().trim();
const existing = await db.query.user.findFirst({ where: eq(user.email, email) });
if (existing) {
// Either someone signed up via a different flow first, or we're
// racing a portal-activation. Refuse rather than silently re-purpose.
throw new ConflictError('A user with this email already exists');
}
const authResult = await auth.api.signUpEmail({
body: {
email,
password: input.password,
name: input.name.trim(),
},
});
const newUserId = authResult.user.id;
await db.insert(userProfiles).values({
userId: newUserId,
displayName: input.name.trim(),
isSuperAdmin: true,
});
return newUserId;
}