212 lines
6.7 KiB
TypeScript
212 lines
6.7 KiB
TypeScript
|
|
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<void> {
|
|
// 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<void> {
|
|
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}`,
|
|
);
|
|
}
|
|
} |