/** * Get Driver Rankings Use Case * * Orchestrates the retrieval of driver rankings data. * Aggregates data from repositories and returns drivers with search, filter, and sort capabilities. */ import { LeaderboardsRepository } from '../ports/LeaderboardsRepository'; import { LeaderboardsEventPublisher } from '../ports/LeaderboardsEventPublisher'; import { DriverRankingsQuery, DriverRankingsResult, DriverRankingEntry, PaginationMetadata, } from '../ports/DriverRankingsQuery'; import { ValidationError } from '../../../shared/errors/ValidationError'; export interface GetDriverRankingsUseCasePorts { leaderboardsRepository: LeaderboardsRepository; eventPublisher: LeaderboardsEventPublisher; } export class GetDriverRankingsUseCase { constructor(private readonly ports: GetDriverRankingsUseCasePorts) {} async execute(query: DriverRankingsQuery = {}): Promise { try { // Validate query parameters this.validateQuery(query); const page = query.page ?? 1; const limit = query.limit ?? 20; // Fetch all drivers const allDrivers = await this.ports.leaderboardsRepository.findAllDrivers(); // Apply search filter let filteredDrivers = allDrivers; if (query.search) { const searchLower = query.search.toLowerCase(); filteredDrivers = filteredDrivers.filter((driver) => driver.name.toLowerCase().includes(searchLower), ); } // Apply rating filter if (query.minRating !== undefined) { filteredDrivers = filteredDrivers.filter( (driver) => driver.rating >= query.minRating!, ); } // Apply team filter if (query.teamId) { filteredDrivers = filteredDrivers.filter( (driver) => driver.teamId === query.teamId, ); } // Sort drivers const sortBy = query.sortBy ?? 'rating'; const sortOrder = query.sortOrder ?? 'desc'; filteredDrivers.sort((a, b) => { let comparison = 0; switch (sortBy) { case 'rating': comparison = a.rating - b.rating; break; case 'name': comparison = a.name.localeCompare(b.name); break; case 'rank': comparison = 0; break; case 'raceCount': comparison = a.raceCount - b.raceCount; break; } // If primary sort is equal, always use name ASC as secondary sort if (comparison === 0 && sortBy !== 'name') { comparison = a.name.localeCompare(b.name); // Secondary sort should not be affected by sortOrder of primary field? // Actually, usually secondary sort is always ASC or follows primary. // Let's keep it simple: if primary is equal, use name ASC. return comparison; } return sortOrder === 'asc' ? comparison : -comparison; }); // Calculate pagination const total = filteredDrivers.length; const totalPages = Math.ceil(total / limit); const startIndex = (page - 1) * limit; const endIndex = Math.min(startIndex + limit, total); // Get paginated drivers const paginatedDrivers = filteredDrivers.slice(startIndex, endIndex); // Map to ranking entries with rank const driverEntries: DriverRankingEntry[] = paginatedDrivers.map( (driver, index): DriverRankingEntry => ({ rank: startIndex + index + 1, id: driver.id, name: driver.name, rating: driver.rating, ...(driver.teamId !== undefined && { teamId: driver.teamId }), ...(driver.teamName !== undefined && { teamName: driver.teamName }), raceCount: driver.raceCount, }), ); // Publish event await this.ports.eventPublisher.publishDriverRankingsAccessed({ type: 'driver_rankings_accessed', timestamp: new Date(), }); return { drivers: driverEntries, pagination: { total, page, limit, totalPages, }, }; } catch (error) { // Publish error event await this.ports.eventPublisher.publishLeaderboardsError({ type: 'leaderboards_error', error: error instanceof Error ? error.message : String(error), timestamp: new Date(), }); throw error; } } private validateQuery(query: DriverRankingsQuery): void { if (query.page !== undefined && query.page < 1) { throw new ValidationError('Page must be a positive integer'); } if (query.limit !== undefined && query.limit < 1) { throw new ValidationError('Limit must be a positive integer'); } if (query.minRating !== undefined && query.minRating < 0) { throw new ValidationError('Min rating must be a non-negative number'); } if (query.sortBy && !['rating', 'name', 'rank', 'raceCount'].includes(query.sortBy)) { throw new ValidationError('Invalid sort field'); } if (query.sortOrder && !['asc', 'desc'].includes(query.sortOrder)) { throw new ValidationError('Sort order must be "asc" or "desc"'); } } }