This commit is contained in:
2025-12-17 14:04:11 +01:00
parent 1ea9c9649f
commit daa4bb6576
238 changed files with 4263 additions and 1752 deletions

View File

@@ -1,7 +1,25 @@
import { Controller, Get, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { Controller, Get, Post, Body, HttpCode, HttpStatus, Param, Query } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation, ApiParam, ApiQuery } from '@nestjs/swagger';
import { RaceService } from './RaceService';
import { AllRacesPageViewModel, RaceStatsDto } from './dto/RaceDto';
import { AllRacesPageDTO } from './dtos/AllRacesPageDTO';
import { RaceStatsDTO } from './dtos/RaceStatsDTO';
import { RaceDetailDTO } from './dtos/RaceDetailDTO';
import { RacesPageDataDTO } from './dtos/RacesPageDataDTO';
import { RaceResultsDetailDTO } from './dtos/RaceResultsDetailDTO';
import { RaceWithSOFDTO } from './dtos/RaceWithSOFDTO';
import { RaceProtestsDTO } from './dtos/RaceProtestsDTO';
import { RacePenaltiesDTO } from './dtos/RacePenaltiesDTO';
import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO';
import { RegisterForRaceParamsDTO } from './dtos/RegisterForRaceParamsDTO';
import { WithdrawFromRaceParamsDTO } from './dtos/WithdrawFromRaceParamsDTO';
import { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO';
import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO';
import { ImportRaceResultsSummaryDTO } from './dtos/ImportRaceResultsSummaryDTO';
import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO';
import { FileProtestCommandDTO } from './dtos/FileProtestCommandDTO';
import { QuickPenaltyCommandDTO } from './dtos/QuickPenaltyCommandDTO';
import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO';
import { RequestProtestDefenseCommandDTO } from './dtos/RequestProtestDefenseCommandDTO';
@ApiTags('races')
@Controller('races')
@@ -10,17 +28,167 @@ export class RaceController {
@Get('all')
@ApiOperation({ summary: 'Get all races' })
@ApiResponse({ status: 200, description: 'List of all races', type: AllRacesPageViewModel })
async getAllRaces(): Promise<AllRacesPageViewModel> {
@ApiResponse({ status: 200, description: 'List of all races', type: AllRacesPageDTO })
async getAllRaces(): Promise<AllRacesPageDTO> {
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> {
@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
@Get('page-data')
@ApiOperation({ summary: 'Get races page data' })
@ApiResponse({ status: 200, description: 'Races page data', type: RacesPageDataDTO })
async getRacesPageData(): Promise<RacesPageDataDTO> {
return this.raceService.getRacesPageData();
}
@Get('all/page-data')
@ApiOperation({ summary: 'Get all races page data' })
@ApiResponse({ status: 200, description: 'All races page data', type: RacesPageDataDTO })
async getAllRacesPageData(): Promise<RacesPageDataDTO> {
return this.raceService.getAllRacesPageData();
}
@Get(':raceId')
@ApiOperation({ summary: 'Get race detail' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiQuery({ name: 'driverId', description: 'Driver ID' })
@ApiResponse({ status: 200, description: 'Race detail', type: RaceDetailDTO })
async getRaceDetail(
@Param('raceId') raceId: string,
@Query('driverId') driverId: string,
): Promise<RaceDetailDTO> {
return this.raceService.getRaceDetail({ raceId, driverId });
}
@Get(':raceId/results')
@ApiOperation({ summary: 'Get race results detail' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Race results detail', type: RaceResultsDetailDTO })
async getRaceResultsDetail(@Param('raceId') raceId: string): Promise<RaceResultsDetailDTO> {
return this.raceService.getRaceResultsDetail(raceId);
}
@Get(':raceId/sof')
@ApiOperation({ summary: 'Get race with strength of field' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Race with SOF', type: RaceWithSOFDTO })
async getRaceWithSOF(@Param('raceId') raceId: string): Promise<RaceWithSOFDTO> {
return this.raceService.getRaceWithSOF(raceId);
}
@Get(':raceId/protests')
@ApiOperation({ summary: 'Get race protests' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Race protests', type: RaceProtestsDTO })
async getRaceProtests(@Param('raceId') raceId: string): Promise<RaceProtestsDTO> {
return this.raceService.getRaceProtests(raceId);
}
@Get(':raceId/penalties')
@ApiOperation({ summary: 'Get race penalties' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Race penalties', type: RacePenaltiesDTO })
async getRacePenalties(@Param('raceId') raceId: string): Promise<RacePenaltiesDTO> {
return this.raceService.getRacePenalties(raceId);
}
@Post(':raceId/register')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Register for race' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Successfully registered for race' })
async registerForRace(
@Param('raceId') raceId: string,
@Body() body: Omit<RegisterForRaceParamsDTO, 'raceId'>,
): Promise<void> {
return this.raceService.registerForRace({ raceId, ...body });
}
@Post(':raceId/withdraw')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Withdraw from race' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Successfully withdrew from race' })
async withdrawFromRace(
@Param('raceId') raceId: string,
@Body() body: Omit<WithdrawFromRaceParamsDTO, 'raceId'>,
): Promise<void> {
return this.raceService.withdrawFromRace({ raceId, ...body });
}
@Post(':raceId/cancel')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Cancel race' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Successfully cancelled race' })
async cancelRace(@Param('raceId') raceId: string): Promise<void> {
return this.raceService.cancelRace({ raceId });
}
@Post(':raceId/complete')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Complete race' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Successfully completed race' })
async completeRace(@Param('raceId') raceId: string): Promise<void> {
return this.raceService.completeRace({ raceId });
}
@Post(':raceId/import-results')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Import race results' })
@ApiParam({ name: 'raceId', description: 'Race ID' })
@ApiResponse({ status: 200, description: 'Successfully imported race results', type: ImportRaceResultsSummaryDTO })
async importRaceResults(
@Param('raceId') raceId: string,
@Body() body: Omit<ImportRaceResultsDTO, 'raceId'>,
): Promise<ImportRaceResultsSummaryDTO> {
return this.raceService.importRaceResults({ raceId, ...body });
}
@Get('dashboard/overview')
@ApiOperation({ summary: 'Get dashboard overview' })
@ApiQuery({ name: 'driverId', description: 'Driver ID' })
@ApiResponse({ status: 200, description: 'Dashboard overview', type: DashboardOverviewDTO })
async getDashboardOverview(@Query('driverId') driverId: string): Promise<DashboardOverviewDTO> {
return this.raceService.getDashboardOverview(driverId);
}
@Post('protests/file')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'File a protest' })
@ApiResponse({ status: 200, description: 'Protest filed successfully' })
async fileProtest(@Body() body: FileProtestCommandDTO): Promise<any> {
return this.raceService.fileProtest(body);
}
@Post('penalties/quick')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Apply a quick penalty' })
@ApiResponse({ status: 200, description: 'Penalty applied successfully' })
async applyQuickPenalty(@Body() body: QuickPenaltyCommandDTO): Promise<any> {
return this.raceService.applyQuickPenalty(body);
}
@Post('penalties/apply')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Apply a penalty' })
@ApiResponse({ status: 200, description: 'Penalty applied successfully' })
async applyPenalty(@Body() body: ApplyPenaltyCommandDTO): Promise<any> {
return this.raceService.applyPenalty(body);
}
@Post('protests/defense/request')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Request protest defense' })
@ApiResponse({ status: 200, description: 'Defense requested successfully' })
async requestProtestDefense(@Body() body: RequestProtestDefenseCommandDTO): Promise<any> {
return this.raceService.requestProtestDefense(body);
}
}

View File

@@ -5,20 +5,61 @@ import { RaceService } from './RaceService';
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 { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
import { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
import { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
import { IPenaltyRepository } from '@core/racing/domain/repositories/IPenaltyRepository';
import { IProtestRepository } from '@core/racing/domain/repositories/IProtestRepository';
import { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
import { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
// Import concrete in-memory implementations
import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { InMemoryRaceRegistrationRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRegistrationRepository';
import { InMemoryResultRepository } from '@adapters/racing/persistence/inmemory/InMemoryResultRepository';
import { InMemoryLeagueMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository';
import { InMemoryPenaltyRepository } from '@adapters/racing/persistence/inmemory/InMemoryPenaltyRepository';
import { InMemoryProtestRepository } from '@adapters/racing/persistence/inmemory/InMemoryProtestRepository';
import { InMemoryDriverRatingProvider } from '@adapters/racing/ports/InMemoryDriverRatingProvider';
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
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';
import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
import { GetRacesPageDataUseCase } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
import { GetAllRacesPageDataUseCase } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
import { GetRaceWithSOFUseCase } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
import { GetRacePenaltiesUseCase } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
import { RegisterForRaceUseCase } from '@core/racing/application/use-cases/RegisterForRaceUseCase';
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
// Define injection tokens
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
export const PENALTY_REPOSITORY_TOKEN = 'IPenaltyRepository';
export const PROTEST_REPOSITORY_TOKEN = 'IProtestRepository';
export const DRIVER_RATING_PROVIDER_TOKEN = 'DriverRatingProvider';
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
export const LOGGER_TOKEN = 'Logger';
export const RaceProviders: Provider[] = [
@@ -33,6 +74,46 @@ export const RaceProviders: Provider[] = [
useFactory: (logger: Logger) => new InMemoryLeagueRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: DRIVER_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryDriverRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: RACE_REGISTRATION_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryRaceRegistrationRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: RESULT_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryResultRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryLeagueMembershipRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: PENALTY_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryPenaltyRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: PROTEST_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryProtestRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: DRIVER_RATING_PROVIDER_TOKEN,
useFactory: (logger: Logger) => new InMemoryDriverRatingProvider(logger),
inject: [LOGGER_TOKEN],
},
{
provide: IMAGE_SERVICE_TOKEN,
useFactory: (logger: Logger) => new InMemoryImageServiceAdapter(logger),
inject: [LOGGER_TOKEN],
},
{
provide: LOGGER_TOKEN,
useClass: ConsoleLogger,
@@ -48,5 +129,114 @@ export const RaceProviders: Provider[] = [
useFactory: (raceRepo: IRaceRepository) => new GetTotalRacesUseCase(raceRepo),
inject: [RACE_REPOSITORY_TOKEN],
},
{
provide: GetRaceDetailUseCase,
useFactory: (
raceRepo: IRaceRepository,
leagueRepo: ILeagueRepository,
driverRepo: IDriverRepository,
raceRegRepo: IRaceRegistrationRepository,
resultRepo: IResultRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
driverRatingProvider: DriverRatingProvider,
imageService: IImageServicePort,
) => new GetRaceDetailUseCase(
raceRepo,
leagueRepo,
driverRepo,
raceRegRepo,
resultRepo,
leagueMembershipRepo,
driverRatingProvider,
imageService,
),
inject: [
RACE_REPOSITORY_TOKEN,
LEAGUE_REPOSITORY_TOKEN,
DRIVER_REPOSITORY_TOKEN,
RACE_REGISTRATION_REPOSITORY_TOKEN,
RESULT_REPOSITORY_TOKEN,
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
DRIVER_RATING_PROVIDER_TOKEN,
IMAGE_SERVICE_TOKEN,
],
},
{
provide: GetRacesPageDataUseCase,
useFactory: (raceRepo: IRaceRepository, leagueRepo: ILeagueRepository) => new GetRacesPageDataUseCase(raceRepo, leagueRepo),
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN],
},
{
provide: GetAllRacesPageDataUseCase,
useFactory: (raceRepo: IRaceRepository, leagueRepo: ILeagueRepository) => new GetAllRacesPageDataUseCase(raceRepo, leagueRepo),
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN],
},
{
provide: GetRaceResultsDetailUseCase,
useFactory: (resultRepo: IResultRepository, driverRepo: IDriverRepository, imageService: IImageServicePort) =>
new GetRaceResultsDetailUseCase(resultRepo, driverRepo, imageService),
inject: [RESULT_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN],
},
{
provide: GetRaceWithSOFUseCase,
useFactory: (raceRepo: IRaceRepository) => new GetRaceWithSOFUseCase(raceRepo),
inject: [RACE_REPOSITORY_TOKEN],
},
{
provide: GetRaceProtestsUseCase,
useFactory: (protestRepo: IProtestRepository, driverRepo: IDriverRepository) => new GetRaceProtestsUseCase(protestRepo, driverRepo),
inject: [PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN],
},
{
provide: GetRacePenaltiesUseCase,
useFactory: (penaltyRepo: IPenaltyRepository, driverRepo: IDriverRepository) => new GetRacePenaltiesUseCase(penaltyRepo, driverRepo),
inject: [PENALTY_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN],
},
{
provide: RegisterForRaceUseCase,
useFactory: (raceRegRepo: IRaceRegistrationRepository, leagueMembershipRepo: ILeagueMembershipRepository, logger: Logger) =>
new RegisterForRaceUseCase(raceRegRepo, leagueMembershipRepo, logger),
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: WithdrawFromRaceUseCase,
useFactory: (raceRegRepo: IRaceRegistrationRepository, logger: Logger) => new WithdrawFromRaceUseCase(raceRegRepo, logger),
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: CancelRaceUseCase,
useFactory: (raceRepo: IRaceRepository, logger: Logger) => new CancelRaceUseCase(raceRepo, logger),
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: CompleteRaceUseCase,
useFactory: (raceRepo: IRaceRepository, logger: Logger) => new CompleteRaceUseCase(raceRepo, logger),
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
ImportRaceResultsApiUseCase,
ImportRaceResultsUseCase,
{
provide: FileProtestUseCase,
useFactory: (protestRepo: IProtestRepository, raceRepo: IRaceRepository, driverRepo: IDriverRepository, logger: Logger) =>
new FileProtestUseCase(protestRepo, raceRepo, driverRepo, logger),
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: QuickPenaltyUseCase,
useFactory: (penaltyRepo: IPenaltyRepository, raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository, logger: Logger) =>
new QuickPenaltyUseCase(penaltyRepo, raceRepo, leagueMembershipRepo, logger),
inject: [PENALTY_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: ApplyPenaltyUseCase,
useFactory: (penaltyRepo: IPenaltyRepository, protestRepo: IProtestRepository, raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository, logger: Logger) =>
new ApplyPenaltyUseCase(penaltyRepo, protestRepo, raceRepo, leagueMembershipRepo, logger),
inject: [PENALTY_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: RequestProtestDefenseUseCase,
useFactory: (protestRepo: IProtestRepository, raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository) =>
new RequestProtestDefenseUseCase(protestRepo, raceRepo, leagueMembershipRepo),
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
},
];

View File

@@ -1,5 +1,20 @@
import { Injectable, Inject } from '@nestjs/common';
import { AllRacesPageViewModel, RaceStatsDto, ImportRaceResultsInput, ImportRaceResultsSummaryViewModel } from './dto/RaceDto';
import {
AllRacesPageViewModel,
RaceStatsDto,
ImportRaceResultsInput,
ImportRaceResultsSummaryViewModel,
RaceDetailViewModelDto,
RacesPageDataViewModelDto,
RaceResultsDetailViewModelDto,
RaceWithSOFViewModelDto,
RaceProtestsViewModelDto,
RacePenaltiesViewModelDto,
GetRaceDetailParamsDto,
RegisterForRaceParamsDto,
WithdrawFromRaceParamsDto,
RaceActionParamsDto,
} from './dtos/RaceDTO';
// Core imports
import type { Logger } from '@core/shared/application/Logger';
@@ -8,6 +23,23 @@ import type { Logger } from '@core/shared/application/Logger';
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';
import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
import { GetRacesPageDataUseCase } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
import { GetAllRacesPageDataUseCase } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
import { GetRaceWithSOFUseCase } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
import { GetRacePenaltiesUseCase } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
import { RegisterForRaceUseCase } from '@core/racing/application/use-cases/RegisterForRaceUseCase';
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
// Presenters
import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter';
@@ -23,6 +55,23 @@ export class RaceService {
private readonly getAllRacesUseCase: GetAllRacesUseCase,
private readonly getTotalRacesUseCase: GetTotalRacesUseCase,
private readonly importRaceResultsApiUseCase: ImportRaceResultsApiUseCase,
private readonly getRaceDetailUseCase: GetRaceDetailUseCase,
private readonly getRacesPageDataUseCase: GetRacesPageDataUseCase,
private readonly getAllRacesPageDataUseCase: GetAllRacesPageDataUseCase,
private readonly getRaceResultsDetailUseCase: GetRaceResultsDetailUseCase,
private readonly getRaceWithSOFUseCase: GetRaceWithSOFUseCase,
private readonly getRaceProtestsUseCase: GetRaceProtestsUseCase,
private readonly getRacePenaltiesUseCase: GetRacePenaltiesUseCase,
private readonly registerForRaceUseCase: RegisterForRaceUseCase,
private readonly withdrawFromRaceUseCase: WithdrawFromRaceUseCase,
private readonly cancelRaceUseCase: CancelRaceUseCase,
private readonly completeRaceUseCase: CompleteRaceUseCase,
private readonly importRaceResultsUseCase: ImportRaceResultsUseCase,
private readonly dashboardOverviewUseCase: DashboardOverviewUseCase,
private readonly fileProtestUseCase: FileProtestUseCase,
private readonly quickPenaltyUseCase: QuickPenaltyUseCase,
private readonly applyPenaltyUseCase: ApplyPenaltyUseCase,
private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
@@ -47,4 +96,202 @@ export class RaceService {
await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent }, presenter);
return presenter.getViewModel()!;
}
async getRaceDetail(params: GetRaceDetailParamsDto): Promise<RaceDetailViewModelDto> {
this.logger.debug('[RaceService] Fetching race detail:', params);
const presenter = new RaceDetailPresenter();
const result = await this.getRaceDetailUseCase.execute(params);
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to get race detail');
}
return result.value;
}
async getRacesPageData(): Promise<RacesPageDataViewModelDto> {
this.logger.debug('[RaceService] Fetching races page data.');
const result = await this.getRacesPageDataUseCase.execute();
if (result.isErr()) {
throw new Error('Failed to get races page data');
}
return result.value;
}
async getAllRacesPageData(): Promise<RacesPageDataViewModelDto> {
this.logger.debug('[RaceService] Fetching all races page data.');
const result = await this.getAllRacesPageDataUseCase.execute();
if (result.isErr()) {
throw new Error('Failed to get all races page data');
}
return result.value;
}
async getRaceResultsDetail(raceId: string): Promise<RaceResultsDetailViewModelDto> {
this.logger.debug('[RaceService] Fetching race results detail:', { raceId });
const result = await this.getRaceResultsDetailUseCase.execute({ raceId });
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to get race results detail');
}
return result.value;
}
async getRaceWithSOF(raceId: string): Promise<RaceWithSOFViewModelDto> {
this.logger.debug('[RaceService] Fetching race with SOF:', { raceId });
const result = await this.getRaceWithSOFUseCase.execute({ raceId });
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to get race with SOF');
}
return result.value;
}
async getRaceProtests(raceId: string): Promise<RaceProtestsViewModelDto> {
this.logger.debug('[RaceService] Fetching race protests:', { raceId });
const result = await this.getRaceProtestsUseCase.execute({ raceId });
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to get race protests');
}
return result.value;
}
async getRacePenalties(raceId: string): Promise<RacePenaltiesViewModelDto> {
this.logger.debug('[RaceService] Fetching race penalties:', { raceId });
const result = await this.getRacePenaltiesUseCase.execute({ raceId });
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to get race penalties');
}
return result.value;
}
async registerForRace(params: RegisterForRaceParamsDto): Promise<void> {
this.logger.debug('[RaceService] Registering for race:', params);
const result = await this.registerForRaceUseCase.execute(params);
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to register for race');
}
}
async withdrawFromRace(params: WithdrawFromRaceParamsDto): Promise<void> {
this.logger.debug('[RaceService] Withdrawing from race:', params);
const result = await this.withdrawFromRaceUseCase.execute(params);
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to withdraw from race');
}
}
async cancelRace(params: RaceActionParamsDto): Promise<void> {
this.logger.debug('[RaceService] Cancelling race:', params);
const result = await this.cancelRaceUseCase.execute({ raceId: params.raceId });
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to cancel race');
}
}
async completeRace(params: RaceActionParamsDto): Promise<void> {
this.logger.debug('[RaceService] Completing race:', params);
const result = await this.completeRaceUseCase.execute({ raceId: params.raceId });
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to complete race');
}
}
async importRaceResultsAlt(params: { raceId: string; resultsFileContent: string }): Promise<void> {
this.logger.debug('[RaceService] Importing race results (alt):', params);
const result = await this.importRaceResultsUseCase.execute({
raceId: params.raceId,
resultsFileContent: params.resultsFileContent,
});
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to import race results');
}
}
async getDashboardOverview(driverId: string): Promise<any> {
this.logger.debug('[RaceService] Getting dashboard overview:', { driverId });
const result = await this.dashboardOverviewUseCase.execute({ driverId });
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to get dashboard overview');
}
return result.value;
}
async fileProtest(command: any): Promise<any> {
this.logger.debug('[RaceService] Filing protest:', command);
const result = await this.fileProtestUseCase.execute(command);
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to file protest');
}
return result.value;
}
async applyQuickPenalty(command: any): Promise<any> {
this.logger.debug('[RaceService] Applying quick penalty:', command);
const result = await this.quickPenaltyUseCase.execute(command);
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to apply quick penalty');
}
return result.value;
}
async applyPenalty(command: any): Promise<any> {
this.logger.debug('[RaceService] Applying penalty:', command);
const result = await this.applyPenaltyUseCase.execute(command);
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to apply penalty');
}
return result.value;
}
async requestProtestDefense(command: any): Promise<any> {
this.logger.debug('[RaceService] Requesting protest defense:', command);
const result = await this.requestProtestDefenseUseCase.execute(command);
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to request protest defense');
}
return result.value;
}
}

