import type { Logger } from '@core/shared/application'; import type { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData'; import { SeedRacingData, type RacingSeedDependencies } from '../../../../../adapters/bootstrap/SeedRacingData'; import { Inject, Module, OnModuleInit } from '@nestjs/common'; import { getApiPersistence, getEnableBootstrap, getForceReseed } from '../../env'; import { RacingPersistenceModule } from '../../persistence/racing/RacingPersistenceModule'; import { SocialPersistenceModule } from '../../persistence/social/SocialPersistenceModule'; import { AchievementPersistenceModule } from '../../persistence/achievement/AchievementPersistenceModule'; import { IdentityPersistenceModule } from '../../persistence/identity/IdentityPersistenceModule'; import { BootstrapProviders, ENSURE_INITIAL_DATA_TOKEN } from './BootstrapProviders'; @Module({ imports: [RacingPersistenceModule, SocialPersistenceModule, AchievementPersistenceModule, IdentityPersistenceModule], providers: BootstrapProviders, }) export class BootstrapModule implements OnModuleInit { constructor( @Inject(ENSURE_INITIAL_DATA_TOKEN) private readonly ensureInitialData: EnsureInitialData, @Inject('Logger') private readonly logger: Logger, @Inject('RacingSeedDependencies') private readonly seedDeps: RacingSeedDependencies, ) {} async onModuleInit() { console.log('[Bootstrap] Initializing application data...'); try { if (!getEnableBootstrap()) { this.logger.info('[Bootstrap] Bootstrap disabled via GRIDPILOT_API_BOOTSTRAP; skipping initialization'); return; } await this.ensureInitialData.execute(); if (await this.shouldSeedRacingData()) { await new SeedRacingData(this.logger, this.seedDeps).execute(); } console.log('[Bootstrap] Application data initialized successfully'); } catch (error) { console.error('[Bootstrap] Failed to initialize application data:', error); throw error; } } private async shouldSeedRacingData(): Promise { const persistence = getApiPersistence(); if (persistence === 'inmemory') return true; if (persistence !== 'postgres') return false; if (process.env.NODE_ENV === 'production') return false; // Check for force reseed flag const forceReseed = getForceReseed(); if (forceReseed) { this.logger.info('[Bootstrap] Force reseed enabled via GRIDPILOT_API_FORCE_RESEED'); return true; } // Check if database is empty const isEmpty = await this.isRacingDatabaseEmpty(); if (!isEmpty) { // Database has data, check if it needs reseeding return await this.needsReseed(); } return true; } private async isRacingDatabaseEmpty(): Promise { const count = await this.seedDeps.leagueRepository.countAll?.(); if (typeof count === 'number') return count === 0; const leagues = await this.seedDeps.leagueRepository.findAll(); return leagues.length === 0; } private async needsReseed(): Promise { // Check if driver count is less than expected (150) // This indicates old seed data that needs updating try { const drivers = await this.seedDeps.driverRepository.findAll(); const driverCount = drivers.length; // If we have fewer than 150 drivers, we need to reseed if (driverCount < 150) { this.logger.info(`[Bootstrap] Found ${driverCount} drivers (expected 150), triggering reseed`); return true; } return false; } catch (error) { this.logger.warn('[Bootstrap] Error checking driver count for reseed:', error); return false; } } }