/** * Application Use Case: ReviewProtestUseCase * * Allows a steward to review a protest and make a decision (uphold or dismiss). */ import type { IProtestRepository } from '../../domain/repositories/IProtestRepository'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; export type ReviewProtestErrorCode = 'PROTEST_NOT_FOUND' | 'RACE_NOT_FOUND' | 'NOT_LEAGUE_ADMIN' | 'REPOSITORY_ERROR'; export type ReviewProtestApplicationError = ApplicationErrorCode; export interface ReviewProtestInput { protestId: string; stewardId: string; decision: 'uphold' | 'dismiss'; decisionNotes: string; } export interface ReviewProtestResult { leagueId: string; protestId: string; status: 'upheld' | 'dismissed'; } export class ReviewProtestUseCase { constructor( private readonly protestRepository: IProtestRepository, private readonly raceRepository: IRaceRepository, private readonly leagueMembershipRepository: ILeagueMembershipRepository, private readonly logger: Logger, private readonly output: UseCaseOutputPort, ) {} async execute(input: ReviewProtestInput): Promise> { this.logger.debug('Executing ReviewProtestUseCase', { input }); try { // Load the protest const protest = await this.protestRepository.findById(input.protestId); if (!protest) { this.logger.warn('Protest not found', { protestId: input.protestId }); return Result.err({ code: 'PROTEST_NOT_FOUND', details: { message: 'Protest not found' } }); } // Load the race to get league ID const race = await this.raceRepository.findById(protest.raceId); if (!race) { this.logger.warn('Race not found for protest', { protestId: input.protestId, raceId: protest.raceId }); return Result.err({ code: 'RACE_NOT_FOUND', details: { message: 'Race not 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 === input.stewardId && m.status === 'active' ); if (!stewardMembership || (stewardMembership.role !== 'owner' && stewardMembership.role !== 'admin')) { this.logger.warn('Unauthorized steward attempting to review protest', { stewardId: input.stewardId, leagueId: race.leagueId }); return Result.err({ code: 'NOT_LEAGUE_ADMIN', details: { message: 'Only league owners and admins can review protests' } }); } // Apply the decision const updatedProtest = input.decision === 'uphold' ? protest.uphold(input.stewardId, input.decisionNotes) : protest.dismiss(input.stewardId, input.decisionNotes); await this.protestRepository.update(updatedProtest); const result: ReviewProtestResult = { leagueId: race.leagueId, protestId: typeof protest.id === 'string' ? protest.id : (protest as any).id, status: input.decision === 'uphold' ? 'upheld' : 'dismissed', }; this.output.present(result); this.logger.info('Protest reviewed successfully', { protestId: result.protestId, leagueId: result.leagueId, status: result.status, }); return Result.ok(undefined); } catch (error) { const message = error instanceof Error ? error.message : 'Failed to review protest'; this.logger.error('Failed to review protest', { error: message }); return Result.err({ code: 'REPOSITORY_ERROR', details: { message } }); } } }