/** * Dependency Injection Container * * Initializes all in-memory repositories and provides accessor functions. * Allows easy swapping to persistent repositories later. */ import { Driver } from '@gridpilot/racing/domain/entities/Driver'; import { League } from '@gridpilot/racing/domain/entities/League'; import { Race } from '@gridpilot/racing/domain/entities/Race'; import { Result } from '@gridpilot/racing/domain/entities/Result'; import { Standing } from '@gridpilot/racing/domain/entities/Standing'; import { Game } from '@gridpilot/racing/domain/entities/Game'; import { Season } from '@gridpilot/racing/domain/entities/Season'; import { Track } from '@gridpilot/racing/domain/entities/Track'; import { Car } from '@gridpilot/racing/domain/entities/Car'; import type { IDriverRepository } from '@gridpilot/racing/domain/repositories/IDriverRepository'; import type { ILeagueRepository } from '@gridpilot/racing/domain/repositories/ILeagueRepository'; import type { IRaceRepository } from '@gridpilot/racing/domain/repositories/IRaceRepository'; import type { IResultRepository } from '@gridpilot/racing/domain/repositories/IResultRepository'; import type { IStandingRepository } from '@gridpilot/racing/domain/repositories/IStandingRepository'; import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/IPenaltyRepository'; import type { IGameRepository } from '@gridpilot/racing/domain/repositories/IGameRepository'; import type { ISeasonRepository } from '@gridpilot/racing/domain/repositories/ISeasonRepository'; import type { ILeagueScoringConfigRepository } from '@gridpilot/racing/domain/repositories/ILeagueScoringConfigRepository'; import type { ITrackRepository } from '@gridpilot/racing/domain/repositories/ITrackRepository'; import type { ICarRepository } from '@gridpilot/racing/domain/repositories/ICarRepository'; import type { ITeamRepository, ITeamMembershipRepository, IRaceRegistrationRepository, } from '@gridpilot/racing'; import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository'; import type { LeagueMembership, JoinRequest } from '@gridpilot/racing/domain/entities/LeagueMembership'; import type { IFeedRepository } from '@gridpilot/social/domain/repositories/IFeedRepository'; import type { ISocialGraphRepository } from '@gridpilot/social/domain/repositories/ISocialGraphRepository'; import type { ImageServicePort } from '@gridpilot/media'; import { InMemoryDriverRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryDriverRepository'; import { InMemoryLeagueRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryLeagueRepository'; import { InMemoryRaceRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryRaceRepository'; import { InMemoryResultRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryResultRepository'; import { InMemoryStandingRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryStandingRepository'; import { InMemoryPenaltyRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryPenaltyRepository'; import { InMemoryTrackRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTrackRepository'; import { InMemoryCarRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryCarRepository'; import { InMemoryGameRepository, InMemorySeasonRepository, InMemoryLeagueScoringConfigRepository, getLeagueScoringPresetById, } from '@gridpilot/racing/infrastructure/repositories/InMemoryScoringRepositories'; import { InMemoryLeagueScoringPresetProvider } from '@gridpilot/racing/infrastructure/repositories/InMemoryLeagueScoringPresetProvider'; import { InMemoryTeamRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTeamRepository'; import { InMemoryTeamMembershipRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTeamMembershipRepository'; import { InMemoryRaceRegistrationRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryRaceRegistrationRepository'; import { InMemoryLeagueMembershipRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryLeagueMembershipRepository'; import { JoinLeagueUseCase, RegisterForRaceUseCase, WithdrawFromRaceUseCase, IsDriverRegisteredForRaceQuery, GetRaceRegistrationsQuery, CreateTeamUseCase, JoinTeamUseCase, LeaveTeamUseCase, ApproveTeamJoinRequestUseCase, RejectTeamJoinRequestUseCase, UpdateTeamUseCase, GetAllTeamsQuery, GetTeamDetailsQuery, GetTeamMembersQuery, GetTeamJoinRequestsQuery, GetDriverTeamQuery, GetLeagueStandingsQuery, GetLeagueDriverSeasonStatsQuery, GetAllLeaguesWithCapacityQuery, GetAllLeaguesWithCapacityAndScoringQuery, ListLeagueScoringPresetsQuery, GetLeagueScoringConfigQuery, CreateLeagueWithSeasonAndScoringUseCase, GetLeagueFullConfigQuery, GetRaceWithSOFQuery, GetLeagueStatsQuery, } from '@gridpilot/racing/application'; import type { DriverRatingProvider } from '@gridpilot/racing/application'; import { createStaticRacingSeed, type RacingSeedData, getDemoLeagueArchetypeByName, } from '@gridpilot/testing-support'; import type { LeagueScheduleDTO, LeagueSchedulePreviewDTO, } from '@gridpilot/racing/application'; import { PreviewLeagueScheduleQuery } from '@gridpilot/racing/application'; import { InMemoryFeedRepository, InMemorySocialGraphRepository, } from '@gridpilot/social/infrastructure/inmemory/InMemorySocialAndFeed'; import { DemoImageServiceAdapter } from '@gridpilot/demo-infrastructure'; import type { LeagueScoringPresetProvider } from '@gridpilot/racing/application/ports/LeagueScoringPresetProvider'; /** * Seed data for development */ /** * Driver statistics and ranking data */ export interface DriverStats { driverId: string; rating: number; totalRaces: number; wins: number; podiums: number; dnfs: number; avgFinish: number; bestFinish: number; worstFinish: number; consistency: number; overallRank: number; percentile: number; } /** * Mock driver stats with calculated rankings */ const driverStats: Record = {}; function createSeedData(): RacingSeedData { const seed = createStaticRacingSeed(42); const { drivers } = seed; drivers.forEach((driver, index) => { const totalRaces = 40 + index * 5; const wins = Math.max(0, Math.floor(totalRaces * 0.2) - index); const podiums = Math.max(wins * 2, 0); const dnfs = Math.max(0, Math.floor(index / 2)); const rating = 1500 + index * 25; driverStats[driver.id] = { driverId: driver.id, rating, totalRaces, wins, podiums, dnfs, avgFinish: 4, bestFinish: 1, worstFinish: 20, consistency: 80, overallRank: index + 1, percentile: Math.max(0, 100 - index), }; }); return seed; } /** * DI Container class */ class DIContainer { private static instance: DIContainer; private _driverRepository: IDriverRepository; private _leagueRepository: ILeagueRepository; private _raceRepository: IRaceRepository; private _resultRepository: IResultRepository; private _standingRepository: IStandingRepository; private _penaltyRepository: IPenaltyRepository; private _teamRepository: ITeamRepository; private _teamMembershipRepository: ITeamMembershipRepository; private _raceRegistrationRepository: IRaceRegistrationRepository; private _leagueMembershipRepository: ILeagueMembershipRepository; private _gameRepository: IGameRepository; private _seasonRepository: ISeasonRepository; private _leagueScoringConfigRepository: ILeagueScoringConfigRepository; private _leagueScoringPresetProvider: LeagueScoringPresetProvider; private _feedRepository: IFeedRepository; private _socialRepository: ISocialGraphRepository; private _imageService: ImageServicePort; private _trackRepository: ITrackRepository; private _carRepository: ICarRepository; // Racing application use-cases / queries private _joinLeagueUseCase: JoinLeagueUseCase; private _registerForRaceUseCase: RegisterForRaceUseCase; private _withdrawFromRaceUseCase: WithdrawFromRaceUseCase; private _isDriverRegisteredForRaceQuery: IsDriverRegisteredForRaceQuery; private _getRaceRegistrationsQuery: GetRaceRegistrationsQuery; private _getLeagueStandingsQuery: GetLeagueStandingsQuery; private _getLeagueDriverSeasonStatsQuery: GetLeagueDriverSeasonStatsQuery; private _getAllLeaguesWithCapacityQuery: GetAllLeaguesWithCapacityQuery; private _getAllLeaguesWithCapacityAndScoringQuery: GetAllLeaguesWithCapacityAndScoringQuery; private _listLeagueScoringPresetsQuery: ListLeagueScoringPresetsQuery; private _getLeagueScoringConfigQuery: GetLeagueScoringConfigQuery; private _createLeagueWithSeasonAndScoringUseCase: CreateLeagueWithSeasonAndScoringUseCase; private _getLeagueFullConfigQuery: GetLeagueFullConfigQuery; // Placeholder for future schedule preview wiring private _previewLeagueScheduleQuery: PreviewLeagueScheduleQuery; private _getRaceWithSOFQuery: GetRaceWithSOFQuery; private _getLeagueStatsQuery: GetLeagueStatsQuery; private _driverRatingProvider: DriverRatingProvider; private _createTeamUseCase: CreateTeamUseCase; private _joinTeamUseCase: JoinTeamUseCase; private _leaveTeamUseCase: LeaveTeamUseCase; private _approveTeamJoinRequestUseCase: ApproveTeamJoinRequestUseCase; private _rejectTeamJoinRequestUseCase: RejectTeamJoinRequestUseCase; private _updateTeamUseCase: UpdateTeamUseCase; private _getAllTeamsQuery: GetAllTeamsQuery; private _getTeamDetailsQuery: GetTeamDetailsQuery; private _getTeamMembersQuery: GetTeamMembersQuery; private _getTeamJoinRequestsQuery: GetTeamJoinRequestsQuery; private _getDriverTeamQuery: GetDriverTeamQuery; private constructor() { // Create seed data const seedData = createSeedData(); const primaryDriverId = seedData.drivers[0]?.id ?? 'driver-1'; // Initialize repositories with seed data this._driverRepository = new InMemoryDriverRepository(seedData.drivers); this._leagueRepository = new InMemoryLeagueRepository(seedData.leagues); this._raceRepository = new InMemoryRaceRepository(seedData.races); // Result repository needs race repository for league-based queries this._resultRepository = new InMemoryResultRepository( seedData.results, this._raceRepository ); // Standing repository needs all three for recalculation this._standingRepository = new InMemoryStandingRepository( seedData.standings, this._resultRepository, this._raceRepository, this._leagueRepository ); // Race registrations - seed from results for completed races, plus some upcoming races const seedRaceRegistrations: Array<{ raceId: string; driverId: string; registeredAt: Date }> = []; // For completed races, extract driver registrations from results for (const result of seedData.results) { seedRaceRegistrations.push({ raceId: result.raceId, driverId: result.driverId, registeredAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago }); } // For some upcoming races, add random registrations const upcomingRaces = seedData.races.filter(r => r.status === 'scheduled').slice(0, 10); for (const race of upcomingRaces) { const participantCount = Math.floor(Math.random() * 12) + 8; // 8-20 participants const shuffledDrivers = [...seedData.drivers].sort(() => Math.random() - 0.5).slice(0, participantCount); for (const driver of shuffledDrivers) { seedRaceRegistrations.push({ raceId: race.id, driverId: driver.id, registeredAt: new Date(Date.now() - Math.floor(Math.random() * 5) * 24 * 60 * 60 * 1000), }); } } this._raceRegistrationRepository = new InMemoryRaceRegistrationRepository(seedRaceRegistrations); // Penalties (seeded in-memory adapter) this._penaltyRepository = new InMemoryPenaltyRepository(); // Scoring preset provider and seeded game/season/scoring config repositories this._leagueScoringPresetProvider = new InMemoryLeagueScoringPresetProvider(); const game = Game.create({ id: 'iracing', name: 'iRacing' }); const seededSeasons: Season[] = []; const seededScoringConfigs = []; for (const league of seedData.leagues) { const archetype = getDemoLeagueArchetypeByName(league.name); if (!archetype) continue; const season = Season.create({ id: `season-${league.id}-demo`, leagueId: league.id, gameId: game.id, name: `${league.name} Demo Season`, year: new Date().getFullYear(), order: 1, status: 'active', startDate: new Date(), endDate: new Date(), }); seededSeasons.push(season); const infraPreset = getLeagueScoringPresetById( archetype.scoringPresetId, ); if (!infraPreset) { // If a preset is missing, skip scoring config for this league in alpha seed. continue; } const config = infraPreset.createConfig({ seasonId: season.id }); seededScoringConfigs.push(config); } this._gameRepository = new InMemoryGameRepository([game]); this._seasonRepository = new InMemorySeasonRepository(seededSeasons); this._leagueScoringConfigRepository = new InMemoryLeagueScoringConfigRepository(seededScoringConfigs); // League memberships seeded from static memberships with guaranteed owner roles const seededMemberships: LeagueMembership[] = seedData.memberships.map((m) => ({ leagueId: m.leagueId, driverId: m.driverId, role: 'member', status: 'active', joinedAt: new Date(), })); // Ensure each league owner has an owner membership for (const league of seedData.leagues) { const existing = seededMemberships.find( (m) => m.leagueId === league.id && m.driverId === league.ownerId, ); if (!existing) { seededMemberships.push({ leagueId: league.id, driverId: league.ownerId, role: 'owner', status: 'active', joinedAt: new Date(), }); } else { existing.role = 'owner'; } } // Ensure the primary demo driver owns at least one league in memberships const hasPrimaryOwnerMembership = seededMemberships.some( (m: LeagueMembership) => m.driverId === primaryDriverId && m.role === 'owner', ); if (!hasPrimaryOwnerMembership && seedData.leagues.length > 0) { const targetLeague = seedData.leagues.find((l) => l.ownerId === primaryDriverId) ?? seedData.leagues[0]; const existingForPrimary = seededMemberships.find( (m) => m.leagueId === targetLeague.id && m.driverId === primaryDriverId, ); if (existingForPrimary) { existingForPrimary.role = 'owner'; } else { seededMemberships.push({ leagueId: targetLeague.id, driverId: primaryDriverId, role: 'owner', status: 'active', joinedAt: new Date(), }); } } // Seed sample league admins for the primary driver's league (alpha demo) const primaryLeagueForAdmins = seedData.leagues.find((l) => l.ownerId === primaryDriverId) ?? seedData.leagues[0]; if (primaryLeagueForAdmins) { const adminCandidates = seedData.drivers .filter((d) => d.id !== primaryLeagueForAdmins.ownerId) .slice(0, 2); adminCandidates.forEach((driver) => { const existing = seededMemberships.find( (m) => m.leagueId === primaryLeagueForAdmins.id && m.driverId === driver.id, ); if (existing) { if (existing.role !== 'owner') { existing.role = 'admin'; } } else { seededMemberships.push({ leagueId: primaryLeagueForAdmins.id, driverId: driver.id, role: 'admin', status: 'active', joinedAt: new Date(), }); } }); } // Seed a few pending join requests for demo leagues const seededJoinRequests: JoinRequest[] = []; const demoLeagues = seedData.leagues.slice(0, 2); const extraDrivers = seedData.drivers.slice(3, 8); demoLeagues.forEach((league) => { extraDrivers.forEach((driver, index) => { seededJoinRequests.push({ id: `join-${league.id}-${driver.id}`, leagueId: league.id, driverId: driver.id, requestedAt: new Date(Date.now() - (index + 1) * 24 * 60 * 60 * 1000), message: index % 2 === 0 ? 'Would love to race in this series!' : 'Looking to join for the upcoming season.', }); }); }); this._leagueMembershipRepository = new InMemoryLeagueMembershipRepository( seededMemberships, seededJoinRequests, ); // Team repositories seeded from static memberships/teams this._teamRepository = new InMemoryTeamRepository( seedData.teams.map((t) => ({ id: t.id, name: t.name, tag: t.tag, description: t.description, ownerId: seedData.drivers[0]?.id ?? 'driver-1', leagues: [t.primaryLeagueId], createdAt: new Date(), })), ); this._teamMembershipRepository = new InMemoryTeamMembershipRepository( seedData.memberships .filter((m) => m.teamId) .map((m) => ({ teamId: m.teamId!, driverId: m.driverId, role: 'driver', status: 'active', joinedAt: new Date(), })), ); // Application-layer use-cases and queries wired with repositories this._joinLeagueUseCase = new JoinLeagueUseCase(this._leagueMembershipRepository); this._registerForRaceUseCase = new RegisterForRaceUseCase( this._raceRegistrationRepository, this._leagueMembershipRepository, ); this._withdrawFromRaceUseCase = new WithdrawFromRaceUseCase( this._raceRegistrationRepository, ); this._isDriverRegisteredForRaceQuery = new IsDriverRegisteredForRaceQuery( this._raceRegistrationRepository, ); this._getRaceRegistrationsQuery = new GetRaceRegistrationsQuery( this._raceRegistrationRepository, ); this._getLeagueStandingsQuery = new GetLeagueStandingsQuery(this._standingRepository); this._getLeagueDriverSeasonStatsQuery = new GetLeagueDriverSeasonStatsQuery( this._standingRepository, this._resultRepository, this._penaltyRepository, { getRating: (driverId: string) => { const stats = driverStats[driverId]; if (!stats) { return { rating: null, ratingChange: null }; } // For alpha we expose current rating and a mock delta const baseline = 1500; const delta = stats.rating - baseline; return { rating: stats.rating, ratingChange: delta !== 0 ? delta : null, }; }, }, ); this._getAllLeaguesWithCapacityQuery = new GetAllLeaguesWithCapacityQuery( this._leagueRepository, this._leagueMembershipRepository, ); this._getAllLeaguesWithCapacityAndScoringQuery = new GetAllLeaguesWithCapacityAndScoringQuery( this._leagueRepository, this._leagueMembershipRepository, this._seasonRepository, this._leagueScoringConfigRepository, this._gameRepository, this._leagueScoringPresetProvider, ); this._listLeagueScoringPresetsQuery = new ListLeagueScoringPresetsQuery( this._leagueScoringPresetProvider, ); this._getLeagueScoringConfigQuery = new GetLeagueScoringConfigQuery( this._leagueRepository, this._seasonRepository, this._leagueScoringConfigRepository, this._gameRepository, this._leagueScoringPresetProvider, ); this._getLeagueFullConfigQuery = new GetLeagueFullConfigQuery( this._leagueRepository, this._seasonRepository, this._leagueScoringConfigRepository, this._gameRepository, ); this._createLeagueWithSeasonAndScoringUseCase = new CreateLeagueWithSeasonAndScoringUseCase( this._leagueRepository, this._seasonRepository, this._leagueScoringConfigRepository, this._leagueScoringPresetProvider, ); // Schedule preview query (used by league creation wizard step 3) this._previewLeagueScheduleQuery = new PreviewLeagueScheduleQuery(); // DriverRatingProvider adapter using driverStats this._driverRatingProvider = { getRating: (driverId: string): number | null => { const stats = driverStats[driverId]; return stats?.rating ?? null; }, getRatings: (driverIds: string[]): Map => { const result = new Map(); for (const id of driverIds) { const stats = driverStats[id]; if (stats?.rating) { result.set(id, stats.rating); } } return result; }, }; // SOF queries this._getRaceWithSOFQuery = new GetRaceWithSOFQuery( this._raceRepository, this._raceRegistrationRepository, this._resultRepository, this._driverRatingProvider, ); this._getLeagueStatsQuery = new GetLeagueStatsQuery( this._leagueRepository, this._raceRepository, this._resultRepository, this._driverRatingProvider, ); this._createTeamUseCase = new CreateTeamUseCase( this._teamRepository, this._teamMembershipRepository, ); this._joinTeamUseCase = new JoinTeamUseCase( this._teamRepository, this._teamMembershipRepository, ); this._leaveTeamUseCase = new LeaveTeamUseCase(this._teamMembershipRepository); this._approveTeamJoinRequestUseCase = new ApproveTeamJoinRequestUseCase( this._teamMembershipRepository, ); this._rejectTeamJoinRequestUseCase = new RejectTeamJoinRequestUseCase( this._teamMembershipRepository, ); this._updateTeamUseCase = new UpdateTeamUseCase( this._teamRepository, this._teamMembershipRepository, ); this._getAllTeamsQuery = new GetAllTeamsQuery(this._teamRepository); this._getTeamDetailsQuery = new GetTeamDetailsQuery( this._teamRepository, this._teamMembershipRepository, ); this._getTeamMembersQuery = new GetTeamMembersQuery(this._teamMembershipRepository); this._getTeamJoinRequestsQuery = new GetTeamJoinRequestsQuery( this._teamMembershipRepository, ); this._getDriverTeamQuery = new GetDriverTeamQuery( this._teamRepository, this._teamMembershipRepository, ); // Social and feed adapters backed by static seed this._feedRepository = new InMemoryFeedRepository(seedData); this._socialRepository = new InMemorySocialGraphRepository(seedData); // Image service backed by demo adapter this._imageService = new DemoImageServiceAdapter(); // Seed Track and Car data for demo const seedTracks = [ Track.create({ id: 'track-spa', name: 'Spa-Francorchamps', shortName: 'SPA', country: 'Belgium', category: 'road', difficulty: 'advanced', lengthKm: 7.004, turns: 19, imageUrl: '/images/tracks/spa.jpg', gameId: 'iracing', }), Track.create({ id: 'track-monza', name: 'Autodromo Nazionale Monza', shortName: 'MON', country: 'Italy', category: 'road', difficulty: 'intermediate', lengthKm: 5.793, turns: 11, imageUrl: '/images/tracks/monza.jpg', gameId: 'iracing', }), Track.create({ id: 'track-nurburgring', name: 'Nürburgring Grand Prix', shortName: 'NUR', country: 'Germany', category: 'road', difficulty: 'advanced', lengthKm: 5.148, turns: 15, imageUrl: '/images/tracks/nurburgring.jpg', gameId: 'iracing', }), Track.create({ id: 'track-silverstone', name: 'Silverstone Circuit', shortName: 'SIL', country: 'United Kingdom', category: 'road', difficulty: 'intermediate', lengthKm: 5.891, turns: 18, imageUrl: '/images/tracks/silverstone.jpg', gameId: 'iracing', }), Track.create({ id: 'track-suzuka', name: 'Suzuka International Racing Course', shortName: 'SUZ', country: 'Japan', category: 'road', difficulty: 'expert', lengthKm: 5.807, turns: 18, imageUrl: '/images/tracks/suzuka.jpg', gameId: 'iracing', }), Track.create({ id: 'track-daytona', name: 'Daytona International Speedway', shortName: 'DAY', country: 'United States', category: 'oval', difficulty: 'intermediate', lengthKm: 4.023, turns: 4, imageUrl: '/images/tracks/daytona.jpg', gameId: 'iracing', }), Track.create({ id: 'track-laguna', name: 'WeatherTech Raceway Laguna Seca', shortName: 'LAG', country: 'United States', category: 'road', difficulty: 'advanced', lengthKm: 3.602, turns: 11, imageUrl: '/images/tracks/laguna.jpg', gameId: 'iracing', }), ]; const seedCars = [ Car.create({ id: 'car-porsche-992', name: '911 GT3 R', shortName: '992 GT3R', manufacturer: 'Porsche', carClass: 'gt', license: 'B', year: 2023, horsepower: 565, weight: 1300, gameId: 'iracing', }), Car.create({ id: 'car-ferrari-296', name: '296 GT3', shortName: '296 GT3', manufacturer: 'Ferrari', carClass: 'gt', license: 'B', year: 2023, horsepower: 600, weight: 1270, gameId: 'iracing', }), Car.create({ id: 'car-mclaren-720s', name: '720S GT3 Evo', shortName: '720S', manufacturer: 'McLaren', carClass: 'gt', license: 'B', year: 2023, horsepower: 552, weight: 1290, gameId: 'iracing', }), Car.create({ id: 'car-mercedes-gt3', name: 'AMG GT3 2020', shortName: 'AMG GT3', manufacturer: 'Mercedes', carClass: 'gt', license: 'B', year: 2020, horsepower: 550, weight: 1285, gameId: 'iracing', }), Car.create({ id: 'car-lmp2', name: 'Dallara P217 LMP2', shortName: 'LMP2', manufacturer: 'Dallara', carClass: 'prototype', license: 'A', year: 2021, horsepower: 600, weight: 930, gameId: 'iracing', }), Car.create({ id: 'car-f4', name: 'Formula 4', shortName: 'F4', manufacturer: 'Tatuus', carClass: 'formula', license: 'D', year: 2022, horsepower: 160, weight: 570, gameId: 'iracing', }), Car.create({ id: 'car-mx5', name: 'MX-5 Cup', shortName: 'MX5', manufacturer: 'Mazda', carClass: 'sports', license: 'D', year: 2023, horsepower: 181, weight: 1128, gameId: 'iracing', }), ]; this._trackRepository = new InMemoryTrackRepository(seedTracks); this._carRepository = new InMemoryCarRepository(seedCars); } /** * Get singleton instance */ static getInstance(): DIContainer { if (!DIContainer.instance) { DIContainer.instance = new DIContainer(); } return DIContainer.instance; } /** * Reset the container (useful for testing) */ static reset(): void { DIContainer.instance = new DIContainer(); } /** * Repository getters */ get driverRepository(): IDriverRepository { return this._driverRepository; } get leagueRepository(): ILeagueRepository { return this._leagueRepository; } get raceRepository(): IRaceRepository { return this._raceRepository; } get resultRepository(): IResultRepository { return this._resultRepository; } get standingRepository(): IStandingRepository { return this._standingRepository; } get penaltyRepository(): IPenaltyRepository { return this._penaltyRepository; } get raceRegistrationRepository(): IRaceRegistrationRepository { return this._raceRegistrationRepository; } get leagueMembershipRepository(): ILeagueMembershipRepository { return this._leagueMembershipRepository; } get gameRepository(): IGameRepository { return this._gameRepository; } get seasonRepository(): ISeasonRepository { return this._seasonRepository; } get leagueScoringConfigRepository(): ILeagueScoringConfigRepository { return this._leagueScoringConfigRepository; } get leagueScoringPresetProvider(): LeagueScoringPresetProvider { return this._leagueScoringPresetProvider; } get joinLeagueUseCase(): JoinLeagueUseCase { return this._joinLeagueUseCase; } get registerForRaceUseCase(): RegisterForRaceUseCase { return this._registerForRaceUseCase; } get withdrawFromRaceUseCase(): WithdrawFromRaceUseCase { return this._withdrawFromRaceUseCase; } get isDriverRegisteredForRaceQuery(): IsDriverRegisteredForRaceQuery { return this._isDriverRegisteredForRaceQuery; } get getRaceRegistrationsQuery(): GetRaceRegistrationsQuery { return this._getRaceRegistrationsQuery; } get getLeagueStandingsQuery(): GetLeagueStandingsQuery { return this._getLeagueStandingsQuery; } get getLeagueDriverSeasonStatsQuery(): GetLeagueDriverSeasonStatsQuery { return this._getLeagueDriverSeasonStatsQuery; } get getAllLeaguesWithCapacityQuery(): GetAllLeaguesWithCapacityQuery { return this._getAllLeaguesWithCapacityQuery; } get getAllLeaguesWithCapacityAndScoringQuery(): GetAllLeaguesWithCapacityAndScoringQuery { return this._getAllLeaguesWithCapacityAndScoringQuery; } get listLeagueScoringPresetsQuery(): ListLeagueScoringPresetsQuery { return this._listLeagueScoringPresetsQuery; } get getLeagueScoringConfigQuery(): GetLeagueScoringConfigQuery { return this._getLeagueScoringConfigQuery; } get getLeagueFullConfigQuery(): GetLeagueFullConfigQuery { return this._getLeagueFullConfigQuery; } // Placeholder accessor for schedule preview; API route/UI can call this later. get previewLeagueScheduleQuery(): PreviewLeagueScheduleQuery { return this._previewLeagueScheduleQuery; } get getRaceWithSOFQuery(): GetRaceWithSOFQuery { return this._getRaceWithSOFQuery; } get getLeagueStatsQuery(): GetLeagueStatsQuery { return this._getLeagueStatsQuery; } get driverRatingProvider(): DriverRatingProvider { return this._driverRatingProvider; } get createLeagueWithSeasonAndScoringUseCase(): CreateLeagueWithSeasonAndScoringUseCase { return this._createLeagueWithSeasonAndScoringUseCase; } get createTeamUseCase(): CreateTeamUseCase { return this._createTeamUseCase; } get joinTeamUseCase(): JoinTeamUseCase { return this._joinTeamUseCase; } get leaveTeamUseCase(): LeaveTeamUseCase { return this._leaveTeamUseCase; } get approveTeamJoinRequestUseCase(): ApproveTeamJoinRequestUseCase { return this._approveTeamJoinRequestUseCase; } get rejectTeamJoinRequestUseCase(): RejectTeamJoinRequestUseCase { return this._rejectTeamJoinRequestUseCase; } get updateTeamUseCase(): UpdateTeamUseCase { return this._updateTeamUseCase; } get getAllTeamsQuery(): GetAllTeamsQuery { return this._getAllTeamsQuery; } get getTeamDetailsQuery(): GetTeamDetailsQuery { return this._getTeamDetailsQuery; } get getTeamMembersQuery(): GetTeamMembersQuery { return this._getTeamMembersQuery; } get getTeamJoinRequestsQuery(): GetTeamJoinRequestsQuery { return this._getTeamJoinRequestsQuery; } get getDriverTeamQuery(): GetDriverTeamQuery { return this._getDriverTeamQuery; } get teamRepository(): ITeamRepository { return this._teamRepository; } get teamMembershipRepository(): ITeamMembershipRepository { return this._teamMembershipRepository; } get feedRepository(): IFeedRepository { return this._feedRepository; } get socialRepository(): ISocialGraphRepository { return this._socialRepository; } get imageService(): ImageServicePort { return this._imageService; } get trackRepository(): ITrackRepository { return this._trackRepository; } get carRepository(): ICarRepository { return this._carRepository; } } /** * Exported accessor functions */ export function getDriverRepository(): IDriverRepository { return DIContainer.getInstance().driverRepository; } export function getLeagueRepository(): ILeagueRepository { return DIContainer.getInstance().leagueRepository; } export function getRaceRepository(): IRaceRepository { return DIContainer.getInstance().raceRepository; } export function getResultRepository(): IResultRepository { return DIContainer.getInstance().resultRepository; } export function getStandingRepository(): IStandingRepository { return DIContainer.getInstance().standingRepository; } export function getPenaltyRepository(): IPenaltyRepository { return DIContainer.getInstance().penaltyRepository; } export function getRaceRegistrationRepository(): IRaceRegistrationRepository { return DIContainer.getInstance().raceRegistrationRepository; } export function getLeagueMembershipRepository(): ILeagueMembershipRepository { return DIContainer.getInstance().leagueMembershipRepository; } export function getJoinLeagueUseCase(): JoinLeagueUseCase { return DIContainer.getInstance().joinLeagueUseCase; } export function getRegisterForRaceUseCase(): RegisterForRaceUseCase { return DIContainer.getInstance().registerForRaceUseCase; } export function getWithdrawFromRaceUseCase(): WithdrawFromRaceUseCase { return DIContainer.getInstance().withdrawFromRaceUseCase; } export function getIsDriverRegisteredForRaceQuery(): IsDriverRegisteredForRaceQuery { return DIContainer.getInstance().isDriverRegisteredForRaceQuery; } export function getGetRaceRegistrationsQuery(): GetRaceRegistrationsQuery { return DIContainer.getInstance().getRaceRegistrationsQuery; } export function getGetLeagueStandingsQuery(): GetLeagueStandingsQuery { return DIContainer.getInstance().getLeagueStandingsQuery; } export function getGetLeagueDriverSeasonStatsQuery(): GetLeagueDriverSeasonStatsQuery { return DIContainer.getInstance().getLeagueDriverSeasonStatsQuery; } export function getGetAllLeaguesWithCapacityQuery(): GetAllLeaguesWithCapacityQuery { return DIContainer.getInstance().getAllLeaguesWithCapacityQuery; } export function getGetAllLeaguesWithCapacityAndScoringQuery(): GetAllLeaguesWithCapacityAndScoringQuery { return DIContainer.getInstance().getAllLeaguesWithCapacityAndScoringQuery; } export function getGetLeagueScoringConfigQuery(): GetLeagueScoringConfigQuery { return DIContainer.getInstance().getLeagueScoringConfigQuery; } export function getGetLeagueFullConfigQuery(): GetLeagueFullConfigQuery { return DIContainer.getInstance().getLeagueFullConfigQuery; } // Placeholder export for future schedule preview API wiring. export function getPreviewLeagueScheduleQuery(): PreviewLeagueScheduleQuery { return DIContainer.getInstance().previewLeagueScheduleQuery; } export function getListLeagueScoringPresetsQuery(): ListLeagueScoringPresetsQuery { return DIContainer.getInstance().listLeagueScoringPresetsQuery; } export function getCreateLeagueWithSeasonAndScoringUseCase(): CreateLeagueWithSeasonAndScoringUseCase { return DIContainer.getInstance().createLeagueWithSeasonAndScoringUseCase; } export function getGetRaceWithSOFQuery(): GetRaceWithSOFQuery { return DIContainer.getInstance().getRaceWithSOFQuery; } export function getGetLeagueStatsQuery(): GetLeagueStatsQuery { return DIContainer.getInstance().getLeagueStatsQuery; } export function getDriverRatingProvider(): DriverRatingProvider { return DIContainer.getInstance().driverRatingProvider; } export function getTeamRepository(): ITeamRepository { return DIContainer.getInstance().teamRepository; } export function getTeamMembershipRepository(): ITeamMembershipRepository { return DIContainer.getInstance().teamMembershipRepository; } export function getCreateTeamUseCase(): CreateTeamUseCase { return DIContainer.getInstance().createTeamUseCase; } export function getJoinTeamUseCase(): JoinTeamUseCase { return DIContainer.getInstance().joinTeamUseCase; } export function getLeaveTeamUseCase(): LeaveTeamUseCase { return DIContainer.getInstance().leaveTeamUseCase; } export function getApproveTeamJoinRequestUseCase(): ApproveTeamJoinRequestUseCase { return DIContainer.getInstance().approveTeamJoinRequestUseCase; } export function getRejectTeamJoinRequestUseCase(): RejectTeamJoinRequestUseCase { return DIContainer.getInstance().rejectTeamJoinRequestUseCase; } export function getUpdateTeamUseCase(): UpdateTeamUseCase { return DIContainer.getInstance().updateTeamUseCase; } export function getGetAllTeamsQuery(): GetAllTeamsQuery { return DIContainer.getInstance().getAllTeamsQuery; } export function getGetTeamDetailsQuery(): GetTeamDetailsQuery { return DIContainer.getInstance().getTeamDetailsQuery; } export function getGetTeamMembersQuery(): GetTeamMembersQuery { return DIContainer.getInstance().getTeamMembersQuery; } export function getGetTeamJoinRequestsQuery(): GetTeamJoinRequestsQuery { return DIContainer.getInstance().getTeamJoinRequestsQuery; } export function getGetDriverTeamQuery(): GetDriverTeamQuery { return DIContainer.getInstance().getDriverTeamQuery; } export function getFeedRepository(): IFeedRepository { return DIContainer.getInstance().feedRepository; } export function getSocialRepository(): ISocialGraphRepository { return DIContainer.getInstance().socialRepository; } export function getImageService(): ImageServicePort { return DIContainer.getInstance().imageService; } export function getTrackRepository(): ITrackRepository { return DIContainer.getInstance().trackRepository; } export function getCarRepository(): ICarRepository { return DIContainer.getInstance().carRepository; } /** * Reset function for testing */ export function resetContainer(): void { DIContainer.reset(); } /** * Get driver statistics and rankings */ export function getDriverStats(driverId: string): DriverStats | null { return driverStats[driverId] || null; } /** * Get all driver rankings sorted by rating */ export function getAllDriverRankings(): DriverStats[] { return Object.values(driverStats).sort((a, b) => b.rating - a.rating); } /** * Get league-specific rankings for a driver */ export function getLeagueRankings(driverId: string, leagueId: string): { rank: number; totalDrivers: number; percentile: number; } { // Mock league rankings (in production, calculate from actual league membership) const mockLeagueRanks: Record> = { 'league-1': { 'driver-1': { rank: 1, totalDrivers: 12, percentile: 92 }, 'driver-2': { rank: 2, totalDrivers: 12, percentile: 84 }, 'driver-3': { rank: 4, totalDrivers: 12, percentile: 67 }, 'driver-4': { rank: 5, totalDrivers: 12, percentile: 58 } } }; return mockLeagueRanks[leagueId]?.[driverId] || { rank: 0, totalDrivers: 0, percentile: 0 }; }