123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
import { RatingEvent, type RatingEventProps } from '../../domain/entities/RatingEvent';
|
|
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
|
|
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
|
|
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
|
|
import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator';
|
|
import { RatingDelta } from '../../domain/value-objects/RatingDelta';
|
|
import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey';
|
|
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
|
|
import { CreateRatingEventDto } from '../dtos/CreateRatingEventDto';
|
|
|
|
/**
|
|
* Input for AppendRatingEventsUseCase
|
|
*/
|
|
export interface AppendRatingEventsInput {
|
|
userId: string;
|
|
events?: CreateRatingEventDto[]; // Optional: direct event creation
|
|
// Alternative: raceId, penaltyId, etc. for factory-based creation
|
|
raceId?: string;
|
|
raceResults?: Array<{
|
|
position: number;
|
|
totalDrivers: number;
|
|
startPosition: number;
|
|
incidents: number;
|
|
fieldStrength: number;
|
|
status: 'finished' | 'dnf' | 'dns' | 'dsq' | 'afk';
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* Output for AppendRatingEventsUseCase
|
|
*/
|
|
export interface AppendRatingEventsOutput {
|
|
events: string[]; // Event IDs
|
|
snapshotUpdated: boolean;
|
|
}
|
|
|
|
/**
|
|
* Use Case: AppendRatingEventsUseCase
|
|
*
|
|
* Appends rating events to the ledger and recomputes the snapshot.
|
|
* Follows CQRS Light: command side operation.
|
|
*/
|
|
export class AppendRatingEventsUseCase {
|
|
constructor(
|
|
private readonly ratingEventRepository: RatingEventRepository,
|
|
private readonly userRatingRepository: UserRatingRepository,
|
|
) {}
|
|
|
|
async execute(input: AppendRatingEventsInput): Promise<AppendRatingEventsOutput> {
|
|
const eventsToSave = [];
|
|
|
|
// 1. Create events from direct input
|
|
if (input.events) {
|
|
for (const eventDto of input.events) {
|
|
const event = this.createEventFromDto(eventDto);
|
|
eventsToSave.push(event);
|
|
}
|
|
}
|
|
|
|
// 2. Create events from race results (using factory)
|
|
if (input.raceId && input.raceResults) {
|
|
for (const result of input.raceResults) {
|
|
const raceEvents = RatingEventFactory.createFromRaceFinish({
|
|
userId: input.userId,
|
|
raceId: input.raceId,
|
|
position: result.position,
|
|
totalDrivers: result.totalDrivers,
|
|
startPosition: result.startPosition,
|
|
incidents: result.incidents,
|
|
fieldStrength: result.fieldStrength,
|
|
status: result.status,
|
|
});
|
|
eventsToSave.push(...raceEvents);
|
|
}
|
|
}
|
|
|
|
// 3. Save all events to ledger
|
|
const savedEventIds: string[] = [];
|
|
for (const event of eventsToSave) {
|
|
await this.ratingEventRepository.save(event);
|
|
savedEventIds.push(event.id.value);
|
|
}
|
|
|
|
// 4. Recompute snapshot if events were saved
|
|
let snapshotUpdated = false;
|
|
if (savedEventIds.length > 0) {
|
|
const allEvents = await this.ratingEventRepository.getAllByUserId(input.userId);
|
|
const snapshot = RatingSnapshotCalculator.calculate(input.userId, allEvents);
|
|
await this.userRatingRepository.save(snapshot);
|
|
snapshotUpdated = true;
|
|
}
|
|
|
|
return {
|
|
events: savedEventIds,
|
|
snapshotUpdated,
|
|
};
|
|
}
|
|
|
|
private createEventFromDto(dto: CreateRatingEventDto) {
|
|
const props: RatingEventProps = {
|
|
id: RatingEventId.generate(),
|
|
userId: dto.userId,
|
|
dimension: RatingDimensionKey.create(dto.dimension),
|
|
delta: RatingDelta.create(dto.delta),
|
|
occurredAt: dto.occurredAt ? new Date(dto.occurredAt) : new Date(),
|
|
createdAt: new Date(),
|
|
source: { type: dto.sourceType, id: dto.sourceId },
|
|
reason: {
|
|
code: dto.reasonCode,
|
|
summary: dto.reasonSummary,
|
|
details: dto.reasonDetails || {},
|
|
},
|
|
visibility: { public: true, redactedFields: [] },
|
|
version: 1,
|
|
};
|
|
|
|
if (dto.weight !== undefined) {
|
|
props.weight = dto.weight;
|
|
}
|
|
|
|
return RatingEvent.create(props);
|
|
}
|
|
} |