import type { Logger } from '@core/shared/application'; import type { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository'; import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository'; import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository'; import type { DomainEventPublisher } from '@core/shared/domain/DomainEvent'; import { RaceEventStewardingClosedEvent } from '../../domain/events/RaceEventStewardingClosed'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { RaceEvent } from '../../domain/entities/RaceEvent'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; 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: IRaceEventRepository, private readonly raceRegistrationRepository: IRaceRegistrationRepository, private readonly penaltyRepository: IPenaltyRepository, private readonly domainEventPublisher: DomainEventPublisher, private readonly output: UseCaseOutputPort, ) {} async execute(_: CloseRaceEventStewardingInput): Promise>> { 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 present the last closed event for simplicity const lastClosedEventId = closedRaceEventIds[closedRaceEventIds.length - 1]; if (lastClosedEventId) { const lastClosedEvent = await this.raceEventRepository.findById(lastClosedEventId); if (lastClosedEvent) { this.output.present({ race: lastClosedEvent, }); } } return Result.ok(undefined); } 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 drivers 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; } }