inmemory to postgres
This commit is contained in:
@@ -2,7 +2,6 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AnalyticsModule } from './AnalyticsModule';
|
||||
import { AnalyticsController } from './AnalyticsController';
|
||||
import { AnalyticsService } from './AnalyticsService';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
|
||||
describe('AnalyticsModule', () => {
|
||||
let module: TestingModule;
|
||||
@@ -10,10 +9,7 @@ describe('AnalyticsModule', () => {
|
||||
beforeEach(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
imports: [AnalyticsModule],
|
||||
})
|
||||
.overrideProvider('Logger_TOKEN')
|
||||
.useClass(ConsoleLogger)
|
||||
.compile();
|
||||
}).compile();
|
||||
});
|
||||
|
||||
it('should compile the module', () => {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AnalyticsPersistenceModule } from '../../persistence/analytics/AnalyticsPersistenceModule';
|
||||
import { AnalyticsController } from './AnalyticsController';
|
||||
import { AnalyticsService } from './AnalyticsService';
|
||||
import { AnalyticsProviders } from './AnalyticsProviders';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [AnalyticsPersistenceModule],
|
||||
controllers: [AnalyticsController],
|
||||
providers: AnalyticsProviders,
|
||||
exports: [AnalyticsService],
|
||||
|
||||
@@ -6,18 +6,18 @@ import type { IPageViewRepository } from '@core/analytics/application/repositori
|
||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
import { Provider } from '@nestjs/common';
|
||||
|
||||
const Logger_TOKEN = 'Logger_TOKEN';
|
||||
const IPAGE_VIEW_REPO_TOKEN = 'IPageViewRepository_TOKEN';
|
||||
const IENGAGEMENT_REPO_TOKEN = 'IEngagementRepository_TOKEN';
|
||||
import {
|
||||
ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN,
|
||||
ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN,
|
||||
} from '../../persistence/analytics/AnalyticsPersistenceTokens';
|
||||
|
||||
const LOGGER_TOKEN = 'Logger';
|
||||
|
||||
const RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN = 'RecordPageViewOutputPort_TOKEN';
|
||||
const RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN = 'RecordEngagementOutputPort_TOKEN';
|
||||
const GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN = 'GetDashboardDataOutputPort_TOKEN';
|
||||
const GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN = 'GetAnalyticsMetricsOutputPort_TOKEN';
|
||||
|
||||
import { InMemoryEngagementRepository } from '@adapters/analytics/persistence/inmemory/InMemoryEngagementRepository';
|
||||
import { InMemoryPageViewRepository } from '@adapters/analytics/persistence/inmemory/InMemoryPageViewRepository';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
import { GetAnalyticsMetricsUseCase } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
||||
import { GetDashboardDataOutput, GetDashboardDataUseCase } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
||||
import { RecordEngagementUseCase } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
||||
@@ -34,20 +34,6 @@ export const AnalyticsProviders: Provider[] = [
|
||||
RecordEngagementPresenter,
|
||||
GetDashboardDataPresenter,
|
||||
GetAnalyticsMetricsPresenter,
|
||||
{
|
||||
provide: Logger_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
{
|
||||
provide: IPAGE_VIEW_REPO_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryPageViewRepository(logger),
|
||||
inject: [Logger_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: IENGAGEMENT_REPO_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryEngagementRepository(logger),
|
||||
inject: [Logger_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN,
|
||||
useExisting: RecordPageViewPresenter,
|
||||
@@ -68,24 +54,24 @@ export const AnalyticsProviders: Provider[] = [
|
||||
provide: RecordPageViewUseCase,
|
||||
useFactory: (repo: IPageViewRepository, logger: Logger, output: UseCaseOutputPort<RecordPageViewOutput>) =>
|
||||
new RecordPageViewUseCase(repo, logger, output),
|
||||
inject: [IPAGE_VIEW_REPO_TOKEN, Logger_TOKEN, RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN],
|
||||
inject: [ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN, LOGGER_TOKEN, RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: RecordEngagementUseCase,
|
||||
useFactory: (repo: IEngagementRepository, logger: Logger, output: UseCaseOutputPort<RecordEngagementOutput>) =>
|
||||
new RecordEngagementUseCase(repo, logger, output),
|
||||
inject: [IENGAGEMENT_REPO_TOKEN, Logger_TOKEN, RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN],
|
||||
inject: [ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN, LOGGER_TOKEN, RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetDashboardDataUseCase,
|
||||
useFactory: (logger: Logger, output: UseCaseOutputPort<GetDashboardDataOutput>) =>
|
||||
new GetDashboardDataUseCase(logger, output),
|
||||
inject: [Logger_TOKEN, GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN],
|
||||
inject: [LOGGER_TOKEN, GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetAnalyticsMetricsUseCase,
|
||||
useFactory: (logger: Logger, output: UseCaseOutputPort<GetAnalyticsMetricsOutput>, repo: IPageViewRepository) =>
|
||||
new GetAnalyticsMetricsUseCase(logger, output, repo),
|
||||
inject: [Logger_TOKEN, GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN, IPAGE_VIEW_REPO_TOKEN],
|
||||
inject: [LOGGER_TOKEN, GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN, ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN],
|
||||
},
|
||||
];
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { IdentityPersistenceModule } from '../../persistence/identity/IdentityPersistenceModule';
|
||||
import { AuthService } from './AuthService';
|
||||
import { AuthController } from './AuthController';
|
||||
import { AuthProviders } from './AuthProviders';
|
||||
@@ -7,14 +8,9 @@ import { AuthorizationGuard } from './AuthorizationGuard';
|
||||
import { AuthorizationService } from './AuthorizationService';
|
||||
|
||||
@Module({
|
||||
imports: [IdentityPersistenceModule],
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
AuthService,
|
||||
...AuthProviders,
|
||||
AuthenticationGuard,
|
||||
AuthorizationService,
|
||||
AuthorizationGuard,
|
||||
],
|
||||
providers: [AuthService, ...AuthProviders, AuthenticationGuard, AuthorizationService, AuthorizationGuard],
|
||||
exports: [AuthService, AuthenticationGuard, AuthorizationService, AuthorizationGuard],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
|
||||
// Import interfaces and concrete implementations
|
||||
import { StoredUser } from '@core/identity/domain/repositories/IUserRepository';
|
||||
import type { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
|
||||
|
||||
import { InMemoryAuthRepository } from '@adapters/identity/persistence/inmemory/InMemoryAuthRepository';
|
||||
import { InMemoryUserRepository } from '@adapters/identity/persistence/inmemory/InMemoryUserRepository';
|
||||
import { InMemoryPasswordHashingService } from '@adapters/identity/services/InMemoryPasswordHashingService';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
import { CookieIdentitySessionAdapter } from '@adapters/identity/session/CookieIdentitySessionAdapter';
|
||||
import { LoginUseCase } from '@core/identity/application/use-cases/LoginUseCase';
|
||||
import { LogoutUseCase } from '@core/identity/application/use-cases/LogoutUseCase';
|
||||
import { SignupUseCase } from '@core/identity/application/use-cases/SignupUseCase';
|
||||
import type { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
|
||||
import type { IAuthRepository } from '@core/identity/domain/repositories/IAuthRepository';
|
||||
import type { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
|
||||
import type { LoginResult } from '@core/identity/application/use-cases/LoginUseCase';
|
||||
import type { LogoutResult } from '@core/identity/application/use-cases/LogoutUseCase';
|
||||
import type { SignupResult } from '@core/identity/application/use-cases/SignupUseCase';
|
||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
|
||||
import {
|
||||
AUTH_REPOSITORY_TOKEN,
|
||||
PASSWORD_HASHING_SERVICE_TOKEN,
|
||||
USER_REPOSITORY_TOKEN,
|
||||
} from '../../persistence/identity/IdentityPersistenceTokens';
|
||||
|
||||
import { AuthSessionPresenter } from './presenters/AuthSessionPresenter';
|
||||
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
|
||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { LoginResult } from '@core/identity/application/use-cases/LoginUseCase';
|
||||
import type { SignupResult } from '@core/identity/application/use-cases/SignupUseCase';
|
||||
import type { LogoutResult } from '@core/identity/application/use-cases/LogoutUseCase';
|
||||
import type { IAuthRepository } from '@core/identity/domain/repositories/IAuthRepository';
|
||||
import type { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
|
||||
|
||||
// Define the tokens for dependency injection
|
||||
export const AUTH_REPOSITORY_TOKEN = 'IAuthRepository';
|
||||
export const USER_REPOSITORY_TOKEN = 'IUserRepository';
|
||||
export const PASSWORD_HASHING_SERVICE_TOKEN = 'IPasswordHashingService';
|
||||
export { AUTH_REPOSITORY_TOKEN, USER_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN };
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
export const IDENTITY_SESSION_PORT_TOKEN = 'IdentitySessionPort';
|
||||
export const LOGIN_USE_CASE_TOKEN = 'LoginUseCase';
|
||||
@@ -35,38 +33,6 @@ export const AUTH_SESSION_OUTPUT_PORT_TOKEN = 'AuthSessionOutputPort';
|
||||
export const COMMAND_RESULT_OUTPUT_PORT_TOKEN = 'CommandResultOutputPort';
|
||||
|
||||
export const AuthProviders: Provider[] = [
|
||||
{
|
||||
provide: AUTH_REPOSITORY_TOKEN,
|
||||
useFactory: (userRepository: InMemoryUserRepository, passwordHashingService: IPasswordHashingService, logger: Logger) =>
|
||||
new InMemoryAuthRepository(userRepository, passwordHashingService, logger),
|
||||
inject: [USER_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: USER_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => {
|
||||
const initialUsers: StoredUser[] = [
|
||||
{
|
||||
// Match seeded racing driver id so dashboard works in inmemory mode.
|
||||
id: 'driver-1',
|
||||
email: 'admin@gridpilot.local',
|
||||
passwordHash: 'demo_salt_321nimda', // InMemoryPasswordHashingService: "admin123" reversed.
|
||||
displayName: 'Admin',
|
||||
salt: '',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
];
|
||||
return new InMemoryUserRepository(logger, initialUsers);
|
||||
},
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: PASSWORD_HASHING_SERVICE_TOKEN,
|
||||
useClass: InMemoryPasswordHashingService,
|
||||
},
|
||||
{
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
{
|
||||
provide: IDENTITY_SESSION_PORT_TOKEN,
|
||||
useFactory: (logger: Logger) => new CookieIdentitySessionAdapter(logger),
|
||||
|
||||
@@ -4,11 +4,11 @@ import { SeedRacingData, type RacingSeedDependencies } from '../../../../../adap
|
||||
import { Inject, Module, OnModuleInit } from '@nestjs/common';
|
||||
import { getApiPersistence, getEnableBootstrap } from '../../env';
|
||||
import { RacingPersistenceModule } from '../../persistence/racing/RacingPersistenceModule';
|
||||
import { InMemorySocialPersistenceModule } from '../../persistence/inmemory/InMemorySocialPersistenceModule';
|
||||
import { SocialPersistenceModule } from '../../persistence/social/SocialPersistenceModule';
|
||||
import { BootstrapProviders, ENSURE_INITIAL_DATA_TOKEN } from './BootstrapProviders';
|
||||
|
||||
@Module({
|
||||
imports: [RacingPersistenceModule, InMemorySocialPersistenceModule],
|
||||
imports: [RacingPersistenceModule, SocialPersistenceModule],
|
||||
providers: BootstrapProviders,
|
||||
})
|
||||
export class BootstrapModule implements OnModuleInit {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { SOCIAL_FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN } from '../../persistence/social/SocialPersistenceTokens';
|
||||
import { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData';
|
||||
import type { RacingSeedDependencies } from '../../../../../adapters/bootstrap/SeedRacingData';
|
||||
import { SignupWithEmailUseCase, type SignupWithEmailResult } from '@core/identity/application/use-cases/SignupWithEmailUseCase';
|
||||
@@ -105,8 +106,8 @@ export const BootstrapProviders: Provider[] = [
|
||||
'ITeamRepository',
|
||||
'ITeamMembershipRepository',
|
||||
'ISponsorRepository',
|
||||
'IFeedRepository',
|
||||
'ISocialGraphRepository',
|
||||
SOCIAL_FEED_REPOSITORY_TOKEN,
|
||||
SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RacingPersistenceModule } from '../../persistence/racing/RacingPersistenceModule';
|
||||
import { InMemorySocialPersistenceModule } from '../../persistence/inmemory/InMemorySocialPersistenceModule';
|
||||
import { SocialPersistenceModule } from '../../persistence/social/SocialPersistenceModule';
|
||||
import { DashboardService } from './DashboardService';
|
||||
import { DashboardController } from './DashboardController';
|
||||
import { DashboardProviders } from './DashboardProviders';
|
||||
|
||||
@Module({
|
||||
imports: [RacingPersistenceModule, InMemorySocialPersistenceModule],
|
||||
imports: [RacingPersistenceModule, SocialPersistenceModule],
|
||||
controllers: [DashboardController],
|
||||
providers: [DashboardService, ...DashboardProviders],
|
||||
exports: [DashboardService],
|
||||
|
||||
@@ -11,6 +11,8 @@ import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/IL
|
||||
import { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
|
||||
import { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
||||
import { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
||||
|
||||
import { SOCIAL_FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN } from '../../persistence/social/SocialPersistenceTokens';
|
||||
import { ImageServicePort } from '@core/media/application/ports/ImageServicePort';
|
||||
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
||||
|
||||
@@ -28,8 +30,6 @@ export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
|
||||
export const STANDING_REPOSITORY_TOKEN = 'IStandingRepository';
|
||||
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
|
||||
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
|
||||
export const FEED_REPOSITORY_TOKEN = 'IFeedRepository';
|
||||
export const SOCIAL_GRAPH_REPOSITORY_TOKEN = 'ISocialGraphRepository';
|
||||
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
|
||||
export const DASHBOARD_OVERVIEW_USE_CASE_TOKEN = 'DashboardOverviewUseCase';
|
||||
export const DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN = 'DashboardOverviewOutputPort';
|
||||
@@ -86,7 +86,7 @@ export const DashboardProviders: Provider[] = [
|
||||
STANDING_REPOSITORY_TOKEN,
|
||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||
FEED_REPOSITORY_TOKEN,
|
||||
SOCIAL_FEED_REPOSITORY_TOKEN,
|
||||
SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
||||
IMAGE_SERVICE_TOKEN,
|
||||
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RacingPersistenceModule } from '../../persistence/racing/RacingPersistenceModule';
|
||||
import { InMemorySocialPersistenceModule } from '../../persistence/inmemory/InMemorySocialPersistenceModule';
|
||||
import { SocialPersistenceModule } from '../../persistence/social/SocialPersistenceModule';
|
||||
import { DriverService } from './DriverService';
|
||||
import { DriverController } from './DriverController';
|
||||
import { DriverProviders } from './DriverProviders';
|
||||
|
||||
@Module({
|
||||
imports: [RacingPersistenceModule, InMemorySocialPersistenceModule],
|
||||
imports: [RacingPersistenceModule, SocialPersistenceModule],
|
||||
controllers: [DriverController],
|
||||
providers: [DriverService, ...DriverProviders],
|
||||
exports: [DriverService],
|
||||
|
||||
@@ -7,8 +7,10 @@ export const IMAGE_SERVICE_PORT_TOKEN = 'IImageServicePort';
|
||||
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
|
||||
export const NOTIFICATION_PREFERENCE_REPOSITORY_TOKEN = 'INotificationPreferenceRepository';
|
||||
export const TEAM_REPOSITORY_TOKEN = 'ITeamRepository';
|
||||
import { SOCIAL_GRAPH_REPOSITORY_TOKEN } from '../../persistence/social/SocialPersistenceTokens';
|
||||
|
||||
export const TEAM_MEMBERSHIP_REPOSITORY_TOKEN = 'ITeamMembershipRepository';
|
||||
export const SOCIAL_GRAPH_REPOSITORY_TOKEN = 'ISocialGraphRepository';
|
||||
export { SOCIAL_GRAPH_REPOSITORY_TOKEN };
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
|
||||
export const GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN = 'GetDriversLeaderboardUseCase';
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { PaymentsPersistenceModule } from '../../persistence/payments/PaymentsPersistenceModule';
|
||||
|
||||
import { PaymentsService } from './PaymentsService';
|
||||
import { PaymentsController } from './PaymentsController';
|
||||
import { PaymentsProviders } from './PaymentsProviders';
|
||||
|
||||
@Module({
|
||||
imports: [PaymentsPersistenceModule],
|
||||
controllers: [PaymentsController],
|
||||
providers: [PaymentsService, ...PaymentsProviders],
|
||||
exports: [PaymentsService],
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { IPaymentRepository } from '@core/payments/domain/repositories/IPay
|
||||
import type { IMembershipFeeRepository, IMemberPaymentRepository } from '@core/payments/domain/repositories/IMembershipFeeRepository';
|
||||
import type { IPrizeRepository } from '@core/payments/domain/repositories/IPrizeRepository';
|
||||
import type { IWalletRepository, ITransactionRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
|
||||
// Import use cases
|
||||
import { GetPaymentsUseCase } from '@core/payments/application/use-cases/GetPaymentsUseCase';
|
||||
@@ -22,10 +22,6 @@ import { GetWalletUseCase } from '@core/payments/application/use-cases/GetWallet
|
||||
import { ProcessWalletTransactionUseCase } from '@core/payments/application/use-cases/ProcessWalletTransactionUseCase';
|
||||
|
||||
// Import concrete in-memory implementations
|
||||
import { InMemoryPaymentRepository } from '@adapters/payments/persistence/inmemory/InMemoryPaymentRepository';
|
||||
import { InMemoryMembershipFeeRepository, InMemoryMemberPaymentRepository } from '@adapters/payments/persistence/inmemory/InMemoryMembershipFeeRepository';
|
||||
import { InMemoryPrizeRepository } from '@adapters/payments/persistence/inmemory/InMemoryPrizeRepository';
|
||||
import { InMemoryWalletRepository, InMemoryTransactionRepository } from '@adapters/payments/persistence/inmemory/InMemoryWalletRepository';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
|
||||
// Presenters
|
||||
@@ -150,37 +146,7 @@ export const PaymentsProviders: Provider[] = [
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
|
||||
// Repositories (repositories are injected into use cases, NOT into services)
|
||||
{
|
||||
provide: PAYMENT_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryPaymentRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryMembershipFeeRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: MEMBER_PAYMENT_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryMemberPaymentRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: PRIZE_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryPrizeRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: WALLET_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryWalletRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: TRANSACTION_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryTransactionRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
// Repositories are provided by PaymentsPersistenceModule behind tokens.
|
||||
|
||||
// Use cases (use cases receive repositories, services receive use cases)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
export const PAYMENT_REPOSITORY_TOKEN = 'IPaymentRepository';
|
||||
export const MEMBERSHIP_FEE_REPOSITORY_TOKEN = 'IMembershipFeeRepository';
|
||||
export const MEMBER_PAYMENT_REPOSITORY_TOKEN = 'IMemberPaymentRepository';
|
||||
export const PRIZE_REPOSITORY_TOKEN = 'IPrizeRepository';
|
||||
export const WALLET_REPOSITORY_TOKEN = 'IWalletRepository';
|
||||
export const TRANSACTION_REPOSITORY_TOKEN = 'ITransactionRepository';
|
||||
import {
|
||||
PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PRIZE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_TRANSACTION_REPOSITORY_TOKEN,
|
||||
PAYMENTS_WALLET_REPOSITORY_TOKEN,
|
||||
} from '../../persistence/payments/PaymentsPersistenceTokens';
|
||||
|
||||
export const PAYMENT_REPOSITORY_TOKEN = PAYMENTS_PAYMENT_REPOSITORY_TOKEN;
|
||||
export const MEMBERSHIP_FEE_REPOSITORY_TOKEN = PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN;
|
||||
export const MEMBER_PAYMENT_REPOSITORY_TOKEN = PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN;
|
||||
export const PRIZE_REPOSITORY_TOKEN = PAYMENTS_PRIZE_REPOSITORY_TOKEN;
|
||||
export const WALLET_REPOSITORY_TOKEN = PAYMENTS_WALLET_REPOSITORY_TOKEN;
|
||||
export const TRANSACTION_REPOSITORY_TOKEN = PAYMENTS_TRANSACTION_REPOSITORY_TOKEN;
|
||||
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
|
||||
export const GET_PAYMENTS_USE_CASE_TOKEN = 'GetPaymentsUseCase';
|
||||
|
||||
@@ -36,6 +36,11 @@ export function getApiPersistence(): ApiPersistence {
|
||||
return requireOneOf('GRIDPILOT_API_PERSISTENCE', configured, ['postgres', 'inmemory'] as const);
|
||||
}
|
||||
|
||||
// Tests should default to in-memory even when DATABASE_URL exists, unless explicitly overridden.
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return 'inmemory';
|
||||
}
|
||||
|
||||
return process.env.DATABASE_URL ? 'postgres' : 'inmemory';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { Test } from '@nestjs/testing';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN,
|
||||
ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN,
|
||||
ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN,
|
||||
} from './AnalyticsPersistenceTokens';
|
||||
|
||||
describe('AnalyticsPersistenceModule', () => {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
delete (globalThis as { __GP_TEST_DATA_SOURCE__?: unknown }).__GP_TEST_DATA_SOURCE__;
|
||||
vi.restoreAllMocks();
|
||||
vi.resetModules();
|
||||
vi.unmock('@nestjs/typeorm');
|
||||
});
|
||||
|
||||
it('uses inmemory providers when GRIDPILOT_API_PERSISTENCE=inmemory', async () => {
|
||||
vi.resetModules();
|
||||
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
|
||||
delete process.env.DATABASE_URL;
|
||||
|
||||
const { AnalyticsPersistenceModule } = await import('./AnalyticsPersistenceModule');
|
||||
const { InMemoryPageViewRepository } = await import('@adapters/analytics/persistence/inmemory/InMemoryPageViewRepository');
|
||||
const { InMemoryEngagementRepository } = await import('@adapters/analytics/persistence/inmemory/InMemoryEngagementRepository');
|
||||
const { InMemoryAnalyticsSnapshotRepository } = await import('@adapters/analytics/persistence/inmemory/InMemoryAnalyticsSnapshotRepository');
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AnalyticsPersistenceModule],
|
||||
}).compile();
|
||||
|
||||
expect(module.get(ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN)).toBeInstanceOf(InMemoryPageViewRepository);
|
||||
expect(module.get(ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN)).toBeInstanceOf(InMemoryEngagementRepository);
|
||||
expect(module.get(ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN)).toBeInstanceOf(InMemoryAnalyticsSnapshotRepository);
|
||||
|
||||
await module.close();
|
||||
});
|
||||
|
||||
it('uses postgres providers when GRIDPILOT_API_PERSISTENCE=postgres', async () => {
|
||||
vi.resetModules();
|
||||
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'postgres';
|
||||
delete process.env.DATABASE_URL;
|
||||
|
||||
const DATA_SOURCE_TOKEN = 'TEST_DATA_SOURCE_TOKEN';
|
||||
|
||||
const fakeDataSource = {
|
||||
getRepository: () => ({}),
|
||||
};
|
||||
|
||||
(globalThis as { __GP_TEST_DATA_SOURCE__?: unknown }).__GP_TEST_DATA_SOURCE__ = fakeDataSource;
|
||||
|
||||
vi.doMock('@nestjs/typeorm', async () => {
|
||||
return {
|
||||
TypeOrmModule: {
|
||||
forFeature: () => ({
|
||||
module: class TypeOrmFeatureStubModule {},
|
||||
providers: [
|
||||
{
|
||||
provide: DATA_SOURCE_TOKEN,
|
||||
useValue: (globalThis as { __GP_TEST_DATA_SOURCE__?: unknown }).__GP_TEST_DATA_SOURCE__,
|
||||
},
|
||||
],
|
||||
exports: [DATA_SOURCE_TOKEN],
|
||||
}),
|
||||
},
|
||||
getDataSourceToken: () => DATA_SOURCE_TOKEN,
|
||||
};
|
||||
});
|
||||
|
||||
const { AnalyticsPersistenceModule } = await import('./AnalyticsPersistenceModule');
|
||||
const { TypeOrmPageViewRepository } = await import('@adapters/analytics/persistence/typeorm/repositories/TypeOrmPageViewRepository');
|
||||
const { TypeOrmEngagementRepository } = await import('@adapters/analytics/persistence/typeorm/repositories/TypeOrmEngagementRepository');
|
||||
const { TypeOrmAnalyticsSnapshotRepository } = await import('@adapters/analytics/persistence/typeorm/repositories/TypeOrmAnalyticsSnapshotRepository');
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AnalyticsPersistenceModule],
|
||||
}).compile();
|
||||
|
||||
expect(module.get(ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN)).toBeInstanceOf(TypeOrmPageViewRepository);
|
||||
expect(module.get(ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN)).toBeInstanceOf(TypeOrmEngagementRepository);
|
||||
expect(module.get(ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN)).toBeInstanceOf(TypeOrmAnalyticsSnapshotRepository);
|
||||
|
||||
await module.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { getApiPersistence } from '../../env';
|
||||
import { InMemoryAnalyticsPersistenceModule } from '../inmemory/InMemoryAnalyticsPersistenceModule';
|
||||
import { PostgresAnalyticsPersistenceModule } from '../postgres/PostgresAnalyticsPersistenceModule';
|
||||
|
||||
const selectedPersistenceModule =
|
||||
getApiPersistence() === 'postgres' ? PostgresAnalyticsPersistenceModule : InMemoryAnalyticsPersistenceModule;
|
||||
|
||||
@Module({
|
||||
imports: [selectedPersistenceModule],
|
||||
exports: [selectedPersistenceModule],
|
||||
})
|
||||
export class AnalyticsPersistenceModule {}
|
||||
@@ -0,0 +1,3 @@
|
||||
export const ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN = 'ANALYTICS_IPageViewRepository';
|
||||
export const ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN = 'ANALYTICS_IEngagementRepository';
|
||||
export const ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN = 'ANALYTICS_IAnalyticsSnapshotRepository';
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { MODULE_METADATA } from '@nestjs/common/constants';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { USER_REPOSITORY_TOKEN } from './IdentityPersistenceTokens';
|
||||
|
||||
describe('IdentityPersistenceModule', () => {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('uses inmemory providers when GRIDPILOT_API_PERSISTENCE=inmemory', async () => {
|
||||
vi.resetModules();
|
||||
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
|
||||
delete process.env.DATABASE_URL;
|
||||
|
||||
const { IdentityPersistenceModule } = await import('./IdentityPersistenceModule');
|
||||
const { InMemoryUserRepository } = await import('@adapters/identity/persistence/inmemory/InMemoryUserRepository');
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [IdentityPersistenceModule],
|
||||
}).compile();
|
||||
|
||||
const userRepo = module.get(USER_REPOSITORY_TOKEN);
|
||||
expect(userRepo).toBeInstanceOf(InMemoryUserRepository);
|
||||
|
||||
await module.close();
|
||||
});
|
||||
|
||||
it('uses postgres module when GRIDPILOT_API_PERSISTENCE=postgres', async () => {
|
||||
vi.resetModules();
|
||||
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'postgres';
|
||||
delete process.env.DATABASE_URL;
|
||||
|
||||
const { IdentityPersistenceModule } = await import('./IdentityPersistenceModule');
|
||||
const { PostgresIdentityPersistenceModule } = await import('../postgres/PostgresIdentityPersistenceModule');
|
||||
|
||||
const imports = Reflect.getMetadata(MODULE_METADATA.IMPORTS, IdentityPersistenceModule) as unknown[];
|
||||
expect(imports).toContain(PostgresIdentityPersistenceModule);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { getApiPersistence } from '../../env';
|
||||
import { InMemoryIdentityPersistenceModule } from '../inmemory/InMemoryIdentityPersistenceModule';
|
||||
import { PostgresIdentityPersistenceModule } from '../postgres/PostgresIdentityPersistenceModule';
|
||||
|
||||
const selectedPersistenceModule =
|
||||
getApiPersistence() === 'postgres' ? PostgresIdentityPersistenceModule : InMemoryIdentityPersistenceModule;
|
||||
|
||||
@Module({
|
||||
imports: [selectedPersistenceModule],
|
||||
exports: [selectedPersistenceModule],
|
||||
})
|
||||
export class IdentityPersistenceModule {}
|
||||
@@ -0,0 +1,3 @@
|
||||
export const AUTH_REPOSITORY_TOKEN = 'IAuthRepository';
|
||||
export const USER_REPOSITORY_TOKEN = 'IUserRepository';
|
||||
export const PASSWORD_HASHING_SERVICE_TOKEN = 'IPasswordHashingService';
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { LoggingModule } from '../../domain/logging/LoggingModule';
|
||||
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
|
||||
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
|
||||
import type { IAnalyticsSnapshotRepository } from '@core/analytics/domain/repositories/IAnalyticsSnapshotRepository';
|
||||
import type { IPageViewRepository } from '@core/analytics/application/repositories/IPageViewRepository';
|
||||
|
||||
import { InMemoryAnalyticsSnapshotRepository } from '@adapters/analytics/persistence/inmemory/InMemoryAnalyticsSnapshotRepository';
|
||||
import { InMemoryEngagementRepository } from '@adapters/analytics/persistence/inmemory/InMemoryEngagementRepository';
|
||||
import { InMemoryPageViewRepository } from '@adapters/analytics/persistence/inmemory/InMemoryPageViewRepository';
|
||||
|
||||
import {
|
||||
ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN,
|
||||
ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN,
|
||||
ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN,
|
||||
} from '../analytics/AnalyticsPersistenceTokens';
|
||||
|
||||
@Module({
|
||||
imports: [LoggingModule],
|
||||
providers: [
|
||||
{
|
||||
provide: ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IPageViewRepository => new InMemoryPageViewRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IEngagementRepository => new InMemoryEngagementRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IAnalyticsSnapshotRepository => new InMemoryAnalyticsSnapshotRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN,
|
||||
ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN,
|
||||
ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN,
|
||||
],
|
||||
})
|
||||
export class InMemoryAnalyticsPersistenceModule {}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { LoggingModule } from '../../domain/logging/LoggingModule';
|
||||
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
|
||||
import type { StoredUser } from '@core/identity/domain/repositories/IUserRepository';
|
||||
|
||||
import { InMemoryAuthRepository } from '@adapters/identity/persistence/inmemory/InMemoryAuthRepository';
|
||||
import { InMemoryUserRepository } from '@adapters/identity/persistence/inmemory/InMemoryUserRepository';
|
||||
import { InMemoryPasswordHashingService } from '@adapters/identity/services/InMemoryPasswordHashingService';
|
||||
|
||||
import { AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, USER_REPOSITORY_TOKEN } from '../identity/IdentityPersistenceTokens';
|
||||
|
||||
@Module({
|
||||
imports: [LoggingModule],
|
||||
providers: [
|
||||
{
|
||||
provide: USER_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => {
|
||||
const initialUsers: StoredUser[] = [
|
||||
{
|
||||
// Match seeded racing driver id so dashboard works in inmemory mode.
|
||||
id: 'driver-1',
|
||||
email: 'admin@gridpilot.local',
|
||||
passwordHash: 'demo_salt_321nimda', // InMemoryPasswordHashingService: "admin123" reversed.
|
||||
displayName: 'Admin',
|
||||
salt: '',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
];
|
||||
return new InMemoryUserRepository(logger, initialUsers);
|
||||
},
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: AUTH_REPOSITORY_TOKEN,
|
||||
useFactory: (userRepository: InMemoryUserRepository, passwordHashingService: IPasswordHashingService, logger: Logger) =>
|
||||
new InMemoryAuthRepository(userRepository, passwordHashingService, logger),
|
||||
inject: [USER_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, 'Logger'],
|
||||
},
|
||||
{
|
||||
provide: PASSWORD_HASHING_SERVICE_TOKEN,
|
||||
useClass: InMemoryPasswordHashingService,
|
||||
},
|
||||
],
|
||||
exports: [USER_REPOSITORY_TOKEN, AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN],
|
||||
})
|
||||
export class InMemoryIdentityPersistenceModule {}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { LoggingModule } from '../../domain/logging/LoggingModule';
|
||||
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
|
||||
import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository';
|
||||
import type {
|
||||
IMemberPaymentRepository,
|
||||
IMembershipFeeRepository,
|
||||
} from '@core/payments/domain/repositories/IMembershipFeeRepository';
|
||||
import type { IPrizeRepository } from '@core/payments/domain/repositories/IPrizeRepository';
|
||||
import type { ITransactionRepository, IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||
|
||||
import { InMemoryMembershipFeeRepository, InMemoryMemberPaymentRepository } from '@adapters/payments/persistence/inmemory/InMemoryMembershipFeeRepository';
|
||||
import { InMemoryPaymentRepository } from '@adapters/payments/persistence/inmemory/InMemoryPaymentRepository';
|
||||
import { InMemoryPrizeRepository } from '@adapters/payments/persistence/inmemory/InMemoryPrizeRepository';
|
||||
import { InMemoryTransactionRepository, InMemoryWalletRepository } from '@adapters/payments/persistence/inmemory/InMemoryWalletRepository';
|
||||
|
||||
import {
|
||||
PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PRIZE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_TRANSACTION_REPOSITORY_TOKEN,
|
||||
PAYMENTS_WALLET_REPOSITORY_TOKEN,
|
||||
} from '../payments/PaymentsPersistenceTokens';
|
||||
|
||||
@Module({
|
||||
imports: [LoggingModule],
|
||||
providers: [
|
||||
{
|
||||
provide: PAYMENTS_PAYMENT_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IPaymentRepository => new InMemoryPaymentRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IMembershipFeeRepository => new InMemoryMembershipFeeRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IMemberPaymentRepository => new InMemoryMemberPaymentRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_PRIZE_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IPrizeRepository => new InMemoryPrizeRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_WALLET_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IWalletRepository => new InMemoryWalletRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_TRANSACTION_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): ITransactionRepository => new InMemoryTransactionRepository(logger),
|
||||
inject: ['Logger'],
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
PAYMENTS_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PRIZE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_WALLET_REPOSITORY_TOKEN,
|
||||
PAYMENTS_TRANSACTION_REPOSITORY_TOKEN,
|
||||
],
|
||||
})
|
||||
export class InMemoryPaymentsPersistenceModule {}
|
||||
@@ -12,14 +12,13 @@ import {
|
||||
InMemorySocialGraphRepository,
|
||||
} from '@adapters/social/persistence/inmemory/InMemorySocialAndFeed';
|
||||
|
||||
export const FEED_REPOSITORY_TOKEN = 'IFeedRepository';
|
||||
export const SOCIAL_GRAPH_REPOSITORY_TOKEN = 'ISocialGraphRepository';
|
||||
import { SOCIAL_FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN } from '../social/SocialPersistenceTokens';
|
||||
|
||||
@Module({
|
||||
imports: [LoggingModule],
|
||||
providers: [
|
||||
{
|
||||
provide: FEED_REPOSITORY_TOKEN,
|
||||
provide: SOCIAL_FEED_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger): IFeedRepository =>
|
||||
new InMemoryFeedRepository(logger, { drivers: [], friendships: [], feedEvents: [] }),
|
||||
inject: ['Logger'],
|
||||
@@ -31,6 +30,6 @@ export const SOCIAL_GRAPH_REPOSITORY_TOKEN = 'ISocialGraphRepository';
|
||||
inject: ['Logger'],
|
||||
},
|
||||
],
|
||||
exports: [FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN],
|
||||
exports: [SOCIAL_FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN],
|
||||
})
|
||||
export class InMemorySocialPersistenceModule {}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { MODULE_METADATA } from '@nestjs/common/constants';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { PAYMENTS_WALLET_REPOSITORY_TOKEN } from './PaymentsPersistenceTokens';
|
||||
|
||||
describe('PaymentsPersistenceModule', () => {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('uses inmemory providers when GRIDPILOT_API_PERSISTENCE=inmemory', async () => {
|
||||
vi.resetModules();
|
||||
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
|
||||
delete process.env.DATABASE_URL;
|
||||
|
||||
const { PaymentsPersistenceModule } = await import('./PaymentsPersistenceModule');
|
||||
const { InMemoryWalletRepository } = await import('@adapters/payments/persistence/inmemory/InMemoryWalletRepository');
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [PaymentsPersistenceModule],
|
||||
}).compile();
|
||||
|
||||
const walletRepo = module.get(PAYMENTS_WALLET_REPOSITORY_TOKEN);
|
||||
expect(walletRepo).toBeInstanceOf(InMemoryWalletRepository);
|
||||
|
||||
await module.close();
|
||||
});
|
||||
|
||||
it('uses postgres module when GRIDPILOT_API_PERSISTENCE=postgres', async () => {
|
||||
vi.resetModules();
|
||||
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'postgres';
|
||||
delete process.env.DATABASE_URL;
|
||||
|
||||
const { PaymentsPersistenceModule } = await import('./PaymentsPersistenceModule');
|
||||
const { PostgresPaymentsPersistenceModule } = await import('../postgres/PostgresPaymentsPersistenceModule');
|
||||
|
||||
const imports = Reflect.getMetadata(MODULE_METADATA.IMPORTS, PaymentsPersistenceModule) as unknown[];
|
||||
expect(imports).toContain(PostgresPaymentsPersistenceModule);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { getApiPersistence } from '../../env';
|
||||
import { InMemoryPaymentsPersistenceModule } from '../inmemory/InMemoryPaymentsPersistenceModule';
|
||||
import { PostgresPaymentsPersistenceModule } from '../postgres/PostgresPaymentsPersistenceModule';
|
||||
|
||||
const selectedPersistenceModule =
|
||||
getApiPersistence() === 'postgres' ? PostgresPaymentsPersistenceModule : InMemoryPaymentsPersistenceModule;
|
||||
|
||||
@Module({
|
||||
imports: [selectedPersistenceModule],
|
||||
exports: [selectedPersistenceModule],
|
||||
})
|
||||
export class PaymentsPersistenceModule {}
|
||||
@@ -0,0 +1,6 @@
|
||||
export const PAYMENTS_PAYMENT_REPOSITORY_TOKEN = 'PAYMENTS_IPaymentRepository';
|
||||
export const PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN = 'PAYMENTS_IMembershipFeeRepository';
|
||||
export const PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN = 'PAYMENTS_IMemberPaymentRepository';
|
||||
export const PAYMENTS_PRIZE_REPOSITORY_TOKEN = 'PAYMENTS_IPrizeRepository';
|
||||
export const PAYMENTS_WALLET_REPOSITORY_TOKEN = 'PAYMENTS_IWalletRepository';
|
||||
export const PAYMENTS_TRANSACTION_REPOSITORY_TOKEN = 'PAYMENTS_ITransactionRepository';
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm';
|
||||
import type { DataSource } from 'typeorm';
|
||||
|
||||
import { LoggingModule } from '../../domain/logging/LoggingModule';
|
||||
import {
|
||||
ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN,
|
||||
ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN,
|
||||
ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN,
|
||||
} from '../analytics/AnalyticsPersistenceTokens';
|
||||
|
||||
import { AnalyticsSnapshotOrmEntity } from '@adapters/analytics/persistence/typeorm/entities/AnalyticsSnapshotOrmEntity';
|
||||
import { EngagementEventOrmEntity } from '@adapters/analytics/persistence/typeorm/entities/EngagementEventOrmEntity';
|
||||
import { PageViewOrmEntity } from '@adapters/analytics/persistence/typeorm/entities/PageViewOrmEntity';
|
||||
|
||||
import { AnalyticsSnapshotOrmMapper } from '@adapters/analytics/persistence/typeorm/mappers/AnalyticsSnapshotOrmMapper';
|
||||
import { EngagementEventOrmMapper } from '@adapters/analytics/persistence/typeorm/mappers/EngagementEventOrmMapper';
|
||||
import { PageViewOrmMapper } from '@adapters/analytics/persistence/typeorm/mappers/PageViewOrmMapper';
|
||||
|
||||
import { TypeOrmAnalyticsSnapshotRepository } from '@adapters/analytics/persistence/typeorm/repositories/TypeOrmAnalyticsSnapshotRepository';
|
||||
import { TypeOrmEngagementRepository } from '@adapters/analytics/persistence/typeorm/repositories/TypeOrmEngagementRepository';
|
||||
import { TypeOrmPageViewRepository } from '@adapters/analytics/persistence/typeorm/repositories/TypeOrmPageViewRepository';
|
||||
|
||||
const typeOrmFeatureImports = [
|
||||
TypeOrmModule.forFeature([PageViewOrmEntity, EngagementEventOrmEntity, AnalyticsSnapshotOrmEntity]),
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [LoggingModule, ...typeOrmFeatureImports],
|
||||
providers: [
|
||||
{ provide: PageViewOrmMapper, useFactory: () => new PageViewOrmMapper() },
|
||||
{ provide: EngagementEventOrmMapper, useFactory: () => new EngagementEventOrmMapper() },
|
||||
{ provide: AnalyticsSnapshotOrmMapper, useFactory: () => new AnalyticsSnapshotOrmMapper() },
|
||||
|
||||
{
|
||||
provide: ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: PageViewOrmMapper) =>
|
||||
new TypeOrmPageViewRepository(dataSource.getRepository(PageViewOrmEntity), mapper),
|
||||
inject: [getDataSourceToken(), PageViewOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: EngagementEventOrmMapper) =>
|
||||
new TypeOrmEngagementRepository(dataSource.getRepository(EngagementEventOrmEntity), mapper),
|
||||
inject: [getDataSourceToken(), EngagementEventOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: AnalyticsSnapshotOrmMapper) =>
|
||||
new TypeOrmAnalyticsSnapshotRepository(dataSource.getRepository(AnalyticsSnapshotOrmEntity), mapper),
|
||||
inject: [getDataSourceToken(), AnalyticsSnapshotOrmMapper],
|
||||
},
|
||||
],
|
||||
exports: [ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN, ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN, ANALYTICS_SNAPSHOT_REPOSITORY_TOKEN],
|
||||
})
|
||||
export class PostgresAnalyticsPersistenceModule {}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm';
|
||||
import type { DataSource } from 'typeorm';
|
||||
|
||||
import { UserOrmEntity } from '@adapters/identity/persistence/typeorm/entities/UserOrmEntity';
|
||||
import { TypeOrmAuthRepository } from '@adapters/identity/persistence/typeorm/TypeOrmAuthRepository';
|
||||
import { TypeOrmUserRepository } from '@adapters/identity/persistence/typeorm/TypeOrmUserRepository';
|
||||
import { UserOrmMapper } from '@adapters/identity/persistence/typeorm/mappers/UserOrmMapper';
|
||||
import { InMemoryPasswordHashingService } from '@adapters/identity/services/InMemoryPasswordHashingService';
|
||||
|
||||
import {
|
||||
AUTH_REPOSITORY_TOKEN,
|
||||
PASSWORD_HASHING_SERVICE_TOKEN,
|
||||
USER_REPOSITORY_TOKEN,
|
||||
} from '../identity/IdentityPersistenceTokens';
|
||||
|
||||
const typeOrmFeatureImports = [TypeOrmModule.forFeature([UserOrmEntity])];
|
||||
|
||||
@Module({
|
||||
imports: [...typeOrmFeatureImports],
|
||||
providers: [
|
||||
{ provide: UserOrmMapper, useFactory: () => new UserOrmMapper() },
|
||||
{
|
||||
provide: USER_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: UserOrmMapper) => new TypeOrmUserRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), UserOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: AUTH_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: UserOrmMapper) => new TypeOrmAuthRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), UserOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: PASSWORD_HASHING_SERVICE_TOKEN,
|
||||
useClass: InMemoryPasswordHashingService,
|
||||
},
|
||||
],
|
||||
exports: [USER_REPOSITORY_TOKEN, AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN],
|
||||
})
|
||||
export class PostgresIdentityPersistenceModule {}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm';
|
||||
import type { DataSource } from 'typeorm';
|
||||
|
||||
import { LoggingModule } from '../../domain/logging/LoggingModule';
|
||||
|
||||
import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository';
|
||||
import type { IMemberPaymentRepository, IMembershipFeeRepository } from '@core/payments/domain/repositories/IMembershipFeeRepository';
|
||||
import type { IPrizeRepository } from '@core/payments/domain/repositories/IPrizeRepository';
|
||||
import type { ITransactionRepository, IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||
|
||||
import { PaymentsMemberPaymentOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsMemberPaymentOrmEntity';
|
||||
import { PaymentsMembershipFeeOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsMembershipFeeOrmEntity';
|
||||
import { PaymentsPaymentOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsPaymentOrmEntity';
|
||||
import { PaymentsPrizeOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsPrizeOrmEntity';
|
||||
import { PaymentsTransactionOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsTransactionOrmEntity';
|
||||
import { PaymentsWalletOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsWalletOrmEntity';
|
||||
|
||||
import { PaymentsMemberPaymentOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsMemberPaymentOrmMapper';
|
||||
import { PaymentsMembershipFeeOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsMembershipFeeOrmMapper';
|
||||
import { PaymentsPaymentOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsPaymentOrmMapper';
|
||||
import { PaymentsPrizeOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsPrizeOrmMapper';
|
||||
import { PaymentsWalletOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsWalletOrmMapper';
|
||||
|
||||
import { TypeOrmMemberPaymentRepository, TypeOrmMembershipFeeRepository } from '@adapters/payments/persistence/typeorm/repositories/TypeOrmMembershipFeeRepository';
|
||||
import { TypeOrmPaymentRepository } from '@adapters/payments/persistence/typeorm/repositories/TypeOrmPaymentRepository';
|
||||
import { TypeOrmPrizeRepository } from '@adapters/payments/persistence/typeorm/repositories/TypeOrmPrizeRepository';
|
||||
import { TypeOrmTransactionRepository, TypeOrmWalletRepository } from '@adapters/payments/persistence/typeorm/repositories/TypeOrmWalletRepository';
|
||||
|
||||
import {
|
||||
PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PRIZE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_TRANSACTION_REPOSITORY_TOKEN,
|
||||
PAYMENTS_WALLET_REPOSITORY_TOKEN,
|
||||
} from '../payments/PaymentsPersistenceTokens';
|
||||
|
||||
const typeOrmFeatureImports = [
|
||||
TypeOrmModule.forFeature([
|
||||
PaymentsWalletOrmEntity,
|
||||
PaymentsTransactionOrmEntity,
|
||||
PaymentsPaymentOrmEntity,
|
||||
PaymentsPrizeOrmEntity,
|
||||
PaymentsMembershipFeeOrmEntity,
|
||||
PaymentsMemberPaymentOrmEntity,
|
||||
]),
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [LoggingModule, ...typeOrmFeatureImports],
|
||||
providers: [
|
||||
{ provide: PaymentsWalletOrmMapper, useFactory: () => new PaymentsWalletOrmMapper() },
|
||||
{ provide: PaymentsPaymentOrmMapper, useFactory: () => new PaymentsPaymentOrmMapper() },
|
||||
{ provide: PaymentsPrizeOrmMapper, useFactory: () => new PaymentsPrizeOrmMapper() },
|
||||
{ provide: PaymentsMembershipFeeOrmMapper, useFactory: () => new PaymentsMembershipFeeOrmMapper() },
|
||||
{ provide: PaymentsMemberPaymentOrmMapper, useFactory: () => new PaymentsMemberPaymentOrmMapper() },
|
||||
|
||||
{
|
||||
provide: PAYMENTS_WALLET_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: PaymentsWalletOrmMapper): IWalletRepository =>
|
||||
new TypeOrmWalletRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), PaymentsWalletOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_TRANSACTION_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: PaymentsWalletOrmMapper): ITransactionRepository =>
|
||||
new TypeOrmTransactionRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), PaymentsWalletOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_PAYMENT_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: PaymentsPaymentOrmMapper): IPaymentRepository =>
|
||||
new TypeOrmPaymentRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), PaymentsPaymentOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_PRIZE_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: PaymentsPrizeOrmMapper): IPrizeRepository =>
|
||||
new TypeOrmPrizeRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), PaymentsPrizeOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: PaymentsMembershipFeeOrmMapper): IMembershipFeeRepository =>
|
||||
new TypeOrmMembershipFeeRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), PaymentsMembershipFeeOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: PaymentsMemberPaymentOrmMapper): IMemberPaymentRepository =>
|
||||
new TypeOrmMemberPaymentRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), PaymentsMemberPaymentOrmMapper],
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
PAYMENTS_WALLET_REPOSITORY_TOKEN,
|
||||
PAYMENTS_TRANSACTION_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PAYMENT_REPOSITORY_TOKEN,
|
||||
PAYMENTS_PRIZE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||
PAYMENTS_MEMBER_PAYMENT_REPOSITORY_TOKEN,
|
||||
],
|
||||
})
|
||||
export class PostgresPaymentsPersistenceModule {}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm';
|
||||
import type { DataSource } from 'typeorm';
|
||||
import { TypeOrmModule, getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm';
|
||||
import type { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { LoggingModule } from '../../domain/logging/LoggingModule';
|
||||
|
||||
@@ -27,112 +27,240 @@ import {
|
||||
TRANSACTION_REPOSITORY_TOKEN,
|
||||
} from '../inmemory/InMemoryRacingPersistenceModule';
|
||||
|
||||
import { DriverOrmEntity } from '@adapters/racing/persistence/typeorm/entities/DriverOrmEntity';
|
||||
import { LeagueMembershipOrmEntity } from '@adapters/racing/persistence/typeorm/entities/LeagueMembershipOrmEntity';
|
||||
import { LeagueOrmEntity } from '@adapters/racing/persistence/typeorm/entities/LeagueOrmEntity';
|
||||
import { LeagueScoringConfigOrmEntity } from '@adapters/racing/persistence/typeorm/entities/LeagueScoringConfigOrmEntity';
|
||||
import { RaceOrmEntity } from '@adapters/racing/persistence/typeorm/entities/RaceOrmEntity';
|
||||
import { RaceRegistrationOrmEntity } from '@adapters/racing/persistence/typeorm/entities/RaceRegistrationOrmEntity';
|
||||
import { SeasonOrmEntity } from '@adapters/racing/persistence/typeorm/entities/SeasonOrmEntity';
|
||||
import { ResultOrmEntity } from '@adapters/racing/persistence/typeorm/entities/ResultOrmEntity';
|
||||
import { StandingOrmEntity } from '@adapters/racing/persistence/typeorm/entities/StandingOrmEntity';
|
||||
import {
|
||||
GameOrmEntity,
|
||||
LeagueWalletOrmEntity,
|
||||
PenaltyOrmEntity,
|
||||
ProtestOrmEntity,
|
||||
SeasonSponsorshipOrmEntity,
|
||||
SponsorOrmEntity,
|
||||
SponsorshipPricingOrmEntity,
|
||||
SponsorshipRequestOrmEntity,
|
||||
TransactionOrmEntity,
|
||||
} from '@adapters/racing/persistence/typeorm/entities/MissingRacingOrmEntities';
|
||||
import {
|
||||
TeamJoinRequestOrmEntity,
|
||||
TeamMembershipOrmEntity,
|
||||
TeamOrmEntity,
|
||||
} from '@adapters/racing/persistence/typeorm/entities/TeamOrmEntities';
|
||||
|
||||
import { TypeOrmDriverRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmDriverRepository';
|
||||
import { TypeOrmLeagueMembershipRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueMembershipRepository';
|
||||
import { TypeOrmLeagueRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueRepository';
|
||||
import { TypeOrmLeagueScoringConfigRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueScoringConfigRepository';
|
||||
import { TypeOrmRaceRegistrationRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmRaceRegistrationRepository';
|
||||
import { TypeOrmRaceRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmRaceRepository';
|
||||
import { TypeOrmSeasonRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmSeasonRepository';
|
||||
import { TypeOrmResultRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmResultRepository';
|
||||
import { TypeOrmStandingRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmStandingRepository';
|
||||
import {
|
||||
TypeOrmGameRepository,
|
||||
TypeOrmLeagueWalletRepository,
|
||||
TypeOrmSeasonSponsorshipRepository,
|
||||
TypeOrmSponsorRepository,
|
||||
TypeOrmSponsorshipPricingRepository,
|
||||
TypeOrmSponsorshipRequestRepository,
|
||||
TypeOrmTransactionRepository,
|
||||
} from '@adapters/racing/persistence/typeorm/repositories/CommerceTypeOrmRepositories';
|
||||
import { TypeOrmPenaltyRepository, TypeOrmProtestRepository } from '@adapters/racing/persistence/typeorm/repositories/StewardingTypeOrmRepositories';
|
||||
import { TypeOrmTeamMembershipRepository, TypeOrmTeamRepository } from '@adapters/racing/persistence/typeorm/repositories/TeamTypeOrmRepositories';
|
||||
|
||||
import { DriverOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/DriverOrmMapper';
|
||||
import { LeagueMembershipOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueMembershipOrmMapper';
|
||||
import { LeagueOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueOrmMapper';
|
||||
import { RaceRegistrationOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/RaceRegistrationOrmMapper';
|
||||
import { RaceOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/RaceOrmMapper';
|
||||
import { SeasonOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/SeasonOrmMapper';
|
||||
import { ResultOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/ResultOrmMapper';
|
||||
import { StandingOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/StandingOrmMapper';
|
||||
import { PointsTableJsonMapper } from '@adapters/racing/persistence/typeorm/mappers/PointsTableJsonMapper';
|
||||
import { ChampionshipConfigJsonMapper } from '@adapters/racing/persistence/typeorm/mappers/ChampionshipConfigJsonMapper';
|
||||
import { LeagueScoringConfigOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueScoringConfigOrmMapper';
|
||||
import {
|
||||
GameOrmMapper,
|
||||
LeagueWalletOrmMapper,
|
||||
SeasonSponsorshipOrmMapper,
|
||||
SponsorOrmMapper,
|
||||
SponsorshipPricingOrmMapper,
|
||||
SponsorshipRequestOrmMapper,
|
||||
TransactionOrmMapper,
|
||||
} from '@adapters/racing/persistence/typeorm/mappers/CommerceOrmMappers';
|
||||
import { MoneyOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/MoneyOrmMapper';
|
||||
import { PenaltyOrmMapper, ProtestOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/StewardingOrmMappers';
|
||||
import { TeamMembershipOrmMapper, TeamOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/TeamOrmMappers';
|
||||
|
||||
function makePlaceholder(token: string): unknown {
|
||||
return Object.freeze({
|
||||
__token: token,
|
||||
__kind: 'postgres-placeholder',
|
||||
__notImplemented(): never {
|
||||
throw new Error(`[PostgresRacingPersistenceModule] Placeholder provider "${token}" is not implemented yet`);
|
||||
},
|
||||
});
|
||||
}
|
||||
import { getPointsSystems } from '@adapters/bootstrap/PointsSystems';
|
||||
|
||||
const RACING_POINTS_SYSTEMS_TOKEN = 'RACING_POINTS_SYSTEMS_TOKEN';
|
||||
|
||||
const typeOrmFeatureImports = [
|
||||
TypeOrmModule.forFeature([LeagueOrmEntity, SeasonOrmEntity, RaceOrmEntity, LeagueScoringConfigOrmEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
DriverOrmEntity,
|
||||
LeagueMembershipOrmEntity,
|
||||
LeagueOrmEntity,
|
||||
SeasonOrmEntity,
|
||||
RaceOrmEntity,
|
||||
RaceRegistrationOrmEntity,
|
||||
LeagueScoringConfigOrmEntity,
|
||||
ResultOrmEntity,
|
||||
StandingOrmEntity,
|
||||
|
||||
TeamOrmEntity,
|
||||
TeamMembershipOrmEntity,
|
||||
TeamJoinRequestOrmEntity,
|
||||
|
||||
PenaltyOrmEntity,
|
||||
ProtestOrmEntity,
|
||||
|
||||
GameOrmEntity,
|
||||
LeagueWalletOrmEntity,
|
||||
TransactionOrmEntity,
|
||||
|
||||
SponsorOrmEntity,
|
||||
SponsorshipPricingOrmEntity,
|
||||
SponsorshipRequestOrmEntity,
|
||||
SeasonSponsorshipOrmEntity,
|
||||
]),
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [LoggingModule, ...typeOrmFeatureImports],
|
||||
providers: [
|
||||
{ provide: DriverOrmMapper, useFactory: () => new DriverOrmMapper() },
|
||||
{ provide: LeagueMembershipOrmMapper, useFactory: () => new LeagueMembershipOrmMapper() },
|
||||
{ provide: RaceRegistrationOrmMapper, useFactory: () => new RaceRegistrationOrmMapper() },
|
||||
{ provide: ResultOrmMapper, useFactory: () => new ResultOrmMapper() },
|
||||
{ provide: StandingOrmMapper, useFactory: () => new StandingOrmMapper() },
|
||||
{ provide: LeagueOrmMapper, useFactory: () => new LeagueOrmMapper() },
|
||||
{ provide: RaceOrmMapper, useFactory: () => new RaceOrmMapper() },
|
||||
{ provide: SeasonOrmMapper, useFactory: () => new SeasonOrmMapper() },
|
||||
|
||||
{ provide: TeamOrmMapper, useFactory: () => new TeamOrmMapper() },
|
||||
{ provide: TeamMembershipOrmMapper, useFactory: () => new TeamMembershipOrmMapper() },
|
||||
|
||||
{ provide: PenaltyOrmMapper, useFactory: () => new PenaltyOrmMapper() },
|
||||
{ provide: ProtestOrmMapper, useFactory: () => new ProtestOrmMapper() },
|
||||
|
||||
{ provide: MoneyOrmMapper, useFactory: () => new MoneyOrmMapper() },
|
||||
{ provide: GameOrmMapper, useFactory: () => new GameOrmMapper() },
|
||||
{ provide: SponsorOrmMapper, useFactory: () => new SponsorOrmMapper() },
|
||||
{
|
||||
provide: LeagueWalletOrmMapper,
|
||||
useFactory: (moneyMapper: MoneyOrmMapper) => new LeagueWalletOrmMapper(moneyMapper),
|
||||
inject: [MoneyOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: TransactionOrmMapper,
|
||||
useFactory: (moneyMapper: MoneyOrmMapper) => new TransactionOrmMapper(moneyMapper),
|
||||
inject: [MoneyOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SponsorshipPricingOrmMapper,
|
||||
useFactory: (moneyMapper: MoneyOrmMapper) => new SponsorshipPricingOrmMapper(moneyMapper),
|
||||
inject: [MoneyOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SponsorshipRequestOrmMapper,
|
||||
useFactory: (moneyMapper: MoneyOrmMapper) => new SponsorshipRequestOrmMapper(moneyMapper),
|
||||
inject: [MoneyOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SeasonSponsorshipOrmMapper,
|
||||
useFactory: (moneyMapper: MoneyOrmMapper) => new SeasonSponsorshipOrmMapper(moneyMapper),
|
||||
inject: [MoneyOrmMapper],
|
||||
},
|
||||
|
||||
{ provide: RACING_POINTS_SYSTEMS_TOKEN, useFactory: () => getPointsSystems() },
|
||||
{
|
||||
provide: DRIVER_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(DRIVER_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (dataSource: DataSource, mapper: DriverOrmMapper) => new TypeOrmDriverRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), DriverOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource) => {
|
||||
const leagueMapper = new LeagueOrmMapper();
|
||||
return new TypeOrmLeagueRepository(dataSource, leagueMapper);
|
||||
},
|
||||
inject: [getDataSourceToken()],
|
||||
useFactory: (dataSource: DataSource, mapper: LeagueOrmMapper) => new TypeOrmLeagueRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), LeagueOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: RACE_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource) => {
|
||||
const raceMapper = new RaceOrmMapper();
|
||||
return new TypeOrmRaceRepository(dataSource, raceMapper);
|
||||
},
|
||||
inject: [getDataSourceToken()],
|
||||
useFactory: (dataSource: DataSource, mapper: RaceOrmMapper) => new TypeOrmRaceRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), RaceOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: RESULT_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(RESULT_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (dataSource: DataSource, mapper: ResultOrmMapper) => new TypeOrmResultRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), ResultOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: STANDING_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(STANDING_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (
|
||||
dataSource: DataSource,
|
||||
standingMapper: StandingOrmMapper,
|
||||
resultMapper: ResultOrmMapper,
|
||||
leagueMapper: LeagueOrmMapper,
|
||||
pointsSystems: Record<string, Record<number, number>>,
|
||||
) => new TypeOrmStandingRepository(dataSource, standingMapper, resultMapper, leagueMapper, pointsSystems),
|
||||
inject: [getDataSourceToken(), StandingOrmMapper, ResultOrmMapper, LeagueOrmMapper, RACING_POINTS_SYSTEMS_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (dataSource: DataSource, mapper: LeagueMembershipOrmMapper) =>
|
||||
new TypeOrmLeagueMembershipRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), LeagueMembershipOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(RACE_REGISTRATION_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (dataSource: DataSource, mapper: RaceRegistrationOrmMapper) =>
|
||||
new TypeOrmRaceRegistrationRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), RaceRegistrationOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: TEAM_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(TEAM_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<TeamOrmEntity>, mapper: TeamOrmMapper) => new TypeOrmTeamRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(TeamOrmEntity), TeamOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(TEAM_MEMBERSHIP_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (
|
||||
membershipRepo: Repository<TeamMembershipOrmEntity>,
|
||||
joinRequestRepo: Repository<TeamJoinRequestOrmEntity>,
|
||||
mapper: TeamMembershipOrmMapper,
|
||||
) => new TypeOrmTeamMembershipRepository(membershipRepo, joinRequestRepo, mapper),
|
||||
inject: [
|
||||
getRepositoryToken(TeamMembershipOrmEntity),
|
||||
getRepositoryToken(TeamJoinRequestOrmEntity),
|
||||
TeamMembershipOrmMapper,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: PENALTY_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(PENALTY_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<PenaltyOrmEntity>, mapper: PenaltyOrmMapper) => new TypeOrmPenaltyRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(PenaltyOrmEntity), PenaltyOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: PROTEST_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(PROTEST_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<ProtestOrmEntity>, mapper: ProtestOrmMapper) => new TypeOrmProtestRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(ProtestOrmEntity), ProtestOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SEASON_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource) => {
|
||||
const seasonMapper = new SeasonOrmMapper();
|
||||
return new TypeOrmSeasonRepository(dataSource, seasonMapper);
|
||||
},
|
||||
inject: [getDataSourceToken()],
|
||||
useFactory: (dataSource: DataSource, mapper: SeasonOrmMapper) => new TypeOrmSeasonRepository(dataSource, mapper),
|
||||
inject: [getDataSourceToken(), SeasonOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(SEASON_SPONSORSHIP_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<SeasonSponsorshipOrmEntity>, mapper: SeasonSponsorshipOrmMapper) =>
|
||||
new TypeOrmSeasonSponsorshipRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(SeasonSponsorshipOrmEntity), SeasonSponsorshipOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN,
|
||||
@@ -146,33 +274,36 @@ const typeOrmFeatureImports = [
|
||||
},
|
||||
{
|
||||
provide: GAME_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(GAME_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<GameOrmEntity>, mapper: GameOrmMapper) => new TypeOrmGameRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(GameOrmEntity), GameOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_WALLET_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(LEAGUE_WALLET_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<LeagueWalletOrmEntity>, mapper: LeagueWalletOrmMapper) =>
|
||||
new TypeOrmLeagueWalletRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(LeagueWalletOrmEntity), LeagueWalletOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: TRANSACTION_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(TRANSACTION_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<TransactionOrmEntity>, mapper: TransactionOrmMapper) => new TypeOrmTransactionRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(TransactionOrmEntity), TransactionOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SPONSOR_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(SPONSOR_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<SponsorOrmEntity>, mapper: SponsorOrmMapper) => new TypeOrmSponsorRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(SponsorOrmEntity), SponsorOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SPONSORSHIP_PRICING_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(SPONSORSHIP_PRICING_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<SponsorshipPricingOrmEntity>, mapper: SponsorshipPricingOrmMapper) =>
|
||||
new TypeOrmSponsorshipPricingRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(SponsorshipPricingOrmEntity), SponsorshipPricingOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SPONSORSHIP_REQUEST_REPOSITORY_TOKEN,
|
||||
useFactory: () => makePlaceholder(SPONSORSHIP_REQUEST_REPOSITORY_TOKEN),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<SponsorshipRequestOrmEntity>, mapper: SponsorshipRequestOrmMapper) =>
|
||||
new TypeOrmSponsorshipRequestRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(SponsorshipRequestOrmEntity), SponsorshipRequestOrmMapper],
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm';
|
||||
import type { DataSource } from 'typeorm';
|
||||
|
||||
import { LoggingModule } from '../../domain/logging/LoggingModule';
|
||||
import { SOCIAL_FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN } from '../social/SocialPersistenceTokens';
|
||||
|
||||
import { DriverOrmEntity } from '@adapters/racing/persistence/typeorm/entities/DriverOrmEntity';
|
||||
import { DriverOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/DriverOrmMapper';
|
||||
|
||||
import { FeedItemOrmEntity } from '@adapters/social/persistence/typeorm/entities/FeedItemOrmEntity';
|
||||
import { FriendshipOrmEntity } from '@adapters/social/persistence/typeorm/entities/FriendshipOrmEntity';
|
||||
import { FeedItemOrmMapper } from '@adapters/social/persistence/typeorm/mappers/FeedItemOrmMapper';
|
||||
import { TypeOrmFeedRepository } from '@adapters/social/persistence/typeorm/repositories/TypeOrmFeedRepository';
|
||||
import { TypeOrmSocialGraphRepository } from '@adapters/social/persistence/typeorm/repositories/TypeOrmSocialGraphRepository';
|
||||
|
||||
const typeOrmFeatureImports = [
|
||||
TypeOrmModule.forFeature([FeedItemOrmEntity, FriendshipOrmEntity, DriverOrmEntity]),
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [LoggingModule, ...typeOrmFeatureImports],
|
||||
providers: [
|
||||
{ provide: FeedItemOrmMapper, useFactory: () => new FeedItemOrmMapper() },
|
||||
{ provide: DriverOrmMapper, useFactory: () => new DriverOrmMapper() },
|
||||
|
||||
{
|
||||
provide: SOCIAL_FEED_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, mapper: FeedItemOrmMapper) =>
|
||||
new TypeOrmFeedRepository(
|
||||
dataSource.getRepository(FeedItemOrmEntity),
|
||||
dataSource.getRepository(FriendshipOrmEntity),
|
||||
mapper,
|
||||
),
|
||||
inject: [getDataSourceToken(), FeedItemOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
||||
useFactory: (dataSource: DataSource, driverMapper: DriverOrmMapper) =>
|
||||
new TypeOrmSocialGraphRepository(
|
||||
dataSource.getRepository(FriendshipOrmEntity),
|
||||
dataSource.getRepository(DriverOrmEntity),
|
||||
driverMapper,
|
||||
),
|
||||
inject: [getDataSourceToken(), DriverOrmMapper],
|
||||
},
|
||||
],
|
||||
exports: [SOCIAL_FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN],
|
||||
})
|
||||
export class PostgresSocialPersistenceModule {}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { PageView } from '@core/analytics/domain/entities/PageView';
|
||||
|
||||
import { PageViewOrmEntity } from '@adapters/analytics/persistence/typeorm/entities/PageViewOrmEntity';
|
||||
import { EngagementEventOrmEntity } from '@adapters/analytics/persistence/typeorm/entities/EngagementEventOrmEntity';
|
||||
import { AnalyticsSnapshotOrmEntity } from '@adapters/analytics/persistence/typeorm/entities/AnalyticsSnapshotOrmEntity';
|
||||
|
||||
import { PageViewOrmMapper } from '@adapters/analytics/persistence/typeorm/mappers/PageViewOrmMapper';
|
||||
import { EngagementEventOrmMapper } from '@adapters/analytics/persistence/typeorm/mappers/EngagementEventOrmMapper';
|
||||
import { AnalyticsSnapshotOrmMapper } from '@adapters/analytics/persistence/typeorm/mappers/AnalyticsSnapshotOrmMapper';
|
||||
|
||||
import { TypeOrmPageViewRepository } from '@adapters/analytics/persistence/typeorm/repositories/TypeOrmPageViewRepository';
|
||||
import { TypeOrmEngagementRepository } from '@adapters/analytics/persistence/typeorm/repositories/TypeOrmEngagementRepository';
|
||||
import { TypeOrmAnalyticsSnapshotRepository } from '@adapters/analytics/persistence/typeorm/repositories/TypeOrmAnalyticsSnapshotRepository';
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
const describeIfDatabase = databaseUrl ? describe : describe.skip;
|
||||
|
||||
describeIfDatabase('TypeORM Analytics repositories (postgres slice)', () => {
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL is required to run postgres integration tests');
|
||||
}
|
||||
|
||||
dataSource = new DataSource({
|
||||
type: 'postgres',
|
||||
url: databaseUrl,
|
||||
entities: [PageViewOrmEntity, EngagementEventOrmEntity, AnalyticsSnapshotOrmEntity],
|
||||
synchronize: true,
|
||||
});
|
||||
|
||||
await dataSource.initialize();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (dataSource?.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports: persist page view + read it back + count unique visitors', async () => {
|
||||
const pageViewOrmRepo = dataSource.getRepository(PageViewOrmEntity);
|
||||
const engagementOrmRepo = dataSource.getRepository(EngagementEventOrmEntity);
|
||||
const snapshotOrmRepo = dataSource.getRepository(AnalyticsSnapshotOrmEntity);
|
||||
|
||||
await snapshotOrmRepo.clear();
|
||||
await engagementOrmRepo.clear();
|
||||
await pageViewOrmRepo.clear();
|
||||
|
||||
const pageViewRepo = new TypeOrmPageViewRepository(pageViewOrmRepo, new PageViewOrmMapper());
|
||||
const engagementRepo = new TypeOrmEngagementRepository(engagementOrmRepo, new EngagementEventOrmMapper());
|
||||
const snapshotRepo = new TypeOrmAnalyticsSnapshotRepository(snapshotOrmRepo, new AnalyticsSnapshotOrmMapper());
|
||||
|
||||
// Ensure all repositories at least construct and basic ops work
|
||||
expect(pageViewRepo).toBeDefined();
|
||||
expect(engagementRepo).toBeDefined();
|
||||
expect(snapshotRepo).toBeDefined();
|
||||
|
||||
const pv1 = PageView.create({
|
||||
id: `pv-it-${Date.now()}`,
|
||||
entityType: 'league',
|
||||
entityId: 'league-it-1',
|
||||
visitorType: 'anonymous',
|
||||
sessionId: 'sess-it-1',
|
||||
visitorId: 'visitor-it-1',
|
||||
timestamp: new Date(),
|
||||
durationMs: 8000,
|
||||
});
|
||||
|
||||
await pageViewRepo.save(pv1);
|
||||
|
||||
const loaded = await pageViewRepo.findById(pv1.id);
|
||||
expect(loaded?.id).toBe(pv1.id);
|
||||
|
||||
const unique = await pageViewRepo.countUniqueVisitors('league', 'league-it-1');
|
||||
expect(unique).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { User } from '@core/identity/domain/entities/User';
|
||||
import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress';
|
||||
import { PasswordHash } from '@core/identity/domain/value-objects/PasswordHash';
|
||||
import { UserId } from '@core/identity/domain/value-objects/UserId';
|
||||
|
||||
import { UserOrmEntity } from '@adapters/identity/persistence/typeorm/entities/UserOrmEntity';
|
||||
import { TypeOrmAuthRepository } from '@adapters/identity/persistence/typeorm/TypeOrmAuthRepository';
|
||||
import { TypeOrmUserRepository } from '@adapters/identity/persistence/typeorm/TypeOrmUserRepository';
|
||||
import { UserOrmMapper } from '@adapters/identity/persistence/typeorm/mappers/UserOrmMapper';
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
const describeIfDatabase = databaseUrl ? describe : describe.skip;
|
||||
|
||||
describeIfDatabase('TypeORM Identity repositories (postgres slice)', () => {
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL is required to run postgres integration tests');
|
||||
}
|
||||
|
||||
dataSource = new DataSource({
|
||||
type: 'postgres',
|
||||
url: databaseUrl,
|
||||
entities: [UserOrmEntity],
|
||||
synchronize: true,
|
||||
});
|
||||
|
||||
await dataSource.initialize();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (dataSource?.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports: persist user + find by email + update displayName', async () => {
|
||||
const mapper = new UserOrmMapper();
|
||||
const userRepo = new TypeOrmUserRepository(dataSource, mapper);
|
||||
const authRepo = new TypeOrmAuthRepository(dataSource, mapper);
|
||||
|
||||
const email = EmailAddress.create(`it-${Date.now()}@example.com`);
|
||||
const passwordHash = PasswordHash.fromHash('bcrypt-hash-for-it');
|
||||
const userId = UserId.create();
|
||||
|
||||
const user = User.create({
|
||||
id: userId,
|
||||
email: email.value,
|
||||
displayName: 'Integration User',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
await authRepo.save(user);
|
||||
|
||||
const loaded = await authRepo.findByEmail(email);
|
||||
expect(loaded).not.toBeNull();
|
||||
expect(loaded!.getId().value).toBe(user.getId().value);
|
||||
expect(loaded!.getEmail()).toBe(email.value.toLowerCase());
|
||||
expect(loaded!.getDisplayName()).toBe('Integration User');
|
||||
|
||||
const stored = await userRepo.findByEmail(email.value.toUpperCase());
|
||||
expect(stored).not.toBeNull();
|
||||
expect(stored!.email).toBe(email.value.toLowerCase());
|
||||
|
||||
const updated = User.create({
|
||||
id: userId,
|
||||
email: email.value,
|
||||
displayName: 'Integration User Updated',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
await authRepo.save(updated);
|
||||
|
||||
const loadedUpdated = await authRepo.findByEmail(email);
|
||||
expect(loadedUpdated!.getDisplayName()).toBe('Integration User Updated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,179 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { MemberPaymentStatus } from '@core/payments/domain/entities/MemberPayment';
|
||||
import { MembershipFeeType } from '@core/payments/domain/entities/MembershipFee';
|
||||
import { PaymentStatus, PaymentType, PayerType } from '@core/payments/domain/entities/Payment';
|
||||
import { PrizeType } from '@core/payments/domain/entities/Prize';
|
||||
import { TransactionType } from '@core/payments/domain/entities/Wallet';
|
||||
|
||||
import { PaymentsMemberPaymentOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsMemberPaymentOrmEntity';
|
||||
import { PaymentsMembershipFeeOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsMembershipFeeOrmEntity';
|
||||
import { PaymentsPaymentOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsPaymentOrmEntity';
|
||||
import { PaymentsPrizeOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsPrizeOrmEntity';
|
||||
import { PaymentsTransactionOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsTransactionOrmEntity';
|
||||
import { PaymentsWalletOrmEntity } from '@adapters/payments/persistence/typeorm/entities/PaymentsWalletOrmEntity';
|
||||
|
||||
import { PaymentsMemberPaymentOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsMemberPaymentOrmMapper';
|
||||
import { PaymentsMembershipFeeOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsMembershipFeeOrmMapper';
|
||||
import { PaymentsPaymentOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsPaymentOrmMapper';
|
||||
import { PaymentsPrizeOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsPrizeOrmMapper';
|
||||
import { PaymentsWalletOrmMapper } from '@adapters/payments/persistence/typeorm/mappers/PaymentsWalletOrmMapper';
|
||||
|
||||
import { TypeOrmMemberPaymentRepository, TypeOrmMembershipFeeRepository } from '@adapters/payments/persistence/typeorm/repositories/TypeOrmMembershipFeeRepository';
|
||||
import { TypeOrmPaymentRepository } from '@adapters/payments/persistence/typeorm/repositories/TypeOrmPaymentRepository';
|
||||
import { TypeOrmPrizeRepository } from '@adapters/payments/persistence/typeorm/repositories/TypeOrmPrizeRepository';
|
||||
import { TypeOrmTransactionRepository, TypeOrmWalletRepository } from '@adapters/payments/persistence/typeorm/repositories/TypeOrmWalletRepository';
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
const describeIfDatabase = databaseUrl ? describe : describe.skip;
|
||||
|
||||
describeIfDatabase('TypeORM Payments repositories (postgres slice)', () => {
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL is required to run postgres integration tests');
|
||||
}
|
||||
|
||||
dataSource = new DataSource({
|
||||
type: 'postgres',
|
||||
url: databaseUrl,
|
||||
entities: [
|
||||
PaymentsWalletOrmEntity,
|
||||
PaymentsTransactionOrmEntity,
|
||||
PaymentsPaymentOrmEntity,
|
||||
PaymentsPrizeOrmEntity,
|
||||
PaymentsMembershipFeeOrmEntity,
|
||||
PaymentsMemberPaymentOrmEntity,
|
||||
],
|
||||
synchronize: true,
|
||||
});
|
||||
|
||||
await dataSource.initialize();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (dataSource?.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports: wallet+tx+payment+prize+membership fee+member payment', async () => {
|
||||
const walletMapper = new PaymentsWalletOrmMapper();
|
||||
const walletRepo = new TypeOrmWalletRepository(dataSource, walletMapper);
|
||||
const txRepo = new TypeOrmTransactionRepository(dataSource, walletMapper);
|
||||
|
||||
const paymentRepo = new TypeOrmPaymentRepository(dataSource, new PaymentsPaymentOrmMapper());
|
||||
const prizeRepo = new TypeOrmPrizeRepository(dataSource, new PaymentsPrizeOrmMapper());
|
||||
const feeRepo = new TypeOrmMembershipFeeRepository(dataSource, new PaymentsMembershipFeeOrmMapper());
|
||||
const memberPaymentRepo = new TypeOrmMemberPaymentRepository(dataSource, new PaymentsMemberPaymentOrmMapper());
|
||||
|
||||
const now = Date.now();
|
||||
const leagueId = `league-it-${now}`;
|
||||
|
||||
const wallet = {
|
||||
id: `wallet-it-${now}`,
|
||||
leagueId,
|
||||
balance: 0,
|
||||
totalRevenue: 0,
|
||||
totalPlatformFees: 0,
|
||||
totalWithdrawn: 0,
|
||||
currency: 'USD',
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
await walletRepo.create(wallet);
|
||||
|
||||
const tx = {
|
||||
id: `txn-it-${now}`,
|
||||
walletId: wallet.id,
|
||||
type: TransactionType.DEPOSIT,
|
||||
amount: 25,
|
||||
description: 'Integration deposit',
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
await txRepo.create(tx);
|
||||
|
||||
const loadedWallet = await walletRepo.findByLeagueId(wallet.leagueId);
|
||||
expect(loadedWallet).not.toBeNull();
|
||||
expect(loadedWallet!.leagueId).toBe(wallet.leagueId);
|
||||
|
||||
const loadedTxs = await txRepo.findByWalletId(wallet.id);
|
||||
expect(loadedTxs.map(t => t.id)).toContain(tx.id);
|
||||
|
||||
const payment = {
|
||||
id: `payment-it-${now}`,
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: `driver-it-${now}`,
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId,
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
await paymentRepo.create(payment);
|
||||
|
||||
const loadedPayments = await paymentRepo.findByLeagueId(leagueId);
|
||||
expect(loadedPayments.map(p => p.id)).toContain(payment.id);
|
||||
|
||||
const prize = {
|
||||
id: `prize-it-${now}`,
|
||||
leagueId,
|
||||
seasonId: `season-it-${now}`,
|
||||
position: 1,
|
||||
name: 'Winner',
|
||||
amount: 250,
|
||||
type: PrizeType.CASH,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
await prizeRepo.create(prize);
|
||||
|
||||
const loadedPrizes = await prizeRepo.findByLeagueIdAndSeasonId(leagueId, prize.seasonId);
|
||||
expect(loadedPrizes.map(p => p.id)).toContain(prize.id);
|
||||
|
||||
const fee = {
|
||||
id: `fee-it-${now}`,
|
||||
leagueId,
|
||||
type: MembershipFeeType.SEASON,
|
||||
amount: 100,
|
||||
enabled: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
await feeRepo.create(fee);
|
||||
|
||||
const loadedFee = await feeRepo.findByLeagueId(leagueId);
|
||||
expect(loadedFee?.id).toBe(fee.id);
|
||||
|
||||
const memberPayment = {
|
||||
id: `member-payment-it-${now}`,
|
||||
feeId: fee.id,
|
||||
driverId: payment.payerId,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
status: MemberPaymentStatus.PAID,
|
||||
dueDate: new Date(),
|
||||
paidAt: new Date(),
|
||||
};
|
||||
|
||||
await memberPaymentRepo.create(memberPayment);
|
||||
|
||||
const loadedMemberPayments = await memberPaymentRepo.findByLeagueIdAndDriverId(
|
||||
leagueId,
|
||||
memberPayment.driverId,
|
||||
feeRepo,
|
||||
);
|
||||
expect(loadedMemberPayments.map(mp => mp.id)).toContain(memberPayment.id);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { Game } from '@core/racing/domain/entities/Game';
|
||||
import { LeagueWallet } from '@core/racing/domain/entities/league-wallet/LeagueWallet';
|
||||
import { Transaction } from '@core/racing/domain/entities/league-wallet/Transaction';
|
||||
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { SeasonSponsorship } from '@core/racing/domain/entities/season/SeasonSponsorship';
|
||||
import { SponsorshipRequest } from '@core/racing/domain/entities/SponsorshipRequest';
|
||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||
import { SponsorshipPricing } from '@core/racing/domain/value-objects/SponsorshipPricing';
|
||||
|
||||
import {
|
||||
GameOrmEntity,
|
||||
LeagueWalletOrmEntity,
|
||||
SeasonSponsorshipOrmEntity,
|
||||
SponsorOrmEntity,
|
||||
SponsorshipPricingOrmEntity,
|
||||
SponsorshipRequestOrmEntity,
|
||||
TransactionOrmEntity,
|
||||
} from '../../../../../../adapters/racing/persistence/typeorm/entities/MissingRacingOrmEntities';
|
||||
|
||||
import { MoneyOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/MoneyOrmMapper';
|
||||
import {
|
||||
GameOrmMapper,
|
||||
LeagueWalletOrmMapper,
|
||||
SeasonSponsorshipOrmMapper,
|
||||
SponsorOrmMapper,
|
||||
SponsorshipPricingOrmMapper,
|
||||
SponsorshipRequestOrmMapper,
|
||||
TransactionOrmMapper,
|
||||
} from '../../../../../../adapters/racing/persistence/typeorm/mappers/CommerceOrmMappers';
|
||||
|
||||
import {
|
||||
TypeOrmGameRepository,
|
||||
TypeOrmLeagueWalletRepository,
|
||||
TypeOrmSeasonSponsorshipRepository,
|
||||
TypeOrmSponsorRepository,
|
||||
TypeOrmSponsorshipPricingRepository,
|
||||
TypeOrmSponsorshipRequestRepository,
|
||||
TypeOrmTransactionRepository,
|
||||
} from '../../../../../../adapters/racing/persistence/typeorm/repositories/CommerceTypeOrmRepositories';
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
const describeIfDatabase = databaseUrl ? describe : describe.skip;
|
||||
|
||||
describeIfDatabase('TypeORM Racing repositories (commerce slice)', () => {
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL is required to run postgres integration tests');
|
||||
}
|
||||
|
||||
dataSource = new DataSource({
|
||||
type: 'postgres',
|
||||
url: databaseUrl,
|
||||
entities: [
|
||||
GameOrmEntity,
|
||||
SponsorOrmEntity,
|
||||
LeagueWalletOrmEntity,
|
||||
TransactionOrmEntity,
|
||||
SponsorshipPricingOrmEntity,
|
||||
SponsorshipRequestOrmEntity,
|
||||
SeasonSponsorshipOrmEntity,
|
||||
],
|
||||
synchronize: true,
|
||||
});
|
||||
|
||||
await dataSource.initialize();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (dataSource?.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports: game + sponsor + wallet + transaction + sponsorship objects', async () => {
|
||||
const moneyMapper = new MoneyOrmMapper();
|
||||
|
||||
const gameRepo = new TypeOrmGameRepository(
|
||||
dataSource.getRepository(GameOrmEntity),
|
||||
new GameOrmMapper(),
|
||||
);
|
||||
const sponsorRepo = new TypeOrmSponsorRepository(
|
||||
dataSource.getRepository(SponsorOrmEntity),
|
||||
new SponsorOrmMapper(),
|
||||
);
|
||||
const walletRepo = new TypeOrmLeagueWalletRepository(
|
||||
dataSource.getRepository(LeagueWalletOrmEntity),
|
||||
new LeagueWalletOrmMapper(moneyMapper),
|
||||
);
|
||||
const txRepo = new TypeOrmTransactionRepository(
|
||||
dataSource.getRepository(TransactionOrmEntity),
|
||||
new TransactionOrmMapper(moneyMapper),
|
||||
);
|
||||
|
||||
const pricingRepo = new TypeOrmSponsorshipPricingRepository(
|
||||
dataSource.getRepository(SponsorshipPricingOrmEntity),
|
||||
new SponsorshipPricingOrmMapper(moneyMapper),
|
||||
);
|
||||
const requestRepo = new TypeOrmSponsorshipRequestRepository(
|
||||
dataSource.getRepository(SponsorshipRequestOrmEntity),
|
||||
new SponsorshipRequestOrmMapper(moneyMapper),
|
||||
);
|
||||
const seasonSponsorshipRepo = new TypeOrmSeasonSponsorshipRepository(
|
||||
dataSource.getRepository(SeasonSponsorshipOrmEntity),
|
||||
new SeasonSponsorshipOrmMapper(moneyMapper),
|
||||
);
|
||||
|
||||
const game = Game.create({ id: 'iracing', name: 'iRacing' });
|
||||
await dataSource.getRepository(GameOrmEntity).save(new GameOrmMapper().toOrmEntity(game));
|
||||
const persistedGame = await gameRepo.findById('iracing');
|
||||
expect(persistedGame?.name.toString()).toBe('iRacing');
|
||||
|
||||
const sponsor = Sponsor.create({
|
||||
id: '00000000-0000-4000-8000-000000000010',
|
||||
name: 'Sponsor Inc',
|
||||
contactEmail: 'sponsor@example.com',
|
||||
createdAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
});
|
||||
await sponsorRepo.create(sponsor);
|
||||
|
||||
const leagueId = '00000000-0000-4000-8000-000000000020';
|
||||
const wallet = LeagueWallet.create({
|
||||
id: '00000000-0000-4000-8000-000000000030',
|
||||
leagueId,
|
||||
balance: Money.create(0, 'USD'),
|
||||
createdAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
transactionIds: [],
|
||||
});
|
||||
await walletRepo.create(wallet);
|
||||
|
||||
const tx = Transaction.rehydrate({
|
||||
id: '00000000-0000-4000-8000-000000000040',
|
||||
walletId: wallet.id.toString(),
|
||||
type: 'refund',
|
||||
amount: Money.create(10, 'USD'),
|
||||
platformFee: Money.create(1, 'USD'),
|
||||
netAmount: Money.create(9, 'USD'),
|
||||
status: 'completed',
|
||||
createdAt: new Date('2025-01-02T00:00:00.000Z'),
|
||||
});
|
||||
await txRepo.create(tx);
|
||||
|
||||
const walletTxs = await txRepo.findByWalletId(wallet.id.toString());
|
||||
expect(walletTxs.map((t) => t.id.toString())).toContain(tx.id.toString());
|
||||
|
||||
const pricing = SponsorshipPricing.create({
|
||||
acceptingApplications: true,
|
||||
mainSlot: {
|
||||
tier: 'main',
|
||||
price: Money.create(100, 'USD'),
|
||||
benefits: ['Logo'],
|
||||
available: true,
|
||||
maxSlots: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await pricingRepo.save('team', 'team-1', pricing);
|
||||
const persistedPricing = await pricingRepo.findByEntity('team', 'team-1');
|
||||
expect(persistedPricing?.acceptingApplications).toBe(true);
|
||||
|
||||
const request = SponsorshipRequest.create({
|
||||
id: '00000000-0000-4000-8000-000000000050',
|
||||
sponsorId: sponsor.id.toString(),
|
||||
entityType: 'team',
|
||||
entityId: 'team-1',
|
||||
tier: 'main',
|
||||
offeredAmount: Money.create(100, 'USD'),
|
||||
message: 'We would like to sponsor you',
|
||||
});
|
||||
await requestRepo.create(request);
|
||||
|
||||
const pending = await requestRepo.findPendingByEntity('team', 'team-1');
|
||||
expect(pending.map((r) => r.id)).toContain(request.id);
|
||||
|
||||
const seasonSponsorship = SeasonSponsorship.create({
|
||||
id: '00000000-0000-4000-8000-000000000060',
|
||||
seasonId: '00000000-0000-4000-8000-000000000070',
|
||||
sponsorId: sponsor.id.toString(),
|
||||
tier: 'main',
|
||||
pricing: Money.create(100, 'USD'),
|
||||
});
|
||||
await seasonSponsorshipRepo.create(seasonSponsorship);
|
||||
|
||||
const seasonSponsorships = await seasonSponsorshipRepo.findBySponsorId(sponsor.id.toString());
|
||||
expect(seasonSponsorships.map((s) => s.id)).toContain(seasonSponsorship.id);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,164 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { Team } from '@core/racing/domain/entities/Team';
|
||||
import { Penalty } from '@core/racing/domain/entities/penalty/Penalty';
|
||||
import { Protest } from '@core/racing/domain/entities/Protest';
|
||||
|
||||
import {
|
||||
PenaltyOrmEntity,
|
||||
ProtestOrmEntity,
|
||||
} from '../../../../../../adapters/racing/persistence/typeorm/entities/MissingRacingOrmEntities';
|
||||
import {
|
||||
TeamJoinRequestOrmEntity,
|
||||
TeamMembershipOrmEntity,
|
||||
TeamOrmEntity,
|
||||
} from '../../../../../../adapters/racing/persistence/typeorm/entities/TeamOrmEntities';
|
||||
|
||||
import { TeamMembershipOrmMapper, TeamOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/TeamOrmMappers';
|
||||
import { PenaltyOrmMapper, ProtestOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/StewardingOrmMappers';
|
||||
|
||||
import {
|
||||
TypeOrmTeamMembershipRepository,
|
||||
TypeOrmTeamRepository,
|
||||
} from '../../../../../../adapters/racing/persistence/typeorm/repositories/TeamTypeOrmRepositories';
|
||||
import {
|
||||
TypeOrmPenaltyRepository,
|
||||
TypeOrmProtestRepository,
|
||||
} from '../../../../../../adapters/racing/persistence/typeorm/repositories/StewardingTypeOrmRepositories';
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
const describeIfDatabase = databaseUrl ? describe : describe.skip;
|
||||
|
||||
describeIfDatabase('TypeORM Racing repositories (teams + stewarding slice)', () => {
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL is required to run postgres integration tests');
|
||||
}
|
||||
|
||||
dataSource = new DataSource({
|
||||
type: 'postgres',
|
||||
url: databaseUrl,
|
||||
entities: [
|
||||
TeamOrmEntity,
|
||||
TeamMembershipOrmEntity,
|
||||
TeamJoinRequestOrmEntity,
|
||||
PenaltyOrmEntity,
|
||||
ProtestOrmEntity,
|
||||
],
|
||||
synchronize: true,
|
||||
});
|
||||
|
||||
await dataSource.initialize();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (dataSource?.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports: create team + membership + join request + stewarding records', async () => {
|
||||
const teamRepo = new TypeOrmTeamRepository(
|
||||
dataSource.getRepository(TeamOrmEntity),
|
||||
new TeamOrmMapper(),
|
||||
);
|
||||
const teamMembershipRepo = new TypeOrmTeamMembershipRepository(
|
||||
dataSource.getRepository(TeamMembershipOrmEntity),
|
||||
dataSource.getRepository(TeamJoinRequestOrmEntity),
|
||||
new TeamMembershipOrmMapper(),
|
||||
);
|
||||
|
||||
const penaltyRepo = new TypeOrmPenaltyRepository(
|
||||
dataSource.getRepository(PenaltyOrmEntity),
|
||||
new PenaltyOrmMapper(),
|
||||
);
|
||||
const protestRepo = new TypeOrmProtestRepository(
|
||||
dataSource.getRepository(ProtestOrmEntity),
|
||||
new ProtestOrmMapper(),
|
||||
);
|
||||
|
||||
const leagueId = '00000000-0000-4000-8000-000000000010';
|
||||
|
||||
const team = Team.create({
|
||||
id: '00000000-0000-4000-8000-000000000001',
|
||||
name: 'Integration Team',
|
||||
tag: 'INT',
|
||||
description: 'Team for integration tests',
|
||||
ownerId: '00000000-0000-4000-8000-000000000002',
|
||||
leagues: [leagueId],
|
||||
createdAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
await teamRepo.create(team);
|
||||
|
||||
const membership = {
|
||||
teamId: team.id,
|
||||
driverId: '00000000-0000-4000-8000-000000000003',
|
||||
role: 'driver' as const,
|
||||
status: 'active' as const,
|
||||
joinedAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
};
|
||||
|
||||
await teamMembershipRepo.saveMembership(membership);
|
||||
|
||||
const joinRequest = {
|
||||
id: 'join-req-1',
|
||||
teamId: team.id,
|
||||
driverId: '00000000-0000-4000-8000-000000000004',
|
||||
requestedAt: new Date('2025-01-02T00:00:00.000Z'),
|
||||
message: 'Please let me in',
|
||||
};
|
||||
|
||||
await teamMembershipRepo.saveJoinRequest(joinRequest);
|
||||
|
||||
const byLeague = await teamRepo.findByLeagueId(leagueId);
|
||||
expect(byLeague.map((t) => t.id)).toContain(team.id);
|
||||
|
||||
const members = await teamMembershipRepo.getTeamMembers(team.id);
|
||||
expect(members).toHaveLength(1);
|
||||
expect(members[0]?.driverId).toBe(membership.driverId);
|
||||
|
||||
const requests = await teamMembershipRepo.getJoinRequests(team.id);
|
||||
expect(requests.map((r) => r.id)).toContain('join-req-1');
|
||||
|
||||
const raceId = '00000000-0000-4000-8000-000000000020';
|
||||
|
||||
const protest = Protest.create({
|
||||
id: '00000000-0000-4000-8000-000000000030',
|
||||
raceId,
|
||||
protestingDriverId: membership.driverId,
|
||||
accusedDriverId: '00000000-0000-4000-8000-000000000005',
|
||||
incident: { lap: 1, description: 'Contact' },
|
||||
status: 'pending',
|
||||
filedAt: new Date('2025-01-03T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
await protestRepo.create(protest);
|
||||
|
||||
const penalty = Penalty.rehydrate({
|
||||
id: '00000000-0000-4000-8000-000000000040',
|
||||
leagueId,
|
||||
raceId,
|
||||
driverId: membership.driverId,
|
||||
type: 'warning',
|
||||
reason: 'Unsafe rejoin',
|
||||
issuedBy: '00000000-0000-4000-8000-000000000006',
|
||||
status: 'pending',
|
||||
issuedAt: new Date('2025-01-04T00:00:00.000Z'),
|
||||
protestId: protest.id,
|
||||
});
|
||||
|
||||
await penaltyRepo.create(penalty);
|
||||
|
||||
const penaltiesForRace = await penaltyRepo.findByRaceId(raceId);
|
||||
expect(penaltiesForRace.map((p) => p.id)).toContain(penalty.id);
|
||||
|
||||
const protestsForRace = await protestRepo.findByRaceId(raceId);
|
||||
expect(protestsForRace.map((p) => p.id)).toContain(protest.id);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { getDataSourceToken } from '@nestjs/typeorm';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import type { DataSource } from 'typeorm';
|
||||
|
||||
import { DatabaseModule } from '../../domain/database/DatabaseModule';
|
||||
import { PostgresSocialPersistenceModule } from '../postgres/PostgresSocialPersistenceModule';
|
||||
import { SOCIAL_FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN } from './SocialPersistenceTokens';
|
||||
|
||||
import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
||||
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
||||
|
||||
import { FeedItemOrmEntity } from '@adapters/social/persistence/typeorm/entities/FeedItemOrmEntity';
|
||||
import { FriendshipOrmEntity } from '@adapters/social/persistence/typeorm/entities/FriendshipOrmEntity';
|
||||
import { DriverOrmEntity } from '@adapters/racing/persistence/typeorm/entities/DriverOrmEntity';
|
||||
|
||||
describe('PostgresSocialPersistenceModule (integration)', () => {
|
||||
const shouldRun = Boolean(process.env.DATABASE_URL);
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.GRIDPILOT_API_PERSISTENCE;
|
||||
});
|
||||
|
||||
(shouldRun ? it : it.skip)('reads driver feed against a real database', async () => {
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'postgres';
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [DatabaseModule, PostgresSocialPersistenceModule],
|
||||
}).compile();
|
||||
|
||||
const dataSource = module.get<DataSource>(getDataSourceToken());
|
||||
|
||||
const driverRepo = dataSource.getRepository(DriverOrmEntity);
|
||||
const friendshipRepo = dataSource.getRepository(FriendshipOrmEntity);
|
||||
const feedOrmRepo = dataSource.getRepository(FeedItemOrmEntity);
|
||||
|
||||
await feedOrmRepo.clear();
|
||||
await friendshipRepo.clear();
|
||||
await driverRepo.clear();
|
||||
|
||||
const driverA = new DriverOrmEntity();
|
||||
driverA.id = '00000000-0000-0000-0000-000000000001';
|
||||
driverA.iracingId = '1';
|
||||
driverA.name = 'A';
|
||||
driverA.country = 'DE';
|
||||
driverA.bio = null;
|
||||
driverA.joinedAt = new Date('2025-01-01T00:00:00.000Z');
|
||||
|
||||
const driverB = new DriverOrmEntity();
|
||||
driverB.id = '00000000-0000-0000-0000-000000000002';
|
||||
driverB.iracingId = '2';
|
||||
driverB.name = 'B';
|
||||
driverB.country = 'DE';
|
||||
driverB.bio = null;
|
||||
driverB.joinedAt = new Date('2025-01-01T00:00:00.000Z');
|
||||
|
||||
await driverRepo.save([driverA, driverB]);
|
||||
|
||||
const friendship = new FriendshipOrmEntity();
|
||||
friendship.driverId = driverA.id;
|
||||
friendship.friendId = driverB.id;
|
||||
friendship.createdAt = new Date('2025-01-01T00:00:00.000Z');
|
||||
await friendshipRepo.save(friendship);
|
||||
|
||||
const item = new FeedItemOrmEntity();
|
||||
item.id = '00000000-0000-0000-0000-0000000000aa';
|
||||
item.timestamp = new Date('2025-02-01T00:00:00.000Z');
|
||||
item.type = 'new-result-posted';
|
||||
item.actorFriendId = null;
|
||||
item.actorDriverId = driverB.id;
|
||||
item.leagueId = null;
|
||||
item.raceId = null;
|
||||
item.teamId = null;
|
||||
item.position = null;
|
||||
item.headline = 'Hello';
|
||||
item.body = null;
|
||||
item.ctaLabel = null;
|
||||
item.ctaHref = null;
|
||||
|
||||
await feedOrmRepo.save(item);
|
||||
|
||||
const feedRepo = module.get<IFeedRepository>(SOCIAL_FEED_REPOSITORY_TOKEN);
|
||||
const socialGraphRepo = module.get<ISocialGraphRepository>(SOCIAL_GRAPH_REPOSITORY_TOKEN);
|
||||
|
||||
const friendIds = await socialGraphRepo.getFriendIds(driverA.id);
|
||||
expect(friendIds).toEqual([driverB.id]);
|
||||
|
||||
const feed = await feedRepo.getFeedForDriver(driverA.id);
|
||||
expect(feed).toHaveLength(1);
|
||||
expect(feed[0]?.id).toBe(item.id);
|
||||
|
||||
await module.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { MODULE_METADATA } from '@nestjs/common/constants';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { SOCIAL_FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN } from './SocialPersistenceTokens';
|
||||
|
||||
describe('SocialPersistenceModule', () => {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('uses inmemory providers when GRIDPILOT_API_PERSISTENCE=inmemory', async () => {
|
||||
vi.resetModules();
|
||||
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
|
||||
delete process.env.DATABASE_URL;
|
||||
|
||||
const { SocialPersistenceModule } = await import('./SocialPersistenceModule');
|
||||
const { InMemoryFeedRepository } = await import('@adapters/social/persistence/inmemory/InMemorySocialAndFeed');
|
||||
const { InMemorySocialGraphRepository } = await import('@adapters/social/persistence/inmemory/InMemorySocialAndFeed');
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [SocialPersistenceModule],
|
||||
}).compile();
|
||||
|
||||
const feedRepo = module.get(SOCIAL_FEED_REPOSITORY_TOKEN);
|
||||
const socialRepo = module.get(SOCIAL_GRAPH_REPOSITORY_TOKEN);
|
||||
|
||||
expect(feedRepo).toBeInstanceOf(InMemoryFeedRepository);
|
||||
expect(socialRepo).toBeInstanceOf(InMemorySocialGraphRepository);
|
||||
|
||||
await module.close();
|
||||
});
|
||||
|
||||
it('uses postgres module when GRIDPILOT_API_PERSISTENCE=postgres', async () => {
|
||||
vi.resetModules();
|
||||
|
||||
process.env.GRIDPILOT_API_PERSISTENCE = 'postgres';
|
||||
delete process.env.DATABASE_URL;
|
||||
|
||||
const { SocialPersistenceModule } = await import('./SocialPersistenceModule');
|
||||
const { PostgresSocialPersistenceModule } = await import('../postgres/PostgresSocialPersistenceModule');
|
||||
|
||||
const imports = Reflect.getMetadata(MODULE_METADATA.IMPORTS, SocialPersistenceModule) as unknown[];
|
||||
expect(imports).toContain(PostgresSocialPersistenceModule);
|
||||
});
|
||||
});
|
||||
14
apps/api/src/persistence/social/SocialPersistenceModule.ts
Normal file
14
apps/api/src/persistence/social/SocialPersistenceModule.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { getApiPersistence } from '../../env';
|
||||
import { InMemorySocialPersistenceModule } from '../inmemory/InMemorySocialPersistenceModule';
|
||||
import { PostgresSocialPersistenceModule } from '../postgres/PostgresSocialPersistenceModule';
|
||||
|
||||
const selectedPersistenceModule =
|
||||
getApiPersistence() === 'postgres' ? PostgresSocialPersistenceModule : InMemorySocialPersistenceModule;
|
||||
|
||||
@Module({
|
||||
imports: [selectedPersistenceModule],
|
||||
exports: [selectedPersistenceModule],
|
||||
})
|
||||
export class SocialPersistenceModule {}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const SOCIAL_FEED_REPOSITORY_TOKEN = 'IFeedRepository';
|
||||
export const SOCIAL_GRAPH_REPOSITORY_TOKEN = 'ISocialGraphRepository';
|
||||
Reference in New Issue
Block a user