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,65 +1,128 @@
import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';
import type { LeagueScheduleDTO } from '../dto/LeagueScheduleDTO';
import { scheduleDTOToSeasonSchedule } from '../dto/LeagueScheduleDTO';
import type { LeagueSchedulePreviewOutputPort } from '../ports/output/LeagueSchedulePreviewOutputPort';
import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import type { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
interface PreviewLeagueScheduleQueryParams {
schedule: LeagueScheduleDTO;
export type PreviewLeagueScheduleSeasonConfig = {
seasonStartDate: string;
recurrenceStrategy: string;
weekdays?: string[];
raceStartTime: string;
timezoneId: string;
plannedRounds: number;
intervalWeeks?: number;
monthlyOrdinal?: 1 | 2 | 3 | 4;
monthlyWeekday?: string;
};
export type PreviewLeagueScheduleInput = {
schedule: PreviewLeagueScheduleSeasonConfig;
maxRounds?: number;
};
export interface PreviewLeagueScheduleRound {
roundNumber: number;
scheduledAt: string;
timezoneId: string;
}
type PreviewLeagueScheduleErrorCode = 'INVALID_SCHEDULE';
export interface PreviewLeagueScheduleResult {
rounds: PreviewLeagueScheduleRound[];
summary: string;
}
type PreviewLeagueScheduleApplicationError = ApplicationErrorCode<PreviewLeagueScheduleErrorCode, { message: string }>;
export type PreviewLeagueScheduleErrorCode =
| 'INVALID_SCHEDULE'
| 'REPOSITORY_ERROR';
export class PreviewLeagueScheduleUseCase implements AsyncUseCase<PreviewLeagueScheduleQueryParams, LeagueSchedulePreviewOutputPort, PreviewLeagueScheduleErrorCode> {
export class PreviewLeagueScheduleUseCase {
constructor(
private readonly scheduleGenerator: typeof SeasonScheduleGenerator = SeasonScheduleGenerator,
private readonly scheduleGenerator: Pick<
typeof SeasonScheduleGenerator,
'generateSlotsUpTo'
> = SeasonScheduleGenerator,
private readonly logger: Logger,
private readonly output: UseCaseOutputPort<PreviewLeagueScheduleResult>,
) {}
async execute(params: PreviewLeagueScheduleQueryParams): Promise<Result<LeagueSchedulePreviewOutputPort, PreviewLeagueScheduleApplicationError>> {
async execute(
params: PreviewLeagueScheduleInput,
): Promise<
Result<
void,
ApplicationErrorCode<PreviewLeagueScheduleErrorCode, { message: string }>
>
> {
this.logger.debug('Previewing league schedule', { params });
let seasonSchedule: SeasonSchedule;
try {
seasonSchedule = scheduleDTOToSeasonSchedule(params.schedule);
let seasonSchedule: SeasonSchedule;
try {
seasonSchedule = scheduleDTOToSeasonSchedule(params.schedule as any);
} catch (error) {
this.logger.warn('Invalid schedule data provided', {
schedule: params.schedule,
error: error instanceof Error ? error.message : 'Unknown error',
});
return Result.err({
code: 'INVALID_SCHEDULE',
details: { message: 'Invalid schedule data' },
});
}
const maxRounds =
params.maxRounds && params.maxRounds > 0
? Math.min(params.maxRounds, seasonSchedule.plannedRounds)
: seasonSchedule.plannedRounds;
const slots = this.scheduleGenerator.generateSlotsUpTo(
seasonSchedule,
maxRounds,
);
const rounds: PreviewLeagueScheduleRound[] = slots.map((slot) => ({
roundNumber: slot.roundNumber,
scheduledAt: slot.scheduledAt.toISOString(),
timezoneId: slot.timezone.id,
}));
const summary = this.buildSummary(params.schedule, rounds);
const result: PreviewLeagueScheduleResult = {
rounds,
summary,
};
this.logger.info('Successfully generated league schedule preview', {
roundCount: rounds.length,
});
this.output.present(result);
return Result.ok(undefined);
} catch (error) {
this.logger.warn('Invalid schedule data provided', { schedule: params.schedule, error: error instanceof Error ? error.message : 'Unknown error' });
return Result.err({ code: 'INVALID_SCHEDULE', details: { message: 'Invalid schedule data' } });
this.logger.error(
'Failed to preview league schedule due to an unexpected error',
error instanceof Error ? error : new Error('Unknown error'),
);
return Result.err({
code: 'REPOSITORY_ERROR',
details: {
message:
error instanceof Error
? error.message
: 'Failed to preview league schedule',
},
});
}
const maxRounds =
params.maxRounds && params.maxRounds > 0
? Math.min(params.maxRounds, seasonSchedule.plannedRounds)
: seasonSchedule.plannedRounds;
const slots = this.scheduleGenerator.generateSlotsUpTo(seasonSchedule, maxRounds);
const rounds = slots.map((slot) => ({
roundNumber: slot.roundNumber,
scheduledAt: slot.scheduledAt.toISOString(),
timezoneId: slot.timezone.getId(),
}));
const summary = this.buildSummary(params.schedule, rounds);
const result: LeagueSchedulePreviewOutputPort = {
rounds,
summary,
};
this.logger.info('Successfully generated league schedule preview', { roundCount: rounds.length });
return Result.ok(result);
}
private buildSummary(
schedule: LeagueScheduleDTO,
schedule: PreviewLeagueScheduleSeasonConfig,
rounds: Array<{ roundNumber: number; scheduledAt: string; timezoneId: string }>,
): string {
if (rounds.length === 0) {