View File

@@ -1,78 +0,0 @@
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,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { RaceViewModel } from './RaceViewModel';
export class AllRacesPageDTO {
@ApiProperty({ type: [RaceViewModel] })
races!: RaceViewModel[];
@ApiProperty()
totalCount!: number;
}

View File

@@ -0,0 +1,65 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsOptional, IsNumber, IsEnum } from 'class-validator';
export class ApplyPenaltyCommandDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
driverId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
stewardId!: string;
@ApiProperty({
enum: [
'time_penalty',
'grid_penalty',
'points_deduction',
'disqualification',
'warning',
'license_points',
'probation',
'fine',
'race_ban',
],
})
@IsEnum([
'time_penalty',
'grid_penalty',
'points_deduction',
'disqualification',
'warning',
'license_points',
'probation',
'fine',
'race_ban',
])
type!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
value?: number;
@ApiProperty()
@IsString()
@IsNotEmpty()
reason!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
protestId?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
notes?: string;
}

View File

@@ -0,0 +1,47 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, IsOptional } from 'class-validator';
export class DashboardDriverSummaryDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
name!: string;
@ApiProperty()
@IsString()
country!: string;
@ApiProperty()
@IsString()
avatarUrl!: string;
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
rating?: number | null;
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
globalRank?: number | null;
@ApiProperty()
@IsNumber()
totalRaces!: number;
@ApiProperty()
@IsNumber()
wins!: number;
@ApiProperty()
@IsNumber()
podiums!: number;
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
consistency?: number | null;
}

