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 { 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); } }