refactor
This commit is contained in:
@@ -96,25 +96,20 @@ export class RaceService {
|
||||
async getAllRaces(): Promise<GetAllRacesPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching all races.');
|
||||
|
||||
const result = await this.getAllRacesUseCase.execute();
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error('Failed to get all races');
|
||||
}
|
||||
|
||||
const result = await this.getAllRacesUseCase.execute({});
|
||||
|
||||
const presenter = new GetAllRacesPresenter();
|
||||
await presenter.present(result.unwrap());
|
||||
presenter.reset();
|
||||
presenter.present(result);
|
||||
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async getTotalRaces(): Promise<GetTotalRacesPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching total races count.');
|
||||
const result = await this.getTotalRacesUseCase.execute();
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().code);
|
||||
}
|
||||
const result = await this.getTotalRacesUseCase.execute({});
|
||||
const presenter = new GetTotalRacesPresenter();
|
||||
presenter.present(result.unwrap());
|
||||
presenter.present(result);
|
||||
return presenter;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,50 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetAllRacesPageDataResult,
|
||||
GetAllRacesPageDataErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
|
||||
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
||||
|
||||
export type AllRacesPageDataResponseModel = AllRacesPageDTO;
|
||||
|
||||
export type GetAllRacesPageDataApplicationError = ApplicationErrorCode<
|
||||
GetAllRacesPageDataErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class AllRacesPageDataPresenter {
|
||||
private result: AllRacesPageDTO | null = null;
|
||||
private model: AllRacesPageDataResponseModel | null = null;
|
||||
|
||||
present(output: AllRacesPageDTO): void {
|
||||
this.result = output;
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
getViewModel(): AllRacesPageDTO | null {
|
||||
return this.result;
|
||||
present(
|
||||
result: Result<GetAllRacesPageDataResult, GetAllRacesPageDataApplicationError>,
|
||||
): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get all races page data');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
races: output.races,
|
||||
filters: output.filters,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): AllRacesPageDTO {
|
||||
if (!this.result) {
|
||||
getResponseModel(): AllRacesPageDataResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): AllRacesPageDataResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.result;
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,62 @@
|
||||
export interface CommandResultViewModel {
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
export interface CommandResultDTO {
|
||||
success: boolean;
|
||||
errorCode?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class CommandResultPresenter {
|
||||
private result: CommandResultViewModel | null = null;
|
||||
export type CommandApplicationError<E extends string = string> = ApplicationErrorCode<
|
||||
E,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class CommandResultPresenter<E extends string = string> {
|
||||
private model: CommandResultDTO | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<unknown, CommandApplicationError<E>>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
this.model = {
|
||||
success: false,
|
||||
errorCode: error.code,
|
||||
message: error.details?.message,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
this.model = { success: true };
|
||||
}
|
||||
|
||||
presentSuccess(message?: string): void {
|
||||
this.result = {
|
||||
this.model = {
|
||||
success: true,
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
presentFailure(errorCode: string, message?: string): void {
|
||||
this.result = {
|
||||
this.model = {
|
||||
success: false,
|
||||
errorCode,
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): CommandResultViewModel | null {
|
||||
return this.result;
|
||||
getResponseModel(): CommandResultDTO | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get viewModel(): CommandResultViewModel {
|
||||
if (!this.result) {
|
||||
get responseModel(): CommandResultDTO {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.result;
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import { GetAllRacesPresenter } from './GetAllRacesPresenter';
|
||||
import type { GetAllRacesOutputPort } from '@core/racing/application/ports/output/GetAllRacesOutputPort';
|
||||
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||
|
||||
describe('GetAllRacesPresenter', () => {
|
||||
it('should map races and distinct leagues into the DTO', async () => {
|
||||
|
||||
@@ -1,33 +1,53 @@
|
||||
import { GetAllRacesOutputPort } from '@core/racing/application/ports/output/GetAllRacesOutputPort';
|
||||
import { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetAllRacesResult,
|
||||
GetAllRacesErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
||||
|
||||
export type GetAllRacesResponseModel = AllRacesPageDTO;
|
||||
|
||||
export type GetAllRacesApplicationError = ApplicationErrorCode<
|
||||
GetAllRacesErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class GetAllRacesPresenter {
|
||||
private result: AllRacesPageDTO | null = null;
|
||||
private model: GetAllRacesResponseModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
async present(output: GetAllRacesOutputPort) {
|
||||
const uniqueLeagues = new Map<string, { id: string; name: string }>();
|
||||
|
||||
for (const race of output.races) {
|
||||
uniqueLeagues.set(race.leagueId, {
|
||||
id: race.leagueId,
|
||||
name: race.leagueName,
|
||||
});
|
||||
present(result: Result<GetAllRacesResult, GetAllRacesApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get all races');
|
||||
}
|
||||
|
||||
this.result = {
|
||||
const output = result.unwrap();
|
||||
|
||||
const leagueMap = new Map<string, string>();
|
||||
const uniqueLeagues = new Map<string, { id: string; name: string }>();
|
||||
|
||||
for (const league of output.leagues) {
|
||||
const id = league.id.toString();
|
||||
const name = league.name.toString();
|
||||
leagueMap.set(id, name);
|
||||
uniqueLeagues.set(id, { id, name });
|
||||
}
|
||||
|
||||
this.model = {
|
||||
races: output.races.map(race => ({
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
scheduledAt: race.scheduledAt,
|
||||
scheduledAt: race.scheduledAt.toISOString(),
|
||||
status: race.status,
|
||||
leagueId: race.leagueId,
|
||||
leagueName: race.leagueName,
|
||||
strengthOfField: race.strengthOfField,
|
||||
leagueName: leagueMap.get(race.leagueId) ?? 'Unknown League',
|
||||
strengthOfField: race.strengthOfField ?? null,
|
||||
})),
|
||||
filters: {
|
||||
statuses: [
|
||||
@@ -42,7 +62,15 @@ export class GetAllRacesPresenter {
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): AllRacesPageDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): GetAllRacesResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): GetAllRacesResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,47 @@
|
||||
import { GetTotalRacesOutputPort } from '@core/racing/application/ports/output/GetTotalRacesOutputPort';
|
||||
import { RaceStatsDTO } from '../dtos/RaceStatsDTO';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetTotalRacesResult,
|
||||
GetTotalRacesErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetTotalRacesUseCase';
|
||||
import type { RaceStatsDTO } from '../dtos/RaceStatsDTO';
|
||||
|
||||
export type GetTotalRacesResponseModel = RaceStatsDTO;
|
||||
|
||||
export type GetTotalRacesApplicationError = ApplicationErrorCode<
|
||||
GetTotalRacesErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class GetTotalRacesPresenter {
|
||||
private result: RaceStatsDTO | null = null;
|
||||
private model: GetTotalRacesResponseModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(output: GetTotalRacesOutputPort) {
|
||||
this.result = {
|
||||
present(result: Result<GetTotalRacesResult, GetTotalRacesApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get total races');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
totalRaces: output.totalRaces,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): RaceStatsDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): GetTotalRacesResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): GetTotalRacesResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,36 @@
|
||||
import { ImportRaceResultsApiOutputPort } from '@core/racing/application/ports/output/ImportRaceResultsApiOutputPort';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
ImportRaceResultsApiResult,
|
||||
ImportRaceResultsApiErrorCode,
|
||||
} from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
|
||||
import { ImportRaceResultsSummaryDTO } from '../dtos/ImportRaceResultsSummaryDTO';
|
||||
|
||||
export class ImportRaceResultsApiPresenter {
|
||||
private result: ImportRaceResultsSummaryDTO | null = null;
|
||||
export type ImportRaceResultsApiResponseModel = ImportRaceResultsSummaryDTO;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
export type ImportRaceResultsApiApplicationError = ApplicationErrorCode<
|
||||
ImportRaceResultsApiErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class ImportRaceResultsApiPresenter {
|
||||
private model: ImportRaceResultsApiResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(output: ImportRaceResultsApiOutputPort) {
|
||||
this.result = {
|
||||
present(
|
||||
result: Result<ImportRaceResultsApiResult, ImportRaceResultsApiApplicationError>,
|
||||
): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to import race results');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
success: output.success,
|
||||
raceId: output.raceId,
|
||||
driversProcessed: output.driversProcessed,
|
||||
@@ -18,7 +39,15 @@ export class ImportRaceResultsApiPresenter {
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): ImportRaceResultsSummaryDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): ImportRaceResultsApiResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): ImportRaceResultsApiResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
import type { RacePenaltiesOutputPort } from '@core/racing/application/ports/output/RacePenaltiesOutputPort';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetRacePenaltiesResult,
|
||||
GetRacePenaltiesErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
|
||||
import type { RacePenaltiesDTO } from '../dtos/RacePenaltiesDTO';
|
||||
import type { RacePenaltyDTO } from '../dtos/RacePenaltyDTO';
|
||||
|
||||
export class RacePenaltiesPresenter {
|
||||
private result: RacePenaltiesDTO | null = null;
|
||||
export type GetRacePenaltiesResponseModel = RacePenaltiesDTO;
|
||||
|
||||
present(outputPort: RacePenaltiesOutputPort): void {
|
||||
const penalties: RacePenaltyDTO[] = outputPort.penalties.map(penalty => ({
|
||||
export type GetRacePenaltiesApplicationError = ApplicationErrorCode<
|
||||
GetRacePenaltiesErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class RacePenaltiesPresenter {
|
||||
private model: GetRacePenaltiesResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<GetRacePenaltiesResult, GetRacePenaltiesApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get race penalties');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
const penalties: RacePenaltyDTO[] = output.penalties.map(penalty => ({
|
||||
id: penalty.id,
|
||||
driverId: penalty.driverId,
|
||||
type: penalty.type,
|
||||
@@ -18,25 +41,25 @@ export class RacePenaltiesPresenter {
|
||||
} as RacePenaltyDTO));
|
||||
|
||||
const driverMap: Record<string, string> = {};
|
||||
outputPort.drivers.forEach(driver => {
|
||||
output.drivers.forEach(driver => {
|
||||
driverMap[driver.id] = driver.name.toString();
|
||||
});
|
||||
|
||||
this.result = {
|
||||
this.model = {
|
||||
penalties,
|
||||
driverMap,
|
||||
} as RacePenaltiesDTO;
|
||||
}
|
||||
|
||||
getViewModel(): RacePenaltiesDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): GetRacePenaltiesResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get viewModel(): RacePenaltiesDTO {
|
||||
if (!this.result) {
|
||||
get responseModel(): GetRacePenaltiesResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.result;
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
import type { RaceProtestsOutputPort } from '@core/racing/application/ports/output/RaceProtestsOutputPort';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetRaceProtestsResult,
|
||||
GetRaceProtestsErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
||||
import type { RaceProtestsDTO } from '../dtos/RaceProtestsDTO';
|
||||
import type { RaceProtestDTO } from '../dtos/RaceProtestDTO';
|
||||
|
||||
export class RaceProtestsPresenter {
|
||||
private result: RaceProtestsDTO | null = null;
|
||||
export type GetRaceProtestsResponseModel = RaceProtestsDTO;
|
||||
|
||||
present(outputPort: RaceProtestsOutputPort): void {
|
||||
const protests: RaceProtestDTO[] = outputPort.protests.map(protest => ({
|
||||
export type GetRaceProtestsApplicationError = ApplicationErrorCode<
|
||||
GetRaceProtestsErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class RaceProtestsPresenter {
|
||||
private model: GetRaceProtestsResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<GetRaceProtestsResult, GetRaceProtestsApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get race protests');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
const protests: RaceProtestDTO[] = output.protests.map(protest => ({
|
||||
id: protest.id,
|
||||
protestingDriverId: protest.protestingDriverId,
|
||||
accusedDriverId: protest.accusedDriverId,
|
||||
@@ -19,25 +42,25 @@ export class RaceProtestsPresenter {
|
||||
} as RaceProtestDTO));
|
||||
|
||||
const driverMap: Record<string, string> = {};
|
||||
outputPort.drivers.forEach(driver => {
|
||||
output.drivers.forEach(driver => {
|
||||
driverMap[driver.id] = driver.name.toString();
|
||||
});
|
||||
|
||||
this.result = {
|
||||
this.model = {
|
||||
protests,
|
||||
driverMap,
|
||||
} as RaceProtestsDTO;
|
||||
}
|
||||
|
||||
getViewModel(): RaceProtestsDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): GetRaceProtestsResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get viewModel(): RaceProtestsDTO {
|
||||
if (!this.result) {
|
||||
get responseModel(): GetRaceProtestsResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.result;
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,53 @@
|
||||
import type { RaceResultsDetailOutputPort } from '@core/racing/application/ports/output/RaceResultsDetailOutputPort';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetRaceResultsDetailResult,
|
||||
GetRaceResultsDetailErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
|
||||
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
|
||||
import type { RaceResultsDetailDTO } from '../dtos/RaceResultsDetailDTO';
|
||||
import type { RaceResultDTO } from '../dtos/RaceResultDTO';
|
||||
|
||||
export type GetRaceResultsDetailResponseModel = RaceResultsDetailDTO;
|
||||
|
||||
export type GetRaceResultsDetailApplicationError = ApplicationErrorCode<
|
||||
GetRaceResultsDetailErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class RaceResultsDetailPresenter {
|
||||
private result: RaceResultsDetailDTO | null = null;
|
||||
private model: GetRaceResultsDetailResponseModel | null = null;
|
||||
|
||||
constructor(private readonly imageService: IImageServicePort) {}
|
||||
|
||||
async present(outputPort: RaceResultsDetailOutputPort): Promise<void> {
|
||||
const driverMap = new Map(outputPort.drivers.map(driver => [driver.id, driver]));
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
async present(
|
||||
result: Result<GetRaceResultsDetailResult, GetRaceResultsDetailApplicationError>,
|
||||
): Promise<void> {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
if (error.code === 'RACE_NOT_FOUND') {
|
||||
this.model = {
|
||||
raceId: '',
|
||||
track: '',
|
||||
results: [],
|
||||
} as RaceResultsDetailDTO;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(error.details?.message ?? 'Failed to get race results detail');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
const driverMap = new Map(output.drivers.map(driver => [driver.id, driver]));
|
||||
|
||||
const results: RaceResultDTO[] = await Promise.all(
|
||||
outputPort.results.map(async singleResult => {
|
||||
output.results.map(async singleResult => {
|
||||
const driver = driverMap.get(singleResult.driverId.toString());
|
||||
if (!driver) {
|
||||
throw new Error(`Driver not found for result: ${singleResult.driverId}`);
|
||||
@@ -35,22 +70,22 @@ export class RaceResultsDetailPresenter {
|
||||
}),
|
||||
);
|
||||
|
||||
this.result = {
|
||||
raceId: outputPort.race.id,
|
||||
track: outputPort.race.track,
|
||||
this.model = {
|
||||
raceId: output.race.id,
|
||||
track: output.race.track,
|
||||
results,
|
||||
} as RaceResultsDetailDTO;
|
||||
}
|
||||
|
||||
getViewModel(): RaceResultsDetailDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): GetRaceResultsDetailResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get viewModel(): RaceResultsDetailDTO {
|
||||
if (!this.result) {
|
||||
get responseModel(): GetRaceResultsDetailResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.result;
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,58 @@
|
||||
import type { RaceWithSOFOutputPort } from '@core/racing/application/ports/output/RaceWithSOFOutputPort';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetRaceWithSOFResult,
|
||||
GetRaceWithSOFErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
|
||||
import type { RaceWithSOFDTO } from '../dtos/RaceWithSOFDTO';
|
||||
|
||||
export class RaceWithSOFPresenter {
|
||||
private result: RaceWithSOFDTO | null = null;
|
||||
export type GetRaceWithSOFResponseModel = RaceWithSOFDTO;
|
||||
|
||||
present(outputPort: RaceWithSOFOutputPort): void {
|
||||
this.result = {
|
||||
id: outputPort.id,
|
||||
track: outputPort.track,
|
||||
strengthOfField: outputPort.strengthOfField,
|
||||
export type GetRaceWithSOFApplicationError = ApplicationErrorCode<
|
||||
GetRaceWithSOFErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class RaceWithSOFPresenter {
|
||||
private model: GetRaceWithSOFResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<GetRaceWithSOFResult, GetRaceWithSOFApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
if (error.code === 'RACE_NOT_FOUND') {
|
||||
this.model = {
|
||||
id: '',
|
||||
track: '',
|
||||
strengthOfField: null,
|
||||
} as RaceWithSOFDTO;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(error.details?.message ?? 'Failed to get race with SOF');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
id: output.race.id,
|
||||
track: output.race.track,
|
||||
strengthOfField: output.strengthOfField,
|
||||
} as RaceWithSOFDTO;
|
||||
}
|
||||
|
||||
getViewModel(): RaceWithSOFDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): GetRaceWithSOFResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get viewModel(): RaceWithSOFDTO {
|
||||
if (!this.result) {
|
||||
get responseModel(): GetRaceWithSOFResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.result;
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,62 @@
|
||||
import type { RacesPageOutputPort } from '@core/racing/application/ports/output/RacesPageOutputPort';
|
||||
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetRacesPageDataResult,
|
||||
GetRacesPageDataErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
|
||||
import type { RacesPageDataDTO } from '../dtos/RacesPageDataDTO';
|
||||
import type { RacesPageDataRaceDTO } from '../dtos/RacesPageDataRaceDTO';
|
||||
|
||||
export type GetRacesPageDataResponseModel = RacesPageDataDTO;
|
||||
|
||||
export type GetRacesPageDataApplicationError = ApplicationErrorCode<
|
||||
GetRacesPageDataErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class RacesPageDataPresenter {
|
||||
private result: RacesPageDataDTO | null = null;
|
||||
private model: GetRacesPageDataResponseModel | null = null;
|
||||
|
||||
constructor(private readonly leagueRepository: ILeagueRepository) {}
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
async present(outputPort: RacesPageOutputPort): Promise<void> {
|
||||
const allLeagues = await this.leagueRepository.findAll();
|
||||
const leagueMap = new Map(allLeagues.map(l => [l.id, l.name]));
|
||||
present(
|
||||
result: Result<GetRacesPageDataResult, GetRacesPageDataApplicationError>,
|
||||
): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get races page data');
|
||||
}
|
||||
|
||||
const races: RacesPageDataRaceDTO[] = outputPort.races.map(race => ({
|
||||
const output = result.unwrap();
|
||||
|
||||
const races: RacesPageDataRaceDTO[] = output.races.map(({ race, leagueName }) => ({
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
scheduledAt: race.scheduledAt.toISOString(),
|
||||
status: race.status,
|
||||
leagueId: race.leagueId,
|
||||
leagueName: leagueMap.get(race.leagueId) ?? 'Unknown League',
|
||||
strengthOfField: race.strengthOfField,
|
||||
leagueName,
|
||||
strengthOfField: race.strengthOfField ?? null,
|
||||
isUpcoming: race.scheduledAt > new Date(),
|
||||
isLive: race.status === 'running',
|
||||
isPast: race.scheduledAt < new Date() && race.status === 'completed',
|
||||
}));
|
||||
|
||||
this.result = { races } as RacesPageDataDTO;
|
||||
this.model = { races } as RacesPageDataDTO;
|
||||
}
|
||||
|
||||
getViewModel(): RacesPageDataDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): GetRacesPageDataResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get viewModel(): RacesPageDataDTO {
|
||||
if (!this.result) {
|
||||
get responseModel(): GetRacesPageDataResponseModel {
|
||||
if (!this.model) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.result;
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user