View File

@@ -0,0 +1,53 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsOptional } from 'class-validator';
export type DashboardFeedItemType =
| 'friend-joined-league'
| 'friend-joined-team'
| 'friend-finished-race'
| 'friend-new-personal-best'
| 'new-race-scheduled'
| 'new-result-posted'
| 'league-highlight';
export class DashboardFeedItemSummaryDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty({
enum: [
'friend-joined-league',
'friend-joined-team',
'friend-finished-race',
'friend-new-personal-best',
'new-race-scheduled',
'new-result-posted',
'league-highlight',
],
})
type!: DashboardFeedItemType;
@ApiProperty()
@IsString()
headline!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
body?: string;
@ApiProperty()
@IsString()
timestamp!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
ctaLabel?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
ctaHref?: string;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
import { DashboardFeedItemSummaryDTO } from './DashboardFeedItemSummaryDTO';
export class DashboardFeedSummaryDTO {
@ApiProperty()
@IsNumber()
notificationCount!: number;
@ApiProperty({ type: [DashboardFeedItemSummaryDTO] })
items!: DashboardFeedItemSummaryDTO[];
}

View File

@@ -0,0 +1,20 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
export class DashboardFriendSummaryDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
name!: string;
@ApiProperty()
@IsString()
country!: string;
@ApiProperty()
@IsString()
avatarUrl!: string;
}

