Files
gridpilot.gg/apps/website/lib/services/protests/ProtestService.ts
2025-12-28 12:04:12 +01:00

107 lines
3.6 KiB
TypeScript

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<string, DriverDTO>;
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<void> {
await this.apiClient.applyPenalty(input);
}
/**
* Request protest defense
*/
async requestDefense(input: RequestProtestDefenseCommandDTO): Promise<void> {
await this.apiClient.requestDefense(input);
}
/**
* Review protest
*/
async reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): Promise<void> {
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<any[]> {
const dto = await this.apiClient.getRaceProtests(raceId);
return dto.protests;
}
}