/** * Application Use Case: ApplyPenaltyUseCase * * Allows a steward to apply a penalty to a driver for an incident during a race. * The penalty can be standalone or linked to an upheld protest. */ import { Penalty } from '../../domain/entities/penalty/Penalty'; import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository'; import type { IProtestRepository } from '../../domain/repositories/IProtestRepository'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; import { randomUUID } from 'crypto'; import type { Logger } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; export interface ApplyPenaltyInput { raceId: string; driverId: string; stewardId: string; type: Penalty['type']; value?: Penalty['value']; reason: string; protestId?: string; notes?: string; } export interface ApplyPenaltyResult { penaltyId: string; } export class ApplyPenaltyUseCase { constructor( private readonly penaltyRepository: IPenaltyRepository, private readonly protestRepository: IProtestRepository, private readonly raceRepository: IRaceRepository, private readonly leagueMembershipRepository: ILeagueMembershipRepository, private readonly logger: Logger, private readonly output: UseCaseOutputPort, ) {} async execute( command: ApplyPenaltyInput, ): Promise< Result< void, ApplicationErrorCode< | 'RACE_NOT_FOUND' | 'INSUFFICIENT_AUTHORITY' | 'PROTEST_NOT_FOUND' | 'PROTEST_NOT_UPHELD' | 'PROTEST_NOT_FOR_RACE' > > > { this.logger.debug('ApplyPenaltyUseCase: Executing with command', command); // Validate race exists const race = await this.raceRepository.findById(command.raceId); if (!race) { this.logger.warn(`ApplyPenaltyUseCase: Race with ID ${command.raceId} not found.`); return Result.err({ code: 'RACE_NOT_FOUND' }); } this.logger.debug(`ApplyPenaltyUseCase: Race ${race.id} found.`); // Validate steward has authority (owner or admin of the league) const memberships = await this.leagueMembershipRepository.getLeagueMembers(race.leagueId); const stewardMembership = memberships.find( m => m.driverId.toString() === command.stewardId && m.status.toString() === 'active' ); if (!stewardMembership || (stewardMembership.role.toString() !== 'owner' && stewardMembership.role.toString() !== 'admin')) { this.logger.warn(`ApplyPenaltyUseCase: Steward ${command.stewardId} does not have authority for league ${race.leagueId}.`); return Result.err({ code: 'INSUFFICIENT_AUTHORITY' }); } this.logger.debug(`ApplyPenaltyUseCase: Steward ${command.stewardId} has authority.`); // If linked to a protest, validate the protest exists and is upheld if (command.protestId) { const protest = await this.protestRepository.findById(command.protestId); if (!protest) { this.logger.warn(`ApplyPenaltyUseCase: Protest with ID ${command.protestId} not found.`); return Result.err({ code: 'PROTEST_NOT_FOUND' }); } if (protest.status.toString() !== 'upheld') { this.logger.warn(`ApplyPenaltyUseCase: Protest ${protest.id} is not upheld. Status: ${protest.status}`); return Result.err({ code: 'PROTEST_NOT_UPHELD' }); } if (protest.raceId !== command.raceId) { this.logger.warn(`ApplyPenaltyUseCase: Protest ${protest.id} is for race ${protest.raceId}, not ${command.raceId}.`); return Result.err({ code: 'PROTEST_NOT_FOR_RACE' }); } this.logger.debug(`ApplyPenaltyUseCase: Protest ${protest.id} is valid and upheld.`); } // Create the penalty const penalty = Penalty.create({ id: randomUUID(), leagueId: race.leagueId, raceId: command.raceId, driverId: command.driverId, type: command.type, ...(command.value !== undefined ? { value: command.value } : {}), reason: command.reason, ...(command.protestId !== undefined ? { protestId: command.protestId } : {}), issuedBy: command.stewardId, status: 'pending', issuedAt: new Date(), ...(command.notes !== undefined ? { notes: command.notes } : {}), }); await this.penaltyRepository.create(penalty); this.logger.info( `ApplyPenaltyUseCase: Successfully applied penalty ${penalty.id} for driver ${command.driverId} in race ${command.raceId}.`, ); const result: ApplyPenaltyResult = { penaltyId: penalty.id }; this.output.present(result); return Result.ok(undefined); } }