View File

@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber } from 'class-validator';
export class DashboardLeagueStandingSummaryDTO {
@ApiProperty()
@IsString()
leagueId!: string;
@ApiProperty()
@IsString()
leagueName!: string;
@ApiProperty()
@IsNumber()
position!: number;
@ApiProperty()
@IsNumber()
totalDrivers!: number;
@ApiProperty()
@IsNumber()
points!: number;
}

View File

@@ -0,0 +1,41 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsOptional } from 'class-validator';
import { DashboardDriverSummaryDTO } from './DashboardDriverSummaryDTO';
import { DashboardRaceSummaryDTO } from './DashboardRaceSummaryDTO';
import { DashboardRecentResultDTO } from './DashboardRecentResultDTO';
import { DashboardLeagueStandingSummaryDTO } from './DashboardLeagueStandingSummaryDTO';
import { DashboardFeedSummaryDTO } from './DashboardFeedSummaryDTO';
import { DashboardFriendSummaryDTO } from './DashboardFriendSummaryDTO';
export class DashboardOverviewDTO {
@ApiProperty({ nullable: true })
currentDriver!: DashboardDriverSummaryDTO | null;
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
myUpcomingRaces!: DashboardRaceSummaryDTO[];
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
otherUpcomingRaces!: DashboardRaceSummaryDTO[];
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
upcomingRaces!: DashboardRaceSummaryDTO[];
@ApiProperty()
@IsNumber()
activeLeaguesCount!: number;
@ApiProperty({ nullable: true })
nextRace!: DashboardRaceSummaryDTO | null;
@ApiProperty({ type: [DashboardRecentResultDTO] })
recentResults!: DashboardRecentResultDTO[];
@ApiProperty({ type: [DashboardLeagueStandingSummaryDTO] })
leagueStandingsSummaries!: DashboardLeagueStandingSummaryDTO[];
@ApiProperty()
feedSummary!: DashboardFeedSummaryDTO;
@ApiProperty({ type: [DashboardFriendSummaryDTO] })
friends!: DashboardFriendSummaryDTO[];
}

