import 'dotenv/config'; import * as bcrypt from 'bcryptjs'; import * as crypto from 'node:crypto'; import { PrismaPg } from '@prisma/adapter-pg'; import { Prisma, PrismaClient } from '../generated/prisma'; const BCRYPT_SALT_ROUNDS = 12; const DEFAULT_ADMIN_EMAIL = 'admin@apicore.local'; // No fixed fallback password: a fixed literal shipped in source would be a // well-known, guessable credential for any deployment that forgets to set // SEED_ADMIN_PASSWORD. A random one is generated instead and printed once // below, same as an operator-supplied one would be. function generateRandomPassword(): string { return crypto.randomBytes(18).toString('base64url'); } // Only printed the moment the account is actually created (first deploy on // an empty database), never on later restarts: by then the real admin may // have changed the password via /profile, so repeating it would just be // stale and would leak credentials into logs indefinitely for no reason. function printLoginBanner(email: string, password: string): void { const url = process.env.FRONTEND_ORIGIN ?? 'http://localhost:3000'; const line = '='.repeat(60); console.log(line); console.log('Seed: Admin-Zugang eingerichtet.'); console.log(` URL: ${url}`); console.log(` Benutzer: ${email}`); console.log(` Passwort: ${password}`); console.log(line); } async function main(): Promise { const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }), }); const email = process.env.SEED_ADMIN_EMAIL ?? DEFAULT_ADMIN_EMAIL; const password = process.env.SEED_ADMIN_PASSWORD ?? generateRandomPassword(); const existing = await prisma.user.findUnique({ where: { email } }); if (existing) { console.log(`Seed: admin user ${email} already exists, skipping.`); await prisma.$disconnect(); return; } const passwordHash = await bcrypt.hash(password, BCRYPT_SALT_ROUNDS); const adminRole = await prisma.role.findUniqueOrThrow({ where: { key: 'ADMIN' }, }); try { await prisma.user.create({ data: { email, passwordHash, name: 'Administrator', roleId: adminRole.id, }, }); printLoginBanner(email, password); } catch (error: unknown) { // P2002 = unique constraint violation on email: another replica's // startup won this race between the existence check above and this // create() call. The user now exists either way, so this is a no-op, // not a failure - container startup must not crash on it. if ( error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002' ) { console.log( `Seed: admin user ${email} was created concurrently, skipping.`, ); } else { throw error; } } await prisma.$disconnect(); } main().catch((error: unknown) => { console.error(error); process.exit(1); });