import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { IStandingRepository } from '../../domain/repositories/IStandingRepository'; import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository'; import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository'; import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { League } from '../../domain/entities/League'; import { Race } from '../../domain/entities/Race'; import { Result as RaceResult } from '../../domain/entities/Result'; import { Driver } from '../../domain/entities/Driver'; import { Standing } from '../../domain/entities/Standing'; import type { FeedItem } from '@core/social/domain/types/FeedItem'; export interface DashboardOverviewInput { driverId: string; } interface DashboardDriverStatsAdapter { rating: number | null; wins: number; podiums: number; totalRaces: number; overallRank: number | null; consistency: number | null; } export interface DashboardDriverSummary { driver: Driver; avatarUrl: string | null; rating: number | null; globalRank: number | null; totalRaces: number; wins: number; podiums: number; consistency: number | null; } export interface DashboardRaceSummary { race: Race; league: League | null; isMyLeague: boolean; } export interface DashboardRecentRaceResultSummary { race: Race; league: League | null; result: RaceResult; } export interface DashboardLeagueStandingSummary { league: League; standing: Standing | null; totalDrivers: number; } export interface DashboardFeedSummary { notificationCount: number; items: FeedItem[]; } export interface DashboardFriendSummary { driver: Driver; avatarUrl: string | null; } export interface DashboardOverviewResult { currentDriver: DashboardDriverSummary | null; myUpcomingRaces: DashboardRaceSummary[]; otherUpcomingRaces: DashboardRaceSummary[]; upcomingRaces: DashboardRaceSummary[]; activeLeaguesCount: number; nextRace: DashboardRaceSummary | null; recentResults: DashboardRecentRaceResultSummary[]; leagueStandingsSummaries: DashboardLeagueStandingSummary[]; feedSummary: DashboardFeedSummary; friends: DashboardFriendSummary[]; } export class DashboardOverviewUseCase { constructor( private readonly driverRepository: IDriverRepository, private readonly raceRepository: IRaceRepository, private readonly resultRepository: IResultRepository, private readonly leagueRepository: ILeagueRepository, private readonly standingRepository: IStandingRepository, private readonly leagueMembershipRepository: ILeagueMembershipRepository, private readonly raceRegistrationRepository: IRaceRegistrationRepository, private readonly feedRepository: IFeedRepository, private readonly socialRepository: ISocialGraphRepository, private readonly getDriverAvatar: (driverId: string) => Promise, private readonly getDriverStats: ( driverId: string, ) => DashboardDriverStatsAdapter | null, ) {} async execute( input: DashboardOverviewInput, ): Promise< Result< DashboardOverviewResult, ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }> > > { const { driverId } = input; try { const [driver, allLeagues, allRaces, allResults, feedItems, friends] = await Promise.all([ this.driverRepository.findById(driverId), this.leagueRepository.findAll(), this.raceRepository.findAll(), this.resultRepository.findAll(), this.feedRepository.getFeedForDriver(driverId), this.socialRepository.getFriends(driverId), ]); if (!driver) { return Result.err({ code: 'DRIVER_NOT_FOUND', details: { message: 'Driver not found' }, }); } const leagueMap = new Map(allLeagues.map(league => [league.id, league])); const driverStats = this.getDriverStats(driverId); const currentDriver: DashboardDriverSummary = { driver, avatarUrl: await this.getDriverAvatar(driver.id), rating: driverStats?.rating ?? null, globalRank: driverStats?.overallRank ?? null, totalRaces: driverStats?.totalRaces ?? 0, wins: driverStats?.wins ?? 0, podiums: driverStats?.podiums ?? 0, consistency: driverStats?.consistency ?? null, }; const driverLeagues = await this.getDriverLeagues(allLeagues, driverId); const driverLeagueIds = new Set(driverLeagues.map(league => league.id)); const now = new Date(); const upcomingRaces = allRaces .filter(race => race.status === 'scheduled' && race.scheduledAt > now) .sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime()); const upcomingRacesInDriverLeagues = upcomingRaces.filter(race => driverLeagueIds.has(race.leagueId), ); const { myUpcomingRaces, otherUpcomingRaces } = await this.partitionUpcomingRacesByRegistration( upcomingRacesInDriverLeagues, driverId, leagueMap, ); const nextRace: DashboardRaceSummary | null = myUpcomingRaces.length > 0 ? myUpcomingRaces[0]! : null; const upcomingRacesSummaries: DashboardRaceSummary[] = [ ...myUpcomingRaces, ...otherUpcomingRaces, ] .slice() .sort( (a, b) => a.race.scheduledAt.getTime() - b.race.scheduledAt.getTime(), ); const recentResults = this.buildRecentResults( allResults, allRaces, allLeagues, driverId, ); const leagueStandingsSummaries = await this.buildLeagueStandingsSummaries( driverLeagues, driverId, ); const activeLeaguesCount = this.computeActiveLeaguesCount( upcomingRacesSummaries, leagueStandingsSummaries, ); const feedSummary = this.buildFeedSummary(feedItems); const friendsSummary = await this.buildFriendsSummary(friends); const result: DashboardOverviewResult = { currentDriver, myUpcomingRaces, otherUpcomingRaces, upcomingRaces: upcomingRacesSummaries, activeLeaguesCount, nextRace, recentResults, leagueStandingsSummaries, feedSummary, friends: friendsSummary, }; return Result.ok(result); } catch (error) { return Result.err({ code: 'REPOSITORY_ERROR', details: { message: error instanceof Error ? error.message : 'Unknown error', }, }); } } private async getDriverLeagues( allLeagues: League[], driverId: string, ): Promise { const driverLeagues: League[] = []; for (const league of allLeagues) { const membership = await this.leagueMembershipRepository.getMembership( league.id, driverId, ); if (membership && membership.status === 'active') { driverLeagues.push(league); } } return driverLeagues; } private async partitionUpcomingRacesByRegistration( upcomingRaces: Race[], driverId: string, leagueMap: Map, ): Promise<{ myUpcomingRaces: DashboardRaceSummary[]; otherUpcomingRaces: DashboardRaceSummary[]; }> { const myUpcomingRaces: DashboardRaceSummary[] = []; const otherUpcomingRaces: DashboardRaceSummary[] = []; for (const race of upcomingRaces) { const isRegistered = await this.raceRegistrationRepository.isRegistered( race.id, driverId, ); const summary = this.mapRaceToSummary(race, leagueMap, true); if (isRegistered) { myUpcomingRaces.push(summary); } else { otherUpcomingRaces.push(summary); } } return { myUpcomingRaces, otherUpcomingRaces }; } private mapRaceToSummary( race: Race, leagueMap: Map, isMyLeague: boolean, ): DashboardRaceSummary { return { race, league: leagueMap.get(race.leagueId) ?? null, isMyLeague, }; } private buildRecentResults( allResults: RaceResult[], allRaces: Race[], allLeagues: League[], driverId: string, ): DashboardRecentRaceResultSummary[] { const raceById = new Map(allRaces.map(race => [race.id, race])); const leagueById = new Map(allLeagues.map(league => [league.id, league])); const driverResults = allResults.filter(result => result.driverId === driverId); const enriched = driverResults .map(result => { const race = raceById.get(result.raceId); if (!race) return null; const league = leagueById.get(race.leagueId) ?? null; const item: DashboardRecentRaceResultSummary = { race, league, result, }; return item; }) .filter((item): item is DashboardRecentRaceResultSummary => !!item) .sort( (a, b) => b.race.scheduledAt.getTime() - a.race.scheduledAt.getTime(), ); const RECENT_RESULTS_LIMIT = 5; return enriched.slice(0, RECENT_RESULTS_LIMIT); } private async buildLeagueStandingsSummaries( driverLeagues: League[], driverId: string, ): Promise { const summaries: DashboardLeagueStandingSummary[] = []; for (const league of driverLeagues.slice(0, 3)) { const standings = await this.standingRepository.findByLeagueId(league.id); const driverStanding = standings.find( (standing: Standing) => standing.driverId === driverId, ); summaries.push({ league, standing: driverStanding ?? null, totalDrivers: standings.length, }); } return summaries; } private computeActiveLeaguesCount( upcomingRaces: DashboardRaceSummary[], leagueStandingsSummaries: DashboardLeagueStandingSummary[], ): number { const activeLeagueIds = new Set(); for (const race of upcomingRaces) { activeLeagueIds.add(race.race.leagueId); } for (const standing of leagueStandingsSummaries) { activeLeagueIds.add(standing.league.id); } return activeLeagueIds.size; } private buildFeedSummary(feedItems: FeedItem[]): DashboardFeedSummary { return { notificationCount: feedItems.length, items: feedItems, }; } private async buildFriendsSummary( friends: Driver[], ): Promise { const friendSummaries: DashboardFriendSummary[] = []; for (const friend of friends) { const avatarUrl = await this.getDriverAvatar(friend.id); friendSummaries.push({ driver: friend, avatarUrl, }); } return friendSummaries; } }