View File

@@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsBoolean } from 'class-validator';
export class DashboardRaceSummaryDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
leagueId!: string;
@ApiProperty()
@IsString()
leagueName!: string;
@ApiProperty()
@IsString()
track!: string;
@ApiProperty()
@IsString()
car!: string;
@ApiProperty()
@IsString()
scheduledAt!: string;
@ApiProperty({ enum: ['scheduled', 'running', 'completed', 'cancelled'] })
@IsString()
status!: 'scheduled' | 'running' | 'completed' | 'cancelled';
@ApiProperty()
@IsBoolean()
isMyLeague!: boolean;
}

View File

@@ -0,0 +1,32 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber } from 'class-validator';
export class DashboardRecentResultDTO {
@ApiProperty()
@IsString()
raceId!: string;
@ApiProperty()
@IsString()
raceName!: string;
@ApiProperty()
@IsString()
leagueId!: string;
@ApiProperty()
@IsString()
leagueName!: string;
@ApiProperty()
@IsString()
finishedAt!: string;
@ApiProperty()
@IsNumber()
position!: number;
@ApiProperty()
@IsNumber()
incidents!: number;
}

View File

@@ -0,0 +1,37 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsOptional, IsNumber, IsUrl } from 'class-validator';
export class FileProtestCommandDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
protestingDriverId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
accusedDriverId!: string;
@ApiProperty()
incident!: {
lap: number;
description: string;
timeInRace?: number;
};
@ApiProperty({ required: false })
@IsOptional()
@IsString()
comment?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
@IsUrl()
proofVideoUrl?: string;
}

