Files
gridpilot.gg/apps/website/lib/services/leagues/LeagueStewardingService.ts
2025-12-19 21:58:03 +01:00

93 lines
3.3 KiB
TypeScript

import { RaceService } from '../races/RaceService';
import { ProtestService } from '../protests/ProtestService';
import { PenaltyService } from '../penalties/PenaltyService';
import { DriverService } from '../drivers/DriverService';
import { LeagueMembershipService } from './LeagueMembershipService';
import { LeagueStewardingViewModel, RaceWithProtests } from '../../view-models/LeagueStewardingViewModel';
/**
* League Stewarding Service
*
* Orchestrates league stewarding operations by coordinating calls to race, protest, penalty, driver, and membership services.
* All dependencies are injected via constructor.
*/
export class LeagueStewardingService {
constructor(
private readonly raceService: RaceService,
private readonly protestService: ProtestService,
private readonly penaltyService: PenaltyService,
private readonly driverService: DriverService,
private readonly leagueMembershipService: LeagueMembershipService
) {}
/**
* Get league stewarding data for all races in a league
*/
async getLeagueStewardingData(leagueId: string): Promise<LeagueStewardingViewModel> {
// Get all races for this league
const leagueRaces = await this.raceService.findByLeagueId(leagueId);
// Get protests and penalties for each race
const protestsMap: Record<string, any[]> = {};
const penaltiesMap: Record<string, any[]> = {};
const driverIds = new Set<string>();
for (const race of leagueRaces) {
const raceProtests = await this.protestService.findByRaceId(race.id);
const racePenalties = await this.penaltyService.findByRaceId(race.id);
protestsMap[race.id] = raceProtests;
penaltiesMap[race.id] = racePenalties;
// Collect driver IDs
raceProtests.forEach((p: any) => {
driverIds.add(p.protestingDriverId);
driverIds.add(p.accusedDriverId);
});
racePenalties.forEach((p: any) => {
driverIds.add(p.driverId);
});
}
// Load driver info
const driverEntities = await this.driverService.findByIds(Array.from(driverIds));
const driverMap: Record<string, any> = {};
driverEntities.forEach((driver) => {
if (driver) {
driverMap[driver.id] = driver;
}
});
// Compute race data with protest/penalty info
const racesWithData: RaceWithProtests[] = leagueRaces.map(race => {
const protests = protestsMap[race.id] || [];
const penalties = penaltiesMap[race.id] || [];
return {
race: {
id: race.id,
track: race.track,
scheduledAt: new Date(race.scheduledAt),
},
pendingProtests: protests.filter(p => p.status === 'pending' || p.status === 'under_review'),
resolvedProtests: protests.filter(p => p.status === 'upheld' || p.status === 'dismissed' || p.status === 'withdrawn'),
penalties
};
}).sort((a, b) => b.race.scheduledAt.getTime() - a.race.scheduledAt.getTime());
return new LeagueStewardingViewModel(racesWithData, driverMap);
}
/**
* Review a protest
*/
async reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): Promise<void> {
await this.protestService.reviewProtest(input);
}
/**
* Apply a penalty
*/
async applyPenalty(input: any): Promise<void> {
await this.penaltyService.applyPenalty(input);
}
}