import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl'; import { Result } from '@/lib/contracts/Result'; import { DomainError, Service } from '@/lib/contracts/services/Service'; import { ApiError } from '@/lib/gateways/api/base/ApiError'; import { PenaltiesApiClient } from '@/lib/gateways/api/penalties/PenaltiesApiClient'; import { ProtestsApiClient } from '@/lib/gateways/api/protests/ProtestsApiClient'; import { RacesApiClient } from '@/lib/gateways/api/races/RacesApiClient'; import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter'; import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; import { RaceStewardingViewModel } from '@/lib/view-models/RaceStewardingViewModel'; import { injectable, unmanaged } from 'inversify'; /** * Race Stewarding Service * * Orchestration service for race stewarding operations. * Returns raw API DTOs. No ViewModels or UX logic. */ @injectable() export class RaceStewardingService implements Service { private racesApiClient: RacesApiClient; private protestsApiClient: ProtestsApiClient; private penaltiesApiClient: PenaltiesApiClient; constructor( @unmanaged() racesApiClient?: RacesApiClient, @unmanaged() protestsApiClient?: ProtestsApiClient, @unmanaged() penaltiesApiClient?: PenaltiesApiClient ) { if (racesApiClient && protestsApiClient && penaltiesApiClient) { this.racesApiClient = racesApiClient; this.protestsApiClient = protestsApiClient; this.penaltiesApiClient = penaltiesApiClient; } else { // Service creates its own dependencies const baseUrl = getWebsiteApiBaseUrl(); const logger = new ConsoleLogger(); const errorReporter = new ConsoleErrorReporter(); this.racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger); this.protestsApiClient = new ProtestsApiClient(baseUrl, errorReporter, logger); this.penaltiesApiClient = new PenaltiesApiClient(baseUrl, errorReporter, logger); } } async getRaceStewardingData(raceId: string, driverId: string): Promise { const res = await this.getRaceStewarding(raceId, driverId); if (res.isErr()) throw new Error((res as any).error.message); const data = (res as any).value; return new RaceStewardingViewModel(data); } /** * Get race stewarding data * Returns protests and penalties for a race */ async getRaceStewarding(raceId: string, driverId: string = ''): Promise> { try { // Fetch data in parallel const [raceDetail, protests, penalties] = await Promise.all([ this.racesApiClient.getDetail(raceId, driverId), this.protestsApiClient.getRaceProtests(raceId), this.penaltiesApiClient.getRacePenalties(raceId), ]); // Transform data to match view model structure // eslint-disable-next-line @typescript-eslint/no-explicit-any const protestsData = protests.protests.map((p: any) => ({ id: p.id, protestingDriverId: p.protestingDriverId, accusedDriverId: p.accusedDriverId, incident: { lap: p.lap, description: p.description, }, filedAt: p.filedAt, status: p.status, })); // eslint-disable-next-line @typescript-eslint/no-explicit-any const pendingProtests = protestsData.filter((p: any) => p.status === 'pending' || p.status === 'under_review'); // eslint-disable-next-line @typescript-eslint/no-explicit-any const resolvedProtests = protestsData.filter((p: any) => p.status === 'upheld' || p.status === 'dismissed' || p.status === 'withdrawn' ); const data = { race: raceDetail.race, league: raceDetail.league, protests: protestsData, penalties: penalties.penalties, driverMap: { ...protests.driverMap, ...penalties.driverMap }, pendingProtests, resolvedProtests, pendingCount: pendingProtests.length, resolvedCount: resolvedProtests.length, penaltiesCount: penalties.penalties.length, }; return Result.ok(data); } catch (error: unknown) { if (error instanceof ApiError) { return Result.err({ type: this.mapApiErrorType(error.type), message: error.message }); } return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch stewarding data' }); } } private mapApiErrorType(apiErrorType: string): DomainError['type'] { switch (apiErrorType) { case 'NOT_FOUND': return 'notFound'; case 'AUTH_ERROR': return 'unauthorized'; case 'VALIDATION_ERROR': return 'validation'; case 'SERVER_ERROR': return 'serverError'; case 'NETWORK_ERROR': return 'networkError'; default: return 'unknown'; } } }