import { IAdminVoteSessionRepository } from '../../domain/repositories/IAdminVoteSessionRepository'; import { IRatingEventRepository } from '../../domain/repositories/IRatingEventRepository'; import { IUserRatingRepository } from '../../domain/repositories/IUserRatingRepository'; import { AdminTrustRatingCalculator } from '../../domain/services/AdminTrustRatingCalculator'; import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator'; import { RatingEventFactory } from '../../domain/services/RatingEventFactory'; import { CloseAdminVoteSessionInput, CloseAdminVoteSessionOutput } from '../dtos/AdminVoteSessionDto'; /** * Use Case: CloseAdminVoteSessionUseCase * * Closes an admin vote session and generates rating events. * This is the key use case that triggers events on close per plans section 7.1.1. * * Flow: * 1. Load and validate session * 2. Close session and calculate outcome * 3. Create rating events from outcome * 4. Append events to ledger for each affected admin * 5. Recompute snapshots * * Per plans section 7.1.1 and 10.2 */ export class CloseAdminVoteSessionUseCase { constructor( private readonly adminVoteSessionRepository: IAdminVoteSessionRepository, private readonly ratingEventRepository: IRatingEventRepository, private readonly userRatingRepository: IUserRatingRepository, private readonly appendRatingEventsUseCase: any, // Will be typed properly in integration ) {} async execute(input: CloseAdminVoteSessionInput): Promise { try { // Validate input const errors = this.validateInput(input); if (errors.length > 0) { return { success: false, voteSessionId: input.voteSessionId, errors, }; } // Load the vote session const session = await this.adminVoteSessionRepository.findById(input.voteSessionId); if (!session) { return { success: false, voteSessionId: input.voteSessionId, errors: ['Vote session not found'], }; } // Validate admin ownership if (session.adminId !== input.adminId) { return { success: false, voteSessionId: input.voteSessionId, errors: ['Admin does not own this vote session'], }; } // Check if already closed if (session.closed) { return { success: false, voteSessionId: input.voteSessionId, errors: ['Vote session is already closed'], }; } // Check if within voting window const now = new Date(); if (now < session.startDate || now > session.endDate) { return { success: false, voteSessionId: input.voteSessionId, errors: ['Cannot close session outside the voting window'], }; } // Close session and calculate outcome const outcome = session.close(); // Save closed session await this.adminVoteSessionRepository.save(session); // Create rating events from outcome // Per plans: events are created for the admin being voted on const eventsCreated = await this.createRatingEvents(session, outcome); return { success: true, voteSessionId: input.voteSessionId, outcome: { percentPositive: outcome.percentPositive, count: outcome.count, eligibleVoterCount: outcome.eligibleVoterCount, participationRate: outcome.participationRate, outcome: outcome.outcome, }, eventsCreated, }; } catch (error) { const errorMsg = error instanceof Error ? error.message : 'Unknown error'; return { success: false, voteSessionId: input.voteSessionId, errors: [`Failed to close vote session: ${errorMsg}`], }; } } /** * Create rating events from vote outcome * Events are created for the admin being voted on */ private async createRatingEvents(session: any, outcome: any): Promise { let eventsCreated = 0; // Use RatingEventFactory to create vote outcome events const voteInput = { userId: session.adminId, // The admin being voted on voteSessionId: session.id, outcome: (outcome.outcome === 'positive' ? 'positive' : 'negative') as 'positive' | 'negative', voteCount: outcome.count.total, eligibleVoterCount: outcome.eligibleVoterCount, percentPositive: outcome.percentPositive, }; const events = RatingEventFactory.createFromVote(voteInput); // Save each event to ledger for (const event of events) { await this.ratingEventRepository.save(event); eventsCreated++; } // Recompute snapshot for the admin if (eventsCreated > 0) { const allEvents = await this.ratingEventRepository.getAllByUserId(session.adminId); const snapshot = RatingSnapshotCalculator.calculate(session.adminId, allEvents); await this.userRatingRepository.save(snapshot); } return eventsCreated; } private validateInput(input: CloseAdminVoteSessionInput): string[] { const errors: string[] = []; if (!input.voteSessionId || input.voteSessionId.trim().length === 0) { errors.push('voteSessionId is required'); } if (!input.adminId || input.adminId.trim().length === 0) { errors.push('adminId is required'); } return errors; } }