This commit is contained in:
2025-12-14 18:11:59 +01:00
parent acc15e8d8d
commit 217337862c
91 changed files with 5919 additions and 1999 deletions

View File

@@ -30,7 +30,7 @@ import {
} from '@gridpilot/automation/infrastructure/adapters/automation';
import { MockAutomationEngineAdapter } from '@gridpilot/automation/infrastructure/adapters/automation/engine/MockAutomationEngineAdapter';
import { AutomationEngineAdapter } from '@gridpilot/automation/infrastructure/adapters/automation/engine/AutomationEngineAdapter';
import { PinoLogAdapter } from '@gridpilot/automation/infrastructure/adapters/logging/PinoLogAdapter';
import { ConsoleLogAdapter } from '@gridpilot/automation/infrastructure/adapters/logging/ConsoleLogAdapter';
import { NoOpLogAdapter } from '@gridpilot/automation/infrastructure/adapters/logging/NoOpLogAdapter';
import {
loadAutomationConfig,
@@ -107,7 +107,7 @@ export function configureDIContainer(): void {
container.registerInstance(DI_TOKENS.AutomationMode, automationMode);
// Logger (singleton)
const logger = process.env.NODE_ENV === 'test' ? new NoOpLogAdapter() : new PinoLogAdapter(loggingConfig);
const logger = process.env.NODE_ENV === 'test' ? new NoOpLogAdapter() : new ConsoleLogAdapter();
container.registerInstance<LoggerPort>(DI_TOKENS.Logger, logger);
// Browser Mode Config Loader (singleton)
@@ -120,7 +120,7 @@ export function configureDIContainer(): void {
// Session Repository (singleton)
container.register<SessionRepositoryPort>(
DI_TOKENS.SessionRepository,
{ useClass: InMemorySessionRepository },
{ useFactory: (c) => new InMemorySessionRepository(c.resolve(DI_TOKENS.Logger)) },
{ lifecycle: Lifecycle.Singleton }
);
@@ -225,7 +225,8 @@ export function configureDIContainer(): void {
const startAutomationUseCase = new StartAutomationSessionUseCase(
automationEngine,
browserAutomation,
sessionRepository
sessionRepository,
logger
);
container.registerInstance(DI_TOKENS.StartAutomationUseCase, startAutomationUseCase);
@@ -235,17 +236,17 @@ export function configureDIContainer(): void {
container.registerInstance(
DI_TOKENS.CheckAuthenticationUseCase,
new CheckAuthenticationUseCase(authService)
new CheckAuthenticationUseCase(authService, logger)
);
container.registerInstance(
DI_TOKENS.InitiateLoginUseCase,
new InitiateLoginUseCase(authService)
new InitiateLoginUseCase(authService, logger)
);
container.registerInstance(
DI_TOKENS.ClearSessionUseCase,
new ClearSessionUseCase(authService)
new ClearSessionUseCase(authService, logger)
);
container.registerInstance<AuthenticationServicePort>(

View File

@@ -11,18 +11,25 @@ import { CookieIdentitySessionAdapter } from '@gridpilot/identity/infrastructure
import { IracingDemoIdentityProviderAdapter } from '@gridpilot/identity/infrastructure/providers/IracingDemoIdentityProviderAdapter';
import { InMemoryUserRepository } from '@gridpilot/identity/infrastructure/repositories/InMemoryUserRepository';
import type { IUserRepository } from '@gridpilot/identity/domain/repositories/IUserRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
// Singleton user repository to persist across requests (in-memory demo)
let userRepositoryInstance: IUserRepository | null = null;
function getUserRepository(): IUserRepository {
function getUserRepository(logger: ILogger): IUserRepository {
if (!userRepositoryInstance) {
userRepositoryInstance = new InMemoryUserRepository();
userRepositoryInstance = new InMemoryUserRepository(logger);
}
return userRepositoryInstance;
}
export class InMemoryAuthService implements AuthService {
private readonly logger: ILogger;
constructor(logger: ILogger) {
this.logger = logger;
}
async getCurrentSession(): Promise<AuthSession | null> {
const sessionPort = new CookieIdentitySessionAdapter();
const useCase = new GetCurrentUserSessionUseCase(sessionPort);

View File

@@ -1,11 +1,14 @@
import type { AuthService } from './AuthService';
import { InMemoryAuthService } from './InMemoryAuthService';
let authService: AuthService | null = null;
import { getDIContainer } from '../di-container';
import { DI_TOKENS } from '../di-tokens';
export function getAuthService(): AuthService {
if (!authService) {
authService = new InMemoryAuthService();
const container = getDIContainer();
if (!container.isRegistered(DI_TOKENS.AuthService)) {
throw new Error(
`${DI_TOKENS.AuthService.description} not registered in DI container.`,
);
}
return authService;
return container.resolve<AuthService>(DI_TOKENS.AuthService);
}

View File

@@ -37,6 +37,31 @@ import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repos
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 type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { ConsoleLogger } from '@gridpilot/shared/logging/ConsoleLogger';
import type { IPageViewRepository, IEngagementRepository } from '@gridpilot/analytics';
import { InMemoryPageViewRepository, InMemoryEngagementRepository } from '@gridpilot/analytics/infrastructure/repositories';
import { RecordPageViewUseCase, RecordEngagementUseCase } from '@gridpilot/analytics/application/use-cases';
import type { AuthService } from './auth/AuthService';
import { InMemoryAuthService } from './auth/InMemoryAuthService';
import type { IUserRepository, StoredUser } from '@gridpilot/identity/domain/repositories/IUserRepository';
import { InMemoryUserRepository } from '@gridpilot/identity/infrastructure/repositories/InMemoryUserRepository';
import type { ISponsorAccountRepository, SponsorAccount } from '@gridpilot/identity/domain/repositories/ISponsorAccountRepository';
import { InMemorySponsorAccountRepository } from '@gridpilot/identity/infrastructure/repositories/InMemorySponsorAccountRepository';
import type { ILiveryRepository } from '@gridpilot/racing/domain/repositories/ILiveryRepository';
import { InMemoryLiveryRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryLiveryRepository';
import type { IChampionshipStandingRepository } from '@gridpilot/racing/domain/repositories/IChampionshipStandingRepository';
import { InMemoryChampionshipStandingRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryScoringRepositories';
import type { ILeagueWalletRepository } from '@gridpilot/racing/domain/repositories/ILeagueWalletRepository';
import { InMemoryLeagueWalletRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryLeagueWalletRepository';
import type { ITransactionRepository } from '@gridpilot/racing/domain/repositories/ITransactionRepository';
import { InMemoryTransactionRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTransactionRepository';
import type { ISessionRepository } from '@gridpilot/racing/domain/repositories/ISessionRepository';
import { InMemorySessionRepository } from '@gridpilot/racing/infrastructure/repositories/InMemorySessionRepository';
import type { IAchievementRepository } from '@gridpilot/identity/domain/repositories/IAchievementRepository';
import { InMemoryAchievementRepository } from '@gridpilot/identity/infrastructure/repositories/InMemoryAchievementRepository';
import type { IUserRatingRepository } from '@gridpilot/identity/domain/repositories/IUserRatingRepository';
import { InMemoryUserRatingRepository } from '@gridpilot/identity/infrastructure/repositories/InMemoryUserRatingRepository';
// Notifications
import type { INotificationRepository, INotificationPreferenceRepository } from '@gridpilot/notifications/application';
@@ -189,6 +214,9 @@ import {
* Configure the DI container with all bindings for the website application
*/
export function configureDIContainer(): void {
// Register the logger
container.registerSingleton<ILogger>(DI_TOKENS.Logger, ConsoleLogger);
const logger = container.resolve<ILogger>(DI_TOKENS.Logger);
// Clear any existing registrations
container.clearInstances();
@@ -219,19 +247,29 @@ export function configureDIContainer(): void {
// Register repositories
container.registerInstance<IDriverRepository>(
DI_TOKENS.DriverRepository,
new InMemoryDriverRepository(seedData.drivers)
new InMemoryDriverRepository(logger, seedData.drivers)
);
container.registerInstance<IPageViewRepository>(
DI_TOKENS.PageViewRepository,
new InMemoryPageViewRepository(logger)
);
container.registerInstance<IEngagementRepository>(
DI_TOKENS.EngagementRepository,
new InMemoryEngagementRepository(logger)
);
container.registerInstance<ILeagueRepository>(
DI_TOKENS.LeagueRepository,
new InMemoryLeagueRepository(seedData.leagues)
new InMemoryLeagueRepository(logger, seedData.leagues)
);
const raceRepository = new InMemoryRaceRepository(seedData.races);
const raceRepository = new InMemoryRaceRepository(logger, seedData.races);
container.registerInstance<IRaceRepository>(DI_TOKENS.RaceRepository, raceRepository);
// Result repository needs race repository for league-based queries
const resultRepository = new InMemoryResultRepository(seedData.results, raceRepository);
const resultRepository = new InMemoryResultRepository(logger, seedData.results, raceRepository);
container.registerInstance<IResultRepository>(DI_TOKENS.ResultRepository, resultRepository);
// Standing repository needs all three for recalculation
@@ -239,6 +277,7 @@ export function configureDIContainer(): void {
container.registerInstance<IStandingRepository>(
DI_TOKENS.StandingRepository,
new InMemoryStandingRepository(
logger,
seedData.standings,
resultRepository,
raceRepository,
@@ -293,7 +332,7 @@ export function configureDIContainer(): void {
container.registerInstance<IRaceRegistrationRepository>(
DI_TOKENS.RaceRegistrationRepository,
new InMemoryRaceRegistrationRepository(seedRaceRegistrations)
new InMemoryRaceRegistrationRepository(logger, seedRaceRegistrations)
);
// Seed penalties and protests
@@ -449,12 +488,12 @@ export function configureDIContainer(): void {
container.registerInstance<IPenaltyRepository>(
DI_TOKENS.PenaltyRepository,
new InMemoryPenaltyRepository(seededPenalties)
new InMemoryPenaltyRepository(logger, seededPenalties)
);
container.registerInstance<IProtestRepository>(
DI_TOKENS.ProtestRepository,
new InMemoryProtestRepository(seededProtests)
new InMemoryProtestRepository(logger, seededProtests)
);
// Scoring repositories
@@ -495,17 +534,17 @@ export function configureDIContainer(): void {
container.registerInstance<IGameRepository>(
DI_TOKENS.GameRepository,
new InMemoryGameRepository([game])
new InMemoryGameRepository(logger, [game])
);
container.registerInstance<ISeasonRepository>(
DI_TOKENS.SeasonRepository,
new InMemorySeasonRepository(seededSeasons)
new InMemorySeasonRepository(logger, seededSeasons)
);
container.registerInstance<ILeagueScoringConfigRepository>(
DI_TOKENS.LeagueScoringConfigRepository,
new InMemoryLeagueScoringConfigRepository(seededScoringConfigs)
new InMemoryLeagueScoringConfigRepository(logger, seededScoringConfigs)
);
// League memberships
@@ -702,6 +741,7 @@ export function configureDIContainer(): void {
container.registerInstance<ILeagueMembershipRepository>(
DI_TOKENS.LeagueMembershipRepository,
new InMemoryLeagueMembershipRepository(
logger,
seededMemberships as InMemoryLeagueMembershipSeed,
seededJoinRequests,
)
@@ -713,6 +753,7 @@ export function configureDIContainer(): void {
container.registerInstance<ITeamRepository>(
DI_TOKENS.TeamRepository,
new InMemoryTeamRepository(
logger,
seedData.teams.map((t) => ({
id: t.id,
name: t.name,
@@ -728,6 +769,7 @@ export function configureDIContainer(): void {
container.registerInstance<ITeamMembershipRepository>(
DI_TOKENS.TeamMembershipRepository,
new InMemoryTeamMembershipRepository(
logger,
seedData.memberships
.filter((m) => m.teamId)
.map((m) => ({
@@ -743,12 +785,12 @@ export function configureDIContainer(): void {
// Track and Car repositories
container.registerInstance<ITrackRepository>(
DI_TOKENS.TrackRepository,
new InMemoryTrackRepository(DEMO_TRACKS)
new InMemoryTrackRepository(logger, DEMO_TRACKS)
);
container.registerInstance<ICarRepository>(
DI_TOKENS.CarRepository,
new InMemoryCarRepository(DEMO_CARS)
new InMemoryCarRepository(logger, DEMO_CARS)
);
// Sponsor repositories - seed with demo sponsors
@@ -762,7 +804,7 @@ export function configureDIContainer(): void {
})
);
const sponsorRepo = new InMemorySponsorRepository();
const sponsorRepo = new InMemorySponsorRepository(logger);
sponsorRepo.seed(seededSponsors);
container.registerInstance<ISponsorRepository>(
DI_TOKENS.SponsorRepository,
@@ -781,7 +823,7 @@ export function configureDIContainer(): void {
}),
);
const seasonSponsorshipRepo = new InMemorySeasonSponsorshipRepository();
const seasonSponsorshipRepo = new InMemorySeasonSponsorshipRepository(logger);
seasonSponsorshipRepo.seed(seededSponsorships);
container.registerInstance<ISeasonSponsorshipRepository>(
DI_TOKENS.SeasonSponsorshipRepository,
@@ -789,13 +831,13 @@ export function configureDIContainer(): void {
);
// Sponsorship Request and Pricing repositories
const sponsorshipRequestRepo = new InMemorySponsorshipRequestRepository();
const sponsorshipRequestRepo = new InMemorySponsorshipRequestRepository(logger);
container.registerInstance<ISponsorshipRequestRepository>(
DI_TOKENS.SponsorshipRequestRepository,
sponsorshipRequestRepo
);
const sponsorshipPricingRepo = new InMemorySponsorshipPricingRepository();
const sponsorshipPricingRepo = new InMemorySponsorshipPricingRepository(logger);
// Seed sponsorship pricings from demo data using domain SponsorshipPricing
sponsorshipPricingRepo.seed(seedData.sponsorshipPricings ?? []);
container.registerInstance<ISponsorshipPricingRepository>(
@@ -811,12 +853,12 @@ export function configureDIContainer(): void {
// Social repositories
container.registerInstance<IFeedRepository>(
DI_TOKENS.FeedRepository,
new InMemoryFeedRepository(seedData)
new InMemoryFeedRepository(logger, seedData)
);
container.registerInstance<ISocialGraphRepository>(
DI_TOKENS.SocialRepository,
new InMemorySocialGraphRepository(seedData)
new InMemorySocialGraphRepository(logger, seedData)
);
// Image service
@@ -828,12 +870,12 @@ export function configureDIContainer(): void {
// Notification repositories
container.registerInstance<INotificationRepository>(
DI_TOKENS.NotificationRepository,
new InMemoryNotificationRepository()
new InMemoryNotificationRepository(logger)
);
container.registerInstance<INotificationPreferenceRepository>(
DI_TOKENS.NotificationPreferenceRepository,
new InMemoryNotificationPreferenceRepository()
new InMemoryNotificationPreferenceRepository(logger)
);
const notificationGatewayRegistry = new NotificationGatewayRegistry([
@@ -889,26 +931,39 @@ export function configureDIContainer(): void {
const notificationPreferenceRepository = container.resolve<INotificationPreferenceRepository>(DI_TOKENS.NotificationPreferenceRepository);
const feedRepository = container.resolve<IFeedRepository>(DI_TOKENS.FeedRepository);
const socialRepository = container.resolve<ISocialGraphRepository>(DI_TOKENS.SocialRepository);
const pageViewRepository = container.resolve<IPageViewRepository>(DI_TOKENS.PageViewRepository);
const engagementRepository = container.resolve<IEngagementRepository>(DI_TOKENS.EngagementRepository);
const logger = container.resolve<ILogger>(DI_TOKENS.Logger);
// Register use cases - Racing
container.registerInstance(
DI_TOKENS.JoinLeagueUseCase,
new JoinLeagueUseCase(leagueMembershipRepository)
new JoinLeagueUseCase(leagueMembershipRepository, logger)
);
container.registerInstance(
DI_TOKENS.RecordPageViewUseCase,
new RecordPageViewUseCase(pageViewRepository, logger)
);
container.registerInstance(
DI_TOKENS.RecordEngagementUseCase,
new RecordEngagementUseCase(engagementRepository, logger)
);
container.registerInstance(
DI_TOKENS.RegisterForRaceUseCase,
new RegisterForRaceUseCase(raceRegistrationRepository, leagueMembershipRepository)
new RegisterForRaceUseCase(raceRegistrationRepository, leagueMembershipRepository, logger)
);
container.registerInstance(
DI_TOKENS.WithdrawFromRaceUseCase,
new WithdrawFromRaceUseCase(raceRegistrationRepository)
new WithdrawFromRaceUseCase(raceRegistrationRepository, logger)
);
container.registerInstance(
DI_TOKENS.CancelRaceUseCase,
new CancelRaceUseCase(raceRepository)
new CancelRaceUseCase(raceRepository, logger)
);
container.registerInstance(
@@ -918,7 +973,8 @@ export function configureDIContainer(): void {
raceRegistrationRepository,
resultRepository,
standingRepository,
driverRatingProvider
driverRatingProvider,
logger
)
);
@@ -928,7 +984,8 @@ export function configureDIContainer(): void {
leagueRepository,
seasonRepository,
leagueScoringConfigRepository,
leagueScoringPresetProvider
leagueScoringPresetProvider,
logger
)
);
@@ -945,7 +1002,7 @@ export function configureDIContainer(): void {
container.registerInstance(
DI_TOKENS.JoinTeamUseCase,
new JoinTeamUseCase(teamRepository, teamMembershipRepository)
new JoinTeamUseCase(teamRepository, teamMembershipRepository, logger)
);
container.registerInstance(
@@ -955,7 +1012,7 @@ export function configureDIContainer(): void {
container.registerInstance(
DI_TOKENS.ApproveTeamJoinRequestUseCase,
new ApproveTeamJoinRequestUseCase(teamMembershipRepository)
new ApproveTeamJoinRequestUseCase(teamMembershipRepository, logger)
);
container.registerInstance(
@@ -985,7 +1042,8 @@ export function configureDIContainer(): void {
penaltyRepository,
protestRepository,
raceRepository,
leagueMembershipRepository
leagueMembershipRepository,
logger
)
);
@@ -994,7 +1052,8 @@ export function configureDIContainer(): void {
new QuickPenaltyUseCase(
penaltyRepository,
raceRepository,
leagueMembershipRepository
leagueMembershipRepository,
logger
)
);
@@ -1014,13 +1073,14 @@ export function configureDIContainer(): void {
new SendNotificationUseCase(
notificationRepository,
notificationPreferenceRepository,
notificationGatewayRegistry
notificationGatewayRegistry,
logger
)
);
container.registerInstance(
DI_TOKENS.MarkNotificationReadUseCase,
new MarkNotificationReadUseCase(notificationRepository)
new MarkNotificationReadUseCase(notificationRepository, logger)
);
// Register queries - Racing
@@ -1144,7 +1204,8 @@ export function configureDIContainer(): void {
raceRepository,
resultRepository,
driverRatingProvider,
leagueStatsPresenter
leagueStatsPresenter,
logger
)
);
@@ -1155,7 +1216,7 @@ export function configureDIContainer(): void {
container.registerInstance(
DI_TOKENS.GetAllRacesPageDataUseCase,
new GetAllRacesPageDataUseCase(raceRepository, leagueRepository)
new GetAllRacesPageDataUseCase(raceRepository, leagueRepository, logger)
);
const imageService = container.resolve<ImageServicePort>(DI_TOKENS.ImageService);
@@ -1194,7 +1255,8 @@ export function configureDIContainer(): void {
resultRepository,
driverRepository,
standingRepository,
importRaceResultsPresenter
importRaceResultsPresenter,
logger
)
);
@@ -1324,7 +1386,7 @@ export function configureDIContainer(): void {
const allTeamsPresenter = new AllTeamsPresenter();
container.registerInstance(
DI_TOKENS.GetAllTeamsUseCase,
new GetAllTeamsUseCase(teamRepository, teamMembershipRepository),
new GetAllTeamsUseCase(teamRepository, teamMembershipRepository, logger),
);
container.registerInstance(
@@ -1335,7 +1397,7 @@ export function configureDIContainer(): void {
const teamMembersPresenter = new TeamMembersPresenter();
container.registerInstance(
DI_TOKENS.GetTeamMembersUseCase,
new GetTeamMembersUseCase(teamMembershipRepository, driverRepository, imageService, teamMembersPresenter),
new GetTeamMembersUseCase(teamMembershipRepository, driverRepository, imageService, logger, teamMembersPresenter),
);
const teamJoinRequestsPresenter = new TeamJoinRequestsPresenter();
@@ -1345,14 +1407,15 @@ export function configureDIContainer(): void {
teamMembershipRepository,
driverRepository,
imageService,
teamJoinRequestsPresenter,
logger,
teamJoinRequestsPresenter
),
);
const driverTeamPresenter = new DriverTeamPresenter();
container.registerInstance(
DI_TOKENS.GetDriverTeamUseCase,
new GetDriverTeamUseCase(teamRepository, teamMembershipRepository, driverTeamPresenter)
new GetDriverTeamUseCase(teamRepository, teamMembershipRepository, logger, driverTeamPresenter)
);
// Register queries - Stewarding
@@ -1371,7 +1434,7 @@ export function configureDIContainer(): void {
// Register queries - Notifications
container.registerInstance(
DI_TOKENS.GetUnreadNotificationsUseCase,
new GetUnreadNotificationsUseCase(notificationRepository)
new GetUnreadNotificationsUseCase(notificationRepository, logger)
);
// Register use cases - Sponsors
@@ -1421,7 +1484,8 @@ export function configureDIContainer(): void {
sponsorshipPricingRepository,
sponsorshipRequestRepository,
seasonSponsorshipRepository,
entitySponsorshipPricingPresenter
entitySponsorshipPricingPresenter,
logger
)
);
@@ -1430,7 +1494,8 @@ export function configureDIContainer(): void {
new ApplyForSponsorshipUseCase(
sponsorshipRequestRepository,
sponsorshipPricingRepository,
sponsorRepository
sponsorRepository,
logger
)
);
@@ -1440,6 +1505,7 @@ export function configureDIContainer(): void {
sponsorshipRequestRepository,
seasonSponsorshipRepository,
seasonRepository,
logger
)
);

View File

@@ -28,6 +28,17 @@ export const DI_TOKENS = {
SeasonSponsorshipRepository: Symbol.for('ISeasonSponsorshipRepository'),
SponsorshipRequestRepository: Symbol.for('ISponsorshipRequestRepository'),
SponsorshipPricingRepository: Symbol.for('ISponsorshipPricingRepository'),
PageViewRepository: Symbol.for('IPageViewRepository'),
EngagementRepository: Symbol.for('IEngagementRepository'),
UserRepository: Symbol.for('IUserRepository'),
SponsorAccountRepository: Symbol.for('ISponsorAccountRepository'),
LiveryRepository: Symbol.for('ILiveryRepository'),
ChampionshipStandingRepository: Symbol.for('IChampionshipStandingRepository'),
LeagueWalletRepository: Symbol.for('ILeagueWalletRepository'),
TransactionRepository: Symbol.for('ITransactionRepository'),
SessionRepository: Symbol.for('ISessionRepository'),
AchievementRepository: Symbol.for('IAchievementRepository'),
UserRatingRepository: Symbol.for('IUserRatingRepository'),
// Providers
LeagueScoringPresetProvider: Symbol.for('LeagueScoringPresetProvider'),
@@ -36,6 +47,12 @@ export const DI_TOKENS = {
// Services
ImageService: Symbol.for('ImageServicePort'),
NotificationGatewayRegistry: Symbol.for('NotificationGatewayRegistry'),
Logger: Symbol.for('ILogger'),
AuthService: Symbol.for('AuthService'),
// Use Cases - Analytics
RecordPageViewUseCase: Symbol.for('RecordPageViewUseCase'),
RecordEngagementUseCase: Symbol.for('RecordEngagementUseCase'),
// Use Cases - Racing
JoinLeagueUseCase: Symbol.for('JoinLeagueUseCase'),