import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as argon2 from 'argon2'; import { PrismaService } from '../prisma/prisma.service'; /** * Creates the initial Super-Admin account from Docker ENV variables on first boot. * Per D-05, D-07, D-13. */ @Injectable() export class AdminSeedService implements OnApplicationBootstrap { private readonly logger = new Logger(AdminSeedService.name); constructor( private prisma: PrismaService, private configService: ConfigService, ) {} async onApplicationBootstrap() { const username = this.configService.get('TESSERA_ADMIN_USER'); const email = this.configService.get('TESSERA_ADMIN_EMAIL'); const password = this.configService.get('TESSERA_ADMIN_PASSWORD'); const forceChange = this.configService.get('TESSERA_FORCE_CHANGE') === 'true'; if (!username || !email || !password) { this.logger.log( 'Admin seed skipped: TESSERA_ADMIN_USER, TESSERA_ADMIN_EMAIL, or TESSERA_ADMIN_PASSWORD not set', ); return; } // Check if admin already exists const exists = await this.prisma.user.findUnique({ where: { username }, }); if (exists) { this.logger.log(`Admin user "${username}" already exists, skipping seed`); return; } // Upsert default tenant const tenant = await this.prisma.tenant.upsert({ where: { slug: 'default' }, update: {}, create: { name: 'Default', slug: 'default' }, }); // Create Super-Admin user const passwordHash = await argon2.hash(password); await this.prisma.user.create({ data: { username, email, passwordHash, role: 'SUPER_ADMIN', tenantId: tenant.id, mustChangePassword: forceChange, isActive: true, }, }); this.logger.log( `Admin user "${username}" seeded as SUPER_ADMIN in tenant "${tenant.slug}"`, ); } }