This commit is contained in:
2025-12-21 19:53:22 +01:00
parent f2d8a23583
commit 3c64f328e2
105 changed files with 3191 additions and 1706 deletions

View File

@@ -1,4 +1,9 @@
import type { RaceDetailOutputPort } from '@core/racing/application/ports/output/RaceDetailOutputPort';
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type {
GetRaceDetailResult,
GetRaceDetailErrorCode,
} from '@core/racing/application/use-cases/GetRaceDetailUseCase';
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
import type { GetRaceDetailParamsDTO } from '../dtos/GetRaceDetailParamsDTO';
@@ -9,44 +14,79 @@ import type { RaceDetailEntryDTO } from '../dtos/RaceDetailEntryDTO';
import type { RaceDetailRegistrationDTO } from '../dtos/RaceDetailRegistrationDTO';
import type { RaceDetailUserResultDTO } from '../dtos/RaceDetailUserResultDTO';
export type GetRaceDetailResponseModel = RaceDetailDTO;
export type GetRaceDetailApplicationError = ApplicationErrorCode<
GetRaceDetailErrorCode,
{ message: string }
>;
export class RaceDetailPresenter {
private result: RaceDetailDTO | null = null;
private model: GetRaceDetailResponseModel | null = null;
constructor(
private readonly driverRatingProvider: DriverRatingProvider,
private readonly imageService: IImageServicePort,
) {}
async present(outputPort: RaceDetailOutputPort, params: GetRaceDetailParamsDTO): Promise<void> {
const raceDTO: RaceDetailRaceDTO | null = outputPort.race
reset(): void {
this.model = null;
}
async present(
result: Result<GetRaceDetailResult, GetRaceDetailApplicationError>,
params: GetRaceDetailParamsDTO,
): Promise<void> {
if (result.isErr()) {
const error = result.unwrapErr();
if (error.code === 'RACE_NOT_FOUND') {
this.model = {
race: null,
league: null,
entryList: [],
registration: {
isUserRegistered: false,
canRegister: false,
},
userResult: null,
} as RaceDetailDTO;
return;
}
throw new Error(error.details?.message ?? 'Failed to get race detail');
}
const output = result.unwrap();
const raceDTO: RaceDetailRaceDTO | null = output.race
? {
id: outputPort.race.id,
leagueId: outputPort.race.leagueId,
track: outputPort.race.track,
car: outputPort.race.car,
scheduledAt: outputPort.race.scheduledAt.toISOString(),
sessionType: outputPort.race.sessionType,
status: outputPort.race.status,
strengthOfField: outputPort.race.strengthOfField ?? null,
registeredCount: outputPort.race.registeredCount ?? undefined,
maxParticipants: outputPort.race.maxParticipants ?? undefined,
id: output.race.id,
leagueId: output.race.leagueId,
track: output.race.track,
car: output.race.car,
scheduledAt: output.race.scheduledAt.toISOString(),
sessionType: output.race.sessionType,
status: output.race.status,
strengthOfField: output.race.strengthOfField ?? null,
registeredCount: output.race.registeredCount ?? undefined,
maxParticipants: output.race.maxParticipants ?? undefined,
}
: null;
const leagueDTO: RaceDetailLeagueDTO | null = outputPort.league
const leagueDTO: RaceDetailLeagueDTO | null = output.league
? {
id: outputPort.league.id.toString(),
name: outputPort.league.name.toString(),
description: outputPort.league.description.toString(),
id: output.league.id.toString(),
name: output.league.name.toString(),
description: output.league.description.toString(),
settings: {
maxDrivers: outputPort.league.settings.maxDrivers ?? undefined,
qualifyingFormat: outputPort.league.settings.qualifyingFormat ?? undefined,
maxDrivers: output.league.settings.maxDrivers ?? undefined,
qualifyingFormat: output.league.settings.qualifyingFormat ?? undefined,
},
}
: null;
const entryListDTO: RaceDetailEntryDTO[] = await Promise.all(
outputPort.drivers.map(async driver => {
output.drivers.map(async driver => {
const ratingResult = await this.driverRatingProvider.getDriverRating({ driverId: driver.id });
const avatarResult = await this.imageService.getDriverAvatar({ driverId: driver.id });
return {
@@ -61,24 +101,24 @@ export class RaceDetailPresenter {
);
const registrationDTO: RaceDetailRegistrationDTO = {
isUserRegistered: outputPort.isUserRegistered,
canRegister: outputPort.canRegister,
isUserRegistered: output.isUserRegistered,
canRegister: output.canRegister,
};
const userResultDTO: RaceDetailUserResultDTO | null = outputPort.userResult
const userResultDTO: RaceDetailUserResultDTO | null = output.userResult
? {
position: outputPort.userResult.position.toNumber(),
startPosition: outputPort.userResult.startPosition.toNumber(),
incidents: outputPort.userResult.incidents.toNumber(),
fastestLap: outputPort.userResult.fastestLap.toNumber(),
positionChange: outputPort.userResult.getPositionChange(),
isPodium: outputPort.userResult.isPodium(),
isClean: outputPort.userResult.isClean(),
ratingChange: this.calculateRatingChange(outputPort.userResult.position.toNumber()),
position: output.userResult.position.toNumber(),
startPosition: output.userResult.startPosition.toNumber(),
incidents: output.userResult.incidents.toNumber(),
fastestLap: output.userResult.fastestLap.toNumber(),
positionChange: output.userResult.getPositionChange(),
isPodium: output.userResult.isPodium(),
isClean: output.userResult.isClean(),
ratingChange: this.calculateRatingChange(output.userResult.position.toNumber()),
}
: null;
this.result = {
this.model = {
race: raceDTO,
league: leagueDTO,
entryList: entryListDTO,
@@ -87,16 +127,16 @@ export class RaceDetailPresenter {
} as RaceDetailDTO;
}
getViewModel(): RaceDetailDTO | null {
return this.result;
getResponseModel(): GetRaceDetailResponseModel | null {
return this.model;
}
get viewModel(): RaceDetailDTO {
if (!this.result) {
get responseModel(): GetRaceDetailResponseModel {
if (!this.model) {
throw new Error('Presenter not presented');
}
return this.result;
return this.model;
}
private calculateRatingChange(position: number): number {