rating
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
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<CloseAdminVoteSessionOutput> {
|
||||
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<number> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user