View File

@@ -0,0 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class GetRaceDetailParamsDTODTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
driverId!: string;
}

View File

@@ -0,0 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class ImportRaceResultsDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
resultsFileContent!: string;
}

View File

@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsString, IsNumber } from 'class-validator';
export class ImportRaceResultsSummaryDTOViewModel {
@ApiProperty()
@IsBoolean()
success!: boolean;
@ApiProperty()
@IsString()
raceId!: string;
@ApiProperty()
@IsNumber()
driversProcessed!: number;
@ApiProperty()
@IsNumber()
resultsRecorded!: number;
@ApiProperty({ type: [String], required: false })
errors?: string[];
}

View File

@@ -0,0 +1,32 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsOptional, IsEnum } from 'class-validator';
export class QuickPenaltyCommandDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
driverId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
adminId!: string;
@ApiProperty({ enum: ['track_limits', 'unsafe_rejoin', 'aggressive_driving', 'false_start', 'other'] })
@IsEnum(['track_limits', 'unsafe_rejoin', 'aggressive_driving', 'false_start', 'other'])
infractionType!: 'track_limits' | 'unsafe_rejoin' | 'aggressive_driving' | 'false_start' | 'other';
@ApiProperty({ enum: ['warning', 'minor', 'major', 'severe'] })
@IsEnum(['warning', 'minor', 'major', 'severe'])
severity!: 'warning' | 'minor' | 'major' | 'severe';
@ApiProperty({ required: false })
@IsOptional()
@IsString()
notes?: string;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class RaceActionParamsDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId!: string;
}

