diff --git a/adapters/bootstrap/EnsureInitialData.ts b/adapters/bootstrap/EnsureInitialData.ts index 8743c5b98..70cd6d70a 100644 --- a/adapters/bootstrap/EnsureInitialData.ts +++ b/adapters/bootstrap/EnsureInitialData.ts @@ -1,13 +1,19 @@ -import { SignupWithEmailUseCase } from '@core/identity/application/use-cases/SignupWithEmailUseCase'; -/** - * EnsureInitialData - Bootstrap script to ensure initial data exists. - * Idempotent: Can be run multiple times without issues. - * Calls core use cases to create initial admin user if not exists. - */ +import { SignupWithEmailUseCase } from '@core/identity/application/use-cases/SignupWithEmailUseCase'; +import { CreateAchievementUseCase } from '@core/identity/application/use-cases/achievement/CreateAchievementUseCase'; +import type { Logger } from '@core/shared/application/Logger'; +import { + DRIVER_ACHIEVEMENTS, + STEWARD_ACHIEVEMENTS, + ADMIN_ACHIEVEMENTS, + COMMUNITY_ACHIEVEMENTS, +} from '@core/identity/domain/AchievementConstants'; + export class EnsureInitialData { constructor( private readonly signupUseCase: SignupWithEmailUseCase, + private readonly createAchievementUseCase: CreateAchievementUseCase, + private readonly logger: Logger, ) {} async execute(): Promise { @@ -18,17 +24,38 @@ export class EnsureInitialData { password: 'admin123', displayName: 'Admin', }); - // User created successfully + this.logger.info('[Bootstrap] Initial admin user created'); } catch (error) { if (error instanceof Error && error.message === 'An account with this email already exists') { // User already exists, nothing to do - return; + this.logger.info('[Bootstrap] Admin user already exists'); + } else { + // Re-throw other errors + throw error; } - // Re-throw other errors - throw error; } - // Future: Add more initial data creation here - // e.g., create default league, config, etc. + // Ensure initial achievements exist + const allAchievements = [ + ...DRIVER_ACHIEVEMENTS, + ...STEWARD_ACHIEVEMENTS, + ...ADMIN_ACHIEVEMENTS, + ...COMMUNITY_ACHIEVEMENTS, + ]; + + let createdCount = 0; + let existingCount = 0; + + for (const achievementProps of allAchievements) { + try { + await this.createAchievementUseCase.execute(achievementProps); + createdCount++; + } catch { + // If achievement already exists, that's fine + existingCount++; + } + } + + this.logger.info(`[Bootstrap] Achievements: ${createdCount} created, ${existingCount} already exist`); } } \ No newline at end of file diff --git a/adapters/persistence/inmemory/achievement/InMemoryAchievementRepository.ts b/adapters/persistence/inmemory/achievement/InMemoryAchievementRepository.ts new file mode 100644 index 000000000..44de8ec91 --- /dev/null +++ b/adapters/persistence/inmemory/achievement/InMemoryAchievementRepository.ts @@ -0,0 +1,19 @@ +import { IAchievementRepository } from "@core/identity/application/use-cases/achievement/CreateAchievementUseCase"; +import { Achievement } from "@core/identity/domain/entities/Achievement"; + + +export class InMemoryAchievementRepository implements IAchievementRepository { + private readonly achievements: Map = new Map(); + + async save(achievement: Achievement): Promise { + this.achievements.set(achievement.id, achievement); + } + + async findById(id: string): Promise { + return this.achievements.get(id) || null; + } + + async findAll(): Promise { + return Array.from(this.achievements.values()); + } +} diff --git a/adapters/tsconfig.json b/adapters/tsconfig.json index f5c4367ba..da89d9462 100644 --- a/adapters/tsconfig.json +++ b/adapters/tsconfig.json @@ -1,21 +1,12 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "rootDir": ".", - "outDir": "dist", - "baseUrl": ".", - "composite": true, + "outDir": "../dist/adapters", "declaration": true, "declarationMap": true, "sourceMap": true, - "types": ["vitest/globals"], - "paths": { - "@/*": ["./*"], - "@core/*": ["../core/*"], - "@adapters/*": ["./*"], - "@testing/*": ["../testing/*"] - } + "types": ["vitest/globals"] }, - "include": ["**/*.ts"], + "include": ["**/*.ts", "../core/**/*.ts"], "exclude": ["node_modules", "dist"] } \ No newline at end of file diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 405931095..f4369e674 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -2,22 +2,24 @@ import { Module } from '@nestjs/common'; import { HelloController } from './presentation/hello.controller'; import { HelloService } from './application/hello/hello.service'; -import { AnalyticsModule } from './modules/analytics/AnalyticsModule'; +import { AnalyticsModule } from './domain/analytics/AnalyticsModule'; import { DatabaseModule } from './infrastructure/database/database.module'; import { LoggingModule } from './infrastructure/logging/LoggingModule'; -import { AuthModule } from './modules/auth/AuthModule'; -import { LeagueModule } from './modules/league/LeagueModule'; -import { RaceModule } from './modules/race/RaceModule'; -import { TeamModule } from './modules/team/TeamModule'; -import { SponsorModule } from './modules/sponsor/SponsorModule'; -import { DriverModule } from './modules/driver/DriverModule'; -import { MediaModule } from './modules/media/MediaModule'; -import { PaymentsModule } from './modules/payments/PaymentsModule'; +import { BootstrapModule } from './infrastructure/bootstrap/BootstrapModule'; +import { AuthModule } from './domain/auth/AuthModule'; +import { LeagueModule } from './domain/league/LeagueModule'; +import { RaceModule } from './domain/race/RaceModule'; +import { TeamModule } from './domain/team/TeamModule'; +import { SponsorModule } from './domain/sponsor/SponsorModule'; +import { DriverModule } from './domain/driver/DriverModule'; +import { MediaModule } from './domain/media/MediaModule'; +import { PaymentsModule } from './domain/payments/PaymentsModule'; @Module({ imports: [ DatabaseModule, LoggingModule, + BootstrapModule, AnalyticsModule, AuthModule, LeagueModule, diff --git a/apps/api/src/modules/analytics/AnalyticsController.ts b/apps/api/src/domain/analytics/AnalyticsController.ts similarity index 100% rename from apps/api/src/modules/analytics/AnalyticsController.ts rename to apps/api/src/domain/analytics/AnalyticsController.ts diff --git a/apps/api/src/modules/analytics/AnalyticsModule.ts b/apps/api/src/domain/analytics/AnalyticsModule.ts similarity index 100% rename from apps/api/src/modules/analytics/AnalyticsModule.ts rename to apps/api/src/domain/analytics/AnalyticsModule.ts diff --git a/apps/api/src/modules/analytics/AnalyticsProviders.ts b/apps/api/src/domain/analytics/AnalyticsProviders.ts similarity index 100% rename from apps/api/src/modules/analytics/AnalyticsProviders.ts rename to apps/api/src/domain/analytics/AnalyticsProviders.ts diff --git a/apps/api/src/modules/analytics/AnalyticsService.ts b/apps/api/src/domain/analytics/AnalyticsService.ts similarity index 100% rename from apps/api/src/modules/analytics/AnalyticsService.ts rename to apps/api/src/domain/analytics/AnalyticsService.ts diff --git a/apps/api/src/modules/analytics/dto/AnalyticsDto.ts b/apps/api/src/domain/analytics/dto/AnalyticsDto.ts similarity index 100% rename from apps/api/src/modules/analytics/dto/AnalyticsDto.ts rename to apps/api/src/domain/analytics/dto/AnalyticsDto.ts diff --git a/apps/api/src/modules/analytics/use-cases/RecordEngagementUseCase.test.ts b/apps/api/src/domain/analytics/use-cases/RecordEngagementUseCase.test.ts similarity index 100% rename from apps/api/src/modules/analytics/use-cases/RecordEngagementUseCase.test.ts rename to apps/api/src/domain/analytics/use-cases/RecordEngagementUseCase.test.ts diff --git a/apps/api/src/modules/analytics/use-cases/RecordEngagementUseCase.ts b/apps/api/src/domain/analytics/use-cases/RecordEngagementUseCase.ts similarity index 100% rename from apps/api/src/modules/analytics/use-cases/RecordEngagementUseCase.ts rename to apps/api/src/domain/analytics/use-cases/RecordEngagementUseCase.ts diff --git a/apps/api/src/modules/analytics/use-cases/RecordPageViewUseCase.test.ts b/apps/api/src/domain/analytics/use-cases/RecordPageViewUseCase.test.ts similarity index 100% rename from apps/api/src/modules/analytics/use-cases/RecordPageViewUseCase.test.ts rename to apps/api/src/domain/analytics/use-cases/RecordPageViewUseCase.test.ts diff --git a/apps/api/src/modules/analytics/use-cases/RecordPageViewUseCase.ts b/apps/api/src/domain/analytics/use-cases/RecordPageViewUseCase.ts similarity index 100% rename from apps/api/src/modules/analytics/use-cases/RecordPageViewUseCase.ts rename to apps/api/src/domain/analytics/use-cases/RecordPageViewUseCase.ts diff --git a/apps/api/src/modules/auth/AuthController.ts b/apps/api/src/domain/auth/AuthController.ts similarity index 100% rename from apps/api/src/modules/auth/AuthController.ts rename to apps/api/src/domain/auth/AuthController.ts diff --git a/apps/api/src/modules/auth/AuthModule.ts b/apps/api/src/domain/auth/AuthModule.ts similarity index 100% rename from apps/api/src/modules/auth/AuthModule.ts rename to apps/api/src/domain/auth/AuthModule.ts diff --git a/apps/api/src/modules/auth/AuthProviders.ts b/apps/api/src/domain/auth/AuthProviders.ts similarity index 100% rename from apps/api/src/modules/auth/AuthProviders.ts rename to apps/api/src/domain/auth/AuthProviders.ts diff --git a/apps/api/src/modules/auth/AuthService.ts b/apps/api/src/domain/auth/AuthService.ts similarity index 100% rename from apps/api/src/modules/auth/AuthService.ts rename to apps/api/src/domain/auth/AuthService.ts diff --git a/apps/api/src/modules/auth/dto/AuthDto.ts b/apps/api/src/domain/auth/dto/AuthDto.ts similarity index 100% rename from apps/api/src/modules/auth/dto/AuthDto.ts rename to apps/api/src/domain/auth/dto/AuthDto.ts diff --git a/apps/api/src/modules/driver/DriverController.ts b/apps/api/src/domain/driver/DriverController.ts similarity index 100% rename from apps/api/src/modules/driver/DriverController.ts rename to apps/api/src/domain/driver/DriverController.ts diff --git a/apps/api/src/modules/driver/DriverModule.ts b/apps/api/src/domain/driver/DriverModule.ts similarity index 100% rename from apps/api/src/modules/driver/DriverModule.ts rename to apps/api/src/domain/driver/DriverModule.ts diff --git a/apps/api/src/modules/driver/DriverProviders.ts b/apps/api/src/domain/driver/DriverProviders.ts similarity index 100% rename from apps/api/src/modules/driver/DriverProviders.ts rename to apps/api/src/domain/driver/DriverProviders.ts diff --git a/apps/api/src/modules/driver/DriverService.test.ts b/apps/api/src/domain/driver/DriverService.test.ts similarity index 100% rename from apps/api/src/modules/driver/DriverService.test.ts rename to apps/api/src/domain/driver/DriverService.test.ts diff --git a/apps/api/src/modules/driver/DriverService.ts b/apps/api/src/domain/driver/DriverService.ts similarity index 100% rename from apps/api/src/modules/driver/DriverService.ts rename to apps/api/src/domain/driver/DriverService.ts diff --git a/apps/api/src/modules/driver/dto/DriverDto.ts b/apps/api/src/domain/driver/dto/DriverDto.ts similarity index 100% rename from apps/api/src/modules/driver/dto/DriverDto.ts rename to apps/api/src/domain/driver/dto/DriverDto.ts diff --git a/apps/api/src/modules/driver/presenters/CompleteOnboardingPresenter.test.ts b/apps/api/src/domain/driver/presenters/CompleteOnboardingPresenter.test.ts similarity index 100% rename from apps/api/src/modules/driver/presenters/CompleteOnboardingPresenter.test.ts rename to apps/api/src/domain/driver/presenters/CompleteOnboardingPresenter.test.ts diff --git a/apps/api/src/modules/driver/presenters/CompleteOnboardingPresenter.ts b/apps/api/src/domain/driver/presenters/CompleteOnboardingPresenter.ts similarity index 100% rename from apps/api/src/modules/driver/presenters/CompleteOnboardingPresenter.ts rename to apps/api/src/domain/driver/presenters/CompleteOnboardingPresenter.ts diff --git a/apps/api/src/modules/driver/presenters/DriverRegistrationStatusPresenter.test.ts b/apps/api/src/domain/driver/presenters/DriverRegistrationStatusPresenter.test.ts similarity index 100% rename from apps/api/src/modules/driver/presenters/DriverRegistrationStatusPresenter.test.ts rename to apps/api/src/domain/driver/presenters/DriverRegistrationStatusPresenter.test.ts diff --git a/apps/api/src/modules/driver/presenters/DriverRegistrationStatusPresenter.ts b/apps/api/src/domain/driver/presenters/DriverRegistrationStatusPresenter.ts similarity index 100% rename from apps/api/src/modules/driver/presenters/DriverRegistrationStatusPresenter.ts rename to apps/api/src/domain/driver/presenters/DriverRegistrationStatusPresenter.ts diff --git a/apps/api/src/modules/driver/presenters/DriverStatsPresenter.test.ts b/apps/api/src/domain/driver/presenters/DriverStatsPresenter.test.ts similarity index 100% rename from apps/api/src/modules/driver/presenters/DriverStatsPresenter.test.ts rename to apps/api/src/domain/driver/presenters/DriverStatsPresenter.test.ts diff --git a/apps/api/src/modules/driver/presenters/DriverStatsPresenter.ts b/apps/api/src/domain/driver/presenters/DriverStatsPresenter.ts similarity index 100% rename from apps/api/src/modules/driver/presenters/DriverStatsPresenter.ts rename to apps/api/src/domain/driver/presenters/DriverStatsPresenter.ts diff --git a/apps/api/src/modules/driver/presenters/DriversLeaderboardPresenter.test.ts b/apps/api/src/domain/driver/presenters/DriversLeaderboardPresenter.test.ts similarity index 100% rename from apps/api/src/modules/driver/presenters/DriversLeaderboardPresenter.test.ts rename to apps/api/src/domain/driver/presenters/DriversLeaderboardPresenter.test.ts diff --git a/apps/api/src/modules/driver/presenters/DriversLeaderboardPresenter.ts b/apps/api/src/domain/driver/presenters/DriversLeaderboardPresenter.ts similarity index 100% rename from apps/api/src/modules/driver/presenters/DriversLeaderboardPresenter.ts rename to apps/api/src/domain/driver/presenters/DriversLeaderboardPresenter.ts diff --git a/apps/api/src/modules/league/LeagueController.test.ts b/apps/api/src/domain/league/LeagueController.test.ts similarity index 100% rename from apps/api/src/modules/league/LeagueController.test.ts rename to apps/api/src/domain/league/LeagueController.test.ts diff --git a/apps/api/src/modules/league/LeagueController.ts b/apps/api/src/domain/league/LeagueController.ts similarity index 100% rename from apps/api/src/modules/league/LeagueController.ts rename to apps/api/src/domain/league/LeagueController.ts diff --git a/apps/api/src/modules/league/LeagueModule.ts b/apps/api/src/domain/league/LeagueModule.ts similarity index 100% rename from apps/api/src/modules/league/LeagueModule.ts rename to apps/api/src/domain/league/LeagueModule.ts diff --git a/apps/api/src/modules/league/LeagueProviders.ts b/apps/api/src/domain/league/LeagueProviders.ts similarity index 100% rename from apps/api/src/modules/league/LeagueProviders.ts rename to apps/api/src/domain/league/LeagueProviders.ts diff --git a/apps/api/src/modules/league/LeagueService.test.ts b/apps/api/src/domain/league/LeagueService.test.ts similarity index 100% rename from apps/api/src/modules/league/LeagueService.test.ts rename to apps/api/src/domain/league/LeagueService.test.ts diff --git a/apps/api/src/modules/league/LeagueService.ts b/apps/api/src/domain/league/LeagueService.ts similarity index 100% rename from apps/api/src/modules/league/LeagueService.ts rename to apps/api/src/domain/league/LeagueService.ts diff --git a/apps/api/src/modules/league/dto/LeagueDto.ts b/apps/api/src/domain/league/dto/LeagueDto.ts similarity index 100% rename from apps/api/src/modules/league/dto/LeagueDto.ts rename to apps/api/src/domain/league/dto/LeagueDto.ts diff --git a/apps/api/src/modules/league/presenters/AllLeaguesWithCapacityPresenter.ts b/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/AllLeaguesWithCapacityPresenter.ts rename to apps/api/src/domain/league/presenters/AllLeaguesWithCapacityPresenter.ts diff --git a/apps/api/src/modules/league/presenters/ApproveLeagueJoinRequestPresenter.ts b/apps/api/src/domain/league/presenters/ApproveLeagueJoinRequestPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/ApproveLeagueJoinRequestPresenter.ts rename to apps/api/src/domain/league/presenters/ApproveLeagueJoinRequestPresenter.ts diff --git a/apps/api/src/modules/league/presenters/GetLeagueAdminPermissionsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueAdminPermissionsPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/GetLeagueAdminPermissionsPresenter.ts rename to apps/api/src/domain/league/presenters/GetLeagueAdminPermissionsPresenter.ts diff --git a/apps/api/src/modules/league/presenters/GetLeagueMembershipsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/GetLeagueMembershipsPresenter.ts rename to apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.ts diff --git a/apps/api/src/modules/league/presenters/GetLeagueOwnerSummaryPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueOwnerSummaryPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/GetLeagueOwnerSummaryPresenter.ts rename to apps/api/src/domain/league/presenters/GetLeagueOwnerSummaryPresenter.ts diff --git a/apps/api/src/modules/league/presenters/GetLeagueProtestsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/GetLeagueProtestsPresenter.ts rename to apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts diff --git a/apps/api/src/modules/league/presenters/GetLeagueSeasonsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueSeasonsPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/GetLeagueSeasonsPresenter.ts rename to apps/api/src/domain/league/presenters/GetLeagueSeasonsPresenter.ts diff --git a/apps/api/src/modules/league/presenters/LeagueAdminPresenter.ts b/apps/api/src/domain/league/presenters/LeagueAdminPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/LeagueAdminPresenter.ts rename to apps/api/src/domain/league/presenters/LeagueAdminPresenter.ts diff --git a/apps/api/src/modules/league/presenters/LeagueConfigPresenter.ts b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/LeagueConfigPresenter.ts rename to apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts diff --git a/apps/api/src/modules/league/presenters/LeagueJoinRequestsPresenter.ts b/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/LeagueJoinRequestsPresenter.ts rename to apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.ts diff --git a/apps/api/src/modules/league/presenters/LeagueSchedulePresenter.ts b/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/LeagueSchedulePresenter.ts rename to apps/api/src/domain/league/presenters/LeagueSchedulePresenter.ts diff --git a/apps/api/src/modules/league/presenters/LeagueStandingsPresenter.ts b/apps/api/src/domain/league/presenters/LeagueStandingsPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/LeagueStandingsPresenter.ts rename to apps/api/src/domain/league/presenters/LeagueStandingsPresenter.ts diff --git a/apps/api/src/modules/league/presenters/LeagueStatsPresenter.ts b/apps/api/src/domain/league/presenters/LeagueStatsPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/LeagueStatsPresenter.ts rename to apps/api/src/domain/league/presenters/LeagueStatsPresenter.ts diff --git a/apps/api/src/modules/league/presenters/RejectLeagueJoinRequestPresenter.ts b/apps/api/src/domain/league/presenters/RejectLeagueJoinRequestPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/RejectLeagueJoinRequestPresenter.ts rename to apps/api/src/domain/league/presenters/RejectLeagueJoinRequestPresenter.ts diff --git a/apps/api/src/modules/league/presenters/RemoveLeagueMemberPresenter.ts b/apps/api/src/domain/league/presenters/RemoveLeagueMemberPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/RemoveLeagueMemberPresenter.ts rename to apps/api/src/domain/league/presenters/RemoveLeagueMemberPresenter.ts diff --git a/apps/api/src/modules/league/presenters/TotalLeaguesPresenter.ts b/apps/api/src/domain/league/presenters/TotalLeaguesPresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/TotalLeaguesPresenter.ts rename to apps/api/src/domain/league/presenters/TotalLeaguesPresenter.ts diff --git a/apps/api/src/modules/league/presenters/UpdateLeagueMemberRolePresenter.ts b/apps/api/src/domain/league/presenters/UpdateLeagueMemberRolePresenter.ts similarity index 100% rename from apps/api/src/modules/league/presenters/UpdateLeagueMemberRolePresenter.ts rename to apps/api/src/domain/league/presenters/UpdateLeagueMemberRolePresenter.ts diff --git a/apps/api/src/modules/media/MediaController.ts b/apps/api/src/domain/media/MediaController.ts similarity index 100% rename from apps/api/src/modules/media/MediaController.ts rename to apps/api/src/domain/media/MediaController.ts diff --git a/apps/api/src/modules/media/MediaModule.ts b/apps/api/src/domain/media/MediaModule.ts similarity index 100% rename from apps/api/src/modules/media/MediaModule.ts rename to apps/api/src/domain/media/MediaModule.ts diff --git a/apps/api/src/modules/media/MediaProviders.ts b/apps/api/src/domain/media/MediaProviders.ts similarity index 100% rename from apps/api/src/modules/media/MediaProviders.ts rename to apps/api/src/domain/media/MediaProviders.ts diff --git a/apps/api/src/modules/media/MediaService.ts b/apps/api/src/domain/media/MediaService.ts similarity index 100% rename from apps/api/src/modules/media/MediaService.ts rename to apps/api/src/domain/media/MediaService.ts diff --git a/apps/api/src/modules/media/dto/MediaDto.ts b/apps/api/src/domain/media/dto/MediaDto.ts similarity index 100% rename from apps/api/src/modules/media/dto/MediaDto.ts rename to apps/api/src/domain/media/dto/MediaDto.ts diff --git a/apps/api/src/modules/media/presenters/RequestAvatarGenerationPresenter.ts b/apps/api/src/domain/media/presenters/RequestAvatarGenerationPresenter.ts similarity index 100% rename from apps/api/src/modules/media/presenters/RequestAvatarGenerationPresenter.ts rename to apps/api/src/domain/media/presenters/RequestAvatarGenerationPresenter.ts diff --git a/apps/api/src/modules/payments/PaymentsController.ts b/apps/api/src/domain/payments/PaymentsController.ts similarity index 100% rename from apps/api/src/modules/payments/PaymentsController.ts rename to apps/api/src/domain/payments/PaymentsController.ts diff --git a/apps/api/src/modules/payments/PaymentsModule.ts b/apps/api/src/domain/payments/PaymentsModule.ts similarity index 100% rename from apps/api/src/modules/payments/PaymentsModule.ts rename to apps/api/src/domain/payments/PaymentsModule.ts diff --git a/apps/api/src/modules/payments/PaymentsProviders.ts b/apps/api/src/domain/payments/PaymentsProviders.ts similarity index 100% rename from apps/api/src/modules/payments/PaymentsProviders.ts rename to apps/api/src/domain/payments/PaymentsProviders.ts diff --git a/apps/api/src/modules/payments/PaymentsService.ts b/apps/api/src/domain/payments/PaymentsService.ts similarity index 100% rename from apps/api/src/modules/payments/PaymentsService.ts rename to apps/api/src/domain/payments/PaymentsService.ts diff --git a/apps/api/src/modules/payments/dto/PaymentsDto.ts b/apps/api/src/domain/payments/dto/PaymentsDto.ts similarity index 100% rename from apps/api/src/modules/payments/dto/PaymentsDto.ts rename to apps/api/src/domain/payments/dto/PaymentsDto.ts diff --git a/apps/api/src/modules/payments/presenters/AwardPrizePresenter.ts b/apps/api/src/domain/payments/presenters/AwardPrizePresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/AwardPrizePresenter.ts rename to apps/api/src/domain/payments/presenters/AwardPrizePresenter.ts diff --git a/apps/api/src/modules/payments/presenters/CreatePaymentPresenter.ts b/apps/api/src/domain/payments/presenters/CreatePaymentPresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/CreatePaymentPresenter.ts rename to apps/api/src/domain/payments/presenters/CreatePaymentPresenter.ts diff --git a/apps/api/src/modules/payments/presenters/CreatePrizePresenter.ts b/apps/api/src/domain/payments/presenters/CreatePrizePresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/CreatePrizePresenter.ts rename to apps/api/src/domain/payments/presenters/CreatePrizePresenter.ts diff --git a/apps/api/src/modules/payments/presenters/DeletePrizePresenter.ts b/apps/api/src/domain/payments/presenters/DeletePrizePresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/DeletePrizePresenter.ts rename to apps/api/src/domain/payments/presenters/DeletePrizePresenter.ts diff --git a/apps/api/src/modules/payments/presenters/GetMembershipFeesPresenter.ts b/apps/api/src/domain/payments/presenters/GetMembershipFeesPresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/GetMembershipFeesPresenter.ts rename to apps/api/src/domain/payments/presenters/GetMembershipFeesPresenter.ts diff --git a/apps/api/src/modules/payments/presenters/GetPaymentsPresenter.ts b/apps/api/src/domain/payments/presenters/GetPaymentsPresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/GetPaymentsPresenter.ts rename to apps/api/src/domain/payments/presenters/GetPaymentsPresenter.ts diff --git a/apps/api/src/modules/payments/presenters/GetPrizesPresenter.ts b/apps/api/src/domain/payments/presenters/GetPrizesPresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/GetPrizesPresenter.ts rename to apps/api/src/domain/payments/presenters/GetPrizesPresenter.ts diff --git a/apps/api/src/modules/payments/presenters/GetWalletPresenter.ts b/apps/api/src/domain/payments/presenters/GetWalletPresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/GetWalletPresenter.ts rename to apps/api/src/domain/payments/presenters/GetWalletPresenter.ts diff --git a/apps/api/src/modules/payments/presenters/ProcessWalletTransactionPresenter.ts b/apps/api/src/domain/payments/presenters/ProcessWalletTransactionPresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/ProcessWalletTransactionPresenter.ts rename to apps/api/src/domain/payments/presenters/ProcessWalletTransactionPresenter.ts diff --git a/apps/api/src/modules/payments/presenters/UpdateMemberPaymentPresenter.ts b/apps/api/src/domain/payments/presenters/UpdateMemberPaymentPresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/UpdateMemberPaymentPresenter.ts rename to apps/api/src/domain/payments/presenters/UpdateMemberPaymentPresenter.ts diff --git a/apps/api/src/modules/payments/presenters/UpdatePaymentStatusPresenter.ts b/apps/api/src/domain/payments/presenters/UpdatePaymentStatusPresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/UpdatePaymentStatusPresenter.ts rename to apps/api/src/domain/payments/presenters/UpdatePaymentStatusPresenter.ts diff --git a/apps/api/src/modules/payments/presenters/UpsertMembershipFeePresenter.ts b/apps/api/src/domain/payments/presenters/UpsertMembershipFeePresenter.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/UpsertMembershipFeePresenter.ts rename to apps/api/src/domain/payments/presenters/UpsertMembershipFeePresenter.ts diff --git a/apps/api/src/modules/payments/presenters/index.ts b/apps/api/src/domain/payments/presenters/index.ts similarity index 100% rename from apps/api/src/modules/payments/presenters/index.ts rename to apps/api/src/domain/payments/presenters/index.ts diff --git a/apps/api/src/modules/race/RaceController.ts b/apps/api/src/domain/race/RaceController.ts similarity index 100% rename from apps/api/src/modules/race/RaceController.ts rename to apps/api/src/domain/race/RaceController.ts diff --git a/apps/api/src/modules/race/RaceModule.ts b/apps/api/src/domain/race/RaceModule.ts similarity index 100% rename from apps/api/src/modules/race/RaceModule.ts rename to apps/api/src/domain/race/RaceModule.ts diff --git a/apps/api/src/modules/race/RaceProviders.ts b/apps/api/src/domain/race/RaceProviders.ts similarity index 100% rename from apps/api/src/modules/race/RaceProviders.ts rename to apps/api/src/domain/race/RaceProviders.ts diff --git a/apps/api/src/modules/race/RaceService.ts b/apps/api/src/domain/race/RaceService.ts similarity index 100% rename from apps/api/src/modules/race/RaceService.ts rename to apps/api/src/domain/race/RaceService.ts diff --git a/apps/api/src/modules/race/dto/RaceDto.ts b/apps/api/src/domain/race/dto/RaceDto.ts similarity index 100% rename from apps/api/src/modules/race/dto/RaceDto.ts rename to apps/api/src/domain/race/dto/RaceDto.ts diff --git a/apps/api/src/modules/race/presenters/GetAllRacesPresenter.ts b/apps/api/src/domain/race/presenters/GetAllRacesPresenter.ts similarity index 100% rename from apps/api/src/modules/race/presenters/GetAllRacesPresenter.ts rename to apps/api/src/domain/race/presenters/GetAllRacesPresenter.ts diff --git a/apps/api/src/modules/race/presenters/GetTotalRacesPresenter.ts b/apps/api/src/domain/race/presenters/GetTotalRacesPresenter.ts similarity index 100% rename from apps/api/src/modules/race/presenters/GetTotalRacesPresenter.ts rename to apps/api/src/domain/race/presenters/GetTotalRacesPresenter.ts diff --git a/apps/api/src/modules/race/presenters/ImportRaceResultsApiPresenter.ts b/apps/api/src/domain/race/presenters/ImportRaceResultsApiPresenter.ts similarity index 100% rename from apps/api/src/modules/race/presenters/ImportRaceResultsApiPresenter.ts rename to apps/api/src/domain/race/presenters/ImportRaceResultsApiPresenter.ts diff --git a/apps/api/src/modules/sponsor/SponsorController.ts b/apps/api/src/domain/sponsor/SponsorController.ts similarity index 100% rename from apps/api/src/modules/sponsor/SponsorController.ts rename to apps/api/src/domain/sponsor/SponsorController.ts diff --git a/apps/api/src/modules/sponsor/SponsorModule.ts b/apps/api/src/domain/sponsor/SponsorModule.ts similarity index 100% rename from apps/api/src/modules/sponsor/SponsorModule.ts rename to apps/api/src/domain/sponsor/SponsorModule.ts diff --git a/apps/api/src/modules/sponsor/SponsorProviders.ts b/apps/api/src/domain/sponsor/SponsorProviders.ts similarity index 100% rename from apps/api/src/modules/sponsor/SponsorProviders.ts rename to apps/api/src/domain/sponsor/SponsorProviders.ts diff --git a/apps/api/src/modules/sponsor/SponsorService.ts b/apps/api/src/domain/sponsor/SponsorService.ts similarity index 100% rename from apps/api/src/modules/sponsor/SponsorService.ts rename to apps/api/src/domain/sponsor/SponsorService.ts diff --git a/apps/api/src/modules/sponsor/dto/SponsorDto.ts b/apps/api/src/domain/sponsor/dto/SponsorDto.ts similarity index 100% rename from apps/api/src/modules/sponsor/dto/SponsorDto.ts rename to apps/api/src/domain/sponsor/dto/SponsorDto.ts diff --git a/apps/api/src/modules/sponsor/presenters/CreateSponsorPresenter.ts b/apps/api/src/domain/sponsor/presenters/CreateSponsorPresenter.ts similarity index 100% rename from apps/api/src/modules/sponsor/presenters/CreateSponsorPresenter.ts rename to apps/api/src/domain/sponsor/presenters/CreateSponsorPresenter.ts diff --git a/apps/api/src/modules/sponsor/presenters/GetEntitySponsorshipPricingPresenter.ts b/apps/api/src/domain/sponsor/presenters/GetEntitySponsorshipPricingPresenter.ts similarity index 100% rename from apps/api/src/modules/sponsor/presenters/GetEntitySponsorshipPricingPresenter.ts rename to apps/api/src/domain/sponsor/presenters/GetEntitySponsorshipPricingPresenter.ts diff --git a/apps/api/src/modules/sponsor/presenters/GetSponsorDashboardPresenter.ts b/apps/api/src/domain/sponsor/presenters/GetSponsorDashboardPresenter.ts similarity index 100% rename from apps/api/src/modules/sponsor/presenters/GetSponsorDashboardPresenter.ts rename to apps/api/src/domain/sponsor/presenters/GetSponsorDashboardPresenter.ts diff --git a/apps/api/src/modules/sponsor/presenters/GetSponsorSponsorshipsPresenter.ts b/apps/api/src/domain/sponsor/presenters/GetSponsorSponsorshipsPresenter.ts similarity index 100% rename from apps/api/src/modules/sponsor/presenters/GetSponsorSponsorshipsPresenter.ts rename to apps/api/src/domain/sponsor/presenters/GetSponsorSponsorshipsPresenter.ts diff --git a/apps/api/src/modules/sponsor/presenters/GetSponsorsPresenter.ts b/apps/api/src/domain/sponsor/presenters/GetSponsorsPresenter.ts similarity index 100% rename from apps/api/src/modules/sponsor/presenters/GetSponsorsPresenter.ts rename to apps/api/src/domain/sponsor/presenters/GetSponsorsPresenter.ts diff --git a/apps/api/src/modules/sponsor/presenters/GetSponsorshipPricingPresenter.ts b/apps/api/src/domain/sponsor/presenters/GetSponsorshipPricingPresenter.ts similarity index 100% rename from apps/api/src/modules/sponsor/presenters/GetSponsorshipPricingPresenter.ts rename to apps/api/src/domain/sponsor/presenters/GetSponsorshipPricingPresenter.ts diff --git a/apps/api/src/modules/team/TeamController.ts b/apps/api/src/domain/team/TeamController.ts similarity index 100% rename from apps/api/src/modules/team/TeamController.ts rename to apps/api/src/domain/team/TeamController.ts diff --git a/apps/api/src/modules/team/TeamModule.ts b/apps/api/src/domain/team/TeamModule.ts similarity index 100% rename from apps/api/src/modules/team/TeamModule.ts rename to apps/api/src/domain/team/TeamModule.ts diff --git a/apps/api/src/modules/team/TeamProviders.ts b/apps/api/src/domain/team/TeamProviders.ts similarity index 100% rename from apps/api/src/modules/team/TeamProviders.ts rename to apps/api/src/domain/team/TeamProviders.ts diff --git a/apps/api/src/modules/team/TeamService.test.ts b/apps/api/src/domain/team/TeamService.test.ts similarity index 100% rename from apps/api/src/modules/team/TeamService.test.ts rename to apps/api/src/domain/team/TeamService.test.ts diff --git a/apps/api/src/modules/team/TeamService.ts b/apps/api/src/domain/team/TeamService.ts similarity index 100% rename from apps/api/src/modules/team/TeamService.ts rename to apps/api/src/domain/team/TeamService.ts diff --git a/apps/api/src/modules/team/dto/TeamDto.ts b/apps/api/src/domain/team/dto/TeamDto.ts similarity index 100% rename from apps/api/src/modules/team/dto/TeamDto.ts rename to apps/api/src/domain/team/dto/TeamDto.ts diff --git a/apps/api/src/modules/team/presenters/AllTeamsPresenter.ts b/apps/api/src/domain/team/presenters/AllTeamsPresenter.ts similarity index 100% rename from apps/api/src/modules/team/presenters/AllTeamsPresenter.ts rename to apps/api/src/domain/team/presenters/AllTeamsPresenter.ts diff --git a/apps/api/src/modules/team/presenters/DriverTeamPresenter.ts b/apps/api/src/domain/team/presenters/DriverTeamPresenter.ts similarity index 100% rename from apps/api/src/modules/team/presenters/DriverTeamPresenter.ts rename to apps/api/src/domain/team/presenters/DriverTeamPresenter.ts diff --git a/apps/api/src/modules/team/presenters/TeamDetailsPresenter.ts b/apps/api/src/domain/team/presenters/TeamDetailsPresenter.ts similarity index 100% rename from apps/api/src/modules/team/presenters/TeamDetailsPresenter.ts rename to apps/api/src/domain/team/presenters/TeamDetailsPresenter.ts diff --git a/apps/api/src/modules/team/presenters/TeamJoinRequestsPresenter.ts b/apps/api/src/domain/team/presenters/TeamJoinRequestsPresenter.ts similarity index 100% rename from apps/api/src/modules/team/presenters/TeamJoinRequestsPresenter.ts rename to apps/api/src/domain/team/presenters/TeamJoinRequestsPresenter.ts diff --git a/apps/api/src/modules/team/presenters/TeamMembersPresenter.ts b/apps/api/src/domain/team/presenters/TeamMembersPresenter.ts similarity index 100% rename from apps/api/src/modules/team/presenters/TeamMembersPresenter.ts rename to apps/api/src/domain/team/presenters/TeamMembersPresenter.ts diff --git a/apps/api/src/infrastructure/bootstrap/BootstrapModule.ts b/apps/api/src/infrastructure/bootstrap/BootstrapModule.ts new file mode 100644 index 000000000..b823995db --- /dev/null +++ b/apps/api/src/infrastructure/bootstrap/BootstrapModule.ts @@ -0,0 +1,21 @@ +import { Module, OnModuleInit } from '@nestjs/common'; +import { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData'; +import { BootstrapProviders } from './BootstrapProviders'; + +@Module({ + providers: BootstrapProviders, +}) +export class BootstrapModule implements OnModuleInit { + constructor(private readonly ensureInitialData: EnsureInitialData) {} + + async onModuleInit() { + console.log('[Bootstrap] Initializing application data...'); + try { + await this.ensureInitialData.execute(); + console.log('[Bootstrap] Application data initialized successfully'); + } catch (error) { + console.error('[Bootstrap] Failed to initialize application data:', error); + throw error; + } + } +} \ No newline at end of file diff --git a/apps/api/src/infrastructure/bootstrap/BootstrapProviders.ts b/apps/api/src/infrastructure/bootstrap/BootstrapProviders.ts new file mode 100644 index 000000000..828e99544 --- /dev/null +++ b/apps/api/src/infrastructure/bootstrap/BootstrapProviders.ts @@ -0,0 +1,56 @@ +import { Provider } from '@nestjs/common'; +import { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData'; +import { SignupWithEmailUseCase } from '@core/identity/application/use-cases/SignupWithEmailUseCase'; +import { CreateAchievementUseCase } from '@core/identity/application/use-cases/achievement/CreateAchievementUseCase'; +import type { IUserRepository } from '@core/identity/domain/repositories/IUserRepository'; +import type { IAchievementRepository } from '@core/identity/application/use-cases/achievement/CreateAchievementUseCase'; +import type { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort'; +import type { Logger } from '@core/shared/application'; +import { InMemoryUserRepository } from '../../../../../adapters/identity/persistence/inmemory/InMemoryUserRepository'; +import { InMemoryAchievementRepository } from '../../../../../adapters/persistence/inmemory/achievement/InMemoryAchievementRepository'; +import { CookieIdentitySessionAdapter } from '../../../../../adapters/identity/session/CookieIdentitySessionAdapter'; + +// Define tokens +export const USER_REPOSITORY_TOKEN = 'IUserRepository_Bootstrap'; +export const ACHIEVEMENT_REPOSITORY_TOKEN = 'IAchievementRepository_Bootstrap'; +export const IDENTITY_SESSION_PORT_TOKEN = 'IdentitySessionPort_Bootstrap'; +export const SIGNUP_USE_CASE_TOKEN = 'SignupWithEmailUseCase_Bootstrap'; +export const CREATE_ACHIEVEMENT_USE_CASE_TOKEN = 'CreateAchievementUseCase_Bootstrap'; + +export const BootstrapProviders: Provider[] = [ + { + provide: USER_REPOSITORY_TOKEN, + useFactory: (logger: Logger) => new InMemoryUserRepository(logger), + inject: ['Logger'], + }, + { + provide: ACHIEVEMENT_REPOSITORY_TOKEN, + useClass: InMemoryAchievementRepository, + }, + { + provide: IDENTITY_SESSION_PORT_TOKEN, + useFactory: (logger: Logger) => new CookieIdentitySessionAdapter(logger), + inject: ['Logger'], + }, + { + provide: SIGNUP_USE_CASE_TOKEN, + useFactory: (userRepository: IUserRepository, sessionPort: IdentitySessionPort) => { + return new SignupWithEmailUseCase(userRepository, sessionPort); + }, + inject: [USER_REPOSITORY_TOKEN, IDENTITY_SESSION_PORT_TOKEN], + }, + { + provide: CREATE_ACHIEVEMENT_USE_CASE_TOKEN, + useFactory: (achievementRepository: IAchievementRepository) => { + return new CreateAchievementUseCase(achievementRepository); + }, + inject: [ACHIEVEMENT_REPOSITORY_TOKEN], + }, + { + provide: EnsureInitialData, + useFactory: (signupUseCase: SignupWithEmailUseCase, createAchievementUseCase: CreateAchievementUseCase, logger: Logger) => { + return new EnsureInitialData(signupUseCase, createAchievementUseCase, logger); + }, + inject: [SIGNUP_USE_CASE_TOKEN, CREATE_ACHIEVEMENT_USE_CASE_TOKEN, 'Logger'], + }, +]; \ No newline at end of file diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index 9f87fb3fe..38400f259 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -23,6 +23,6 @@ "@testing/*": ["../../testing/*"] } }, - "include": ["src/**/*"], + "include": ["src/**/*", "../../adapters/bootstrap/EnsureInitialData.ts"], "exclude": ["node_modules", "dist", "**/*.mock.ts"] } diff --git a/core/identity/application/use-cases/GetCurrentSessionUseCase.test.ts b/core/identity/application/use-cases/GetCurrentSessionUseCase.test.ts new file mode 100644 index 000000000..402e3b98f --- /dev/null +++ b/core/identity/application/use-cases/GetCurrentSessionUseCase.test.ts @@ -0,0 +1,57 @@ +import { vi, type Mock } from 'vitest'; +import { GetCurrentSessionUseCase } from './GetCurrentSessionUseCase'; +import { User } from '../../domain/entities/User'; +import { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository'; + +describe('GetCurrentSessionUseCase', () => { + let useCase: GetCurrentSessionUseCase; + let mockUserRepo: { + findByEmail: Mock; + findById: Mock; + create: Mock; + update: Mock; + emailExists: Mock; + }; + + beforeEach(() => { + mockUserRepo = { + findByEmail: vi.fn(), + findById: vi.fn(), + create: vi.fn(), + update: vi.fn(), + emailExists: vi.fn(), + }; + useCase = new GetCurrentSessionUseCase(mockUserRepo as IUserRepository); + }); + + it('should return User when user exists', async () => { + const userId = 'user-123'; + const storedUser: StoredUser = { + id: userId, + email: 'test@example.com', + displayName: 'Test User', + passwordHash: 'hash', + salt: 'salt', + primaryDriverId: 'driver-123', + createdAt: new Date(), + }; + mockUserRepo.findById.mockResolvedValue(storedUser); + + const result = await useCase.execute(userId); + + expect(mockUserRepo.findById).toHaveBeenCalledWith(userId); + expect(result).toBeInstanceOf(User); + expect(result?.getId().value).toBe(userId); + expect(result?.getDisplayName()).toBe('Test User'); + }); + + it('should return null when user does not exist', async () => { + const userId = 'user-123'; + mockUserRepo.findById.mockResolvedValue(null); + + const result = await useCase.execute(userId); + + expect(mockUserRepo.findById).toHaveBeenCalledWith(userId); + expect(result).toBeNull(); + }); +}); \ No newline at end of file diff --git a/core/identity/application/use-cases/GetCurrentSessionUseCase.ts b/core/identity/application/use-cases/GetCurrentSessionUseCase.ts index 32c460cd6..a6b74656c 100644 --- a/core/identity/application/use-cases/GetCurrentSessionUseCase.ts +++ b/core/identity/application/use-cases/GetCurrentSessionUseCase.ts @@ -1,4 +1,5 @@ import { User } from '../../domain/entities/User'; +import { IUserRepository } from '../../domain/repositories/IUserRepository'; // No direct import of apps/api DTOs in core module /** @@ -7,9 +8,13 @@ import { User } from '../../domain/entities/User'; * Retrieves the current user session information. */ export class GetCurrentSessionUseCase { + constructor(private userRepo: IUserRepository) {} + async execute(userId: string): Promise { - // TODO: Implement actual logic to retrieve user and session data - console.warn('GetCurrentSessionUseCase: Method not implemented.'); - return null; + const stored = await this.userRepo.findById(userId); + if (!stored) { + return null; + } + return User.fromStored(stored); } } diff --git a/core/identity/application/use-cases/LoginWithIracingCallbackUseCase.ts b/core/identity/application/use-cases/LoginWithIracingCallbackUseCase.ts deleted file mode 100644 index 375e4cdae..000000000 --- a/core/identity/application/use-cases/LoginWithIracingCallbackUseCase.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { User } from '../../domain/entities/User'; - -export interface LoginWithIracingCallbackParams { - code: string; - state?: string; - returnTo?: string; -} - -/** - * Application Use Case: LoginWithIracingCallbackUseCase - * - * Handles the callback after iRacing authentication. - */ -export class LoginWithIracingCallbackUseCase { - async execute(params: LoginWithIracingCallbackParams): Promise { - // TODO: Implement actual logic for handling iRacing OAuth callback - console.warn('LoginWithIracingCallbackUseCase: Method not implemented.'); - throw new Error('Method not implemented.'); - } -} diff --git a/core/identity/application/use-cases/StartIracingAuthRedirectUseCase.ts b/core/identity/application/use-cases/StartIracingAuthRedirectUseCase.ts deleted file mode 100644 index a923f5ead..000000000 --- a/core/identity/application/use-cases/StartIracingAuthRedirectUseCase.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface IracingAuthRedirectResult { - redirectUrl: string; - state: string; -} - -/** - * Application Use Case: StartIracingAuthRedirectUseCase - * - * Initiates the iRacing authentication flow. - */ -export class StartIracingAuthRedirectUseCase { - async execute(returnTo?: string): Promise { - // TODO: Implement actual logic for initiating iRacing OAuth redirect - console.warn('StartIracingAuthRedirectUseCase: Method not implemented.'); - return { - redirectUrl: '/mock-iracing-redirect', - state: 'mock-state', - }; - } -} diff --git a/core/identity/application/use-cases/achievement/CreateAchievementUseCase.ts b/core/identity/application/use-cases/achievement/CreateAchievementUseCase.ts new file mode 100644 index 000000000..830384a50 --- /dev/null +++ b/core/identity/application/use-cases/achievement/CreateAchievementUseCase.ts @@ -0,0 +1,16 @@ +import { Achievement, AchievementProps } from '@core/identity/domain/entities/Achievement'; + +export interface IAchievementRepository { + save(achievement: Achievement): Promise; + findById(id: string): Promise; +} + +export class CreateAchievementUseCase { + constructor(private readonly achievementRepository: IAchievementRepository) {} + + async execute(props: Omit): Promise { + const achievement = Achievement.create(props); + await this.achievementRepository.save(achievement); + return achievement; + } +} diff --git a/core/identity/domain/AchievementConstants.ts b/core/identity/domain/AchievementConstants.ts new file mode 100644 index 000000000..bec6d0bdb --- /dev/null +++ b/core/identity/domain/AchievementConstants.ts @@ -0,0 +1,267 @@ +import type { AchievementProps } from './entities/Achievement'; + +// Predefined achievements for drivers +export const DRIVER_ACHIEVEMENTS: Omit[] = [ + { + id: 'first-race', + name: 'First Steps', + description: 'Complete your first race', + category: 'driver', + rarity: 'common', + points: 10, + requirements: [{ type: 'races_completed', value: 1, operator: '>=' }], + isSecret: false, + }, + { + id: 'ten-races', + name: 'Getting Started', + description: 'Complete 10 races', + category: 'driver', + rarity: 'common', + points: 25, + requirements: [{ type: 'races_completed', value: 10, operator: '>=' }], + isSecret: false, + }, + { + id: 'fifty-races', + name: 'Regular Racer', + description: 'Complete 50 races', + category: 'driver', + rarity: 'uncommon', + points: 50, + requirements: [{ type: 'races_completed', value: 50, operator: '>=' }], + isSecret: false, + }, + { + id: 'hundred-races', + name: 'Veteran', + description: 'Complete 100 races', + category: 'driver', + rarity: 'rare', + points: 100, + requirements: [{ type: 'races_completed', value: 100, operator: '>=' }], + isSecret: false, + }, + { + id: 'first-win', + name: 'Victory Lane', + description: 'Win your first race', + category: 'driver', + rarity: 'uncommon', + points: 50, + requirements: [{ type: 'wins', value: 1, operator: '>=' }], + isSecret: false, + }, + { + id: 'ten-wins', + name: 'Serial Winner', + description: 'Win 10 races', + category: 'driver', + rarity: 'rare', + points: 100, + requirements: [{ type: 'wins', value: 10, operator: '>=' }], + isSecret: false, + }, + { + id: 'first-podium', + name: 'Podium Finisher', + description: 'Finish on the podium', + category: 'driver', + rarity: 'common', + points: 25, + requirements: [{ type: 'podiums', value: 1, operator: '>=' }], + isSecret: false, + }, + { + id: 'clean-streak-5', + name: 'Clean Racer', + description: 'Complete 5 consecutive races without incidents', + category: 'driver', + rarity: 'uncommon', + points: 50, + requirements: [{ type: 'consecutive_clean', value: 5, operator: '>=' }], + isSecret: false, + }, + { + id: 'clean-streak-10', + name: 'Safety First', + description: 'Complete 10 consecutive races without incidents', + category: 'driver', + rarity: 'rare', + points: 100, + requirements: [{ type: 'consecutive_clean', value: 10, operator: '>=' }], + isSecret: false, + }, + { + id: 'championship-win', + name: 'Champion', + description: 'Win a championship', + category: 'driver', + rarity: 'epic', + points: 200, + requirements: [{ type: 'championships_won', value: 1, operator: '>=' }], + isSecret: false, + }, + { + id: 'triple-crown', + name: 'Triple Crown', + description: 'Win 3 championships', + category: 'driver', + rarity: 'legendary', + points: 500, + requirements: [{ type: 'championships_won', value: 3, operator: '>=' }], + isSecret: false, + }, + { + id: 'elite-driver', + name: 'Elite Driver', + description: 'Reach Elite driver rating', + category: 'driver', + rarity: 'epic', + points: 250, + requirements: [{ type: 'rating_threshold', value: 90, operator: '>=' }], + isSecret: false, + }, +]; + +// Predefined achievements for stewards +export const STEWARD_ACHIEVEMENTS: Omit[] = [ + { + id: 'first-protest', + name: 'Justice Served', + description: 'Handle your first protest', + category: 'steward', + rarity: 'common', + points: 15, + requirements: [{ type: 'protests_handled', value: 1, operator: '>=' }], + isSecret: false, + }, + { + id: 'ten-protests', + name: 'Fair Judge', + description: 'Handle 10 protests', + category: 'steward', + rarity: 'uncommon', + points: 50, + requirements: [{ type: 'protests_handled', value: 10, operator: '>=' }], + isSecret: false, + }, + { + id: 'fifty-protests', + name: 'Senior Steward', + description: 'Handle 50 protests', + category: 'steward', + rarity: 'rare', + points: 100, + requirements: [{ type: 'protests_handled', value: 50, operator: '>=' }], + isSecret: false, + }, + { + id: 'hundred-protests', + name: 'Chief Steward', + description: 'Handle 100 protests', + category: 'steward', + rarity: 'epic', + points: 200, + requirements: [{ type: 'protests_handled', value: 100, operator: '>=' }], + isSecret: false, + }, + { + id: 'event-steward-10', + name: 'Event Official', + description: 'Steward 10 race events', + category: 'steward', + rarity: 'uncommon', + points: 50, + requirements: [{ type: 'events_stewarded', value: 10, operator: '>=' }], + isSecret: false, + }, + { + id: 'trusted-steward', + name: 'Trusted Steward', + description: 'Achieve highly-trusted status', + category: 'steward', + rarity: 'rare', + points: 150, + requirements: [{ type: 'trust_threshold', value: 75, operator: '>=' }], + isSecret: false, + }, +]; + +// Predefined achievements for admins +export const ADMIN_ACHIEVEMENTS: Omit[] = [ + { + id: 'first-league', + name: 'League Founder', + description: 'Create your first league', + category: 'admin', + rarity: 'common', + points: 25, + requirements: [{ type: 'leagues_managed', value: 1, operator: '>=' }], + isSecret: false, + }, + { + id: 'first-season', + name: 'Season Organizer', + description: 'Complete your first full season', + category: 'admin', + rarity: 'uncommon', + points: 50, + requirements: [{ type: 'seasons_completed', value: 1, operator: '>=' }], + isSecret: false, + }, + { + id: 'five-seasons', + name: 'Experienced Organizer', + description: 'Complete 5 seasons', + category: 'admin', + rarity: 'rare', + points: 100, + requirements: [{ type: 'seasons_completed', value: 5, operator: '>=' }], + isSecret: false, + }, + { + id: 'ten-seasons', + name: 'Veteran Organizer', + description: 'Complete 10 seasons', + category: 'admin', + rarity: 'epic', + points: 200, + requirements: [{ type: 'seasons_completed', value: 10, operator: '>=' }], + isSecret: false, + }, + { + id: 'large-league', + name: 'Community Builder', + description: 'Manage a league with 50+ members', + category: 'admin', + rarity: 'rare', + points: 150, + requirements: [{ type: 'members_managed', value: 50, operator: '>=' }], + isSecret: false, + }, + { + id: 'huge-league', + name: 'Empire Builder', + description: 'Manage a league with 100+ members', + category: 'admin', + rarity: 'epic', + points: 300, + requirements: [{ type: 'members_managed', value: 100, operator: '>=' }], + isSecret: false, + }, +]; + +// Community achievements (for all roles) +export const COMMUNITY_ACHIEVEMENTS: Omit[] = [ + { + id: 'community-leader', + name: 'Community Leader', + description: 'Achieve community leader trust level', + category: 'community', + rarity: 'legendary', + points: 500, + requirements: [{ type: 'trust_threshold', value: 90, operator: '>=' }], + isSecret: false, + }, +]; diff --git a/core/identity/domain/entities/Achievement.ts b/core/identity/domain/entities/Achievement.ts index f35d9b3eb..bc65cec25 100644 --- a/core/identity/domain/entities/Achievement.ts +++ b/core/identity/domain/entities/Achievement.ts @@ -124,269 +124,3 @@ export class Achievement implements IEntity { return this.description; } } - -// Predefined achievements for drivers -export const DRIVER_ACHIEVEMENTS: Omit[] = [ - { - id: 'first-race', - name: 'First Steps', - description: 'Complete your first race', - category: 'driver', - rarity: 'common', - points: 10, - requirements: [{ type: 'races_completed', value: 1, operator: '>=' }], - isSecret: false, - }, - { - id: 'ten-races', - name: 'Getting Started', - description: 'Complete 10 races', - category: 'driver', - rarity: 'common', - points: 25, - requirements: [{ type: 'races_completed', value: 10, operator: '>=' }], - isSecret: false, - }, - { - id: 'fifty-races', - name: 'Regular Racer', - description: 'Complete 50 races', - category: 'driver', - rarity: 'uncommon', - points: 50, - requirements: [{ type: 'races_completed', value: 50, operator: '>=' }], - isSecret: false, - }, - { - id: 'hundred-races', - name: 'Veteran', - description: 'Complete 100 races', - category: 'driver', - rarity: 'rare', - points: 100, - requirements: [{ type: 'races_completed', value: 100, operator: '>=' }], - isSecret: false, - }, - { - id: 'first-win', - name: 'Victory Lane', - description: 'Win your first race', - category: 'driver', - rarity: 'uncommon', - points: 50, - requirements: [{ type: 'wins', value: 1, operator: '>=' }], - isSecret: false, - }, - { - id: 'ten-wins', - name: 'Serial Winner', - description: 'Win 10 races', - category: 'driver', - rarity: 'rare', - points: 100, - requirements: [{ type: 'wins', value: 10, operator: '>=' }], - isSecret: false, - }, - { - id: 'first-podium', - name: 'Podium Finisher', - description: 'Finish on the podium', - category: 'driver', - rarity: 'common', - points: 25, - requirements: [{ type: 'podiums', value: 1, operator: '>=' }], - isSecret: false, - }, - { - id: 'clean-streak-5', - name: 'Clean Racer', - description: 'Complete 5 consecutive races without incidents', - category: 'driver', - rarity: 'uncommon', - points: 50, - requirements: [{ type: 'consecutive_clean', value: 5, operator: '>=' }], - isSecret: false, - }, - { - id: 'clean-streak-10', - name: 'Safety First', - description: 'Complete 10 consecutive races without incidents', - category: 'driver', - rarity: 'rare', - points: 100, - requirements: [{ type: 'consecutive_clean', value: 10, operator: '>=' }], - isSecret: false, - }, - { - id: 'championship-win', - name: 'Champion', - description: 'Win a championship', - category: 'driver', - rarity: 'epic', - points: 200, - requirements: [{ type: 'championships_won', value: 1, operator: '>=' }], - isSecret: false, - }, - { - id: 'triple-crown', - name: 'Triple Crown', - description: 'Win 3 championships', - category: 'driver', - rarity: 'legendary', - points: 500, - requirements: [{ type: 'championships_won', value: 3, operator: '>=' }], - isSecret: false, - }, - { - id: 'elite-driver', - name: 'Elite Driver', - description: 'Reach Elite driver rating', - category: 'driver', - rarity: 'epic', - points: 250, - requirements: [{ type: 'rating_threshold', value: 90, operator: '>=' }], - isSecret: false, - }, -]; - -// Predefined achievements for stewards -export const STEWARD_ACHIEVEMENTS: Omit[] = [ - { - id: 'first-protest', - name: 'Justice Served', - description: 'Handle your first protest', - category: 'steward', - rarity: 'common', - points: 15, - requirements: [{ type: 'protests_handled', value: 1, operator: '>=' }], - isSecret: false, - }, - { - id: 'ten-protests', - name: 'Fair Judge', - description: 'Handle 10 protests', - category: 'steward', - rarity: 'uncommon', - points: 50, - requirements: [{ type: 'protests_handled', value: 10, operator: '>=' }], - isSecret: false, - }, - { - id: 'fifty-protests', - name: 'Senior Steward', - description: 'Handle 50 protests', - category: 'steward', - rarity: 'rare', - points: 100, - requirements: [{ type: 'protests_handled', value: 50, operator: '>=' }], - isSecret: false, - }, - { - id: 'hundred-protests', - name: 'Chief Steward', - description: 'Handle 100 protests', - category: 'steward', - rarity: 'epic', - points: 200, - requirements: [{ type: 'protests_handled', value: 100, operator: '>=' }], - isSecret: false, - }, - { - id: 'event-steward-10', - name: 'Event Official', - description: 'Steward 10 race events', - category: 'steward', - rarity: 'uncommon', - points: 50, - requirements: [{ type: 'events_stewarded', value: 10, operator: '>=' }], - isSecret: false, - }, - { - id: 'trusted-steward', - name: 'Trusted Steward', - description: 'Achieve highly-trusted status', - category: 'steward', - rarity: 'rare', - points: 150, - requirements: [{ type: 'trust_threshold', value: 75, operator: '>=' }], - isSecret: false, - }, -]; - -// Predefined achievements for admins -export const ADMIN_ACHIEVEMENTS: Omit[] = [ - { - id: 'first-league', - name: 'League Founder', - description: 'Create your first league', - category: 'admin', - rarity: 'common', - points: 25, - requirements: [{ type: 'leagues_managed', value: 1, operator: '>=' }], - isSecret: false, - }, - { - id: 'first-season', - name: 'Season Organizer', - description: 'Complete your first full season', - category: 'admin', - rarity: 'uncommon', - points: 50, - requirements: [{ type: 'seasons_completed', value: 1, operator: '>=' }], - isSecret: false, - }, - { - id: 'five-seasons', - name: 'Experienced Organizer', - description: 'Complete 5 seasons', - category: 'admin', - rarity: 'rare', - points: 100, - requirements: [{ type: 'seasons_completed', value: 5, operator: '>=' }], - isSecret: false, - }, - { - id: 'ten-seasons', - name: 'Veteran Organizer', - description: 'Complete 10 seasons', - category: 'admin', - rarity: 'epic', - points: 200, - requirements: [{ type: 'seasons_completed', value: 10, operator: '>=' }], - isSecret: false, - }, - { - id: 'large-league', - name: 'Community Builder', - description: 'Manage a league with 50+ members', - category: 'admin', - rarity: 'rare', - points: 150, - requirements: [{ type: 'members_managed', value: 50, operator: '>=' }], - isSecret: false, - }, - { - id: 'huge-league', - name: 'Empire Builder', - description: 'Manage a league with 100+ members', - category: 'admin', - rarity: 'epic', - points: 300, - requirements: [{ type: 'members_managed', value: 100, operator: '>=' }], - isSecret: false, - }, -]; - -// Community achievements (for all roles) -export const COMMUNITY_ACHIEVEMENTS: Omit[] = [ - { - id: 'community-leader', - name: 'Community Leader', - description: 'Achieve community leader trust level', - category: 'community', - rarity: 'legendary', - points: 500, - requirements: [{ type: 'trust_threshold', value: 90, operator: '>=' }], - isSecret: false, - }, -]; \ No newline at end of file diff --git a/core/identity/domain/repositories/IUserRepository.ts b/core/identity/domain/repositories/IUserRepository.ts index 6995a45ff..203a37e86 100644 --- a/core/identity/domain/repositories/IUserRepository.ts +++ b/core/identity/domain/repositories/IUserRepository.ts @@ -4,8 +4,6 @@ * Repository interface for User entity operations. */ -import type { AuthenticatedUserDTO } from '../../application/dto/AuthenticatedUserDTO'; - export interface UserCredentials { email: string; passwordHash: string; diff --git a/core/racing/application/mappers/EntityMappers.ts b/core/racing/application/mappers/EntityMappers.ts index bb2158b39..5df4b7092 100644 --- a/core/racing/application/mappers/EntityMappers.ts +++ b/core/racing/application/mappers/EntityMappers.ts @@ -89,6 +89,18 @@ export class EntityMappers { static toRaceDTO(race: Race | null): RaceDTO | null { if (!race) return null; + + const sessionTypeMap = { + practice: 'practice' as const, + qualifying: 'qualifying' as const, + q1: 'qualifying' as const, + q2: 'qualifying' as const, + q3: 'qualifying' as const, + sprint: 'race' as const, + main: 'race' as const, + timeTrial: 'practice' as const, + }; + return { id: race.id, leagueId: race.leagueId, @@ -97,7 +109,7 @@ export class EntityMappers { trackId: race.trackId ?? '', car: race.car, carId: race.carId ?? '', - sessionType: race.sessionType, + sessionType: sessionTypeMap[race.sessionType.value], status: race.status, ...(race.strengthOfField !== undefined ? { strengthOfField: race.strengthOfField } @@ -112,6 +124,17 @@ export class EntityMappers { } static toRaceDTOs(races: Race[]): RaceDTO[] { + const sessionTypeMap = { + practice: 'practice' as const, + qualifying: 'qualifying' as const, + q1: 'qualifying' as const, + q2: 'qualifying' as const, + q3: 'qualifying' as const, + sprint: 'race' as const, + main: 'race' as const, + timeTrial: 'practice' as const, + }; + return races.map((race) => ({ id: race.id, leagueId: race.leagueId, @@ -120,7 +143,7 @@ export class EntityMappers { trackId: race.trackId ?? '', car: race.car, carId: race.carId ?? '', - sessionType: race.sessionType, + sessionType: sessionTypeMap[race.sessionType.value], status: race.status, ...(race.strengthOfField !== undefined ? { strengthOfField: race.strengthOfField } diff --git a/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.test.ts b/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.test.ts new file mode 100644 index 000000000..8e0675f78 --- /dev/null +++ b/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.test.ts @@ -0,0 +1,174 @@ +import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; +import { AcceptSponsorshipRequestUseCase } from './AcceptSponsorshipRequestUseCase'; +import type { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository'; +import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository'; +import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; +import type { INotificationService } from '@core/notifications/application/ports/INotificationService'; +import type { IPaymentGateway } from '../ports/IPaymentGateway'; +import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository'; +import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository'; +import type { Logger } from '@core/shared/application'; +import { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest'; +import { Season } from '../../domain/entities/Season'; +import { LeagueWallet } from '../../domain/entities/LeagueWallet'; +import { Money } from '../../domain/value-objects/Money'; + +describe('AcceptSponsorshipRequestUseCase', () => { + let mockSponsorshipRequestRepo: { + findById: Mock; + update: Mock; + }; + let mockSeasonSponsorshipRepo: { + create: Mock; + }; + let mockSeasonRepo: { + findById: Mock; + }; + let mockNotificationService: { + sendNotification: Mock; + }; + let mockPaymentGateway: { + processPayment: Mock; + }; + let mockWalletRepo: { + findById: Mock; + update: Mock; + }; + let mockLeagueWalletRepo: { + findById: Mock; + update: Mock; + }; + let mockLogger: { + debug: Mock; + info: Mock; + warn: Mock; + error: Mock; + }; + + beforeEach(() => { + mockSponsorshipRequestRepo = { + findById: vi.fn(), + update: vi.fn(), + }; + mockSeasonSponsorshipRepo = { + create: vi.fn(), + }; + mockSeasonRepo = { + findById: vi.fn(), + }; + mockNotificationService = { + sendNotification: vi.fn(), + }; + mockPaymentGateway = { + processPayment: vi.fn(), + }; + mockWalletRepo = { + findById: vi.fn(), + update: vi.fn(), + }; + mockLeagueWalletRepo = { + findById: vi.fn(), + update: vi.fn(), + }; + mockLogger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; + }); + + it('should send notification to sponsor, process payment, and update wallets when accepting season sponsorship', async () => { + const useCase = new AcceptSponsorshipRequestUseCase( + mockSponsorshipRequestRepo as unknown as ISponsorshipRequestRepository, + mockSeasonSponsorshipRepo as unknown as ISeasonSponsorshipRepository, + mockSeasonRepo as unknown as ISeasonRepository, + mockNotificationService as unknown as INotificationService, + mockPaymentGateway as unknown as IPaymentGateway, + mockWalletRepo as unknown as IWalletRepository, + mockLeagueWalletRepo as unknown as ILeagueWalletRepository, + mockLogger as unknown as Logger, + ); + + const request = SponsorshipRequest.create({ + id: 'req1', + sponsorId: 'sponsor1', + entityId: 'season1', + entityType: 'season', + tier: 'main', + offeredAmount: Money.create(1000), + status: 'pending', + }); + + const season = Season.create({ + id: 'season1', + leagueId: 'league1', + gameId: 'game1', + name: 'Season 1', + startDate: new Date(), + endDate: new Date(), + }); + + mockSponsorshipRequestRepo.findById.mockResolvedValue(request); + mockSeasonRepo.findById.mockResolvedValue(season); + mockNotificationService.sendNotification.mockResolvedValue(undefined); + mockPaymentGateway.processPayment.mockResolvedValue({ + success: true, + transactionId: 'txn1', + timestamp: new Date(), + }); + mockWalletRepo.findById.mockResolvedValue({ + id: 'sponsor1', + leagueId: 'league1', + balance: 2000, + totalRevenue: 0, + totalPlatformFees: 0, + totalWithdrawn: 0, + currency: 'USD', + createdAt: new Date(), + }); + const leagueWallet = LeagueWallet.create({ + id: 'league1', + leagueId: 'league1', + balance: Money.create(500), + }); + mockLeagueWalletRepo.findById.mockResolvedValue(leagueWallet); + + const result = await useCase.execute({ + requestId: 'req1', + respondedBy: 'driver1', + }); + + expect(result).toBeDefined(); + expect(mockNotificationService.sendNotification).toHaveBeenCalledWith({ + recipientId: 'sponsor1', + type: 'sponsorship_request_accepted', + title: 'Sponsorship Accepted', + body: 'Your sponsorship request for Season 1 has been accepted.', + channel: 'in_app', + urgency: 'toast', + data: { + requestId: 'req1', + sponsorshipId: expect.any(String), + }, + }); + expect(mockPaymentGateway.processPayment).toHaveBeenCalledWith( + Money.create(1000), + 'sponsor1', + 'Sponsorship payment for season season1', + { requestId: 'req1' } + ); + expect(mockWalletRepo.update).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'sponsor1', + balance: 1000, + }) + ); + expect(mockLeagueWalletRepo.update).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'league1', + balance: expect.objectContaining({ amount: 1400 }), + }) + ); + }); +}); \ No newline at end of file diff --git a/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts b/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts index e02f62ef3..6253a0901 100644 --- a/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts +++ b/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts @@ -9,6 +9,10 @@ import type { Logger } from '@core/shared/application'; import type { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository'; import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository'; import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; +import type { INotificationService } from '@core/notifications/application/ports/INotificationService'; +import type { IPaymentGateway } from '../ports/IPaymentGateway'; +import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository'; +import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository'; import { SeasonSponsorship } from '../../domain/entities/SeasonSponsorship'; import type { AsyncUseCase } from '@core/shared/application'; @@ -32,6 +36,10 @@ export class AcceptSponsorshipRequestUseCase private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository, private readonly seasonSponsorshipRepo: ISeasonSponsorshipRepository, private readonly seasonRepository: ISeasonRepository, + private readonly notificationService: INotificationService, + private readonly paymentGateway: IPaymentGateway, + private readonly walletRepository: IWalletRepository, + private readonly leagueWalletRepository: ILeagueWalletRepository, private readonly logger: Logger, ) {} @@ -79,12 +87,59 @@ export class AcceptSponsorshipRequestUseCase }); await this.seasonSponsorshipRepo.create(sponsorship); this.logger.info(`Season sponsorship ${sponsorshipId} created for request ${dto.requestId}.`, { sponsorshipId, requestId: dto.requestId }); - } - // TODO: In a real implementation, we would: - // 1. Create notification for the sponsor - // 2. Process payment - // 3. Update wallet balances + // Notify the sponsor + await this.notificationService.sendNotification({ + recipientId: request.sponsorId, + type: 'sponsorship_request_accepted', + title: 'Sponsorship Accepted', + body: `Your sponsorship request for ${season.name} has been accepted.`, + channel: 'in_app', + urgency: 'toast', + data: { + requestId: request.id, + sponsorshipId, + }, + }); + + // Process payment + const paymentResult = await this.paymentGateway.processPayment( + request.offeredAmount, + request.sponsorId, + `Sponsorship payment for ${request.entityType} ${request.entityId}`, + { requestId: request.id } + ); + if (!paymentResult.success) { + this.logger.error(`Payment failed for sponsorship request ${request.id}: ${paymentResult.error}`, undefined, { requestId: request.id }); + throw new Error('Payment processing failed'); + } + + // Update wallets + const sponsorWallet = await this.walletRepository.findById(request.sponsorId); + if (!sponsorWallet) { + this.logger.error(`Sponsor wallet not found for ${request.sponsorId}`, undefined, { sponsorId: request.sponsorId }); + throw new Error('Sponsor wallet not found'); + } + + const leagueWallet = await this.leagueWalletRepository.findById(season.leagueId); + if (!leagueWallet) { + this.logger.error(`League wallet not found for ${season.leagueId}`, undefined, { leagueId: season.leagueId }); + throw new Error('League wallet not found'); + } + + const netAmount = acceptedRequest.getNetAmount(); + + // Deduct from sponsor wallet + const updatedSponsorWallet = { + ...sponsorWallet, + balance: sponsorWallet.balance - request.offeredAmount.amount, + }; + await this.walletRepository.update(updatedSponsorWallet); + + // Add to league wallet + const updatedLeagueWallet = leagueWallet.addFunds(netAmount, paymentResult.transactionId!); + await this.leagueWalletRepository.update(updatedLeagueWallet); + } this.logger.info(`Sponsorship request ${acceptedRequest.id} successfully accepted.`, { requestId: acceptedRequest.id, sponsorshipId }); @@ -97,8 +152,9 @@ export class AcceptSponsorshipRequestUseCase netAmount: acceptedRequest.getNetAmount().amount, }; } catch (error) { - this.logger.error(`Failed to accept sponsorship request ${dto.requestId}: ${error.message}`, { requestId: dto.requestId, error: error.message, stack: error.stack }); - throw error; + const err = error instanceof Error ? error : new Error(String(error)); + this.logger.error(`Failed to accept sponsorship request ${dto.requestId}: ${err.message}`, err, { requestId: dto.requestId }); + throw err; } } } diff --git a/core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts b/core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts index d1515200e..135c832b8 100644 --- a/core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts +++ b/core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts @@ -50,7 +50,7 @@ export class ApplyForSponsorshipUseCase // Validate sponsor exists const sponsor = await this.sponsorRepo.findById(dto.sponsorId); if (!sponsor) { - this.logger.error('Sponsor not found', { sponsorId: dto.sponsorId }); + this.logger.error('Sponsor not found', undefined, { sponsorId: dto.sponsorId }); throw new EntityNotFoundError({ entity: 'sponsor', id: dto.sponsorId }); } diff --git a/core/racing/application/use-cases/ApplyPenaltyUseCase.ts b/core/racing/application/use-cases/ApplyPenaltyUseCase.ts index d1182b3b6..82dd12fa0 100644 --- a/core/racing/application/use-cases/ApplyPenaltyUseCase.ts +++ b/core/racing/application/use-cases/ApplyPenaltyUseCase.ts @@ -97,7 +97,7 @@ export class ApplyPenaltyUseCase return { penaltyId: penalty.id }; } catch (error) { - this.logger.error('ApplyPenaltyUseCase: Failed to apply penalty', { command, error: error.message }); + this.logger.error('ApplyPenaltyUseCase: Failed to apply penalty', error, { command }); throw error; } } diff --git a/core/racing/application/ApproveLeagueJoinRequestUseCase.test.ts b/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.test.ts similarity index 100% rename from core/racing/application/ApproveLeagueJoinRequestUseCase.test.ts rename to core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.test.ts diff --git a/core/racing/application/use-cases/ApproveTeamJoinRequestUseCase.ts b/core/racing/application/use-cases/ApproveTeamJoinRequestUseCase.ts index 713f005a0..31f2da143 100644 --- a/core/racing/application/use-cases/ApproveTeamJoinRequestUseCase.ts +++ b/core/racing/application/use-cases/ApproveTeamJoinRequestUseCase.ts @@ -54,7 +54,7 @@ export class ApproveTeamJoinRequestUseCase await this.membershipRepository.removeJoinRequest(requestId); this.logger.info(`Team join request with ID ${requestId} removed`); } catch (error) { - this.logger.error(`Failed to approve team join request ${requestId}:`, error); + this.logger.error(`Failed to approve team join request ${requestId}`, error instanceof Error ? error : new Error(String(error))); throw error; } } diff --git a/core/racing/application/use-cases/CancelRaceUseCase.ts b/core/racing/application/use-cases/CancelRaceUseCase.ts index 9689ed03a..3e26abca3 100644 --- a/core/racing/application/use-cases/CancelRaceUseCase.ts +++ b/core/racing/application/use-cases/CancelRaceUseCase.ts @@ -37,7 +37,7 @@ export class CancelRaceUseCase await this.raceRepository.update(cancelledRace); this.logger.info(`[CancelRaceUseCase] Race ${raceId} cancelled successfully.`); } catch (error) { - this.logger.error(`[CancelRaceUseCase] Error cancelling race ${raceId}:`, error); + this.logger.error(`[CancelRaceUseCase] Error cancelling race ${raceId}`, error instanceof Error ? error : new Error(String(error))); throw error; } } diff --git a/core/racing/application/use-cases/CloseRaceEventStewardingUseCase.ts b/core/racing/application/use-cases/CloseRaceEventStewardingUseCase.ts index 08a90c227..50994d41b 100644 --- a/core/racing/application/use-cases/CloseRaceEventStewardingUseCase.ts +++ b/core/racing/application/use-cases/CloseRaceEventStewardingUseCase.ts @@ -1,4 +1,5 @@ import type { UseCase } from '@core/shared/application/UseCase'; +import type { Logger } from '@core/shared/application'; import type { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository'; import type { IDomainEventPublisher } from '@core/shared/domain'; import type { RaceEventStewardingClosedEvent } from '../../domain/events/RaceEventStewardingClosed'; @@ -20,6 +21,8 @@ export class CloseRaceEventStewardingUseCase implements UseCase { constructor( + private readonly logger: Logger, + private readonly raceEventRepository: IRaceEventRepository, private readonly domainEventPublisher: IDomainEventPublisher, ) {} @@ -58,7 +61,7 @@ export class CloseRaceEventStewardingUseCase await this.domainEventPublisher.publish(event); } catch (error) { - console.error(`Failed to close stewarding for race event ${raceEvent.id}:`, error); + this.logger.error(`Failed to close stewarding for race event ${raceEvent.id}`, error instanceof Error ? error : new Error(String(error))); // In production, this would trigger alerts/monitoring } } diff --git a/core/racing/application/use-cases/CompleteRaceUseCaseWithRatings.ts b/core/racing/application/use-cases/CompleteRaceUseCaseWithRatings.ts index 11f7f9f30..4d1cc5d51 100644 --- a/core/racing/application/use-cases/CompleteRaceUseCaseWithRatings.ts +++ b/core/racing/application/use-cases/CompleteRaceUseCaseWithRatings.ts @@ -81,8 +81,8 @@ export class CompleteRaceUseCaseWithRatings const completedRace = race.complete(); await this.raceRepository.update(completedRace); this.logger.info(`Race ID: ${raceId} completed successfully.`); - } catch (error: any) { - this.logger.error(`Error completing race ${raceId}: ${error.message}`); + } catch (error) { + this.logger.error(`Error completing race ${raceId}`, error instanceof Error ? error : new Error(String(error))); throw error; } } diff --git a/core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase.ts b/core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase.ts index a96a911b6..32c9345a0 100644 --- a/core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase.ts +++ b/core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase.ts @@ -4,7 +4,6 @@ import { Season } from '../../domain/entities/Season'; import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository'; -import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig'; import type { AsyncUseCase } from '@core/shared/application'; import type { Logger } from '@core/shared/application'; import type { @@ -129,11 +128,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase this.logger.debug('CreateLeagueWithSeasonAndScoringUseCase completed successfully.', { result }); return result; } catch (error) { - this.logger.error('Error during CreateLeagueWithSeasonAndScoringUseCase execution.', { - command, - error: error.message, - stack: error.stack, - }); + this.logger.error('Error during CreateLeagueWithSeasonAndScoringUseCase execution.', error, { command }); throw error; } } diff --git a/core/racing/application/DashboardOverviewUseCase.test.ts b/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts similarity index 100% rename from core/racing/application/DashboardOverviewUseCase.test.ts rename to core/racing/application/use-cases/DashboardOverviewUseCase.test.ts diff --git a/core/racing/application/use-cases/GetAllTeamsUseCase.ts b/core/racing/application/use-cases/GetAllTeamsUseCase.ts index a9b999fba..298afa7aa 100644 --- a/core/racing/application/use-cases/GetAllTeamsUseCase.ts +++ b/core/racing/application/use-cases/GetAllTeamsUseCase.ts @@ -5,7 +5,6 @@ import type { AllTeamsResultDTO, } from '../presenters/IAllTeamsPresenter'; import type { UseCase } from '@core/shared/application'; -import type { Team } from '../../domain/entities/Team'; import { Logger } from "@core/shared/application"; /** @@ -54,7 +53,7 @@ export class GetAllTeamsUseCase presenter.present(dto); this.logger.info('Successfully retrieved all teams.'); } catch (error) { - this.logger.error('Error retrieving all teams:', error); + this.logger.error('Error retrieving all teams', error instanceof Error ? error : new Error(String(error))); throw error; // Re-throw the error after logging } } diff --git a/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts b/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts index 3c693c0aa..3a27f3b51 100644 --- a/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts @@ -1,6 +1,6 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; -import type { IGetLeagueAdminPermissionsPresenter, GetLeagueAdminPermissionsResultDTO, GetLeagueAdminPermissionsViewModel } from '../presenters/IGetLeagueAdminPermissionsPresenter'; +import type { IGetLeagueAdminPermissionsPresenter, GetLeagueAdminPermissionsViewModel } from '../presenters/IGetLeagueAdminPermissionsPresenter'; import type { UseCase } from '@core/shared/application/UseCase'; export interface GetLeagueAdminPermissionsUseCaseParams { diff --git a/core/racing/application/use-cases/GetLeagueAdminUseCase.ts b/core/racing/application/use-cases/GetLeagueAdminUseCase.ts index e1b76e136..0a2ebc0d3 100644 --- a/core/racing/application/use-cases/GetLeagueAdminUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueAdminUseCase.ts @@ -1,5 +1,6 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; -import type { IGetLeagueAdminPresenter, GetLeagueAdminResultDTO, GetLeagueAdminViewModel } from '../presenters/IGetLeagueAdminPresenter'; +import { GetLeagueAdminPermissionsViewModel } from '../presenters/IGetLeagueAdminPermissionsPresenter'; +import type { IGetLeagueAdminPresenter } from '../presenters/IGetLeagueAdminPresenter'; import type { UseCase } from '@core/shared/application/UseCase'; export interface GetLeagueAdminUseCaseParams { @@ -14,7 +15,7 @@ export interface GetLeagueAdminResultDTO { // Additional data would be populated by combining multiple use cases } -export class GetLeagueAdminUseCase implements UseCase { +export class GetLeagueAdminUseCase implements UseCase { constructor( private readonly leagueRepository: ILeagueRepository, ) {} diff --git a/core/racing/application/GetLeagueJoinRequestsUseCase.test.ts b/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.test.ts similarity index 100% rename from core/racing/application/GetLeagueJoinRequestsUseCase.test.ts rename to core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.test.ts diff --git a/core/racing/application/MembershipUseCases.test.ts b/core/racing/application/use-cases/MembershipUseCases.test.ts similarity index 100% rename from core/racing/application/MembershipUseCases.test.ts rename to core/racing/application/use-cases/MembershipUseCases.test.ts diff --git a/core/racing/application/RaceDetailUseCases.test.ts b/core/racing/application/use-cases/RaceDetailUseCases.test.ts similarity index 100% rename from core/racing/application/RaceDetailUseCases.test.ts rename to core/racing/application/use-cases/RaceDetailUseCases.test.ts diff --git a/core/racing/application/RaceResultsUseCases.test.ts b/core/racing/application/use-cases/RaceResultsUseCases.test.ts similarity index 100% rename from core/racing/application/RaceResultsUseCases.test.ts rename to core/racing/application/use-cases/RaceResultsUseCases.test.ts diff --git a/core/racing/application/RegistrationAndTeamUseCases.test.ts b/core/racing/application/use-cases/RegistrationAndTeamUseCases.test.ts similarity index 100% rename from core/racing/application/RegistrationAndTeamUseCases.test.ts rename to core/racing/application/use-cases/RegistrationAndTeamUseCases.test.ts diff --git a/core/racing/application/RejectLeagueJoinRequestUseCase.test.ts b/core/racing/application/use-cases/RejectLeagueJoinRequestUseCase.test.ts similarity index 100% rename from core/racing/application/RejectLeagueJoinRequestUseCase.test.ts rename to core/racing/application/use-cases/RejectLeagueJoinRequestUseCase.test.ts diff --git a/core/racing/application/RemoveLeagueMemberUseCase.test.ts b/core/racing/application/use-cases/RemoveLeagueMemberUseCase.test.ts similarity index 100% rename from core/racing/application/RemoveLeagueMemberUseCase.test.ts rename to core/racing/application/use-cases/RemoveLeagueMemberUseCase.test.ts diff --git a/core/racing/application/SeasonUseCases.test.ts b/core/racing/application/use-cases/SeasonUseCases.test.ts similarity index 100% rename from core/racing/application/SeasonUseCases.test.ts rename to core/racing/application/use-cases/SeasonUseCases.test.ts diff --git a/core/racing/application/UpdateLeagueMemberRoleUseCase.test.ts b/core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase.test.ts similarity index 100% rename from core/racing/application/UpdateLeagueMemberRoleUseCase.test.ts rename to core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase.test.ts diff --git a/package.json b/package.json index 176df7552..8e2d4e751 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "docker:prod:logs": "docker-compose -f docker-compose.prod.yml logs -f", "docker:prod:clean": "docker-compose -f docker-compose.prod.yml down -v", "api:build": "npm run build --workspace=@core/api", - "test": "vitest run && vitest run --config vitest.e2e.config.ts && npm run smoke:website", + "test": "vitest run \"$@\"", "test:unit": "vitest run tests/unit", "test:integration": "vitest run tests/integration", "test:e2e": "vitest run --config vitest.e2e.config.ts", diff --git a/tsconfig.json b/tsconfig.json index bbbee1f53..2f1614239 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,8 +11,9 @@ "include": [ "core/**/*", "apps/**/*", - "tests/**/*" -, "adapters/logging/ConsoleLogger.test.ts" ], + "tests/**/*", + "adapters/**/*" + ], "exclude": [ "node_modules", "dist",