This commit is contained in:
2025-12-16 21:05:01 +01:00
parent f61e3a4e5a
commit 7532c7ed6d
207 changed files with 7861 additions and 2606 deletions

View File

@@ -12,6 +12,12 @@ import type { ILeagueMembershipRepository } from '../../domain/repositories/ILea
import { randomUUID } from 'crypto';
import type { AsyncUseCase } from '@core/shared/application';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
type QuickPenaltyErrorCode = 'RACE_NOT_FOUND' | 'UNAUTHORIZED' | 'UNKNOWN_INFRACTION' | 'REPOSITORY_ERROR';
type QuickPenaltyApplicationError = ApplicationErrorCode<QuickPenaltyErrorCode, { message: string }>;
export interface QuickPenaltyCommand {
raceId: string;
@@ -23,7 +29,7 @@ export interface QuickPenaltyCommand {
}
export class QuickPenaltyUseCase
implements AsyncUseCase<QuickPenaltyCommand, { penaltyId: string }> {
implements AsyncUseCase<QuickPenaltyCommand, { penaltyId: string }, QuickPenaltyErrorCode> {
constructor(
private readonly penaltyRepository: IPenaltyRepository,
private readonly raceRepository: IRaceRepository,
@@ -31,14 +37,15 @@ export class QuickPenaltyUseCase
private readonly logger: Logger,
) {}
async execute(command: QuickPenaltyCommand): Promise<{ penaltyId: string }> {
async execute(command: QuickPenaltyCommand): Promise<Result<{ penaltyId: string }, QuickPenaltyApplicationError>> {
this.logger.debug('Executing QuickPenaltyUseCase', { command });
try {
// Validate race exists
const race = await this.raceRepository.findById(command.raceId);
if (!race) {
this.logger.warn('Race not found', { raceId: command.raceId });
throw new Error('Race not found');
return Result.err({ code: 'RACE_NOT_FOUND', details: { message: 'Race not found' } });
}
// Validate admin has authority
@@ -49,15 +56,22 @@ export class QuickPenaltyUseCase
if (!adminMembership || (adminMembership.role !== 'owner' && adminMembership.role !== 'admin')) {
this.logger.warn('Unauthorized admin attempting to issue penalty', { adminId: command.adminId, leagueId: race.leagueId });
throw new Error('Only league owners and admins can issue penalties');
return Result.err({ code: 'UNAUTHORIZED', details: { message: 'Only league owners and admins can issue penalties' } });
}
// Map infraction + severity to penalty type and value
const { type, value, reason } = this.mapInfractionToPenalty(
const penaltyMapping = this.mapInfractionToPenalty(
command.infractionType,
command.severity
);
if (!penaltyMapping) {
this.logger.error('Unknown infraction type', { infractionType: command.infractionType, severity: command.severity });
return Result.err({ code: 'UNKNOWN_INFRACTION', details: { message: 'Unknown infraction type' } });
}
const { type, value, reason } = penaltyMapping;
// Create the penalty
const penalty = Penalty.create({
id: randomUUID(),
@@ -77,17 +91,17 @@ export class QuickPenaltyUseCase
await this.penaltyRepository.create(penalty);
this.logger.info('Quick penalty applied successfully', { penaltyId: penalty.id, raceId: command.raceId, driverId: command.driverId });
return { penaltyId: penalty.id };
return Result.ok({ penaltyId: penalty.id });
} catch (error) {
this.logger.error('Failed to apply quick penalty', { command, error: error.message });
throw error;
this.logger.error('Failed to apply quick penalty', { error: error instanceof Error ? error.message : 'Unknown error' });
return Result.err({ code: 'REPOSITORY_ERROR', details: { message: error instanceof Error ? error.message : 'Unknown error' } });
}
}
private mapInfractionToPenalty(
infractionType: QuickPenaltyCommand['infractionType'],
severity: QuickPenaltyCommand['severity']
): { type: PenaltyType; value?: number; reason: string } {
): { type: PenaltyType; value?: number; reason: string } | null {
const severityMultipliers = {
warning: 1,
minor: 2,
@@ -143,8 +157,7 @@ export class QuickPenaltyUseCase
};
default:
this.logger.error(`Unknown infraction type: ${infractionType}`);
throw new Error(`Unknown infraction type: ${infractionType}`);
return null;
}
}
}