View File

@@ -0,0 +1,15 @@
import { ApiProperty } from '@nestjs/swagger';
export class RaceDTO {
@ApiProperty()
id!: string;
@ApiProperty()
name!: string;
@ApiProperty()
date!: string;
@ApiProperty({ nullable: true })
leagueName?: string;
}

View File

@@ -0,0 +1,29 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, IsString } from 'class-validator';
import { RaceDetailRaceDTO } from './RaceDetailRaceDTO';
import { RaceDetailLeagueDTO } from './RaceDetailLeagueDTO';
import { RaceDetailEntryDTO } from './RaceDetailEntryDTO';
import { RaceDetailRegistrationDTO } from './RaceDetailRegistrationDTO';
import { RaceDetailUserResultDTO } from './RaceDetailUserResultDTO';
export class RaceDetailDTO {
@ApiProperty({ nullable: true })
race!: RaceDetailRaceDTO | null;
@ApiProperty({ nullable: true })
league!: RaceDetailLeagueDTO | null;
@ApiProperty({ type: [RaceDetailEntryDTO] })
entryList!: RaceDetailEntryDTO[];
@ApiProperty()
registration!: RaceDetailRegistrationDTO;
@ApiProperty({ nullable: true })
userResult!: RaceDetailUserResultDTO | null;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
error?: string;
}

View File

@@ -0,0 +1,27 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsBoolean, IsNumber } from 'class-validator';
export class RaceDetailEntryDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
name!: string;
@ApiProperty()
@IsString()
country!: string;
@ApiProperty()
@IsString()
avatarUrl!: string;
@ApiProperty({ nullable: true })
rating!: number | null;
@ApiProperty()
@IsBoolean()
isCurrentUser!: boolean;
}

View File

@@ -0,0 +1,22 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
export class RaceDetailLeagueDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
name!: string;
@ApiProperty()
@IsString()
description!: string;
@ApiProperty()
settings!: {
maxDrivers?: number;
qualifyingFormat?: string;
};
}

View File

@@ -0,0 +1,45 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsOptional, IsNumber } from 'class-validator';
export class RaceDetailRaceDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
leagueId!: string;
@ApiProperty()
@IsString()
track!: string;
@ApiProperty()
@IsString()
car!: string;
@ApiProperty()
@IsString()
scheduledAt!: string;
@ApiProperty()
@IsString()
sessionType!: string;
@ApiProperty()
@IsString()
status!: string;
@ApiProperty({ nullable: true })
strengthOfField!: number | null;
@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
registeredCount?: number;
@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
maxParticipants?: number;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean } from 'class-validator';
export class RaceDetailRegistrationDTO {
@ApiProperty()
@IsBoolean()
isUserRegistered!: boolean;
@ApiProperty()
@IsBoolean()
canRegister!: boolean;
}

