import { SignupWithEmailUseCase } from '@core/identity/application/use-cases/SignupWithEmailUseCase'; import { CreateAchievementUseCase } from '@core/identity/application/use-cases/achievement/CreateAchievementUseCase'; import type { Logger } from '@core/shared/application'; import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository'; import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository'; import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository'; import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository'; import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository'; import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository'; import type { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository'; import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository'; import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository'; import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository'; import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository'; import { createInMemoryRacingSeed } from './inmemory/InMemoryRacingSeed'; import { DRIVER_ACHIEVEMENTS, STEWARD_ACHIEVEMENTS, ADMIN_ACHIEVEMENTS, COMMUNITY_ACHIEVEMENTS, } from '@core/identity/domain/AchievementConstants'; export type InMemorySeedDependencies = { driverRepository: IDriverRepository; leagueRepository: ILeagueRepository; raceRepository: IRaceRepository; resultRepository: IResultRepository; standingRepository: IStandingRepository; leagueMembershipRepository: ILeagueMembershipRepository; raceRegistrationRepository: IRaceRegistrationRepository; teamRepository: ITeamRepository; teamMembershipRepository: ITeamMembershipRepository; feedRepository: IFeedRepository; socialGraphRepository: ISocialGraphRepository; }; export class EnsureInitialData { constructor( private readonly signupUseCase: SignupWithEmailUseCase, private readonly createAchievementUseCase: CreateAchievementUseCase, private readonly logger: Logger, private readonly seedDeps?: InMemorySeedDependencies, ) {} async execute(): Promise { // Ensure initial admin user exists try { await this.signupUseCase.execute({ email: 'admin@gridpilot.local', password: 'admin123', displayName: 'Admin', }); this.logger.info('[Bootstrap] Initial admin user created'); } catch (error) { if (error instanceof Error && error.message === 'An account with this email already exists') { // User already exists, nothing to do this.logger.info('[Bootstrap] Admin user already exists'); } else { // Re-throw other errors throw error; } } // Ensure initial achievements exist const allAchievements = [ ...DRIVER_ACHIEVEMENTS, ...STEWARD_ACHIEVEMENTS, ...ADMIN_ACHIEVEMENTS, ...COMMUNITY_ACHIEVEMENTS, ]; let createdCount = 0; let existingCount = 0; for (const achievementProps of allAchievements) { try { await this.createAchievementUseCase.execute(achievementProps); createdCount++; } catch { // If achievement already exists, that's fine existingCount++; } } this.logger.info(`[Bootstrap] Achievements: ${createdCount} created, ${existingCount} already exist`); await this.seedInMemoryRacingDataIfNeeded(); } private shouldSeedInMemory(): boolean { const configured = (process.env.GRIDPILOT_API_PERSISTENCE ?? '').toLowerCase(); if (configured) { return configured === 'inmemory'; } return process.env.DATABASE_URL === undefined; } private async seedInMemoryRacingDataIfNeeded(): Promise { if (!this.shouldSeedInMemory()) { return; } if (!this.seedDeps) { this.logger.info('[Bootstrap] In-memory racing seed skipped (missing dependencies)'); return; } const existingDrivers = await this.seedDeps.driverRepository.findAll(); if (existingDrivers.length > 0) { this.logger.info('[Bootstrap] In-memory racing seed skipped (drivers already exist)'); return; } const seed = createInMemoryRacingSeed(); for (const driver of seed.drivers) { try { await this.seedDeps.driverRepository.create(driver); } catch { // ignore duplicates } } for (const league of seed.leagues) { try { await this.seedDeps.leagueRepository.create(league); } catch { // ignore duplicates } } for (const race of seed.races) { try { await this.seedDeps.raceRepository.create(race); } catch { // ignore duplicates } } try { await this.seedDeps.resultRepository.createMany(seed.results); } catch { // ignore duplicates } for (const membership of seed.leagueMemberships) { try { await this.seedDeps.leagueMembershipRepository.saveMembership(membership); } catch { // ignore duplicates } } for (const team of seed.teams) { try { await this.seedDeps.teamRepository.create(team); } catch { // ignore duplicates } } for (const membership of seed.teamMemberships) { try { await this.seedDeps.teamMembershipRepository.saveMembership(membership); } catch { // ignore duplicates } } for (const registration of seed.raceRegistrations) { try { await this.seedDeps.raceRegistrationRepository.register(registration); } catch { // ignore duplicates } } try { await this.seedDeps.standingRepository.saveMany(seed.standings); } catch { // ignore duplicates } const seedableFeed = this.seedDeps.feedRepository as unknown as { seed?: (input: unknown) => void }; if (typeof seedableFeed.seed === 'function') { seedableFeed.seed({ drivers: seed.drivers, friendships: seed.friendships, feedEvents: seed.feedEvents, }); } const seedableSocial = this.seedDeps.socialGraphRepository as unknown as { seed?: (input: unknown) => void }; if (typeof seedableSocial.seed === 'function') { seedableSocial.seed({ drivers: seed.drivers, friendships: seed.friendships, feedEvents: seed.feedEvents, }); } this.logger.info( `[Bootstrap] Seeded in-memory racing data: drivers=${seed.drivers.length}, leagues=${seed.leagues.length}, races=${seed.races.length}`, ); } }