seed data
This commit is contained in:
@@ -2,7 +2,7 @@ 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 } from '../../env';
|
||||
import { getApiPersistence, getEnableBootstrap, getForceReseed } from '../../env';
|
||||
import { RacingPersistenceModule } from '../../persistence/racing/RacingPersistenceModule';
|
||||
import { SocialPersistenceModule } from '../../persistence/social/SocialPersistenceModule';
|
||||
import { AchievementPersistenceModule } from '../../persistence/achievement/AchievementPersistenceModule';
|
||||
@@ -48,7 +48,21 @@ export class BootstrapModule implements OnModuleInit {
|
||||
if (persistence !== 'postgres') return false;
|
||||
if (process.env.NODE_ENV === 'production') return false;
|
||||
|
||||
return this.isRacingDatabaseEmpty();
|
||||
// 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<boolean> {
|
||||
@@ -58,4 +72,24 @@ export class BootstrapModule implements OnModuleInit {
|
||||
const leagues = await this.seedDeps.leagueRepository.findAll();
|
||||
return leagues.length === 0;
|
||||
}
|
||||
|
||||
private async needsReseed(): Promise<boolean> {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,19 @@ export class GetDriverOutputDTO {
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
rating?: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
experienceLevel?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
wins?: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
podiums?: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
totalRaces?: number;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { Driver } from '@core/racing/domain/entities/Driver';
|
||||
import type { GetDriverOutputDTO } from '../dtos/GetDriverOutputDTO';
|
||||
import { DriverStatsStore } from '@adapters/racing/services/DriverStatsStore';
|
||||
|
||||
export class DriverPresenter {
|
||||
private responseModel: GetDriverOutputDTO | null = null;
|
||||
@@ -18,6 +19,10 @@ export class DriverPresenter {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get stats from the store
|
||||
const statsStore = DriverStatsStore.getInstance();
|
||||
const stats = statsStore.getDriverStats(driver.id);
|
||||
|
||||
this.responseModel = {
|
||||
id: driver.id,
|
||||
iracingId: driver.iracingId.toString(),
|
||||
@@ -25,10 +30,25 @@ export class DriverPresenter {
|
||||
country: driver.country.toString(),
|
||||
joinedAt: driver.joinedAt.toDate().toISOString(),
|
||||
...(driver.bio ? { bio: driver.bio.toString() } : {}),
|
||||
// Add stats fields
|
||||
...(stats ? {
|
||||
rating: stats.rating,
|
||||
wins: stats.wins,
|
||||
podiums: stats.podiums,
|
||||
totalRaces: stats.totalRaces,
|
||||
experienceLevel: this.getExperienceLevel(stats.rating),
|
||||
} : {}),
|
||||
};
|
||||
}
|
||||
|
||||
getResponseModel(): GetDriverOutputDTO | null {
|
||||
return this.responseModel;
|
||||
}
|
||||
|
||||
private getExperienceLevel(rating: number): string {
|
||||
if (rating >= 1700) return 'veteran';
|
||||
if (rating >= 1300) return 'advanced';
|
||||
if (rating >= 1000) return 'intermediate';
|
||||
return 'beginner';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,5 +27,20 @@ export class TeamListItemDTO {
|
||||
|
||||
@ApiProperty({ type: [String], required: false })
|
||||
languages?: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
totalWins?: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
totalRaces?: number;
|
||||
|
||||
@ApiProperty({ required: false, enum: ['beginner', 'intermediate', 'advanced', 'pro'] })
|
||||
performanceLevel?: 'beginner' | 'intermediate' | 'advanced' | 'pro';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
logoUrl?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
rating?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type { GetAllTeamsResult } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { GetAllTeamsOutputDTO } from '../dtos/GetAllTeamsOutputDTO';
|
||||
import { TeamStatsStore } from '@adapters/racing/services/TeamStatsStore';
|
||||
|
||||
export class AllTeamsPresenter implements UseCaseOutputPort<GetAllTeamsResult> {
|
||||
private model: GetAllTeamsOutputDTO | null = null;
|
||||
@@ -10,16 +11,41 @@ export class AllTeamsPresenter implements UseCaseOutputPort<GetAllTeamsResult> {
|
||||
}
|
||||
|
||||
present(result: GetAllTeamsResult): void {
|
||||
const statsStore = TeamStatsStore.getInstance();
|
||||
|
||||
this.model = {
|
||||
teams: result.teams.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name.toString(),
|
||||
tag: team.tag.toString(),
|
||||
description: team.description?.toString() || '',
|
||||
memberCount: team.memberCount,
|
||||
leagues: team.leagues?.map(l => l.toString()) || [],
|
||||
// Note: specialization, region, languages not available in output
|
||||
})),
|
||||
teams: result.teams.map(team => {
|
||||
const stats = statsStore.getTeamStats(team.id.toString());
|
||||
|
||||
return {
|
||||
id: team.id,
|
||||
name: team.name.toString(),
|
||||
tag: team.tag.toString(),
|
||||
description: team.description?.toString() || '',
|
||||
memberCount: team.memberCount,
|
||||
leagues: team.leagues?.map(l => l.toString()) || [],
|
||||
// Add stats fields
|
||||
...(stats ? {
|
||||
totalWins: stats.totalWins,
|
||||
totalRaces: stats.totalRaces,
|
||||
performanceLevel: stats.performanceLevel,
|
||||
specialization: stats.specialization,
|
||||
region: stats.region,
|
||||
languages: stats.languages,
|
||||
logoUrl: stats.logoUrl,
|
||||
rating: stats.rating,
|
||||
} : {
|
||||
totalWins: 0,
|
||||
totalRaces: 0,
|
||||
performanceLevel: 'beginner',
|
||||
specialization: 'mixed',
|
||||
region: '',
|
||||
languages: [],
|
||||
logoUrl: '',
|
||||
rating: 0,
|
||||
}),
|
||||
};
|
||||
}),
|
||||
totalCount: result.totalCount ?? result.teams.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -57,6 +57,21 @@ export function getEnableBootstrap(): boolean {
|
||||
return isTruthyEnv(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reseeding of racing data in development mode.
|
||||
*
|
||||
* `GRIDPILOT_API_FORCE_RESEED` uses "truthy" parsing:
|
||||
* - false when unset / "0" / "false"
|
||||
* - true otherwise
|
||||
*
|
||||
* Only works in non-production environments.
|
||||
*/
|
||||
export function getForceReseed(): boolean {
|
||||
const raw = process.env.GRIDPILOT_API_FORCE_RESEED;
|
||||
if (raw === undefined) return false;
|
||||
return isTruthyEnv(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* When set, the API will generate `openapi.json` and optionally reduce logging noise.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user