This commit is contained in:
2025-12-16 15:42:38 +01:00
parent 29410708c8
commit 362894d1a5
147 changed files with 780 additions and 375 deletions

View File

@@ -0,0 +1,26 @@
import { Controller, Get, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { RaceService } from './RaceService';
import { AllRacesPageViewModel, RaceStatsDto } from './dto/RaceDto';
@ApiTags('races')
@Controller('races')
export class RaceController {
constructor(private readonly raceService: RaceService) {}
@Get('all')
@ApiOperation({ summary: 'Get all races' })
@ApiResponse({ status: 200, description: 'List of all races', type: AllRacesPageViewModel })
async getAllRaces(): Promise<AllRacesPageViewModel> {
return this.raceService.getAllRaces();
}
@Get('total-races')
@ApiOperation({ summary: 'Get the total number of races' })
@ApiResponse({ status: 200, description: 'Total number of races', type: RaceStatsDto })
async getTotalRaces(): Promise<RaceStatsDto> {
return this.raceService.getTotalRaces();
}
// Add other Race endpoints here based on other presenters
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { RaceService } from './RaceService';
import { RaceController } from './RaceController';
import { RaceProviders } from './RaceProviders';
@Module({
controllers: [RaceController],
providers: RaceProviders,
exports: [RaceService],
})
export class RaceModule {}

View File

@@ -0,0 +1,52 @@
import { Provider } from '@nestjs/common';
import { RaceService } from './RaceService';
// Import core interfaces
import type { Logger } from '@core/shared/application/Logger';
import { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
import { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
// Import concrete in-memory implementations
import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
// Import use cases
import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase';
import { GetTotalRacesUseCase } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
import { ImportRaceResultsApiUseCase } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
// Define injection tokens
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
export const LOGGER_TOKEN = 'Logger';
export const RaceProviders: Provider[] = [
RaceService,
{
provide: RACE_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryRaceRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: LEAGUE_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryLeagueRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: LOGGER_TOKEN,
useClass: ConsoleLogger,
},
// Use cases
{
provide: GetAllRacesUseCase,
useFactory: (raceRepo: IRaceRepository, leagueRepo: ILeagueRepository) => new GetAllRacesUseCase(raceRepo, leagueRepo),
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN],
},
{
provide: GetTotalRacesUseCase,
useFactory: (raceRepo: IRaceRepository) => new GetTotalRacesUseCase(raceRepo),
inject: [RACE_REPOSITORY_TOKEN],
},
ImportRaceResultsApiUseCase,
];

View File

@@ -0,0 +1,50 @@
import { Injectable, Inject } from '@nestjs/common';
import { AllRacesPageViewModel, RaceStatsDto, ImportRaceResultsInput, ImportRaceResultsSummaryViewModel } from './dto/RaceDto';
// Core imports
import type { Logger } from '@core/shared/application/Logger';
// Use cases
import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase';
import { GetTotalRacesUseCase } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
import { ImportRaceResultsApiUseCase } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
// Presenters
import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter';
import { GetTotalRacesPresenter } from './presenters/GetTotalRacesPresenter';
import { ImportRaceResultsApiPresenter } from './presenters/ImportRaceResultsApiPresenter';
// Tokens
import { LOGGER_TOKEN } from './RaceProviders';
@Injectable()
export class RaceService {
constructor(
private readonly getAllRacesUseCase: GetAllRacesUseCase,
private readonly getTotalRacesUseCase: GetTotalRacesUseCase,
private readonly importRaceResultsApiUseCase: ImportRaceResultsApiUseCase,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
async getAllRaces(): Promise<AllRacesPageViewModel> {
this.logger.debug('[RaceService] Fetching all races.');
const presenter = new GetAllRacesPresenter();
await this.getAllRacesUseCase.execute({}, presenter);
return presenter.getViewModel()!;
}
async getTotalRaces(): Promise<RaceStatsDto> {
this.logger.debug('[RaceService] Fetching total races count.');
const presenter = new GetTotalRacesPresenter();
await this.getTotalRacesUseCase.execute({}, presenter);
return presenter.getViewModel()!;
}
async importRaceResults(input: ImportRaceResultsInput): Promise<ImportRaceResultsSummaryViewModel> {
this.logger.debug('Importing race results:', input);
const presenter = new ImportRaceResultsApiPresenter();
await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent }, presenter);
return presenter.getViewModel()!;
}
}

View File

@@ -0,0 +1,78 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsBoolean, IsNumber } from 'class-validator';
export class RaceViewModel {
@ApiProperty()
id: string; // Assuming a race has an ID
@ApiProperty()
name: string; // Assuming a race has a name
@ApiProperty()
date: string; // Assuming a race has a date
@ApiProperty({ nullable: true })
leagueName?: string; // Assuming a race might belong to a league
// Add more race-related properties as needed based on the DTO from the application layer
}
export class AllRacesPageViewModel {
@ApiProperty({ type: [RaceViewModel] })
races: RaceViewModel[];
@ApiProperty()
totalCount: number;
}
export class RaceStatsDto {
@ApiProperty()
totalRaces: number;
}
export class ImportRaceResultsInput {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
resultsFileContent: string;
}
export class ImportRaceResultsSummaryViewModel {
@ApiProperty()
@IsBoolean()
success: boolean;
@ApiProperty()
@IsString()
raceId: string;
@ApiProperty()
@IsNumber()
driversProcessed: number;
@ApiProperty()
@IsNumber()
resultsRecorded: number;
@ApiProperty({ type: [String], required: false })
errors?: string[];
}
export class RaceDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
date: string;
}

View File

@@ -0,0 +1,17 @@
import { IGetAllRacesPresenter, GetAllRacesResultDTO, AllRacesPageViewModel } from '@core/racing/application/presenters/IGetAllRacesPresenter';
export class GetAllRacesPresenter implements IGetAllRacesPresenter {
private result: AllRacesPageViewModel | null = null;
reset() {
this.result = null;
}
present(dto: GetAllRacesResultDTO) {
this.result = dto;
}
getViewModel(): AllRacesPageViewModel | null {
return this.result;
}
}

View File

@@ -0,0 +1,20 @@
import { IGetTotalRacesPresenter, GetTotalRacesResultDTO } from '@core/racing/application/presenters/IGetTotalRacesPresenter';
import { RaceStatsDto } from '../dto/RaceDto';
export class GetTotalRacesPresenter implements IGetTotalRacesPresenter {
private result: RaceStatsDto | null = null;
reset() {
this.result = null;
}
present(dto: GetTotalRacesResultDTO) {
this.result = {
totalRaces: dto.totalRaces,
};
}
getViewModel(): RaceStatsDto | null {
return this.result;
}
}

View File

@@ -0,0 +1,17 @@
import { IImportRaceResultsApiPresenter, ImportRaceResultsApiResultDTO, ImportRaceResultsSummaryViewModel } from '@core/racing/application/presenters/IImportRaceResultsApiPresenter';
export class ImportRaceResultsApiPresenter implements IImportRaceResultsApiPresenter {
private result: ImportRaceResultsSummaryViewModel | null = null;
reset() {
this.result = null;
}
present(dto: ImportRaceResultsApiResultDTO) {
this.result = dto;
}
getViewModel(): ImportRaceResultsSummaryViewModel | null {
return this.result;
}
}