refactor racing use cases

This commit is contained in:
2025-12-21 00:43:42 +01:00
parent e9d6f90bb2
commit c12656d671
308 changed files with 14401 additions and 7419 deletions

View File

@@ -1,22 +1,19 @@
import { v4 as uuidv4 } from 'uuid';
import { League } from '../../domain/entities/League';
import { Season } from '../../domain/entities/Season';
import { Season } from '../../domain/entities/season/Season';
import { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository';
import type { AsyncUseCase , Logger } from '@core/shared/application';
import type { GetLeagueScoringPresetByIdInputPort } from '../ports/input/GetLeagueScoringPresetByIdInputPort';
import type { LeagueScoringPresetOutputPort } from '../ports/output/LeagueScoringPresetOutputPort';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import {
LeagueVisibility,
MIN_RANKED_LEAGUE_DRIVERS,
} from '../../domain/value-objects/LeagueVisibility';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { LeagueVisibilityInput } from '../dto/LeagueVisibilityInput';
import type { CreateLeagueWithSeasonAndScoringOutputPort } from '../ports/output/CreateLeagueWithSeasonAndScoringOutputPort';
export interface CreateLeagueWithSeasonAndScoringCommand {
export type CreateLeagueWithSeasonAndScoringCommand = {
name: string;
description?: string;
/**
@@ -24,7 +21,7 @@ export interface CreateLeagueWithSeasonAndScoringCommand {
* - 'ranked' (or legacy 'public'): Competitive, public, affects ratings. Requires min 10 drivers.
* - 'unranked' (or legacy 'private'): Casual with friends, no rating impact.
*/
visibility: LeagueVisibilityInput;
visibility: string;
ownerId: string;
gameId: string;
maxDrivers?: number;
@@ -34,21 +31,37 @@ export interface CreateLeagueWithSeasonAndScoringCommand {
enableNationsChampionship: boolean;
enableTrophyChampionship: boolean;
scoringPresetId?: string;
}
};
export class CreateLeagueWithSeasonAndScoringUseCase
implements AsyncUseCase<CreateLeagueWithSeasonAndScoringCommand, CreateLeagueWithSeasonAndScoringOutputPort, 'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR'> {
export type CreateLeagueWithSeasonAndScoringResult = {
league: League;
season: Season;
scoringConfig: LeagueScoringConfig;
};
type CreateLeagueWithSeasonAndScoringErrorCode =
| 'VALIDATION_ERROR'
| 'UNKNOWN_PRESET'
| 'REPOSITORY_ERROR';
type ScoringPreset = {
id: string;
name: string;
};
export class CreateLeagueWithSeasonAndScoringUseCase {
constructor(
private readonly leagueRepository: ILeagueRepository,
private readonly seasonRepository: ISeasonRepository,
private readonly leagueScoringConfigRepository: ILeagueScoringConfigRepository,
private readonly getLeagueScoringPresetById: (input: GetLeagueScoringPresetByIdInputPort) => Promise<LeagueScoringPresetOutputPort | undefined>,
private readonly getLeagueScoringPresetById: (input: { presetId: string }) => Promise<ScoringPreset | undefined>,
private readonly logger: Logger,
private readonly output: UseCaseOutputPort<CreateLeagueWithSeasonAndScoringResult>,
) {}
async execute(
command: CreateLeagueWithSeasonAndScoringCommand,
): Promise<Result<CreateLeagueWithSeasonAndScoringOutputPort, ApplicationErrorCode<'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR', { message: string }>>> {
): Promise<Result<void, ApplicationErrorCode<CreateLeagueWithSeasonAndScoringErrorCode>>> {
this.logger.debug('Executing CreateLeagueWithSeasonAndScoringUseCase', { command });
const validation = this.validate(command);
if (validation.isErr()) {
@@ -93,8 +106,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase
const presetId = command.scoringPresetId ?? 'club-default';
this.logger.debug(`Attempting to retrieve scoring preset: ${presetId}`);
const preset: LeagueScoringPresetOutputPort | undefined =
await this.getLeagueScoringPresetById({ presetId });
const preset = await this.getLeagueScoringPresetById({ presetId });
if (!preset) {
this.logger.error(`Unknown scoring preset: ${presetId}`);
@@ -102,10 +114,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase
}
this.logger.info(`Scoring preset ${preset.name} (${preset.id}) retrieved.`);
// Note: createScoringConfigFromPreset business logic should be moved to domain layer
// For now, we'll create a basic config structure
const finalConfig = {
id: uuidv4(),
const scoringConfig = LeagueScoringConfig.create({
seasonId,
scoringPresetId: preset.id,
championships: {
@@ -114,26 +123,33 @@ export class CreateLeagueWithSeasonAndScoringUseCase
nations: command.enableNationsChampionship,
trophy: command.enableTrophyChampionship,
},
};
});
this.logger.debug(`Scoring configuration created from preset ${preset.id}.`);
await this.leagueScoringConfigRepository.save(finalConfig);
await this.leagueScoringConfigRepository.save(scoringConfig);
this.logger.info(`Scoring configuration saved for season ${seasonId}.`);
const result: CreateLeagueWithSeasonAndScoringOutputPort = {
leagueId: league.id.toString(),
seasonId,
scoringPresetId: preset.id,
scoringPresetName: preset.name,
const result: CreateLeagueWithSeasonAndScoringResult = {
league,
season,
scoringConfig,
};
this.logger.debug('CreateLeagueWithSeasonAndScoringUseCase completed successfully.', { result });
return Result.ok(result);
this.output.present(result);
return Result.ok(undefined);
} catch (error) {
return Result.err({ code: 'REPOSITORY_ERROR', details: { message: 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, ApplicationErrorCode<'VALIDATION_ERROR', { message: string }>> {
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 });