Files
gridpilot.gg/apps/api/src/domain/race/presenters/RaceDetailPresenter.ts
2026-01-17 01:04:36 +01:00

121 lines
4.6 KiB
TypeScript

import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
import type { ImageServicePort } from '@core/racing/application/ports/ImageServicePort';
import type { GetRaceDetailResult } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
import type { GetRaceDetailParamsDTO } from '../dtos/GetRaceDetailParamsDTO';
import type { RaceDetailDTO } from '../dtos/RaceDetailDTO';
import type { RaceDetailEntryDTO } from '../dtos/RaceDetailEntryDTO';
import type { RaceDetailLeagueDTO } from '../dtos/RaceDetailLeagueDTO';
import type { RaceDetailRaceDTO } from '../dtos/RaceDetailRaceDTO';
import type { RaceDetailRegistrationDTO } from '../dtos/RaceDetailRegistrationDTO';
import type { RaceDetailUserResultDTO } from '../dtos/RaceDetailUserResultDTO';
export type GetRaceDetailResponseModel = RaceDetailDTO;
export class RaceDetailPresenter {
private result: GetRaceDetailResult | null = null;
private params: GetRaceDetailParamsDTO | null = null;
constructor(
private readonly driverRatingProvider: DriverRatingProvider,
private readonly imageService: ImageServicePort,
) {}
present(result: GetRaceDetailResult, params: GetRaceDetailParamsDTO): void {
this.result = result;
this.params = params;
}
async getResponseModel(): Promise<GetRaceDetailResponseModel | null> {
if (!this.result || !this.params) {
return null;
}
const output = this.result;
const params = this.params;
const raceDTO: RaceDetailRaceDTO | null = output.race
? {
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.props,
status: output.race.status.toString(),
strengthOfField: output.race.strengthOfField?.toNumber() ?? null,
...(output.race.registeredCount !== undefined && {
registeredCount: output.race.registeredCount.toNumber(),
}),
...(output.race.maxParticipants !== undefined && {
maxParticipants: output.race.maxParticipants.toNumber(),
}),
}
: null;
const leagueDTO: RaceDetailLeagueDTO | null = output.league
? {
id: output.league.id.toString(),
name: output.league.name.toString(),
description: output.league.description.toString(),
settings: {
...(output.league.settings.maxDrivers !== undefined && { maxDrivers: output.league.settings.maxDrivers }),
...(output.league.settings.qualifyingFormat !== undefined && { qualifyingFormat: output.league.settings.qualifyingFormat.toString() }),
},
}
: null;
const entryListDTO: RaceDetailEntryDTO[] = await Promise.all(
output.drivers.map(async driver => {
const rating = this.driverRatingProvider.getRating(driver.id);
const avatarUrl = this.imageService.getDriverAvatar(driver.id);
return {
id: driver.id,
name: driver.name.toString(),
country: driver.country.toString(),
avatarUrl,
rating,
isCurrentUser: driver.id === params.driverId,
};
}),
);
const registrationDTO: RaceDetailRegistrationDTO = {
isUserRegistered: output.isUserRegistered,
canRegister: output.canRegister,
};
const userResultDTO: RaceDetailUserResultDTO | null = output.userResult
? {
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;
return {
race: raceDTO,
league: leagueDTO,
entryList: entryListDTO,
registration: registrationDTO,
userResult: userResultDTO,
} as RaceDetailDTO;
}
get viewModel(): Promise<GetRaceDetailResponseModel> {
return this.getResponseModel().then(model => {
if (!model) throw new Error('Presenter not presented');
return model;
});
}
private calculateRatingChange(position: number): number {
const baseChange = position <= 3 ? 25 : position <= 10 ? 10 : -5;
const positionBonus = Math.max(0, (20 - position) * 2);
return baseChange + positionBonus;
}
}