import { DomainEventPublisher } from '@core/shared/domain/DomainEvent'; import type { Logger } from '@core/shared/domain/Logger'; import { Result } from '@core/shared/domain/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { RaceEvent } from '../../domain/entities/RaceEvent'; import { RaceEventStewardingClosedEvent } from '../../domain/events/RaceEventStewardingClosed'; import { PenaltyRepository } from '../../domain/repositories/PenaltyRepository'; import { RaceEventRepository } from '../../domain/repositories/RaceEventRepository'; import { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository'; export type CloseRaceEventStewardingInput = { raceId: string; closedById: string; }; export type CloseRaceEventStewardingResult = { race: RaceEvent; }; /** * Use Case: CloseRaceEventStewardingUseCase * * Scheduled job that checks for race events with expired stewarding windows * and closes them, triggering final results notifications. * * This would typically be run by a scheduled job (e.g., every 5 minutes) * to automatically close stewarding windows based on league configuration. */ export class CloseRaceEventStewardingUseCase { constructor( private readonly logger: Logger, private readonly raceEventRepository: RaceEventRepository, private readonly raceRegistrationRepository: RaceRegistrationRepository, private readonly penaltyRepository: PenaltyRepository, private readonly domainEventPublisher: DomainEventPublisher, ) {} async execute( input: CloseRaceEventStewardingInput, ): Promise< Result< CloseRaceEventStewardingResult, ApplicationErrorCode<'RACE_NOT_FOUND' | 'STEWARDING_ALREADY_CLOSED' | 'REPOSITORY_ERROR'> > > { void input; try { // Find all race events awaiting stewarding that have expired windows const expiredEvents = await this.raceEventRepository.findAwaitingStewardingClose(); const closedRaceEventIds: string[] = []; for (const raceEvent of expiredEvents) { await this.closeStewardingForRaceEvent(raceEvent); closedRaceEventIds.push(raceEvent.id); } // When multiple race events are processed, we return the last closed event for simplicity const lastClosedEventId = closedRaceEventIds[closedRaceEventIds.length - 1]; if (lastClosedEventId) { const lastClosedEvent = await this.raceEventRepository.findById(lastClosedEventId); if (lastClosedEvent) { const result: CloseRaceEventStewardingResult = { race: lastClosedEvent, }; return Result.ok(result); } } // If no events were closed, return an error return Result.err({ code: 'RACE_NOT_FOUND', details: { message: 'No race events found to close stewarding for' }, }); } catch (error) { this.logger.error('Failed to close race event stewarding', error instanceof Error ? error : new Error(String(error))); return Result.err({ code: 'REPOSITORY_ERROR', details: { message: error instanceof Error ? error.message : String(error), }, }); } } private async closeStewardingForRaceEvent(raceEvent: RaceEvent): Promise { try { // Close the stewarding window const closedRaceEvent = raceEvent.closeStewarding(); await this.raceEventRepository.update(closedRaceEvent); // Get list of participating driver IDs const driverIds = await this.getParticipatingDriverIds(raceEvent); // Check if any penalties were applied during stewarding const hadPenaltiesApplied = await this.checkForAppliedPenalties(raceEvent); // Publish domain event to trigger final results notifications const event = new RaceEventStewardingClosedEvent({ raceEventId: raceEvent.id, leagueId: raceEvent.leagueId, seasonId: raceEvent.seasonId, closedAt: new Date(), driverIds, hadPenaltiesApplied, }); await this.domainEventPublisher.publish(event); } catch (error) { this.logger.error(`Failed to close stewarding for race event ${raceEvent.id}`, error instanceof Error ? error : new Error(String(error))); // In production, this would trigger alerts/monitoring throw error; } } private async getParticipatingDriverIds(raceEvent: RaceEvent): Promise { return await this.raceRegistrationRepository.getRegisteredDrivers(raceEvent.id); } private async checkForAppliedPenalties(raceEvent: RaceEvent): Promise { const penalties = await this.penaltyRepository.findByRaceId(raceEvent.id); return penalties.length > 0; } }