refactor racing use cases
This commit is contained in:
@@ -2,21 +2,38 @@ 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 { DriverRatingProvider } from '../ports/DriverRatingProvider';
|
||||
import { Result } from '../../domain/entities/Result';
|
||||
import { Result as RaceResult } from '../../domain/entities/result/Result';
|
||||
import { Standing } from '../../domain/entities/Standing';
|
||||
import { RaceResultGenerator } from '../utils/RaceResultGenerator';
|
||||
import { RatingUpdateService } from '@core/identity/domain/services/RatingUpdateService';
|
||||
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 CompleteRaceWithRatingsInput {
|
||||
raceId: string;
|
||||
}
|
||||
|
||||
export type CompleteRaceWithRatingsResult = {
|
||||
raceId: string;
|
||||
ratingsUpdatedForDriverIds: string[];
|
||||
};
|
||||
|
||||
export type CompleteRaceWithRatingsErrorCode =
|
||||
| 'RACE_NOT_FOUND'
|
||||
| 'NO_REGISTERED_DRIVERS'
|
||||
| 'ALREADY_COMPLETED'
|
||||
| 'RATING_UPDATE_FAILED'
|
||||
| 'REPOSITORY_ERROR';
|
||||
|
||||
interface DriverRatingProvider {
|
||||
getRatings(driverIds: string[]): Map<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced CompleteRaceUseCase that includes rating updates
|
||||
* Enhanced CompleteRaceUseCase that includes rating updates.
|
||||
*/
|
||||
export class CompleteRaceUseCaseWithRatings
|
||||
implements AsyncUseCase<CompleteRaceCommandDTO, void, string> {
|
||||
export class CompleteRaceUseCaseWithRatings {
|
||||
constructor(
|
||||
private readonly raceRepository: IRaceRepository,
|
||||
private readonly raceRegistrationRepository: IRaceRegistrationRepository,
|
||||
@@ -24,60 +41,77 @@ export class CompleteRaceUseCaseWithRatings
|
||||
private readonly standingRepository: IStandingRepository,
|
||||
private readonly driverRatingProvider: DriverRatingProvider,
|
||||
private readonly ratingUpdateService: RatingUpdateService,
|
||||
private readonly output: UseCaseOutputPort<CompleteRaceWithRatingsResult>,
|
||||
) {}
|
||||
|
||||
async execute(command: CompleteRaceCommandDTO): Promise<SharedResult<void, ApplicationErrorCode<string>>> {
|
||||
async execute(command: CompleteRaceWithRatingsInput): Promise<
|
||||
Result<void, ApplicationErrorCode<CompleteRaceWithRatingsErrorCode, { 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' });
|
||||
}
|
||||
|
||||
if (race.status === 'completed') {
|
||||
return Result.err({ code: 'ALREADY_COMPLETED' });
|
||||
}
|
||||
|
||||
// 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
|
||||
const driverRatings = this.driverRatingProvider.getRatings(registeredDriverIds);
|
||||
|
||||
// Generate realistic race results
|
||||
const results = RaceResultGenerator.generateRaceResults(raceId, registeredDriverIds, driverRatings);
|
||||
|
||||
// Save results
|
||||
for (const result of results) {
|
||||
await this.resultRepository.create(result);
|
||||
}
|
||||
|
||||
// Update standings
|
||||
await this.updateStandings(race.leagueId, results);
|
||||
|
||||
// Update driver ratings based on performance
|
||||
await this.updateDriverRatings(results, registeredDriverIds.length);
|
||||
try {
|
||||
await this.updateDriverRatings(results, registeredDriverIds.length);
|
||||
} catch (error) {
|
||||
return Result.err({
|
||||
code: 'RATING_UPDATE_FAILED',
|
||||
details: {
|
||||
message: error instanceof Error ? error.message : 'Failed to update driver ratings',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Complete the race
|
||||
const completedRace = race.complete();
|
||||
await this.raceRepository.update(completedRace);
|
||||
|
||||
return SharedResult.ok(undefined);
|
||||
} catch {
|
||||
return SharedResult.err({ code: 'UNKNOWN_ERROR' });
|
||||
this.output.present({
|
||||
raceId,
|
||||
ratingsUpdatedForDriverIds: registeredDriverIds,
|
||||
});
|
||||
|
||||
return Result.ok(undefined);
|
||||
} catch (error) {
|
||||
return Result.err({
|
||||
code: 'REPOSITORY_ERROR',
|
||||
details: {
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async updateStandings(leagueId: string, results: Result[]): Promise<void> {
|
||||
// Group results by driver
|
||||
const resultsByDriver = new Map<string, Result[]>();
|
||||
private async updateStandings(leagueId: string, results: RaceResult[]): Promise<void> {
|
||||
const resultsByDriver = new Map<string, RaceResult[]>();
|
||||
for (const result of results) {
|
||||
const existing = resultsByDriver.get(result.driverId) || [];
|
||||
existing.push(result);
|
||||
resultsByDriver.set(result.driverId, existing);
|
||||
}
|
||||
|
||||
// Update or create standings for each driver
|
||||
for (const [driverId, driverResults] of resultsByDriver) {
|
||||
let standing = await this.standingRepository.findByDriverIdAndLeagueId(driverId, leagueId);
|
||||
|
||||
@@ -88,10 +122,18 @@ export class CompleteRaceUseCaseWithRatings
|
||||
});
|
||||
}
|
||||
|
||||
// Add all results for this driver (should be just one for this race)
|
||||
for (const result of driverResults) {
|
||||
standing = standing.addRaceResult(result.position, {
|
||||
1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1
|
||||
1: 25,
|
||||
2: 18,
|
||||
3: 15,
|
||||
4: 12,
|
||||
5: 10,
|
||||
6: 8,
|
||||
7: 6,
|
||||
8: 4,
|
||||
9: 2,
|
||||
10: 1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,8 +141,8 @@ export class CompleteRaceUseCaseWithRatings
|
||||
}
|
||||
}
|
||||
|
||||
private async updateDriverRatings(results: Result[], totalDrivers: number): Promise<void> {
|
||||
const driverResults = results.map(result => ({
|
||||
private async updateDriverRatings(results: RaceResult[], totalDrivers: number): Promise<void> {
|
||||
const driverResults = results.map((result) => ({
|
||||
driverId: result.driverId,
|
||||
position: result.position,
|
||||
totalDrivers,
|
||||
|
||||
Reference in New Issue
Block a user