Files
gridpilot.gg/apps/website/lib/presenters/LeagueSchedulePresenter.ts
2025-12-16 11:52:26 +01:00

115 lines
3.3 KiB
TypeScript

import type { Race } from '@core/racing/domain/entities/Race';
import type { IRaceRepository } from '@core/racing/application/ports/IRaceRepository';
import type { IIsDriverRegisteredForRaceQuery } from '@core/racing/application/queries/IIsDriverRegisteredForRaceQuery';
import type { IRegisterForRaceUseCase } from '@core/racing/application/use-cases/IRegisterForRaceUseCase';
import type { IWithdrawFromRaceUseCase } from '@core/racing/application/use-cases/IWithdrawFromRaceUseCase';
export interface LeagueScheduleRaceItemViewModel {
id: string;
leagueId: string;
track: string;
car: string;
sessionType: string;
scheduledAt: Date;
status: Race['status'];
isUpcoming: boolean;
isPast: boolean;
isRegistered: boolean;
}
export interface LeagueScheduleViewModel {
races: LeagueScheduleRaceItemViewModel[];
}
export interface ILeagueSchedulePresenter {
loadLeagueSchedule(leagueId: string, driverId: string): Promise<LeagueScheduleViewModel>;
registerForRace(raceId: string, leagueId: string, driverId: string): Promise<void>;
withdrawFromRace(raceId: string, driverId: string): Promise<void>;
}
export class LeagueSchedulePresenter implements ILeagueSchedulePresenter {
constructor(
private raceRepository: IRaceRepository,
private isDriverRegisteredForRaceQuery: IIsDriverRegisteredForRaceQuery,
private registerForRaceUseCase: IRegisterForRaceUseCase,
private withdrawFromRaceUseCase: IWithdrawFromRaceUseCase,
) {}
/**
* Load league schedule with registration status for a given driver.
*/
async loadLeagueSchedule(
leagueId: string,
driverId: string,
): Promise<LeagueScheduleViewModel> {
const allRaces = await this.raceRepository.findAll();
const leagueRaces = allRaces
.filter((race) => race.leagueId === leagueId)
.sort(
(a, b) =>
new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime(),
);
const now = new Date();
const registrationStates: Record<string, boolean> = {};
await Promise.all(
leagueRaces.map(async (race) => {
const registered = await this.isDriverRegisteredForRaceQuery.execute({
raceId: race.id,
driverId,
});
registrationStates[race.id] = registered;
}),
);
const races: LeagueScheduleRaceItemViewModel[] = leagueRaces.map((race) => {
const raceDate = new Date(race.scheduledAt);
const isPast = race.status === 'completed' || raceDate <= now;
const isUpcoming = race.status === 'scheduled' && raceDate > now;
return {
id: race.id,
leagueId: race.leagueId,
track: race.track,
car: race.car,
sessionType: race.sessionType,
scheduledAt: raceDate,
status: race.status,
isUpcoming,
isPast,
isRegistered: registrationStates[race.id] ?? false,
};
});
return { races };
}
/**
* Register the driver for a race.
*/
async registerForRace(
raceId: string,
leagueId: string,
driverId: string,
): Promise<void> {
await this.registerForRaceUseCase.execute({
raceId,
leagueId,
driverId,
});
}
/**
* Withdraw the driver from a race.
*/
async withdrawFromRace(
raceId: string,
driverId: string,
): Promise<void> {
await this.withdrawFromRaceUseCase.execute({
raceId,
driverId,
});
}
}