166 lines
5.4 KiB
TypeScript
166 lines
5.4 KiB
TypeScript
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 { 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 { Result } from '@core/shared/application/Result';
|
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
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.
|
|
*/
|
|
export class CompleteRaceUseCaseWithRatings {
|
|
constructor(
|
|
private readonly raceRepository: IRaceRepository,
|
|
private readonly raceRegistrationRepository: IRaceRegistrationRepository,
|
|
private readonly resultRepository: IResultRepository,
|
|
private readonly standingRepository: IStandingRepository,
|
|
private readonly driverRatingProvider: DriverRatingProvider,
|
|
private readonly ratingUpdateService: RatingUpdateService,
|
|
private readonly output: UseCaseOutputPort<CompleteRaceWithRatingsResult>,
|
|
) {}
|
|
|
|
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 Result.err({
|
|
code: 'RACE_NOT_FOUND',
|
|
details: { message: 'Race not found' }
|
|
});
|
|
}
|
|
|
|
if (race.status === 'completed') {
|
|
return Result.err({
|
|
code: 'ALREADY_COMPLETED',
|
|
details: { message: 'Race already completed' }
|
|
});
|
|
}
|
|
|
|
const registeredDriverIds = await this.raceRegistrationRepository.getRegisteredDrivers(raceId);
|
|
if (registeredDriverIds.length === 0) {
|
|
return Result.err({
|
|
code: 'NO_REGISTERED_DRIVERS',
|
|
details: { message: 'No registered drivers' }
|
|
});
|
|
}
|
|
|
|
const driverRatings = this.driverRatingProvider.getRatings(registeredDriverIds);
|
|
|
|
const results = RaceResultGenerator.generateRaceResults(raceId, registeredDriverIds, driverRatings);
|
|
|
|
for (const result of results) {
|
|
await this.resultRepository.create(result);
|
|
}
|
|
|
|
await this.updateStandings(race.leagueId, results);
|
|
|
|
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',
|
|
},
|
|
});
|
|
}
|
|
|
|
const completedRace = race.complete();
|
|
await this.raceRepository.update(completedRace);
|
|
|
|
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: RaceResult[]): Promise<void> {
|
|
const resultsByDriver = new Map<string, RaceResult[]>();
|
|
for (const result of results) {
|
|
const driverIdStr = result.driverId.toString();
|
|
const existing = resultsByDriver.get(driverIdStr) || [];
|
|
existing.push(result);
|
|
resultsByDriver.set(driverIdStr, existing);
|
|
}
|
|
|
|
for (const [driverIdStr, driverResults] of resultsByDriver) {
|
|
let standing = await this.standingRepository.findByDriverIdAndLeagueId(driverIdStr, leagueId);
|
|
|
|
if (!standing) {
|
|
standing = Standing.create({
|
|
leagueId,
|
|
driverId: driverIdStr,
|
|
});
|
|
}
|
|
|
|
for (const result of driverResults) {
|
|
standing = standing.addRaceResult(result.position.toNumber(), {
|
|
1: 25,
|
|
2: 18,
|
|
3: 15,
|
|
4: 12,
|
|
5: 10,
|
|
6: 8,
|
|
7: 6,
|
|
8: 4,
|
|
9: 2,
|
|
10: 1,
|
|
});
|
|
}
|
|
|
|
await this.standingRepository.save(standing);
|
|
}
|
|
}
|
|
|
|
private async updateDriverRatings(results: RaceResult[], totalDrivers: number): Promise<void> {
|
|
const driverResults = results.map((result) => ({
|
|
driverId: result.driverId.toString(),
|
|
position: result.position.toNumber(),
|
|
totalDrivers,
|
|
incidents: result.incidents.toNumber(),
|
|
startPosition: result.startPosition.toNumber(),
|
|
}));
|
|
|
|
await this.ratingUpdateService.updateDriverRatingsAfterRace(driverResults);
|
|
}
|
|
}
|