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

@@ -14,13 +14,33 @@ import {
LeagueVisibility,
MIN_RANKED_LEAGUE_DRIVERS,
} from '../../domain/value-objects/LeagueVisibility';
import { Result } from '@core/shared/result/Result';
import { RacingDomainValidationError } from '../../domain/errors/RacingDomainError';
import type { CreateLeagueWithSeasonAndScoringCommand } from './CreateLeagueWithSeasonAndScoringCommand';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { LeagueVisibilityInput } from './LeagueVisibilityInput';
import type { CreateLeagueWithSeasonAndScoringResultDTO } from '../dto/CreateLeagueWithSeasonAndScoringResultDTO';
export interface CreateLeagueWithSeasonAndScoringCommand {
name: string;
description?: string;
/**
* League visibility/ranking mode.
* - 'ranked' (or legacy 'public'): Competitive, public, affects ratings. Requires min 10 drivers.
* - 'unranked' (or legacy 'private'): Casual with friends, no rating impact.
*/
visibility: LeagueVisibilityInput;
ownerId: string;
gameId: string;
maxDrivers?: number;
maxTeams?: number;
enableDriverChampionship: boolean;
enableTeamChampionship: boolean;
enableNationsChampionship: boolean;
enableTrophyChampionship: boolean;
scoringPresetId?: string;
}
export class CreateLeagueWithSeasonAndScoringUseCase
implements AsyncUseCase<CreateLeagueWithSeasonAndScoringCommand, Result<CreateLeagueWithSeasonAndScoringResultDTO, RacingDomainValidationError>> {
implements AsyncUseCase<CreateLeagueWithSeasonAndScoringCommand, CreateLeagueWithSeasonAndScoringResultDTO, 'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR'> {
constructor(
private readonly leagueRepository: ILeagueRepository,
private readonly seasonRepository: ISeasonRepository,
@@ -31,7 +51,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase
async execute(
command: CreateLeagueWithSeasonAndScoringCommand,
): Promise<Result<CreateLeagueWithSeasonAndScoringResultDTO, RacingDomainValidationError>> {
): Promise<Result<CreateLeagueWithSeasonAndScoringResultDTO, ApplicationErrorCode<'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR', { message: string }>>> {
this.logger.debug('Executing CreateLeagueWithSeasonAndScoringUseCase', { command });
const validation = this.validate(command);
if (validation.isErr()) {
@@ -81,7 +101,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase
if (!preset) {
this.logger.error(`Unknown scoring preset: ${presetId}`);
return Result.err(new RacingDomainValidationError(`Unknown scoring preset: ${presetId}`));
return Result.err({ code: 'UNKNOWN_PRESET', details: { message: `Unknown scoring preset: ${presetId}` } });
}
this.logger.info(`Scoring preset ${preset.name} (${preset.id}) retrieved.`);
@@ -101,31 +121,31 @@ export class CreateLeagueWithSeasonAndScoringUseCase
this.logger.debug('CreateLeagueWithSeasonAndScoringUseCase completed successfully.', { result });
return Result.ok(result);
} catch (error) {
return Result.err(new RacingDomainValidationError(error instanceof Error ? error.message : 'Unknown error'));
return Result.err({ code: 'REPOSITORY_ERROR', details: { message: error instanceof Error ? error.message : 'Unknown error' } });
}
}
private validate(command: CreateLeagueWithSeasonAndScoringCommand): Result<void, RacingDomainValidationError> {
private validate(command: CreateLeagueWithSeasonAndScoringCommand): Result<void, ApplicationErrorCode<'VALIDATION_ERROR', { message: string }>> {
this.logger.debug('Validating CreateLeagueWithSeasonAndScoringCommand', { command });
if (!command.name || command.name.trim().length === 0) {
this.logger.warn('Validation failed: League name is required', { command });
return Result.err(new RacingDomainValidationError('League name is required'));
return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'League name is required' } });
}
if (!command.ownerId || command.ownerId.trim().length === 0) {
this.logger.warn('Validation failed: League ownerId is required', { command });
return Result.err(new RacingDomainValidationError('League ownerId is required'));
return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'League ownerId is required' } });
}
if (!command.gameId || command.gameId.trim().length === 0) {
this.logger.warn('Validation failed: gameId is required', { command });
return Result.err(new RacingDomainValidationError('gameId is required'));
return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'gameId is required' } });
}
if (!command.visibility) {
this.logger.warn('Validation failed: visibility is required', { command });
return Result.err(new RacingDomainValidationError('visibility is required'));
return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'visibility is required' } });
}
if (command.maxDrivers !== undefined && command.maxDrivers <= 0) {
this.logger.warn('Validation failed: maxDrivers must be greater than 0 when provided', { command });
return Result.err(new RacingDomainValidationError('maxDrivers must be greater than 0 when provided'));
return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'maxDrivers must be greater than 0 when provided' } });
}
const visibility = LeagueVisibility.fromString(command.visibility);
@@ -137,11 +157,11 @@ export class CreateLeagueWithSeasonAndScoringUseCase
`Validation failed: Ranked leagues require at least ${MIN_RANKED_LEAGUE_DRIVERS} drivers. Current setting: ${driverCount}.`,
{ command }
);
return Result.err(new RacingDomainValidationError(
return Result.err({ code: 'VALIDATION_ERROR', details: { message:
`Ranked leagues require at least ${MIN_RANKED_LEAGUE_DRIVERS} drivers. ` +
`Current setting: ${driverCount}. ` +
`For smaller groups, consider creating an Unranked (Friends) league instead.`
));
} });
}
}
this.logger.debug('Validation successful.');