refactor racing use cases
This commit is contained in:
@@ -2,14 +2,22 @@ import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'
|
||||
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
|
||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
|
||||
import type { GetDriverRatingInputPort } from '../ports/input/GetDriverRatingInputPort';
|
||||
import type { GetDriverRatingOutputPort } from '../ports/output/GetDriverRatingOutputPort';
|
||||
import { Result } from '../../domain/entities/Result';
|
||||
import { Result as RaceResult } from '../../domain/entities/result/Result';
|
||||
import { Standing } from '../../domain/entities/Standing';
|
||||
import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result as SharedResult } from '@core/shared/application/Result';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { CompleteRaceCommandDTO } from '../dto/CompleteRaceCommandDTO';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
export interface CompleteRaceInput {
|
||||
raceId: string;
|
||||
}
|
||||
|
||||
export type CompleteRaceResult = {
|
||||
raceId: string;
|
||||
registeredDriverIds: string[];
|
||||
};
|
||||
|
||||
export type CompleteRaceErrorCode = 'RACE_NOT_FOUND' | 'NO_REGISTERED_DRIVERS' | 'REPOSITORY_ERROR';
|
||||
|
||||
/**
|
||||
* Use Case: CompleteRaceUseCase
|
||||
@@ -22,41 +30,52 @@ import type { CompleteRaceCommandDTO } from '../dto/CompleteRaceCommandDTO';
|
||||
* - updates league standings
|
||||
* - persists all changes via repositories.
|
||||
*/
|
||||
export class CompleteRaceUseCase
|
||||
implements AsyncUseCase<CompleteRaceCommandDTO, {}, string> {
|
||||
interface DriverRatingInput {
|
||||
driverId: string;
|
||||
}
|
||||
|
||||
interface DriverRatingOutput {
|
||||
rating: number | null;
|
||||
ratingChange: number | null;
|
||||
}
|
||||
|
||||
export class CompleteRaceUseCase {
|
||||
constructor(
|
||||
private readonly raceRepository: IRaceRepository,
|
||||
private readonly raceRegistrationRepository: IRaceRegistrationRepository,
|
||||
private readonly resultRepository: IResultRepository,
|
||||
private readonly standingRepository: IStandingRepository,
|
||||
private readonly getDriverRating: (input: GetDriverRatingInputPort) => Promise<GetDriverRatingOutputPort>,
|
||||
private readonly getDriverRating: (input: DriverRatingInput) => Promise<DriverRatingOutput>,
|
||||
private readonly output: UseCaseOutputPort<CompleteRaceResult>,
|
||||
) {}
|
||||
|
||||
async execute(command: CompleteRaceCommandDTO): Promise<SharedResult<{}, ApplicationErrorCode<string>>> {
|
||||
async execute(command: CompleteRaceInput): Promise<
|
||||
Result<void, ApplicationErrorCode<CompleteRaceErrorCode | 'REPOSITORY_ERROR', { message: string }>>
|
||||
> {
|
||||
try {
|
||||
const { raceId } = command;
|
||||
|
||||
const race = await this.raceRepository.findById(raceId);
|
||||
if (!race) {
|
||||
return SharedResult.err({ code: 'RACE_NOT_FOUND' });
|
||||
return Result.err({ code: 'RACE_NOT_FOUND' });
|
||||
}
|
||||
|
||||
// Get registered drivers for this race
|
||||
const registeredDriverIds = await this.raceRegistrationRepository.getRegisteredDrivers(raceId);
|
||||
if (registeredDriverIds.length === 0) {
|
||||
return SharedResult.err({ code: 'NO_REGISTERED_DRIVERS' });
|
||||
return Result.err({ code: 'NO_REGISTERED_DRIVERS' });
|
||||
}
|
||||
|
||||
// Get driver ratings using clean ports
|
||||
const ratingPromises = registeredDriverIds.map(driverId =>
|
||||
this.getDriverRating({ driverId })
|
||||
// Get driver ratings using injected provider
|
||||
const ratingPromises = registeredDriverIds.map((driverId) =>
|
||||
this.getDriverRating({ driverId }),
|
||||
);
|
||||
|
||||
|
||||
const ratingResults = await Promise.all(ratingPromises);
|
||||
const driverRatings = new Map<string, number>();
|
||||
|
||||
|
||||
registeredDriverIds.forEach((driverId, index) => {
|
||||
const rating = ratingResults[index].rating;
|
||||
const rating = ratingResults[index]?.rating ?? null;
|
||||
if (rating !== null) {
|
||||
driverRatings.set(driverId, rating);
|
||||
}
|
||||
@@ -77,17 +96,24 @@ export class CompleteRaceUseCase
|
||||
const completedRace = race.complete();
|
||||
await this.raceRepository.update(completedRace);
|
||||
|
||||
return SharedResult.ok({});
|
||||
} catch {
|
||||
return SharedResult.err({ code: 'UNKNOWN_ERROR' });
|
||||
this.output.present({ raceId, registeredDriverIds });
|
||||
|
||||
return Result.ok(undefined);
|
||||
} catch (error) {
|
||||
return Result.err({
|
||||
code: 'REPOSITORY_ERROR',
|
||||
details: {
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private generateRaceResults(
|
||||
raceId: string,
|
||||
driverIds: string[],
|
||||
driverRatings: Map<string, number>
|
||||
): Result[] {
|
||||
driverRatings: Map<string, number>,
|
||||
): RaceResult[] {
|
||||
// Create driver performance data
|
||||
const driverPerformances = driverIds.map(driverId => ({
|
||||
driverId,
|
||||
@@ -114,7 +140,7 @@ export class CompleteRaceUseCase
|
||||
});
|
||||
|
||||
// Generate results
|
||||
const results: Result[] = [];
|
||||
const results: RaceResult[] = [];
|
||||
for (let i = 0; i < driverPerformances.length; i++) {
|
||||
const { driverId } = driverPerformances[i]!;
|
||||
const position = i + 1;
|
||||
@@ -130,7 +156,7 @@ export class CompleteRaceUseCase
|
||||
const incidents = Math.random() < incidentProbability ? Math.floor(Math.random() * 3) + 1 : 0;
|
||||
|
||||
results.push(
|
||||
Result.create({
|
||||
RaceResult.create({
|
||||
id: `${raceId}-${driverId}`,
|
||||
raceId,
|
||||
driverId,
|
||||
@@ -138,16 +164,16 @@ export class CompleteRaceUseCase
|
||||
startPosition,
|
||||
fastestLap,
|
||||
incidents,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async updateStandings(leagueId: string, results: Result[]): Promise<void> {
|
||||
private async updateStandings(leagueId: string, results: RaceResult[]): Promise<void> {
|
||||
// Group results by driver
|
||||
const resultsByDriver = new Map<string, Result[]>();
|
||||
const resultsByDriver = new Map<string, RaceResult[]>();
|
||||
for (const result of results) {
|
||||
const existing = resultsByDriver.get(result.driverId) || [];
|
||||
existing.push(result);
|
||||
|
||||
Reference in New Issue
Block a user