Files
gridpilot.gg/packages/racing/application/use-cases/ReviewProtestUseCase.ts
2025-12-09 10:32:59 +01:00

58 lines
2.0 KiB
TypeScript

/**
* 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';
export interface ReviewProtestCommand {
protestId: string;
stewardId: string;
decision: 'uphold' | 'dismiss';
decisionNotes: string;
}
export class ReviewProtestUseCase {
constructor(
private readonly protestRepository: IProtestRepository,
private readonly raceRepository: IRaceRepository,
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
) {}
async execute(command: ReviewProtestCommand): Promise<void> {
// Load the protest
const protest = await this.protestRepository.findById(command.protestId);
if (!protest) {
throw new Error('Protest not found');
}
// Load the race to get league ID
const race = await this.raceRepository.findById(protest.raceId);
if (!race) {
throw new Error('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 === command.stewardId && m.status === 'active'
);
if (!stewardMembership || (stewardMembership.role !== 'owner' && stewardMembership.role !== 'admin')) {
throw new Error('Only league owners and admins can review protests');
}
// Apply the decision
let updatedProtest;
if (command.decision === 'uphold') {
updatedProtest = protest.uphold(command.stewardId, command.decisionNotes);
} else {
updatedProtest = protest.dismiss(command.stewardId, command.decisionNotes);
}
await this.protestRepository.update(updatedProtest);
}
}