View File

@@ -0,0 +1,35 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsBoolean } from 'class-validator';
export class RaceDetailUserResultDTO {
@ApiProperty()
@IsNumber()
position!: number;
@ApiProperty()
@IsNumber()
startPosition!: number;
@ApiProperty()
@IsNumber()
incidents!: number;
@ApiProperty()
@IsNumber()
fastestLap!: number;
@ApiProperty()
@IsNumber()
positionChange!: number;
@ApiProperty()
@IsBoolean()
isPodium!: boolean;
@ApiProperty()
@IsBoolean()
isClean!: boolean;
@ApiProperty({ nullable: true })
ratingChange!: number | null;
}

View File

@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { RacePenaltyDTO } from './RacePenaltyDTO';
export class RacePenaltiesDTO {
@ApiProperty({ type: [RacePenaltyDTO] })
penalties!: RacePenaltyDTO[];
@ApiProperty()
driverMap!: Record<string, string>;
}

View File

@@ -0,0 +1,35 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber } from 'class-validator';
export class RacePenaltyDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
driverId!: string;
@ApiProperty()
@IsString()
type!: string;
@ApiProperty()
@IsNumber()
value!: number;
@ApiProperty()
@IsString()
reason!: string;
@ApiProperty()
@IsString()
issuedBy!: string;
@ApiProperty()
@IsString()
issuedAt!: string;
@ApiProperty({ nullable: true })
notes?: string;
}

View File

@@ -0,0 +1,30 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
export class RaceProtestDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
protestingDriverId!: string;
@ApiProperty()
@IsString()
accusedDriverId!: string;
@ApiProperty()
incident!: {
lap: number;
description: string;
};
@ApiProperty()
@IsString()
status!: string;
@ApiProperty()
@IsString()
filedAt!: string;
}

View File

@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { RaceProtestDto } from './RaceProtestDto';
export class RaceProtestsDTO {
@ApiProperty({ type: [RaceProtestDto] })
protests!: RaceProtestDto[];
@ApiProperty()
driverMap!: Record<string, string>;
}

View File

@@ -0,0 +1,44 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, IsBoolean } from 'class-validator';
export class RaceResultDTO {
@ApiProperty()
@IsString()
driverId!: string;
@ApiProperty()
@IsString()
driverName!: string;
@ApiProperty()
@IsString()
avatarUrl!: string;
@ApiProperty()
@IsNumber()
position!: number;
@ApiProperty()
@IsNumber()
startPosition!: number;
@ApiProperty()
@IsNumber()
incidents!: number;
@ApiProperty()
@IsNumber()
fastestLap!: number;
@ApiProperty()
@IsNumber()
positionChange!: number;
@ApiProperty()
@IsBoolean()
isPodium!: boolean;
@ApiProperty()
@IsBoolean()
isClean!: boolean;
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
import { RaceResultDto } from './RaceResultDto';
export class RaceResultsDetailDTO {
@ApiProperty()
@IsString()
raceId!: string;
@ApiProperty()
@IsString()
track!: string;
@ApiProperty({ type: [RaceResultDto] })
results!: RaceResultDto[];
}

View File

@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class RaceStatsDTO {
@ApiProperty()
totalRaces!: number;
}

View File

@@ -0,0 +1,15 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
export class RaceWithSOFDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
track!: string;
@ApiProperty({ nullable: true })
strengthOfField!: number | null;
}

View File

@@ -0,0 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { RacesPageDataRaceDto } from './RacesPageDataRaceDto';
export class RacesPageDataDTO {
@ApiProperty({ type: [RacesPageDataRaceDto] })
races!: RacesPageDataRaceDto[];
}

View File

@@ -0,0 +1,47 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsBoolean } from 'class-validator';
export class RacesPageDataRaceDTO {
@ApiProperty()
@IsString()
id!: string;
@ApiProperty()
@IsString()
track!: string;
@ApiProperty()
@IsString()
car!: string;
@ApiProperty()
@IsString()
scheduledAt!: string;
@ApiProperty()
@IsString()
status!: string;
@ApiProperty()
@IsString()
leagueId!: string;
@ApiProperty()
@IsString()
leagueName!: string;
@ApiProperty({ nullable: true })
strengthOfField!: number | null;
@ApiProperty()
@IsBoolean()
isUpcoming!: boolean;
@ApiProperty()
@IsBoolean()
isLive!: boolean;
@ApiProperty()
@IsBoolean()
isPast!: boolean;
}

View File

@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class RegisterForRaceParamsDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
leagueId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
driverId!: string;
}

View File

@@ -0,0 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class RequestProtestDefenseCommandDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
protestId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
stewardId!: string;
}

View File

@@ -0,0 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class WithdrawFromRaceParamsDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
raceId!: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
driverId!: string;
}