import 'reflect-metadata'; import { container } from 'tsyringe'; import { DI_TOKENS } from './di-tokens'; // Domain entities and repositories import { Penalty } from '@gridpilot/racing/domain/entities/Penalty'; import { Protest } from '@gridpilot/racing/domain/entities/Protest'; import { Game } from '@gridpilot/racing/domain/entities/Game'; import { Season } from '@gridpilot/racing/domain/entities/Season'; import { Sponsor } from '@gridpilot/racing/domain/entities/Sponsor'; import { SeasonSponsorship } from '@gridpilot/racing/domain/entities/SeasonSponsorship'; import { Money } from '@gridpilot/racing/domain/value-objects/Money'; import type { LeagueMembership, JoinRequest } from '@gridpilot/racing/domain/entities/LeagueMembership'; 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 { IProtestRepository } from '@gridpilot/racing/domain/repositories/IProtestRepository'; 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, ISponsorRepository, ISeasonSponsorshipRepository, ISponsorshipRequestRepository, ISponsorshipPricingRepository, } from '@gridpilot/racing'; import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository'; import type { IFeedRepository } from '@gridpilot/social/domain/repositories/IFeedRepository'; import type { ISocialGraphRepository } from '@gridpilot/social/domain/repositories/ISocialGraphRepository'; import type { ImageServicePort } from '@gridpilot/media'; // Notifications import type { INotificationRepository, INotificationPreferenceRepository } from '@gridpilot/notifications/application'; import { SendNotificationUseCase, MarkNotificationReadUseCase, GetUnreadNotificationsQuery } from '@gridpilot/notifications/application'; import { InMemoryNotificationRepository, InMemoryNotificationPreferenceRepository, NotificationGatewayRegistry, InAppNotificationAdapter, } from '@gridpilot/notifications/infrastructure'; // Infrastructure repositories 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 { InMemoryProtestRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryProtestRepository'; 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 { InMemorySponsorRepository } from '@gridpilot/racing/infrastructure/repositories/InMemorySponsorRepository'; import { InMemorySeasonSponsorshipRepository } from '@gridpilot/racing/infrastructure/repositories/InMemorySeasonSponsorshipRepository'; import { InMemorySponsorshipRequestRepository } from '@gridpilot/racing/infrastructure/repositories/InMemorySponsorshipRequestRepository'; import { InMemorySponsorshipPricingRepository } from '@gridpilot/racing/infrastructure/repositories/InMemorySponsorshipPricingRepository'; 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 { InMemoryFeedRepository, InMemorySocialGraphRepository, } from '@gridpilot/social/infrastructure/inmemory/InMemorySocialAndFeed'; import { DemoImageServiceAdapter } from '@gridpilot/demo-infrastructure'; // Application use cases and queries 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, FileProtestUseCase, ReviewProtestUseCase, ApplyPenaltyUseCase, GetRaceProtestsQuery, GetRacePenaltiesQuery, RequestProtestDefenseUseCase, SubmitProtestDefenseUseCase, GetSponsorDashboardQuery, GetSponsorSponsorshipsQuery, GetPendingSponsorshipRequestsQuery, GetEntitySponsorshipPricingQuery, ApplyForSponsorshipUseCase, AcceptSponsorshipRequestUseCase, RejectSponsorshipRequestUseCase, } from '@gridpilot/racing/application'; import { TransferLeagueOwnershipUseCase } from '@gridpilot/racing/application/use-cases/TransferLeagueOwnershipUseCase'; import type { DriverRatingProvider } from '@gridpilot/racing/application'; import type { LeagueScoringPresetProvider } from '@gridpilot/racing/application/ports/LeagueScoringPresetProvider'; import { PreviewLeagueScheduleQuery } from '@gridpilot/racing/application'; // Testing support import { createStaticRacingSeed, getDemoLeagueArchetypeByName, DEMO_TRACKS, DEMO_CARS, createDemoDriverStats, } from '@gridpilot/testing-support'; /** * Configure the DI container with all bindings for the website application */ export function configureDIContainer(): void { // Clear any existing registrations container.clearInstances(); // Create seed data const seedData = createStaticRacingSeed(42); const primaryDriverId = seedData.drivers[0]?.id ?? 'driver-1'; // Create driver statistics from seed data const driverStats = createDemoDriverStats(seedData.drivers); // Register repositories container.registerInstance( DI_TOKENS.DriverRepository, new InMemoryDriverRepository(seedData.drivers) ); container.registerInstance( DI_TOKENS.LeagueRepository, new InMemoryLeagueRepository(seedData.leagues) ); const raceRepository = new InMemoryRaceRepository(seedData.races); container.registerInstance(DI_TOKENS.RaceRepository, raceRepository); // Result repository needs race repository for league-based queries const resultRepository = new InMemoryResultRepository(seedData.results, raceRepository); container.registerInstance(DI_TOKENS.ResultRepository, resultRepository); // Standing repository needs all three for recalculation const leagueRepository = container.resolve(DI_TOKENS.LeagueRepository); container.registerInstance( DI_TOKENS.StandingRepository, new InMemoryStandingRepository( seedData.standings, resultRepository, raceRepository, 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), }); } // 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; 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), }); } } container.registerInstance( DI_TOKENS.RaceRegistrationRepository, new InMemoryRaceRegistrationRepository(seedRaceRegistrations) ); // Seed penalties and protests const completedRaces = seedData.races.filter(r => r.status === 'completed'); const racesByLeague = new Map(); for (const race of completedRaces) { const existing = racesByLeague.get(race.leagueId) || []; existing.push(race); racesByLeague.set(race.leagueId, existing); } const racesForProtests: Array<{ race: typeof completedRaces[0]; leagueIndex: number }> = []; let leagueIndex = 0; for (const [, leagueRaces] of racesByLeague) { const sorted = [...leagueRaces].sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime()); for (const race of sorted.slice(0, 2)) { racesForProtests.push({ race, leagueIndex }); } leagueIndex++; } const seededPenalties: Penalty[] = []; const seededProtests: Protest[] = []; racesForProtests.forEach(({ race, leagueIndex: leagueIdx }, raceIndex) => { const raceResults = seedData.results.filter(r => r.raceId === race.id); if (raceResults.length < 4) return; const protestCount = Math.min(2, raceResults.length - 2); for (let i = 0; i < protestCount; i++) { const protestingResult = raceResults[i + 2]; const accusedResult = raceResults[i]; if (!protestingResult || !accusedResult) continue; const protestStatuses: Array<'pending' | 'under_review' | 'upheld' | 'dismissed'> = ['pending', 'under_review', 'upheld', 'dismissed']; const status = protestStatuses[(raceIndex + i) % protestStatuses.length]; const protest = Protest.create({ id: `protest-${race.id}-${i}`, raceId: race.id, protestingDriverId: protestingResult.driverId, accusedDriverId: accusedResult.driverId, incident: { lap: 5 + i * 3, description: i === 0 ? 'Unsafe rejoining to the track after going off, causing contact' : 'Aggressive defending, pushing competitor off track', }, comment: i === 0 ? 'Driver rejoined directly into my racing line, causing contact and damaging my front wing.' : 'Driver moved under braking multiple times, forcing me off the circuit.', status, filedAt: new Date(Date.now() - (raceIndex + 1) * 24 * 60 * 60 * 1000), reviewedBy: status !== 'pending' ? primaryDriverId : undefined, decisionNotes: status === 'upheld' ? 'After reviewing the evidence, the accused driver is found at fault. Penalty applied.' : status === 'dismissed' ? 'No clear fault found. Racing incident.' : undefined, reviewedAt: status !== 'pending' ? new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000) : undefined, }); seededProtests.push(protest); if (status === 'upheld') { const penaltyType = i % 2 === 0 ? 'points_deduction' : 'time_penalty'; const penalty = Penalty.create({ id: `penalty-${race.id}-${i}`, raceId: race.id, driverId: accusedResult.driverId, type: penaltyType, value: penaltyType === 'points_deduction' ? 3 : 5, reason: protest.incident.description, protestId: protest.id, issuedBy: primaryDriverId, status: 'applied', issuedAt: new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000), appliedAt: new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000), }); seededPenalties.push(penalty); } } // Add direct penalties if (raceResults.length > 5) { if (raceIndex % 3 === 0) { const penalizedResult = raceResults[4]; if (penalizedResult) { const penalty = Penalty.create({ id: `penalty-direct-${race.id}`, raceId: race.id, driverId: penalizedResult.driverId, type: 'points_deduction', value: 5, reason: 'Causing avoidable collision', issuedBy: primaryDriverId, status: 'applied', issuedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000), appliedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000), }); seededPenalties.push(penalty); } } if (raceIndex % 3 === 1 && raceResults.length > 6) { const penalizedResult = raceResults[5]; if (penalizedResult) { const penalty = Penalty.create({ id: `penalty-direct-2-${race.id}`, raceId: race.id, driverId: penalizedResult.driverId, type: 'points_deduction', value: 2, reason: 'Track limits violation - gained lasting advantage', issuedBy: primaryDriverId, status: 'applied', issuedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000), appliedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000), }); seededPenalties.push(penalty); } } } }); container.registerInstance( DI_TOKENS.PenaltyRepository, new InMemoryPenaltyRepository(seededPenalties) ); container.registerInstance( DI_TOKENS.ProtestRepository, new InMemoryProtestRepository(seededProtests) ); // Scoring repositories const leagueScoringPresetProvider = new InMemoryLeagueScoringPresetProvider(); container.registerInstance( DI_TOKENS.LeagueScoringPresetProvider, leagueScoringPresetProvider ); const game = Game.create({ id: 'iracing', name: 'iRacing' }); const seededSeasons: Season[] = []; const seededScoringConfigs = []; for (const league of seedData.leagues) { const archetype = getDemoLeagueArchetypeByName(league.name); 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 presetId = archetype?.scoringPresetId ?? 'club-default'; const infraPreset = getLeagueScoringPresetById(presetId); if (infraPreset) { const config = infraPreset.createConfig({ seasonId: season.id }); seededScoringConfigs.push(config); } } container.registerInstance( DI_TOKENS.GameRepository, new InMemoryGameRepository([game]) ); container.registerInstance( DI_TOKENS.SeasonRepository, new InMemorySeasonRepository(seededSeasons) ); container.registerInstance( DI_TOKENS.LeagueScoringConfigRepository, new InMemoryLeagueScoringConfigRepository(seededScoringConfigs) ); // League memberships 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 primary driver owns at least one league 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(), }); } } // Add admins for primary league 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(), }); } }); } // Add stewards for primary league if (primaryLeagueForAdmins) { const stewardCandidates = seedData.drivers .filter((d) => d.id !== primaryLeagueForAdmins.ownerId) .slice(2, 5); stewardCandidates.forEach((driver) => { const existing = seededMemberships.find( (m) => m.leagueId === primaryLeagueForAdmins.id && m.driverId === driver.id, ); if (existing) { if (existing.role !== 'owner' && existing.role !== 'admin') { existing.role = 'steward'; } } else { seededMemberships.push({ leagueId: primaryLeagueForAdmins.id, driverId: driver.id, role: 'steward', status: 'active', joinedAt: new Date(), }); } }); } // Seed pending join requests const seededJoinRequests: JoinRequest[] = []; const demoLeagues = seedData.leagues.slice(0, 6); const extraDrivers = seedData.drivers.slice(5, 12); demoLeagues.forEach((league, leagueIndex) => { const memberDriverIds = seededMemberships .filter(m => m.leagueId === league.id) .map(m => m.driverId); const availableDrivers = extraDrivers.filter(d => !memberDriverIds.includes(d.id)); const driversForThisLeague = availableDrivers.slice(0, 3 + (leagueIndex % 3)); driversForThisLeague.forEach((driver, index) => { const messages = [ 'Would love to race in this series!', 'Looking to join for the upcoming season.', 'Heard great things about this league. Can I join?', 'Experienced driver looking for competitive racing.', 'My friend recommended this league. Hope to race with you!', ]; seededJoinRequests.push({ id: `join-${league.id}-${driver.id}`, leagueId: league.id, driverId: driver.id, requestedAt: new Date(Date.now() - (index + 1 + leagueIndex) * 24 * 60 * 60 * 1000), message: messages[(index + leagueIndex) % messages.length], }); }); }); container.registerInstance( DI_TOKENS.LeagueMembershipRepository, new InMemoryLeagueMembershipRepository(seededMemberships, seededJoinRequests) ); // Team repositories container.registerInstance( DI_TOKENS.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(), })) ) ); container.registerInstance( DI_TOKENS.TeamMembershipRepository, new InMemoryTeamMembershipRepository( seedData.memberships .filter((m) => m.teamId) .map((m) => ({ teamId: m.teamId!, driverId: m.driverId, role: 'driver', status: 'active', joinedAt: new Date(), })) ) ); // Track and Car repositories container.registerInstance( DI_TOKENS.TrackRepository, new InMemoryTrackRepository(DEMO_TRACKS) ); container.registerInstance( DI_TOKENS.CarRepository, new InMemoryCarRepository(DEMO_CARS) ); // Sponsor repositories - seed with demo sponsors const seededSponsors = seedData.sponsors.map(s => Sponsor.create({ id: s.id, name: s.name, contactEmail: s.contactEmail, logoUrl: s.logoUrl, websiteUrl: s.websiteUrl, }) ); const sponsorRepo = new InMemorySponsorRepository(); // Use synchronous seeding via internal method seededSponsors.forEach(sponsor => { (sponsorRepo as any).sponsors.set(sponsor.id, sponsor); }); container.registerInstance( DI_TOKENS.SponsorRepository, sponsorRepo ); const seededSponsorships = seedData.seasonSponsorships.map(ss => SeasonSponsorship.create({ id: ss.id, seasonId: ss.seasonId, sponsorId: ss.sponsorId, tier: ss.tier, pricing: Money.create(ss.pricingAmount, ss.pricingCurrency), status: ss.status, description: ss.description, }) ); const seasonSponsorshipRepo = new InMemorySeasonSponsorshipRepository(); // Use synchronous seeding via internal method seededSponsorships.forEach(sponsorship => { (seasonSponsorshipRepo as any).sponsorships.set(sponsorship.id, sponsorship); }); container.registerInstance( DI_TOKENS.SeasonSponsorshipRepository, seasonSponsorshipRepo ); // Sponsorship Request and Pricing repositories const sponsorshipRequestRepo = new InMemorySponsorshipRequestRepository(); container.registerInstance( DI_TOKENS.SponsorshipRequestRepository, sponsorshipRequestRepo ); const sponsorshipPricingRepo = new InMemorySponsorshipPricingRepository(); // Seed sponsorship pricings from demo data seedData.sponsorshipPricings?.forEach(pricing => { (sponsorshipPricingRepo as any).pricings.set( `${pricing.entityType}-${pricing.entityId}`, pricing ); }); container.registerInstance( DI_TOKENS.SponsorshipPricingRepository, sponsorshipPricingRepo ); // Seed sponsorship requests from demo data seedData.sponsorshipRequests?.forEach(request => { (sponsorshipRequestRepo as any).requests.set(request.id, request); }); // Social repositories container.registerInstance( DI_TOKENS.FeedRepository, new InMemoryFeedRepository(seedData) ); container.registerInstance( DI_TOKENS.SocialRepository, new InMemorySocialGraphRepository(seedData) ); // Image service container.registerInstance( DI_TOKENS.ImageService, new DemoImageServiceAdapter() ); // Notification repositories container.registerInstance( DI_TOKENS.NotificationRepository, new InMemoryNotificationRepository() ); container.registerInstance( DI_TOKENS.NotificationPreferenceRepository, new InMemoryNotificationPreferenceRepository() ); const notificationGatewayRegistry = new NotificationGatewayRegistry([ new InAppNotificationAdapter(), ]); container.registerInstance( DI_TOKENS.NotificationGatewayRegistry, notificationGatewayRegistry ); // Register driver stats for access by utility functions container.registerInstance( DI_TOKENS.DriverStats, driverStats ); // Driver Rating Provider const driverRatingProvider: 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; }, }; container.registerInstance( DI_TOKENS.DriverRatingProvider, driverRatingProvider ); // Resolve dependencies for use cases const driverRepository = container.resolve(DI_TOKENS.DriverRepository); const raceRegistrationRepository = container.resolve(DI_TOKENS.RaceRegistrationRepository); const leagueMembershipRepository = container.resolve(DI_TOKENS.LeagueMembershipRepository); const standingRepository = container.resolve(DI_TOKENS.StandingRepository); const penaltyRepository = container.resolve(DI_TOKENS.PenaltyRepository); const protestRepository = container.resolve(DI_TOKENS.ProtestRepository); const teamRepository = container.resolve(DI_TOKENS.TeamRepository); const teamMembershipRepository = container.resolve(DI_TOKENS.TeamMembershipRepository); const seasonRepository = container.resolve(DI_TOKENS.SeasonRepository); const leagueScoringConfigRepository = container.resolve(DI_TOKENS.LeagueScoringConfigRepository); const gameRepository = container.resolve(DI_TOKENS.GameRepository); const notificationRepository = container.resolve(DI_TOKENS.NotificationRepository); const notificationPreferenceRepository = container.resolve(DI_TOKENS.NotificationPreferenceRepository); // Register use cases - Racing container.registerInstance( DI_TOKENS.JoinLeagueUseCase, new JoinLeagueUseCase(leagueMembershipRepository) ); container.registerInstance( DI_TOKENS.RegisterForRaceUseCase, new RegisterForRaceUseCase(raceRegistrationRepository, leagueMembershipRepository) ); container.registerInstance( DI_TOKENS.WithdrawFromRaceUseCase, new WithdrawFromRaceUseCase(raceRegistrationRepository) ); container.registerInstance( DI_TOKENS.CreateLeagueWithSeasonAndScoringUseCase, new CreateLeagueWithSeasonAndScoringUseCase( leagueRepository, seasonRepository, leagueScoringConfigRepository, leagueScoringPresetProvider ) ); container.registerInstance( DI_TOKENS.TransferLeagueOwnershipUseCase, new TransferLeagueOwnershipUseCase(leagueRepository, leagueMembershipRepository) ); // Register use cases - Teams container.registerInstance( DI_TOKENS.CreateTeamUseCase, new CreateTeamUseCase(teamRepository, teamMembershipRepository) ); container.registerInstance( DI_TOKENS.JoinTeamUseCase, new JoinTeamUseCase(teamRepository, teamMembershipRepository) ); container.registerInstance( DI_TOKENS.LeaveTeamUseCase, new LeaveTeamUseCase(teamMembershipRepository) ); container.registerInstance( DI_TOKENS.ApproveTeamJoinRequestUseCase, new ApproveTeamJoinRequestUseCase(teamMembershipRepository) ); container.registerInstance( DI_TOKENS.RejectTeamJoinRequestUseCase, new RejectTeamJoinRequestUseCase(teamMembershipRepository) ); container.registerInstance( DI_TOKENS.UpdateTeamUseCase, new UpdateTeamUseCase(teamRepository, teamMembershipRepository) ); // Register use cases - Stewarding container.registerInstance( DI_TOKENS.FileProtestUseCase, new FileProtestUseCase(protestRepository, raceRepository, leagueMembershipRepository) ); container.registerInstance( DI_TOKENS.ReviewProtestUseCase, new ReviewProtestUseCase(protestRepository, raceRepository, leagueMembershipRepository) ); container.registerInstance( DI_TOKENS.ApplyPenaltyUseCase, new ApplyPenaltyUseCase( penaltyRepository, protestRepository, raceRepository, leagueMembershipRepository ) ); container.registerInstance( DI_TOKENS.RequestProtestDefenseUseCase, new RequestProtestDefenseUseCase(protestRepository, raceRepository, leagueMembershipRepository) ); container.registerInstance( DI_TOKENS.SubmitProtestDefenseUseCase, new SubmitProtestDefenseUseCase(protestRepository) ); // Register use cases - Notifications container.registerInstance( DI_TOKENS.SendNotificationUseCase, new SendNotificationUseCase( notificationRepository, notificationPreferenceRepository, notificationGatewayRegistry ) ); container.registerInstance( DI_TOKENS.MarkNotificationReadUseCase, new MarkNotificationReadUseCase(notificationRepository) ); // Register queries - Racing container.registerInstance( DI_TOKENS.IsDriverRegisteredForRaceQuery, new IsDriverRegisteredForRaceQuery(raceRegistrationRepository) ); container.registerInstance( DI_TOKENS.GetRaceRegistrationsQuery, new GetRaceRegistrationsQuery(raceRegistrationRepository) ); container.registerInstance( DI_TOKENS.GetLeagueStandingsQuery, new GetLeagueStandingsQuery(standingRepository) ); container.registerInstance( DI_TOKENS.GetLeagueDriverSeasonStatsQuery, new GetLeagueDriverSeasonStatsQuery( standingRepository, resultRepository, penaltyRepository, raceRepository, { getRating: (driverId: string) => { const stats = driverStats[driverId]; if (!stats) { return { rating: null, ratingChange: null }; } const baseline = 1500; const delta = stats.rating - baseline; return { rating: stats.rating, ratingChange: delta !== 0 ? delta : null, }; }, } ) ); container.registerInstance( DI_TOKENS.GetAllLeaguesWithCapacityQuery, new GetAllLeaguesWithCapacityQuery(leagueRepository, leagueMembershipRepository) ); container.registerInstance( DI_TOKENS.GetAllLeaguesWithCapacityAndScoringQuery, new GetAllLeaguesWithCapacityAndScoringQuery( leagueRepository, leagueMembershipRepository, seasonRepository, leagueScoringConfigRepository, gameRepository, leagueScoringPresetProvider ) ); container.registerInstance( DI_TOKENS.ListLeagueScoringPresetsQuery, new ListLeagueScoringPresetsQuery(leagueScoringPresetProvider) ); container.registerInstance( DI_TOKENS.GetLeagueScoringConfigQuery, new GetLeagueScoringConfigQuery( leagueRepository, seasonRepository, leagueScoringConfigRepository, gameRepository, leagueScoringPresetProvider ) ); container.registerInstance( DI_TOKENS.GetLeagueFullConfigQuery, new GetLeagueFullConfigQuery( leagueRepository, seasonRepository, leagueScoringConfigRepository, gameRepository ) ); container.registerInstance( DI_TOKENS.PreviewLeagueScheduleQuery, new PreviewLeagueScheduleQuery() ); container.registerInstance( DI_TOKENS.GetRaceWithSOFQuery, new GetRaceWithSOFQuery( raceRepository, raceRegistrationRepository, resultRepository, driverRatingProvider ) ); container.registerInstance( DI_TOKENS.GetLeagueStatsQuery, new GetLeagueStatsQuery( leagueRepository, raceRepository, resultRepository, driverRatingProvider ) ); // Register queries - Teams container.registerInstance( DI_TOKENS.GetAllTeamsQuery, new GetAllTeamsQuery(teamRepository) ); container.registerInstance( DI_TOKENS.GetTeamDetailsQuery, new GetTeamDetailsQuery(teamRepository, teamMembershipRepository) ); container.registerInstance( DI_TOKENS.GetTeamMembersQuery, new GetTeamMembersQuery(teamMembershipRepository) ); container.registerInstance( DI_TOKENS.GetTeamJoinRequestsQuery, new GetTeamJoinRequestsQuery(teamMembershipRepository) ); container.registerInstance( DI_TOKENS.GetDriverTeamQuery, new GetDriverTeamQuery(teamRepository, teamMembershipRepository) ); // Register queries - Stewarding container.registerInstance( DI_TOKENS.GetRaceProtestsQuery, new GetRaceProtestsQuery(protestRepository, driverRepository) ); container.registerInstance( DI_TOKENS.GetRacePenaltiesQuery, new GetRacePenaltiesQuery(penaltyRepository, driverRepository) ); // Register queries - Notifications container.registerInstance( DI_TOKENS.GetUnreadNotificationsQuery, new GetUnreadNotificationsQuery(notificationRepository) ); // Register queries - Sponsors const sponsorRepository = container.resolve(DI_TOKENS.SponsorRepository); const seasonSponsorshipRepository = container.resolve(DI_TOKENS.SeasonSponsorshipRepository); container.registerInstance( DI_TOKENS.GetSponsorDashboardQuery, new GetSponsorDashboardQuery( sponsorRepository, seasonSponsorshipRepository, seasonRepository, leagueRepository, leagueMembershipRepository, raceRepository ) ); container.registerInstance( DI_TOKENS.GetSponsorSponsorshipsQuery, new GetSponsorSponsorshipsQuery( sponsorRepository, seasonSponsorshipRepository, seasonRepository, leagueRepository, leagueMembershipRepository, raceRepository ) ); // Sponsorship request repositories and use cases const sponsorshipRequestRepository = container.resolve(DI_TOKENS.SponsorshipRequestRepository); const sponsorshipPricingRepository = container.resolve(DI_TOKENS.SponsorshipPricingRepository); container.registerInstance( DI_TOKENS.GetPendingSponsorshipRequestsQuery, new GetPendingSponsorshipRequestsQuery( sponsorshipRequestRepository, sponsorRepository ) ); container.registerInstance( DI_TOKENS.GetEntitySponsorshipPricingQuery, new GetEntitySponsorshipPricingQuery( sponsorshipPricingRepository, sponsorshipRequestRepository, seasonSponsorshipRepository ) ); container.registerInstance( DI_TOKENS.ApplyForSponsorshipUseCase, new ApplyForSponsorshipUseCase( sponsorshipRequestRepository, sponsorshipPricingRepository, sponsorRepository ) ); container.registerInstance( DI_TOKENS.AcceptSponsorshipRequestUseCase, new AcceptSponsorshipRequestUseCase(sponsorshipRequestRepository, seasonSponsorshipRepository) ); container.registerInstance( DI_TOKENS.RejectSponsorshipRequestUseCase, new RejectSponsorshipRequestUseCase(sponsorshipRequestRepository) ); } /** * Reset the container (for testing) */ export function resetDIContainer(): void { container.clearInstances(); } /** * Get the TSyringe container instance */ export function getDIContainer() { return container; }