import { ProtestsApiClient } from '../../api/protests/ProtestsApiClient'; import { ProtestViewModel } from '../../view-models/ProtestViewModel'; import { RaceViewModel } from '../../view-models/RaceViewModel'; import { ProtestDriverViewModel } from '../../view-models/ProtestDriverViewModel'; import type { LeagueAdminProtestsDTO } from '../../types/generated/LeagueAdminProtestsDTO'; import type { ApplyPenaltyCommandDTO } from '../../types/generated/ApplyPenaltyCommandDTO'; import type { RequestProtestDefenseCommandDTO } from '../../types/generated/RequestProtestDefenseCommandDTO'; import type { ReviewProtestCommandDTO } from '../../types/generated/ReviewProtestCommandDTO'; import type { DriverDTO } from '../../types/generated/DriverDTO'; /** * Protest Service * * Orchestrates protest operations by coordinating API calls and view model creation. * All dependencies are injected via constructor. */ export class ProtestService { constructor( private readonly apiClient: ProtestsApiClient ) {} /** * Get protests for a league with view model transformation */ async getLeagueProtests(leagueId: string): Promise<{ protests: ProtestViewModel[]; racesById: LeagueAdminProtestsDTO['racesById']; driversById: LeagueAdminProtestsDTO['driversById']; }> { const dto = await this.apiClient.getLeagueProtests(leagueId); return { protests: dto.protests.map(protest => new ProtestViewModel(protest)), racesById: dto.racesById, driversById: dto.driversById, }; } /** * Get a single protest by ID from league protests */ async getProtestById(leagueId: string, protestId: string): Promise<{ protest: ProtestViewModel; race: RaceViewModel; protestingDriver: ProtestDriverViewModel; accusedDriver: ProtestDriverViewModel; } | null> { const dto = await this.apiClient.getLeagueProtest(leagueId, protestId); const protest = dto.protests[0]; if (!protest) return null; const race = Object.values(dto.racesById)[0]; if (!race) return null; // Cast to the correct type for indexing const driversById = dto.driversById as unknown as Record; const protestingDriver = driversById[protest.protestingDriverId]; const accusedDriver = driversById[protest.accusedDriverId]; if (!protestingDriver || !accusedDriver) return null; return { protest: new ProtestViewModel(protest), race: new RaceViewModel(race), protestingDriver: new ProtestDriverViewModel(protestingDriver), accusedDriver: new ProtestDriverViewModel(accusedDriver), }; } /** * Apply a penalty */ async applyPenalty(input: ApplyPenaltyCommandDTO): Promise { await this.apiClient.applyPenalty(input); } /** * Request protest defense */ async requestDefense(input: RequestProtestDefenseCommandDTO): Promise { await this.apiClient.requestDefense(input); } /** * Review protest */ async reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): Promise { const normalizedDecision = input.decision.toLowerCase() === 'upheld' ? 'uphold' : input.decision.toLowerCase(); const command: ReviewProtestCommandDTO = { protestId: input.protestId, stewardId: input.stewardId, decision: normalizedDecision, decisionNotes: input.decisionNotes, }; await this.apiClient.reviewProtest(command); } /** * Find protests by race ID */ async findByRaceId(raceId: string): Promise { const dto = await this.apiClient.getRaceProtests(raceId); return dto.protests; } }