From 03dc81b0ba0ac995fb313cf45f646ab7ac734892 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 22 Dec 2025 12:57:10 +0100 Subject: [PATCH] refactor league module (wip) --- .../InMemoryAnalyticsSnapshotRepository.ts | 3 +- adapters/automation/config/LoggingConfig.ts | 16 +- adapters/bootstrap/PointsSystems.ts | 35 +++ .../inmemory/InMemoryAchievementRepository.ts | 16 +- .../inmemory/InMemoryAuthRepository.ts | 3 +- .../InMemorySponsorAccountRepository.ts | 4 +- .../inmemory/InMemoryUserRatingRepository.ts | 3 +- .../domain/league/LeagueController.test.ts | 4 +- .../api/src/domain/league/LeagueController.ts | 5 +- apps/api/src/domain/league/LeagueProviders.ts | 225 ++++++++++++------ .../src/domain/league/LeagueService.test.ts | 57 ++--- apps/api/src/domain/league/LeagueService.ts | 128 ++++------ .../AllLeaguesWithCapacityPresenter.ts | 18 +- .../ApproveLeagueJoinRequestPresenter.ts | 8 +- .../presenters/CreateLeaguePresenter.ts | 6 +- .../GetLeagueAdminPermissionsPresenter.ts | 14 +- .../GetLeagueMembershipsPresenter.test.ts | 32 ++- .../GetLeagueMembershipsPresenter.ts | 31 ++- .../presenters/GetLeagueProtestsPresenter.ts | 6 +- .../GetSeasonSponsorshipsPresenter.ts | 26 +- .../league/presenters/JoinLeaguePresenter.ts | 9 +- .../league/presenters/LeagueAdminPresenter.ts | 15 +- .../presenters/LeagueConfigPresenter.test.ts | 10 +- .../presenters/LeagueConfigPresenter.ts | 10 +- .../LeagueJoinRequestsPresenter.test.ts | 15 +- .../presenters/LeagueJoinRequestsPresenter.ts | 24 +- .../LeagueOwnerSummaryPresenter.test.ts | 51 ++-- .../presenters/LeagueOwnerSummaryPresenter.ts | 27 +-- .../presenters/LeagueSchedulePresenter.ts | 31 +-- .../LeagueScoringConfigPresenter.ts | 28 +-- .../LeagueScoringPresetsPresenter.ts | 14 +- .../presenters/LeagueStandingsPresenter.ts | 15 +- .../league/presenters/LeagueStatsPresenter.ts | 14 +- .../RejectLeagueJoinRequestPresenter.ts | 11 +- .../presenters/RemoveLeagueMemberPresenter.ts | 9 +- .../presenters/TotalLeaguesPresenter.ts | 6 +- .../TransferLeagueOwnershipPresenter.ts | 9 +- .../UpdateLeagueMemberRolePresenter.ts | 9 +- .../ApproveLeagueJoinRequestUseCase.ts | 4 +- 39 files changed, 546 insertions(+), 405 deletions(-) create mode 100644 adapters/bootstrap/PointsSystems.ts diff --git a/adapters/analytics/persistence/inmemory/InMemoryAnalyticsSnapshotRepository.ts b/adapters/analytics/persistence/inmemory/InMemoryAnalyticsSnapshotRepository.ts index 0b1550047..aaf9f492e 100644 --- a/adapters/analytics/persistence/inmemory/InMemoryAnalyticsSnapshotRepository.ts +++ b/adapters/analytics/persistence/inmemory/InMemoryAnalyticsSnapshotRepository.ts @@ -4,8 +4,7 @@ * In-memory implementation of IAnalyticsSnapshotRepository for development/testing. */ -import type { IAnalyticsSnapshotRepository } from '../../domain/repositories/IAnalyticsSnapshotRepository'; -import { AnalyticsSnapshot, type SnapshotPeriod, type SnapshotEntityType } from '../../domain/entities/AnalyticsSnapshot'; +import { AnalyticsSnapshot, IAnalyticsSnapshotRepository, SnapshotEntityType, SnapshotPeriod } from '@core/analytics'; import { Logger } from '@core/shared/application'; export class InMemoryAnalyticsSnapshotRepository implements IAnalyticsSnapshotRepository { diff --git a/adapters/automation/config/LoggingConfig.ts b/adapters/automation/config/LoggingConfig.ts index 84d376805..8c371249d 100644 --- a/adapters/automation/config/LoggingConfig.ts +++ b/adapters/automation/config/LoggingConfig.ts @@ -1,9 +1,15 @@ -import type { LogLevel } from '@core/automation/application/ports/LoggerLogLevel'; +enum LogLevel { + DEBUG = 'debug', + INFO = 'info', + WARN = 'warn', + ERROR = 'error', + FATAL = 'fatal' +} // TODO move to core export type LogEnvironment = 'development' | 'production' | 'test'; export interface LoggingEnvironmentConfig { - level: LogLevel; + level: LogLevel; // TODO prettyPrint: boolean; fileOutput: boolean; filePath?: string; @@ -46,19 +52,19 @@ function getDefaultsForEnvironment(env: LogEnvironment): LoggingEnvironmentConfi switch (env) { case 'development': return { - level: 'debug', + level: LogLevel.DEBUG, prettyPrint: true, fileOutput: false, }; case 'production': return { - level: 'info', + level: LogLevel.ERROR, prettyPrint: false, fileOutput: true, }; case 'test': return { - level: 'warn', + level: LogLevel.WARN, prettyPrint: false, fileOutput: false, }; diff --git a/adapters/bootstrap/PointsSystems.ts b/adapters/bootstrap/PointsSystems.ts new file mode 100644 index 000000000..eb56c241c --- /dev/null +++ b/adapters/bootstrap/PointsSystems.ts @@ -0,0 +1,35 @@ +export const pointsSystems: Record> = { + 'f1-2024': { + 1: 25, + 2: 18, + 3: 15, + 4: 12, + 5: 10, + 6: 8, + 7: 6, + 8: 4, + 9: 2, + 10: 1, + }, + indycar: { + 1: 50, + 2: 40, + 3: 35, + 4: 32, + 5: 30, + 6: 28, + 7: 26, + 8: 24, + 9: 22, + 10: 20, + 11: 19, + 12: 18, + 13: 17, + 14: 16, + 15: 15, + }, +}; + +export function getPointsSystems(): Record> { + return { ...pointsSystems }; +} \ No newline at end of file diff --git a/adapters/identity/persistence/inmemory/InMemoryAchievementRepository.ts b/adapters/identity/persistence/inmemory/InMemoryAchievementRepository.ts index b65e67091..86bb3a384 100644 --- a/adapters/identity/persistence/inmemory/InMemoryAchievementRepository.ts +++ b/adapters/identity/persistence/inmemory/InMemoryAchievementRepository.ts @@ -4,17 +4,11 @@ * In-memory implementation of IAchievementRepository */ -import { Logger } from '@core/shared/application'; -import { - Achievement, - AchievementCategory, - DRIVER_ACHIEVEMENTS, - STEWARD_ACHIEVEMENTS, - ADMIN_ACHIEVEMENTS, - COMMUNITY_ACHIEVEMENTS, -} from '../../domain/entities/Achievement'; -import { UserAchievement } from '../../domain/entities/UserAchievement'; -import type { IAchievementRepository } from '../../domain/repositories/IAchievementRepository'; +import { Achievement, AchievementCategory, IAchievementRepository, UserAchievement } from "@core/identity"; +import { ADMIN_ACHIEVEMENTS, COMMUNITY_ACHIEVEMENTS, DRIVER_ACHIEVEMENTS, STEWARD_ACHIEVEMENTS } from "@core/identity/domain/AchievementConstants"; +import { Logger } from "@core/shared/application"; + + export class InMemoryAchievementRepository implements IAchievementRepository { private achievements: Map = new Map(); diff --git a/adapters/identity/persistence/inmemory/InMemoryAuthRepository.ts b/adapters/identity/persistence/inmemory/InMemoryAuthRepository.ts index 5de38c348..fd791e5df 100644 --- a/adapters/identity/persistence/inmemory/InMemoryAuthRepository.ts +++ b/adapters/identity/persistence/inmemory/InMemoryAuthRepository.ts @@ -1,10 +1,9 @@ +import { User } from '@core/identity/domain/entities/User'; import { IAuthRepository } from '@core/identity/domain/repositories/IAuthRepository'; import { IUserRepository, StoredUser } from '@core/identity/domain/repositories/IUserRepository'; import { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService'; -import { User } from '@core/identity/domain/entities/User'; import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress'; -import { randomUUID } from 'crypto'; import { Logger } from '@core/shared/application'; export class InMemoryAuthRepository implements IAuthRepository { diff --git a/adapters/identity/persistence/inmemory/InMemorySponsorAccountRepository.ts b/adapters/identity/persistence/inmemory/InMemorySponsorAccountRepository.ts index 7a2e6b701..d4ab01444 100644 --- a/adapters/identity/persistence/inmemory/InMemorySponsorAccountRepository.ts +++ b/adapters/identity/persistence/inmemory/InMemorySponsorAccountRepository.ts @@ -4,9 +4,7 @@ * In-memory implementation of ISponsorAccountRepository for development/testing. */ -import type { ISponsorAccountRepository } from '../../domain/repositories/ISponsorAccountRepository'; -import type { SponsorAccount } from '../../domain/entities/SponsorAccount'; -import type { UserId } from '../../domain/value-objects/UserId'; +import { ISponsorAccountRepository, SponsorAccount, UserId } from '@core/identity'; import { Logger } from '@core/shared/application'; export class InMemorySponsorAccountRepository implements ISponsorAccountRepository { diff --git a/adapters/identity/persistence/inmemory/InMemoryUserRatingRepository.ts b/adapters/identity/persistence/inmemory/InMemoryUserRatingRepository.ts index 6750863b2..966782cd0 100644 --- a/adapters/identity/persistence/inmemory/InMemoryUserRatingRepository.ts +++ b/adapters/identity/persistence/inmemory/InMemoryUserRatingRepository.ts @@ -4,8 +4,7 @@ * In-memory implementation of IUserRatingRepository */ -import { UserRating } from '../../domain/value-objects/UserRating'; -import type { IUserRatingRepository } from '../../domain/repositories/IUserRatingRepository'; +import { IUserRatingRepository, UserRating } from '@core/identity'; import { Logger } from '@core/shared/application'; export class InMemoryUserRatingRepository implements IUserRatingRepository { diff --git a/apps/api/src/domain/league/LeagueController.test.ts b/apps/api/src/domain/league/LeagueController.test.ts index 7af9fb510..584950fc8 100644 --- a/apps/api/src/domain/league/LeagueController.test.ts +++ b/apps/api/src/domain/league/LeagueController.test.ts @@ -5,7 +5,6 @@ import { LeagueProviders } from './LeagueProviders'; describe('LeagueController (integration)', () => { let controller: LeagueController; - let service: LeagueService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -14,7 +13,6 @@ describe('LeagueController (integration)', () => { }).compile(); controller = module.get(LeagueController); - service = module.get(LeagueService); }); it('should get total leagues', async () => { @@ -37,7 +35,7 @@ describe('LeagueController (integration)', () => { expect(Array.isArray(result.standings)).toBe(true); } catch (error) { // Expected for non-existent league - expect(error.message).toContain('not found'); + expect(error).toBeInstanceOf(Error); } }); }); \ No newline at end of file diff --git a/apps/api/src/domain/league/LeagueController.ts b/apps/api/src/domain/league/LeagueController.ts index f47ccf329..ebc3993ae 100644 --- a/apps/api/src/domain/league/LeagueController.ts +++ b/apps/api/src/domain/league/LeagueController.ts @@ -30,6 +30,7 @@ import { GetLeagueAdminConfigQueryDTO } from './dtos/GetLeagueAdminConfigQueryDT import { GetLeagueProtestsQueryDTO } from './dtos/GetLeagueProtestsQueryDTO'; import { GetLeagueSeasonsQueryDTO } from './dtos/GetLeagueSeasonsQueryDTO'; import { GetLeagueWalletOutputDTO } from './dtos/GetLeagueWalletOutputDTO'; +import { TotalLeaguesDTO } from './dtos/TotalLeaguesDTO'; import { WithdrawFromLeagueWalletInputDTO } from './dtos/WithdrawFromLeagueWalletInputDTO'; import { WithdrawFromLeagueWalletOutputDTO } from './dtos/WithdrawFromLeagueWalletOutputDTO'; @@ -47,8 +48,8 @@ export class LeagueController { @Get('total-leagues') @ApiOperation({ summary: 'Get the total number of leagues' }) - @ApiResponse({ status: 200, description: 'Total number of leagues', type: LeagueStatsDTO }) - async getTotalLeagues(): Promise { + @ApiResponse({ status: 200, description: 'Total number of leagues', type: TotalLeaguesDTO }) + async getTotalLeagues(): Promise { return this.leagueService.getTotalLeagues(); } diff --git a/apps/api/src/domain/league/LeagueProviders.ts b/apps/api/src/domain/league/LeagueProviders.ts index c33cdc36c..b249b7bef 100644 --- a/apps/api/src/domain/league/LeagueProviders.ts +++ b/apps/api/src/domain/league/LeagueProviders.ts @@ -2,61 +2,68 @@ import { Provider } from '@nestjs/common'; import { LeagueService } from './LeagueService'; // Import core interfaces -import type { Logger } from '@core/shared/application/Logger'; -import type { ISeasonSponsorshipRepository } from '@core/racing/domain/repositories/ISeasonSponsorshipRepository'; -import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeasonRepository'; -import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository'; -import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository'; -import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository'; -import type { IProtestRepository } from '@core/racing/domain/repositories/IProtestRepository'; import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository'; +import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository'; +import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository'; +import type { IProtestRepository } from '@core/racing/domain/repositories/IProtestRepository'; +import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository'; +import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeasonRepository'; +import type { ISeasonSponsorshipRepository } from '@core/racing/domain/repositories/ISeasonSponsorshipRepository'; +import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository'; +import type { Logger } from '@core/shared/application/Logger'; // Import concrete in-memory implementations -import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository'; -import { InMemoryLeagueMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository'; -import { InMemoryLeagueStandingsRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueStandingsRepository'; -import { InMemorySeasonRepository } from '@adapters/racing/persistence/inmemory/InMemorySeasonRepository'; -import { InMemorySeasonSponsorshipRepository } from '@adapters/racing/persistence/inmemory/InMemorySeasonSponsorshipRepository'; -import { InMemoryLeagueScoringConfigRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueScoringConfigRepository'; +import { listLeagueScoringPresets } from '@adapters/bootstrap/LeagueScoringPresets'; +import { getPointsSystems } from '@adapters/bootstrap/PointsSystems'; +import { ConsoleLogger } from '@adapters/logging/ConsoleLogger'; +import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository'; import { InMemoryGameRepository } from '@adapters/racing/persistence/inmemory/InMemoryGameRepository'; +import { InMemoryLeagueMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository'; +import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository'; +import { InMemoryLeagueScoringConfigRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueScoringConfigRepository'; +import { InMemoryLeagueStandingsRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueStandingsRepository'; +import { InMemoryLeagueWalletRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueWalletRepository'; import { InMemoryProtestRepository } from '@adapters/racing/persistence/inmemory/InMemoryProtestRepository'; import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRepository'; -import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository'; +import { InMemorySeasonRepository } from '@adapters/racing/persistence/inmemory/InMemorySeasonRepository'; +import { InMemorySeasonSponsorshipRepository } from '@adapters/racing/persistence/inmemory/InMemorySeasonSponsorshipRepository'; import { InMemoryStandingRepository } from '@adapters/racing/persistence/inmemory/InMemoryStandingRepository'; -import { InMemoryLeagueWalletRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueWalletRepository'; import { InMemoryTransactionRepository } from '@adapters/racing/persistence/inmemory/InMemoryTransactionRepository'; -import { ConsoleLogger } from '@adapters/logging/ConsoleLogger'; -import { listLeagueScoringPresets } from '@adapters/bootstrap/LeagueScoringPresets'; // Import use cases -import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase'; -import { GetLeagueStandingsUseCase } from '@core/league/application/use-cases/GetLeagueStandingsUseCase'; -import { GetLeagueStandingsUseCaseImpl } from '@core/league/application/use-cases/GetLeagueStandingsUseCaseImpl'; -import { GetLeagueFullConfigUseCase } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase'; -import { GetSeasonSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSeasonSponsorshipsUseCase'; -import { CreateLeagueWithSeasonAndScoringUseCase } from '@core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase'; -import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase'; -import { GetTotalLeaguesUseCase } from '@core/racing/application/use-cases/GetTotalLeaguesUseCase'; -import { GetLeagueJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase'; import { ApproveLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase'; -import { RejectLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/RejectLeagueJoinRequestUseCase'; -import { RemoveLeagueMemberUseCase } from '@core/racing/application/use-cases/RemoveLeagueMemberUseCase'; -import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase'; +import { CreateLeagueWithSeasonAndScoringUseCase } from '@core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase'; +import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase'; +import { GetLeagueAdminPermissionsUseCase } from '@core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase'; +import { GetLeagueFullConfigUseCase } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase'; +import { GetLeagueJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase'; +import { GetLeagueMembershipsUseCase } from '@core/racing/application/use-cases/GetLeagueMembershipsUseCase'; import { GetLeagueOwnerSummaryUseCase } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase'; import { GetLeagueProtestsUseCase } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase'; -import { GetLeagueSeasonsUseCase } from '@core/racing/application/use-cases/GetLeagueSeasonsUseCase'; -import { GetLeagueMembershipsUseCase } from '@core/racing/application/use-cases/GetLeagueMembershipsUseCase'; import { GetLeagueScheduleUseCase } from '@core/racing/application/use-cases/GetLeagueScheduleUseCase'; +import { GetLeagueScoringConfigUseCase } from '@core/racing/application/use-cases/GetLeagueScoringConfigUseCase'; +import { GetLeagueSeasonsUseCase } from '@core/racing/application/use-cases/GetLeagueSeasonsUseCase'; +import { GetLeagueStandingsUseCase } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase'; import { GetLeagueStatsUseCase } from '@core/racing/application/use-cases/GetLeagueStatsUseCase'; -import { GetLeagueAdminPermissionsUseCase } from '@core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase'; import { GetLeagueWalletUseCase } from '@core/racing/application/use-cases/GetLeagueWalletUseCase'; +import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase'; +import { GetSeasonSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSeasonSponsorshipsUseCase'; +import { GetTotalLeaguesUseCase } from '@core/racing/application/use-cases/GetTotalLeaguesUseCase'; +import { JoinLeagueUseCase } from '@core/racing/application/use-cases/JoinLeagueUseCase'; +import { ListLeagueScoringPresetsUseCase } from '@core/racing/application/use-cases/ListLeagueScoringPresetsUseCase'; +import { RejectLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/RejectLeagueJoinRequestUseCase'; +import { RemoveLeagueMemberUseCase } from '@core/racing/application/use-cases/RemoveLeagueMemberUseCase'; +import { TransferLeagueOwnershipUseCase } from '@core/racing/application/use-cases/TransferLeagueOwnershipUseCase'; +import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase'; import { WithdrawFromLeagueWalletUseCase } from '@core/racing/application/use-cases/WithdrawFromLeagueWalletUseCase'; // Import presenters import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter'; import { GetLeagueProtestsPresenter } from './presenters/GetLeagueProtestsPresenter'; +import { GetSeasonSponsorshipsPresenter } from './presenters/GetSeasonSponsorshipsPresenter'; +import { LeagueScoringPresetsPresenter } from './presenters/LeagueScoringPresetsPresenter'; +import { LeagueStandingsPresenter } from './presenters/LeagueStandingsPresenter'; -// Define injection tokens export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository'; export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository'; export const LEAGUE_STANDINGS_REPOSITORY_TOKEN = 'ILeagueStandingsRepository'; @@ -71,6 +78,30 @@ export const LEAGUE_WALLET_REPOSITORY_TOKEN = 'ILeagueWalletRepository'; export const TRANSACTION_REPOSITORY_TOKEN = 'ITransactionRepository'; export const LOGGER_TOKEN = 'Logger'; // Already defined in AuthProviders, but good to have here too export const GET_LEAGUE_STANDINGS_USE_CASE = 'GetLeagueStandingsUseCase'; +export const GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE = 'GetAllLeaguesWithCapacityUseCase'; +export const GET_LEAGUE_STATS_USE_CASE = 'GetLeagueStatsUseCase'; +export const GET_LEAGUE_FULL_CONFIG_USE_CASE = 'GetLeagueFullConfigUseCase'; +export const GET_LEAGUE_SCORING_CONFIG_USE_CASE = 'GetLeagueScoringConfigUseCase'; +export const LIST_LEAGUE_SCORING_PRESETS_USE_CASE = 'ListLeagueScoringPresetsUseCase'; +export const JOIN_LEAGUE_USE_CASE = 'JoinLeagueUseCase'; +export const TRANSFER_LEAGUE_OWNERSHIP_USE_CASE = 'TransferLeagueOwnershipUseCase'; +export const CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE = 'CreateLeagueWithSeasonAndScoringUseCase'; +export const GET_RACE_PROTESTS_USE_CASE = 'GetRaceProtestsUseCase'; +export const GET_TOTAL_LEAGUES_USE_CASE = 'GetTotalLeaguesUseCase'; +export const GET_LEAGUE_JOIN_REQUESTS_USE_CASE = 'GetLeagueJoinRequestsUseCase'; +export const APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE = 'ApproveLeagueJoinRequestUseCase'; +export const REJECT_LEAGUE_JOIN_REQUEST_USE_CASE = 'RejectLeagueJoinRequestUseCase'; +export const REMOVE_LEAGUE_MEMBER_USE_CASE = 'RemoveLeagueMemberUseCase'; +export const UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE = 'UpdateLeagueMemberRoleUseCase'; +export const GET_LEAGUE_OWNER_SUMMARY_USE_CASE = 'GetLeagueOwnerSummaryUseCase'; +export const GET_LEAGUE_PROTESTS_USE_CASE = 'GetLeagueProtestsUseCase'; +export const GET_LEAGUE_SEASONS_USE_CASE = 'GetLeagueSeasonsUseCase'; +export const GET_LEAGUE_MEMBERSHIPS_USE_CASE = 'GetLeagueMembershipsUseCase'; +export const GET_LEAGUE_SCHEDULE_USE_CASE = 'GetLeagueScheduleUseCase'; +export const GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE = 'GetLeagueAdminPermissionsUseCase'; +export const GET_LEAGUE_WALLET_USE_CASE = 'GetLeagueWalletUseCase'; +export const WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE = 'WithdrawFromLeagueWalletUseCase'; +export const GET_SEASON_SPONSORSHIPS_USE_CASE = 'GetSeasonSponsorshipsUseCase'; export const LeagueProviders: Provider[] = [ LeagueService, // Provide the service itself @@ -91,7 +122,7 @@ export const LeagueProviders: Provider[] = [ }, { provide: STANDING_REPOSITORY_TOKEN, - useFactory: (logger: Logger) => new InMemoryStandingRepository(logger), // Factory for InMemoryStandingRepository + useFactory: (logger: Logger) => new InMemoryStandingRepository(logger, getPointsSystems()), // Factory for InMemoryStandingRepository inject: [LOGGER_TOKEN], }, { @@ -143,15 +174,6 @@ export const LeagueProviders: Provider[] = [ provide: LOGGER_TOKEN, useClass: ConsoleLogger, }, - // Presenters - { - provide: 'AllLeaguesWithCapacityPresenter', - useClass: AllLeaguesWithCapacityPresenter, - }, - { - provide: 'GetLeagueProtestsPresenter', - useClass: GetLeagueProtestsPresenter, - }, // Use cases { provide: GetAllLeaguesWithCapacityUseCase, @@ -161,21 +183,56 @@ export const LeagueProviders: Provider[] = [ }, { provide: GET_LEAGUE_STANDINGS_USE_CASE, - useClass: GetLeagueStandingsUseCaseImpl, + useFactory: (standingRepo: IStandingRepository, driverRepo: IDriverRepository, presenter: LeagueStandingsPresenter) => + new GetLeagueStandingsUseCase(standingRepo, driverRepo, presenter), + inject: [STANDING_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, 'LeagueStandingsPresenter'], }, - GetLeagueStatsUseCase, - GetLeagueFullConfigUseCase, - CreateLeagueWithSeasonAndScoringUseCase, - GetRaceProtestsUseCase, - GetTotalLeaguesUseCase, - GetLeagueJoinRequestsUseCase, - ApproveLeagueJoinRequestUseCase, - RejectLeagueJoinRequestUseCase, - RemoveLeagueMemberUseCase, - UpdateLeagueMemberRoleUseCase, - GetLeagueOwnerSummaryUseCase, { - provide: GetLeagueProtestsUseCase, + provide: GET_LEAGUE_STATS_USE_CASE, + useClass: GetLeagueStatsUseCase, + }, + { + provide: GET_LEAGUE_FULL_CONFIG_USE_CASE, + useClass: GetLeagueFullConfigUseCase, + }, + { + provide: CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE, + useClass: CreateLeagueWithSeasonAndScoringUseCase, + }, + { + provide: GET_RACE_PROTESTS_USE_CASE, + useClass: GetRaceProtestsUseCase, + }, + { + provide: GET_TOTAL_LEAGUES_USE_CASE, + useClass: GetTotalLeaguesUseCase, + }, + { + provide: GET_LEAGUE_JOIN_REQUESTS_USE_CASE, + useClass: GetLeagueJoinRequestsUseCase, + }, + { + provide: APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE, + useClass: ApproveLeagueJoinRequestUseCase, + }, + { + provide: REJECT_LEAGUE_JOIN_REQUEST_USE_CASE, + useClass: RejectLeagueJoinRequestUseCase, + }, + { + provide: REMOVE_LEAGUE_MEMBER_USE_CASE, + useClass: RemoveLeagueMemberUseCase, + }, + { + provide: UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE, + useClass: UpdateLeagueMemberRoleUseCase, + }, + { + provide: GET_LEAGUE_OWNER_SUMMARY_USE_CASE, + useClass: GetLeagueOwnerSummaryUseCase, + }, + { + provide: GET_LEAGUE_PROTESTS_USE_CASE, useFactory: ( raceRepo: IRaceRepository, protestRepo: IProtestRepository, @@ -185,32 +242,64 @@ export const LeagueProviders: Provider[] = [ ) => new GetLeagueProtestsUseCase(raceRepo, protestRepo, driverRepo, leagueRepo, presenter), inject: [RACE_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, 'GetLeagueProtestsPresenter'], }, - GetLeagueSeasonsUseCase, - GetLeagueMembershipsUseCase, - GetLeagueScheduleUseCase, - GetLeagueStatsUseCase, - GetLeagueAdminPermissionsUseCase, - GetLeagueWalletUseCase, - WithdrawFromLeagueWalletUseCase, { - provide: GetSeasonSponsorshipsUseCase, + provide: GET_LEAGUE_SEASONS_USE_CASE, + useClass: GetLeagueSeasonsUseCase, + }, + { + provide: GET_LEAGUE_MEMBERSHIPS_USE_CASE, + useClass: GetLeagueMembershipsUseCase, + }, + { + provide: GET_LEAGUE_SCHEDULE_USE_CASE, + useClass: GetLeagueScheduleUseCase, + }, + { + provide: GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE, + useClass: GetLeagueAdminPermissionsUseCase, + }, + { + provide: GET_LEAGUE_WALLET_USE_CASE, + useClass: GetLeagueWalletUseCase, + }, + { + provide: WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE, + useClass: WithdrawFromLeagueWalletUseCase, + }, + { + provide: GET_SEASON_SPONSORSHIPS_USE_CASE, useFactory: ( seasonSponsorshipRepo: ISeasonSponsorshipRepository, seasonRepo: ISeasonRepository, leagueRepo: ILeagueRepository, leagueMembershipRepo: ILeagueMembershipRepository, raceRepo: IRaceRepository, - ) => new GetSeasonSponsorshipsUseCase(seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo), + presenter: GetSeasonSponsorshipsPresenter, + ) => new GetSeasonSponsorshipsUseCase(seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo, presenter), inject: [ 'ISeasonSponsorshipRepository', SEASON_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, + 'GetSeasonSponsorshipsPresenter', ], }, - { - provide: ListLeagueScoringPresetsUseCase, - useFactory: () => new ListLeagueScoringPresetsUseCase(listLeagueScoringPresets()), + { // TODO wtf is this here? doesn't look like it adhers to our concepts + provide: LIST_LEAGUE_SCORING_PRESETS_USE_CASE, + useFactory: (presenter: LeagueScoringPresetsPresenter) => new ListLeagueScoringPresetsUseCase(listLeagueScoringPresets(), presenter), + inject: ['LeagueScoringPresetsPresenter'], }, + { + provide: JOIN_LEAGUE_USE_CASE, + useClass: JoinLeagueUseCase, + }, + { + provide: TRANSFER_LEAGUE_OWNERSHIP_USE_CASE, + useClass: TransferLeagueOwnershipUseCase, + }, + { + provide: GET_LEAGUE_SCORING_CONFIG_USE_CASE, + useClass: GetLeagueScoringConfigUseCase, + } ]; diff --git a/apps/api/src/domain/league/LeagueService.test.ts b/apps/api/src/domain/league/LeagueService.test.ts index eb08ff934..18fb1574c 100644 --- a/apps/api/src/domain/league/LeagueService.test.ts +++ b/apps/api/src/domain/league/LeagueService.test.ts @@ -1,6 +1,7 @@ +import { vi, Mocked } from 'vitest'; import { LeagueService } from './LeagueService'; import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase'; -import { GetLeagueStandingsUseCase } from '@core/league/application/use-cases/GetLeagueStandingsUseCase'; +import { GetLeagueStandingsUseCase } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase'; import { GetLeagueStatsUseCase } from '@core/racing/application/use-cases/GetLeagueStatsUseCase'; import { GetLeagueFullConfigUseCase } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase'; import { GetLeagueScoringConfigUseCase } from '@core/racing/application/use-cases/GetLeagueScoringConfigUseCase'; @@ -29,20 +30,19 @@ import { Result } from '@core/shared/application/Result'; describe('LeagueService', () => { let service: LeagueService; - let mockGetTotalLeaguesUseCase: jest.Mocked; - let mockGetLeagueJoinRequestsUseCase: jest.Mocked; - let mockApproveLeagueJoinRequestUseCase: jest.Mocked; - let mockGetLeagueFullConfigUseCase: jest.Mocked; - let mockGetLeagueOwnerSummaryUseCase: jest.Mocked; - let mockGetLeagueScheduleUseCase: jest.Mocked; - let mockGetSeasonSponsorshipsUseCase: jest.Mocked; - let mockLogger: jest.Mocked; + let mockGetTotalLeaguesUseCase: Mocked; + let mockGetLeagueJoinRequestsUseCase: Mocked; + let mockApproveLeagueJoinRequestUseCase: Mocked; + let mockGetLeagueFullConfigUseCase: Mocked; + let mockGetLeagueOwnerSummaryUseCase: Mocked; + let mockGetLeagueScheduleUseCase: Mocked; + let mockGetSeasonSponsorshipsUseCase: Mocked; + let mockLogger: Mocked; beforeEach(() => { - const createUseCaseMock = (): jest.Mocked => ({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - execute: jest.fn() as any, - }) as jest.Mocked; + const createUseCaseMock = (): Mocked => ({ + execute: vi.fn(), + }) as Mocked; mockGetTotalLeaguesUseCase = createUseCaseMock(); mockGetLeagueJoinRequestsUseCase = createUseCaseMock(); @@ -52,11 +52,11 @@ describe('LeagueService', () => { mockGetLeagueScheduleUseCase = createUseCaseMock(); mockGetSeasonSponsorshipsUseCase = createUseCaseMock(); mockLogger = { - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - } as unknown as jest.Mocked; + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + } as unknown as Mocked; service = new LeagueService( {} as unknown as GetAllLeaguesWithCapacityUseCase, @@ -130,10 +130,10 @@ describe('LeagueService', () => { }); it('should reject league join request', async () => { - const mockRejectUseCase: jest.Mocked = { + const mockRejectUseCase: Mocked = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - execute: jest.fn() as any, - } as unknown as jest.Mocked; + execute: vi.fn() as any, + } as unknown as Mocked; service = new LeagueService( {} as unknown as GetAllLeaguesWithCapacityUseCase, @@ -173,10 +173,10 @@ describe('LeagueService', () => { }); it('should remove league member', async () => { - const mockRemoveUseCase: jest.Mocked = { + const mockRemoveUseCase: Mocked = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - execute: jest.fn() as any, - } as unknown as jest.Mocked; + execute: vi.fn() as any, + } as unknown as Mocked; service = new LeagueService( {} as unknown as GetAllLeaguesWithCapacityUseCase, @@ -227,15 +227,18 @@ describe('LeagueService', () => { } as any; mockGetLeagueFullConfigUseCase.execute.mockResolvedValue(Result.ok(fullConfig)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockGetLeagueOwnerSummaryUseCase.execute.mockResolvedValue(Result.ok({ summary: null } as any)); - const joinRequestsSpy = jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const joinRequestsSpy = vi .spyOn(service, 'getLeagueJoinRequests') .mockResolvedValue({ joinRequests: [] } as any); - const protestsSpy = jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const protestsSpy = vi .spyOn(service, 'getLeagueProtests') .mockResolvedValue({ protests: [], racesById: {}, driversById: {} } as any); - const seasonsSpy = jest + const seasonsSpy = vi .spyOn(service, 'getLeagueSeasons') .mockResolvedValue([]); diff --git a/apps/api/src/domain/league/LeagueService.ts b/apps/api/src/domain/league/LeagueService.ts index 6feb67787..6e2687c57 100644 --- a/apps/api/src/domain/league/LeagueService.ts +++ b/apps/api/src/domain/league/LeagueService.ts @@ -70,6 +70,8 @@ import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-case import { WithdrawFromLeagueWalletUseCase } from '@core/racing/application/use-cases/WithdrawFromLeagueWalletUseCase'; // API Presenters +import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter'; +import { ApproveLeagueJoinRequestPresenter } from './presenters/ApproveLeagueJoinRequestPresenter'; import { CreateLeaguePresenter } from './presenters/CreateLeaguePresenter'; import { GetLeagueAdminPermissionsPresenter } from './presenters/GetLeagueAdminPermissionsPresenter'; import { GetLeagueMembershipsPresenter } from './presenters/GetLeagueMembershipsPresenter'; @@ -81,7 +83,7 @@ import { JoinLeaguePresenter } from './presenters/JoinLeaguePresenter'; import { LeagueAdminPresenter } from './presenters/LeagueAdminPresenter'; import { LeagueConfigPresenter } from './presenters/LeagueConfigPresenter'; import { LeagueJoinRequestsPresenter } from './presenters/LeagueJoinRequestsPresenter'; -import { LeagueRacesPresenter, LeagueSchedulePresenter } from './presenters/LeagueSchedulePresenter'; +import { LeagueSchedulePresenter } from './presenters/LeagueSchedulePresenter'; import { LeagueScoringConfigPresenter } from './presenters/LeagueScoringConfigPresenter'; import { LeagueScoringPresetsPresenter } from './presenters/LeagueScoringPresetsPresenter'; import { LeagueStandingsPresenter } from './presenters/LeagueStandingsPresenter'; @@ -92,36 +94,36 @@ import { TotalLeaguesPresenter } from './presenters/TotalLeaguesPresenter'; import { TransferLeagueOwnershipPresenter } from './presenters/TransferLeagueOwnershipPresenter'; import { UpdateLeagueMemberRolePresenter } from './presenters/UpdateLeagueMemberRolePresenter'; // Tokens -import { LOGGER_TOKEN } from './LeagueProviders'; +import { LOGGER_TOKEN, GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE, GET_LEAGUE_STANDINGS_USE_CASE, GET_LEAGUE_STATS_USE_CASE, GET_LEAGUE_FULL_CONFIG_USE_CASE, GET_LEAGUE_SCORING_CONFIG_USE_CASE, LIST_LEAGUE_SCORING_PRESETS_USE_CASE, JOIN_LEAGUE_USE_CASE, TRANSFER_LEAGUE_OWNERSHIP_USE_CASE, CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE, GET_RACE_PROTESTS_USE_CASE, GET_TOTAL_LEAGUES_USE_CASE, GET_LEAGUE_JOIN_REQUESTS_USE_CASE, APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE, REJECT_LEAGUE_JOIN_REQUEST_USE_CASE, REMOVE_LEAGUE_MEMBER_USE_CASE, UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE, GET_LEAGUE_OWNER_SUMMARY_USE_CASE, GET_LEAGUE_PROTESTS_USE_CASE, GET_LEAGUE_SEASONS_USE_CASE, GET_LEAGUE_MEMBERSHIPS_USE_CASE, GET_LEAGUE_SCHEDULE_USE_CASE, GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE, GET_LEAGUE_WALLET_USE_CASE, WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE, GET_SEASON_SPONSORSHIPS_USE_CASE } from './LeagueProviders'; @Injectable() export class LeagueService { constructor( - private readonly getAllLeaguesWithCapacityUseCase: GetAllLeaguesWithCapacityUseCase, - private readonly getLeagueStandingsUseCase: GetLeagueStandingsUseCase, - private readonly getLeagueStatsUseCase: GetLeagueStatsUseCase, - private readonly getLeagueFullConfigUseCase: GetLeagueFullConfigUseCase, - private readonly getLeagueScoringConfigUseCase: GetLeagueScoringConfigUseCase, - private readonly listLeagueScoringPresetsUseCase: ListLeagueScoringPresetsUseCase, - private readonly joinLeagueUseCase: JoinLeagueUseCase, - private readonly transferLeagueOwnershipUseCase: TransferLeagueOwnershipUseCase, - private readonly createLeagueWithSeasonAndScoringUseCase: CreateLeagueWithSeasonAndScoringUseCase, - private readonly getRaceProtestsUseCase: GetRaceProtestsUseCase, - private readonly getTotalLeaguesUseCase: GetTotalLeaguesUseCase, - private readonly getLeagueJoinRequestsUseCase: GetLeagueJoinRequestsUseCase, - private readonly approveLeagueJoinRequestUseCase: ApproveLeagueJoinRequestUseCase, - private readonly rejectLeagueJoinRequestUseCase: RejectLeagueJoinRequestUseCase, - private readonly removeLeagueMemberUseCase: RemoveLeagueMemberUseCase, - private readonly updateLeagueMemberRoleUseCase: UpdateLeagueMemberRoleUseCase, - private readonly getLeagueOwnerSummaryUseCase: GetLeagueOwnerSummaryUseCase, - private readonly getLeagueProtestsUseCase: GetLeagueProtestsUseCase, - private readonly getLeagueSeasonsUseCase: GetLeagueSeasonsUseCase, - private readonly getLeagueMembershipsUseCase: GetLeagueMembershipsUseCase, - private readonly getLeagueScheduleUseCase: GetLeagueScheduleUseCase, - private readonly getLeagueAdminPermissionsUseCase: GetLeagueAdminPermissionsUseCase, - private readonly getLeagueWalletUseCase: GetLeagueWalletUseCase, - private readonly withdrawFromLeagueWalletUseCase: WithdrawFromLeagueWalletUseCase, - private readonly getSeasonSponsorshipsUseCase: GetSeasonSponsorshipsUseCase, + @Inject(GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE) private readonly getAllLeaguesWithCapacityUseCase: GetAllLeaguesWithCapacityUseCase, + @Inject(GET_LEAGUE_STANDINGS_USE_CASE) private readonly getLeagueStandingsUseCase: GetLeagueStandingsUseCase, + @Inject(GET_LEAGUE_STATS_USE_CASE) private readonly getLeagueStatsUseCase: GetLeagueStatsUseCase, + @Inject(GET_LEAGUE_FULL_CONFIG_USE_CASE) private readonly getLeagueFullConfigUseCase: GetLeagueFullConfigUseCase, + @Inject(GET_LEAGUE_SCORING_CONFIG_USE_CASE) private readonly getLeagueScoringConfigUseCase: GetLeagueScoringConfigUseCase, + @Inject(LIST_LEAGUE_SCORING_PRESETS_USE_CASE) private readonly listLeagueScoringPresetsUseCase: ListLeagueScoringPresetsUseCase, + @Inject(JOIN_LEAGUE_USE_CASE) private readonly joinLeagueUseCase: JoinLeagueUseCase, + @Inject(TRANSFER_LEAGUE_OWNERSHIP_USE_CASE) private readonly transferLeagueOwnershipUseCase: TransferLeagueOwnershipUseCase, + @Inject(CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE) private readonly createLeagueWithSeasonAndScoringUseCase: CreateLeagueWithSeasonAndScoringUseCase, + @Inject(GET_RACE_PROTESTS_USE_CASE) private readonly getRaceProtestsUseCase: GetRaceProtestsUseCase, + @Inject(GET_TOTAL_LEAGUES_USE_CASE) private readonly getTotalLeaguesUseCase: GetTotalLeaguesUseCase, + @Inject(GET_LEAGUE_JOIN_REQUESTS_USE_CASE) private readonly getLeagueJoinRequestsUseCase: GetLeagueJoinRequestsUseCase, + @Inject(APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE) private readonly approveLeagueJoinRequestUseCase: ApproveLeagueJoinRequestUseCase, + @Inject(REJECT_LEAGUE_JOIN_REQUEST_USE_CASE) private readonly rejectLeagueJoinRequestUseCase: RejectLeagueJoinRequestUseCase, + @Inject(REMOVE_LEAGUE_MEMBER_USE_CASE) private readonly removeLeagueMemberUseCase: RemoveLeagueMemberUseCase, + @Inject(UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE) private readonly updateLeagueMemberRoleUseCase: UpdateLeagueMemberRoleUseCase, + @Inject(GET_LEAGUE_OWNER_SUMMARY_USE_CASE) private readonly getLeagueOwnerSummaryUseCase: GetLeagueOwnerSummaryUseCase, + @Inject(GET_LEAGUE_PROTESTS_USE_CASE) private readonly getLeagueProtestsUseCase: GetLeagueProtestsUseCase, + @Inject(GET_LEAGUE_SEASONS_USE_CASE) private readonly getLeagueSeasonsUseCase: GetLeagueSeasonsUseCase, + @Inject(GET_LEAGUE_MEMBERSHIPS_USE_CASE) private readonly getLeagueMembershipsUseCase: GetLeagueMembershipsUseCase, + @Inject(GET_LEAGUE_SCHEDULE_USE_CASE) private readonly getLeagueScheduleUseCase: GetLeagueScheduleUseCase, + @Inject(GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE) private readonly getLeagueAdminPermissionsUseCase: GetLeagueAdminPermissionsUseCase, + @Inject(GET_LEAGUE_WALLET_USE_CASE) private readonly getLeagueWalletUseCase: GetLeagueWalletUseCase, + @Inject(WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE) private readonly withdrawFromLeagueWalletUseCase: WithdrawFromLeagueWalletUseCase, + @Inject(GET_SEASON_SPONSORSHIPS_USE_CASE) private readonly getSeasonSponsorshipsUseCase: GetSeasonSponsorshipsUseCase, @Inject(LOGGER_TOKEN) private readonly logger: Logger, ) {} @@ -132,7 +134,7 @@ export class LeagueService { if (result.isErr()) { throw new Error(result.unwrapErr().code); } - return this.getAllLeaguesWithCapacityUseCase.outputPort.present(result); // TODO wrong, must use presenter + return (this.getAllLeaguesWithCapacityUseCase.outputPort as AllLeaguesWithCapacityPresenter).getViewModel(); } async getTotalLeagues(): Promise { @@ -148,12 +150,8 @@ export class LeagueService { async getLeagueJoinRequests(leagueId: string): Promise { this.logger.debug(`[LeagueService] Fetching join requests for league: ${leagueId}.`); - const result = await this.getLeagueJoinRequestsUseCase.execute({ leagueId }); - if (result.isErr()) { - throw new Error(result.unwrapErr().code); - } const presenter = new LeagueJoinRequestsPresenter(); - presenter.present(result.unwrap()); + await this.getLeagueJoinRequestsUseCase.execute({ leagueId }, presenter); return presenter.getViewModel()!.joinRequests; } @@ -224,12 +222,8 @@ export class LeagueService { async getLeagueOwnerSummary(query: GetLeagueOwnerSummaryQueryDTO): Promise { this.logger.debug('Getting league owner summary:', query); - const result = await this.getLeagueOwnerSummaryUseCase.execute({ ownerId: query.ownerId }); - if (result.isErr()) { - throw new Error(result.unwrapErr().code); - } const presenter = new GetLeagueOwnerSummaryPresenter(); - presenter.present(result.unwrap()); + await this.getLeagueOwnerSummaryUseCase.execute({ leagueId: query.leagueId } as any, presenter); return presenter.getViewModel()!; } @@ -243,7 +237,7 @@ export class LeagueService { return null; } const presenter = new LeagueConfigPresenter(); - presenter.present(result.unwrap()); + presenter.present(result.unwrap() as any); return presenter.getViewModel(); } catch (error) { this.logger.error('Error getting league full config', error instanceof Error ? error : new Error(String(error))); @@ -271,12 +265,8 @@ export class LeagueService { async getLeagueMemberships(leagueId: string): Promise { this.logger.debug('Getting league memberships', { leagueId }); - const result = await this.getLeagueMembershipsUseCase.execute({ leagueId }); - if (result.isErr()) { - throw new Error(result.unwrapErr().code); - } const presenter = new GetLeagueMembershipsPresenter(); - presenter.present(result.unwrap()); + await this.getLeagueMembershipsUseCase.execute({ leagueId }, presenter); return presenter.getViewModel()!.memberships; } @@ -286,9 +276,7 @@ export class LeagueService { if (result.isErr()) { throw new Error(result.unwrapErr().code); } - const presenter = new LeagueStandingsPresenter(); - presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return (this.getLeagueStandingsUseCase.outputPort as LeagueStandingsPresenter).getResponseModel()!; } async getLeagueSchedule(leagueId: string): Promise { @@ -318,9 +306,7 @@ export class LeagueService { if (result.isErr()) { throw new Error(result.unwrapErr().code); } - const presenter = new LeagueStatsPresenter(); - presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return this.leagueStatsPresenter.getResponseModel()!; } private async getLeagueAdminComposite(leagueId: string): Promise { @@ -388,23 +374,19 @@ export class LeagueService { if (result.isErr()) { throw new Error(result.unwrapErr().code); } - const presenter = new CreateLeaguePresenter(); - presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return this.createLeaguePresenter.getViewModel()!; } async getLeagueScoringConfig(leagueId: string): Promise { this.logger.debug('Getting league scoring config', { leagueId }); - const presenter = new LeagueScoringConfigPresenter(); try { const result = await this.getLeagueScoringConfigUseCase.execute({ leagueId }); if (result.isErr()) { this.logger.error('Error getting league scoring config', new Error(result.unwrapErr().code)); return null; } - presenter.present(result.unwrap()); - return presenter.getViewModel(); + return this.leagueScoringConfigPresenter.getViewModel(); } catch (error) { this.logger.error('Error getting league scoring config', error instanceof Error ? error : new Error(String(error))); return null; @@ -414,14 +396,12 @@ export class LeagueService { async listLeagueScoringPresets(): Promise { this.logger.debug('Listing league scoring presets'); - const result = await this.listLeagueScoringPresetsUseCase.execute(); + const result = await this.listLeagueScoringPresetsUseCase.execute({}); if (result.isErr()) { throw new Error(result.unwrapErr().code); } - const presenter = new LeagueScoringPresetsPresenter(); - await presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return this.leagueScoringPresetsPresenter.getViewModel()!; } async joinLeague(leagueId: string, driverId: string): Promise { @@ -435,9 +415,7 @@ export class LeagueService { error: error.code, }; } - const presenter = new JoinLeaguePresenter(); - presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return this.joinLeaguePresenter.getViewModel()!; } async transferLeagueOwnership(leagueId: string, currentOwnerId: string, newOwnerId: string): Promise { @@ -451,9 +429,7 @@ export class LeagueService { error: error.code, }; } - const presenter = new TransferLeagueOwnershipPresenter(); - presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return this.transferLeagueOwnershipPresenter.getViewModel()!; } async getSeasonSponsorships(seasonId: string): Promise { @@ -464,9 +440,7 @@ export class LeagueService { throw new Error(result.unwrapErr().code); } - const presenter = new GetSeasonSponsorshipsPresenter(); - presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return this.getSeasonSponsorshipsPresenter.getViewModel()!; } async getRaces(leagueId: string): Promise { @@ -477,11 +451,8 @@ export class LeagueService { throw new Error(result.unwrapErr().code); } - const presenter = new LeagueRacesPresenter(); - presenter.present(result.unwrap()); - return { - races: presenter.getViewModel()!, + races: this.leagueRacesPresenter.getViewModel()!, }; } @@ -491,7 +462,7 @@ export class LeagueService { if (result.isErr()) { throw new Error(result.unwrapErr().code); } - return result.unwrap(); + return result.unwrap() as GetLeagueWalletOutputDTO; } async withdrawFromLeagueWallet(leagueId: string, input: WithdrawFromLeagueWalletInputDTO): Promise { @@ -499,17 +470,14 @@ export class LeagueService { const result = await this.withdrawFromLeagueWalletUseCase.execute({ leagueId, amount: input.amount, - currency: input.currency, + currency: input.currency as 'USD' | 'EUR' | 'GBP', seasonId: input.seasonId, destinationAccount: input.destinationAccount, }); if (result.isErr()) { const error = result.unwrapErr(); - if (error.code === 'WITHDRAWAL_NOT_ALLOWED') { - return { success: false, message: error.code }; - } - throw new Error(error.code); + return { success: false, message: error.code }; } - return result.unwrap(); + return result.unwrap() as WithdrawFromLeagueWalletOutputDTO; } } diff --git a/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityPresenter.ts b/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityPresenter.ts index deb92d644..d62b317b8 100644 --- a/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityPresenter.ts +++ b/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityPresenter.ts @@ -1,13 +1,12 @@ import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; -import { Result } from '@core/shared/application/Result'; import { AllLeaguesWithCapacityDTO, LeagueWithCapacityDTO } from '../dtos/AllLeaguesWithCapacityDTO'; import type { GetAllLeaguesWithCapacityResult } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase'; -import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -export class AllLeaguesWithCapacityPresenter implements UseCaseOutputPort { - present(result: Result>): AllLeaguesWithCapacityDTO { - const output = result.unwrap(); - const leagues: LeagueWithCapacityDTO[] = output.leagues.map(league => ({ +export class AllLeaguesWithCapacityPresenter implements UseCaseOutputPort { + private result: AllLeaguesWithCapacityDTO | null = null; + + present(result: GetAllLeaguesWithCapacityResult): void { + const leagues: LeagueWithCapacityDTO[] = result.leagues.map(league => ({ id: league.league.id.toString(), name: league.league.name.toString(), description: league.league.description?.toString() || '', @@ -17,9 +16,14 @@ export class AllLeaguesWithCapacityPresenter implements UseCaseOutputPort { private result: ApproveLeagueJoinRequestDTO | null = null; reset() { this.result = null; } - present(output: ApproveLeagueJoinRequestResultPort) { + present(output: ApproveLeagueJoinRequestResult) { this.result = output; } diff --git a/apps/api/src/domain/league/presenters/CreateLeaguePresenter.ts b/apps/api/src/domain/league/presenters/CreateLeaguePresenter.ts index 46a15467a..af5b3381f 100644 --- a/apps/api/src/domain/league/presenters/CreateLeaguePresenter.ts +++ b/apps/api/src/domain/league/presenters/CreateLeaguePresenter.ts @@ -1,4 +1,4 @@ -import type { CreateLeagueWithSeasonAndScoringOutputPort } from '@core/racing/application/ports/output/CreateLeagueWithSeasonAndScoringOutputPort'; +import { CreateLeagueWithSeasonAndScoringResult } from '@core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase'; import type { CreateLeagueViewModel } from '../dtos/CreateLeagueDTO'; export class CreateLeaguePresenter { @@ -8,9 +8,9 @@ export class CreateLeaguePresenter { this.result = null; } - present(dto: CreateLeagueWithSeasonAndScoringOutputPort): void { + present(dto: CreateLeagueWithSeasonAndScoringResult): void { this.result = { - leagueId: dto.leagueId, + leagueId: dto.league.id.toString(), success: true, }; } diff --git a/apps/api/src/domain/league/presenters/GetLeagueAdminPermissionsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueAdminPermissionsPresenter.ts index e3a0593a5..071d44b9e 100644 --- a/apps/api/src/domain/league/presenters/GetLeagueAdminPermissionsPresenter.ts +++ b/apps/api/src/domain/league/presenters/GetLeagueAdminPermissionsPresenter.ts @@ -1,22 +1,22 @@ -import type { GetLeagueAdminPermissionsOutputPort } from '@core/racing/application/ports/output/GetLeagueAdminPermissionsOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application'; +import type { GetLeagueAdminPermissionsResult } from '@core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase'; import { LeagueAdminPermissionsDTO } from '../dtos/LeagueAdminPermissionsDTO'; -import type { Presenter } from '@core/shared/presentation'; -export class GetLeagueAdminPermissionsPresenter implements Presenter { +export class GetLeagueAdminPermissionsPresenter implements UseCaseOutputPort { private result: LeagueAdminPermissionsDTO | null = null; reset(): void { this.result = null; } - present(port: GetLeagueAdminPermissionsOutputPort): void { + present(port: GetLeagueAdminPermissionsResult): void { this.result = { - canRemoveMember: port.canRemoveMember, - canUpdateRoles: port.canUpdateRoles, + canRemoveMember: port.permissions.canManageMembers, + canUpdateRoles: port.permissions.canManageMembers, }; } - getViewModel(): LeagueAdminPermissionsDTO | null { + getResponseModel(): LeagueAdminPermissionsDTO | null { return this.result; } } diff --git a/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.test.ts b/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.test.ts index de39f41d6..2aecbeabb 100644 --- a/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.test.ts +++ b/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.test.ts @@ -1,28 +1,34 @@ import { GetLeagueMembershipsPresenter } from './GetLeagueMembershipsPresenter'; -import type { GetLeagueMembershipsOutputPort } from '@core/racing/application/ports/output/GetLeagueMembershipsOutputPort'; +import type { GetLeagueMembershipsResult } from '@core/racing/application/use-cases/GetLeagueMembershipsUseCase'; describe('GetLeagueMembershipsPresenter', () => { it('presents memberships correctly', () => { const presenter = new GetLeagueMembershipsPresenter(); - const output: GetLeagueMembershipsOutputPort = { - memberships: { - members: [ - { + const output: GetLeagueMembershipsResult = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + league: {} as any, + memberships: [ + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + membership: { driverId: 'driver-1', - driver: { id: 'driver-1', name: 'John Doe' }, role: 'member', - joinedAt: new Date('2023-01-01'), - }, - ], - }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + joinedAt: {} as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + driver: { id: 'driver-1', name: 'John Doe' } as any, + }, + ], }; presenter.present(output); - const vm = presenter.getViewModel(); + const vm = presenter.getViewModel()!; expect(vm).not.toBeNull(); expect(vm!.memberships.members).toHaveLength(1); - expect(vm!.memberships.members[0].driverId).toBe('driver-1'); - expect(vm!.memberships.members[0].driver.name).toBe('John Doe'); + expect(vm!.memberships.members[0]!.driverId).toBe('driver-1'); + expect(vm!.memberships.members[0]!.driver.name).toBe('John Doe'); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.ts index 1a24f9585..837508a2a 100644 --- a/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.ts +++ b/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.ts @@ -1,24 +1,35 @@ -import type { GetLeagueMembershipsOutputPort } from '@core/racing/application/ports/output/GetLeagueMembershipsOutputPort'; -import { LeagueMembershipsDTO, LeagueMemberDTO } from '../dtos/LeagueMembershipsDTO'; +import type { UseCaseOutputPort } from '@core/shared/application'; +import { GetLeagueMembershipsResult } from '@core/racing/application/use-cases/GetLeagueMembershipsUseCase'; +import { LeagueMembershipsDTO } from '../dtos/LeagueMembershipsDTO'; +import type { LeagueMemberDTO } from '../dtos/LeagueMemberDTO'; export interface GetLeagueMembershipsViewModel { memberships: LeagueMembershipsDTO; } -export class GetLeagueMembershipsPresenter { +export class GetLeagueMembershipsPresenter implements UseCaseOutputPort { private result: GetLeagueMembershipsViewModel | null = null; reset() { this.result = null; } - present(output: GetLeagueMembershipsOutputPort) { - const members: LeagueMemberDTO[] = output.memberships.members.map(member => ({ - driverId: member.driverId, - driver: member.driver, - role: member.role, - joinedAt: member.joinedAt, - })); + present(result: GetLeagueMembershipsResult) { + const members: LeagueMemberDTO[] = result.memberships + .filter(({ driver }) => driver !== null) + .map(({ membership, driver }) => ({ + driverId: membership.driverId.toString(), + driver: { + id: driver!.id, + iracingId: driver!.iracingId.toString(), + name: driver!.name.toString(), + country: driver!.country.toString(), + joinedAt: driver!.joinedAt.toDate().toISOString(), + ...(driver!.bio ? { bio: driver!.bio.toString() } : {}), + }, + role: membership.role.toString() as 'owner' | 'manager' | 'member', + joinedAt: membership.joinedAt.toDate(), + })); this.result = { memberships: { members, diff --git a/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts index a22440f31..8626f77d2 100644 --- a/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts +++ b/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts @@ -66,7 +66,7 @@ export class GetLeagueProtestsPresenter implements Presenter { private result: GetSeasonSponsorshipsOutputDTO | null = null; reset() { this.result = null; } - present(output: GetSeasonSponsorshipsOutputPort) { + present(result: GetSeasonSponsorshipsResult): void { this.result = { - sponsorships: output?.sponsorships ?? [], + sponsorships: result.sponsorships.map(s => ({ + id: s.id, + leagueId: s.leagueId.toString(), + leagueName: s.leagueName.toString(), + seasonId: s.seasonId, + seasonName: s.seasonName, + seasonStartDate: s.seasonStartDate, + seasonEndDate: s.seasonEndDate, + tier: s.tier as 'main' | 'secondary', + status: s.status as 'pending' | 'active' | 'expired' | 'cancelled', + pricing: s.pricing, + platformFee: s.platformFee, + netAmount: s.netAmount, + metrics: s.metrics, + createdAt: s.createdAt, + activatedAt: s.activatedAt, + } as SponsorshipDetailDTO)), }; } diff --git a/apps/api/src/domain/league/presenters/JoinLeaguePresenter.ts b/apps/api/src/domain/league/presenters/JoinLeaguePresenter.ts index 0a1255380..75724c410 100644 --- a/apps/api/src/domain/league/presenters/JoinLeaguePresenter.ts +++ b/apps/api/src/domain/league/presenters/JoinLeaguePresenter.ts @@ -1,17 +1,18 @@ -import type { JoinLeagueOutputPort } from '@core/racing/application/ports/output/JoinLeagueOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { JoinLeagueResult } from '@core/racing/application/use-cases/JoinLeagueUseCase'; import type { JoinLeagueOutputDTO } from '../dtos/JoinLeagueOutputDTO'; -export class JoinLeaguePresenter { +export class JoinLeaguePresenter implements UseCaseOutputPort { private result: JoinLeagueOutputDTO | null = null; reset() { this.result = null; } - present(output: JoinLeagueOutputPort) { + present(result: JoinLeagueResult): void { this.result = { success: true, - membershipId: output.membershipId, + membershipId: result.membership.id.toString(), }; } diff --git a/apps/api/src/domain/league/presenters/LeagueAdminPresenter.ts b/apps/api/src/domain/league/presenters/LeagueAdminPresenter.ts index a7fc30ff6..7387374f9 100644 --- a/apps/api/src/domain/league/presenters/LeagueAdminPresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueAdminPresenter.ts @@ -1,4 +1,9 @@ import { LeagueAdminDTO } from '../dtos/LeagueAdminDTO'; +import { LeagueJoinRequestDTO } from '../dtos/LeagueJoinRequestDTO'; +import { LeagueOwnerSummaryDTO } from '../dtos/LeagueOwnerSummaryDTO'; +import { LeagueConfigFormModelDTO } from '../dtos/LeagueConfigFormModelDTO'; +import { LeagueAdminProtestsDTO } from '../dtos/LeagueAdminProtestsDTO'; +import { LeagueSeasonSummaryDTO } from '../dtos/LeagueSeasonSummaryDTO'; export class LeagueAdminPresenter { private result: LeagueAdminDTO | null = null; @@ -15,11 +20,11 @@ export class LeagueAdminPresenter { seasons: unknown[]; }) { this.result = { - joinRequests: data.joinRequests, - ownerSummary: data.ownerSummary, - config: { form: data.config }, - protests: data.protests, - seasons: data.seasons, + joinRequests: data.joinRequests as LeagueJoinRequestDTO[], + ownerSummary: data.ownerSummary as LeagueOwnerSummaryDTO | null, + config: { form: data.config as LeagueConfigFormModelDTO | null }, + protests: data.protests as LeagueAdminProtestsDTO, + seasons: data.seasons as LeagueSeasonSummaryDTO[], }; } diff --git a/apps/api/src/domain/league/presenters/LeagueConfigPresenter.test.ts b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.test.ts index 38a1dad87..6f6eae006 100644 --- a/apps/api/src/domain/league/presenters/LeagueConfigPresenter.test.ts +++ b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.test.ts @@ -1,9 +1,9 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { LeagueConfigPresenter } from './LeagueConfigPresenter'; -import type { LeagueFullConfigOutputPort } from '@core/racing/application/ports/output/LeagueFullConfigOutputPort'; describe('LeagueConfigPresenter', () => { - const createFullConfig = (overrides: Partial = {}): LeagueFullConfigOutputPort => { - const base: LeagueFullConfigOutputPort = { + const createFullConfig = (overrides: Partial = {}): any => { + const base: any = { // eslint-disable-next-line @typescript-eslint/no-explicit-any league: { id: 'league-1', @@ -65,8 +65,8 @@ describe('LeagueConfigPresenter', () => { const presenter = new LeagueConfigPresenter(); const fullConfig = createFullConfig(); - presenter.present(fullConfig); - const vm = presenter.getViewModel(); + presenter.present({ config: fullConfig }); + const vm = presenter.getViewModel()!; expect(vm).not.toBeNull(); expect(vm!.leagueId).toBe('league-1'); diff --git a/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts index e6c13e895..2907c0ac8 100644 --- a/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts @@ -1,15 +1,17 @@ -import { LeagueFullConfigOutputPort } from '@core/racing/application/ports/output/LeagueFullConfigOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { GetLeagueFullConfigResult } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase'; import { LeagueConfigFormModelDTO } from '../dtos/LeagueConfigFormModelDTO'; -import type { Presenter } from '@core/shared/presentation'; -export class LeagueConfigPresenter implements Presenter { +export class LeagueConfigPresenter implements UseCaseOutputPort { private result: LeagueConfigFormModelDTO | null = null; reset() { this.result = null; } - present(dto: LeagueFullConfigOutputPort) { + present(result: GetLeagueFullConfigResult): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const dto = result.config as any; const league = dto.league; const settings = league.settings; const stewarding = dto.activeSeason?.stewardingConfig; diff --git a/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.test.ts b/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.test.ts index 9f5c7356b..1a163cc0f 100644 --- a/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.test.ts +++ b/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.test.ts @@ -1,10 +1,10 @@ import { LeagueJoinRequestsPresenter } from './LeagueJoinRequestsPresenter'; -import type { GetLeagueJoinRequestsOutputPort } from '@core/racing/application/ports/output/GetLeagueJoinRequestsOutputPort'; +import type { GetLeagueJoinRequestsResult } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase'; describe('LeagueJoinRequestsPresenter', () => { it('presents join requests correctly', () => { const presenter = new LeagueJoinRequestsPresenter(); - const output: GetLeagueJoinRequestsOutputPort = { + const output: GetLeagueJoinRequestsResult = { joinRequests: [ { id: 'req-1', @@ -12,17 +12,18 @@ describe('LeagueJoinRequestsPresenter', () => { driverId: 'driver-1', requestedAt: new Date('2023-01-01'), message: 'Please accept me', - driver: { id: 'driver-1', name: 'John Doe' }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + driver: { id: 'driver-1', name: 'John Doe' } as any, }, ], }; presenter.present(output); - const vm = presenter.getViewModel(); + const vm = presenter.getViewModel()!; expect(vm).not.toBeNull(); - expect(vm!.joinRequests).toHaveLength(1); - expect(vm!.joinRequests[0].id).toBe('req-1'); - expect(vm!.joinRequests[0].driver.name).toBe('John Doe'); + expect(vm.joinRequests).toHaveLength(1); + expect(vm.joinRequests[0]!.id).toBe('req-1'); + expect(vm.joinRequests[0]!.driver.name).toBe('John Doe'); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.ts b/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.ts index df875b972..03eacd632 100644 --- a/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueJoinRequestsPresenter.ts @@ -1,25 +1,29 @@ -import type { GetLeagueJoinRequestsOutputPort } from '@core/racing/application/ports/output/GetLeagueJoinRequestsOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application'; +import { GetLeagueJoinRequestsResult } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase'; import { LeagueJoinRequestWithDriverDTO } from '../dtos/LeagueJoinRequestWithDriverDTO'; export interface LeagueJoinRequestsViewModel { joinRequests: LeagueJoinRequestWithDriverDTO[]; } -export class LeagueJoinRequestsPresenter { +export class LeagueJoinRequestsPresenter implements UseCaseOutputPort { private result: LeagueJoinRequestsViewModel | null = null; reset() { this.result = null; } - present(output: GetLeagueJoinRequestsOutputPort) { - const joinRequests: LeagueJoinRequestWithDriverDTO[] = output.joinRequests.map(request => ({ - id: request.id, - leagueId: request.leagueId, - driverId: request.driverId, - requestedAt: request.requestedAt, - message: request.message, - driver: request.driver, + present(result: GetLeagueJoinRequestsResult) { + const joinRequests: LeagueJoinRequestWithDriverDTO[] = result.joinRequests.map(item => ({ + id: item.id, + leagueId: item.leagueId, + driverId: item.driverId, + requestedAt: item.requestedAt, + ...(item.message ? { message: item.message } : {}), + driver: { + id: item.driver.id, + name: item.driver.name.toString(), + }, })); this.result = { joinRequests, diff --git a/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.test.ts b/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.test.ts index 0a89ad450..b1b819590 100644 --- a/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.test.ts +++ b/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.test.ts @@ -1,42 +1,33 @@ import { LeagueOwnerSummaryPresenter } from './LeagueOwnerSummaryPresenter'; -import type { GetLeagueOwnerSummaryOutputPort } from '@core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort'; +import type { GetLeagueOwnerSummaryResult } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase'; describe('LeagueOwnerSummaryPresenter', () => { it('presents owner summary correctly', () => { const presenter = new LeagueOwnerSummaryPresenter(); - const output: GetLeagueOwnerSummaryOutputPort = { - summary: { - driver: { - id: 'driver-1', - iracingId: '12345', - name: 'John Doe', - country: 'US', - bio: 'Racing enthusiast', - joinedAt: '2023-01-01', - }, - rating: 1500, - rank: 100, - }, + const output: GetLeagueOwnerSummaryResult = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + league: {} as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + owner: { + id: 'driver-1', + iracingId: '12345', + name: 'John Doe', + country: 'US', + bio: 'Racing enthusiast', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + joinedAt: {} as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + rating: 1500, + rank: 100, }; presenter.present(output); - const vm = presenter.getViewModel(); + const vm = presenter.getViewModel()!; expect(vm).not.toBeNull(); - expect(vm!.driver.id).toBe('driver-1'); - expect(vm!.rating).toBe(1500); - expect(vm!.rank).toBe(100); - }); - - it('handles null summary', () => { - const presenter = new LeagueOwnerSummaryPresenter(); - const output: GetLeagueOwnerSummaryOutputPort = { - summary: null, - }; - - presenter.present(output); - const vm = presenter.getViewModel(); - - expect(vm).toBeNull(); + expect(vm.driver.id).toBe('driver-1'); + expect(vm.rating).toBe(1500); + expect(vm.rank).toBe(100); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.ts b/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.ts index 2ae2e2450..7877923a5 100644 --- a/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.ts @@ -1,29 +1,26 @@ -import type { GetLeagueOwnerSummaryOutputPort } from '@core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application'; +import { GetLeagueOwnerSummaryResult } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase'; import { LeagueOwnerSummaryDTO } from '../dtos/LeagueOwnerSummaryDTO'; -export class LeagueOwnerSummaryPresenter { +export class LeagueOwnerSummaryPresenter implements UseCaseOutputPort { private result: LeagueOwnerSummaryDTO | null = null; reset() { this.result = null; } - present(output: GetLeagueOwnerSummaryOutputPort) { - if (!output.summary) { - this.result = null; - return; - } + present(result: GetLeagueOwnerSummaryResult) { this.result = { driver: { - id: output.summary.driver.id, - iracingId: output.summary.driver.iracingId, - name: output.summary.driver.name, - country: output.summary.driver.country, - bio: output.summary.driver.bio, - joinedAt: output.summary.driver.joinedAt, + id: result.owner.id, + iracingId: result.owner.iracingId.toString(), + name: result.owner.name.toString(), + country: result.owner.country.toString(), + joinedAt: result.owner.joinedAt.toDate().toISOString(), + ...(result.owner.bio ? { bio: result.owner.bio.toString() } : {}), }, - rating: output.summary.rating, - rank: output.summary.rank, + rating: result.rating, + rank: result.rank, }; } diff --git a/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.ts b/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.ts index c5d601d3d..b7803da3b 100644 --- a/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.ts @@ -1,21 +1,22 @@ -import { GetLeagueScheduleOutputPort } from '@core/racing/application/ports/output/GetLeagueScheduleOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application'; +import { GetLeagueScheduleResult } from '@core/racing/application/use-cases/GetLeagueScheduleUseCase'; import { LeagueScheduleDTO } from '../dtos/LeagueScheduleDTO'; import { RaceDTO } from '../../race/dtos/RaceDTO'; -export class LeagueSchedulePresenter { +export class LeagueSchedulePresenter implements UseCaseOutputPort { private result: LeagueScheduleDTO | null = null; reset() { this.result = null; } - present(output: GetLeagueScheduleOutputPort, leagueName?: string) { + present(result: GetLeagueScheduleResult, leagueName?: string) { this.result = { - races: output.races.map(race => ({ - id: race.id, - name: race.name, - date: race.scheduledAt.toISOString(), - leagueName, + races: result.races.map(race => ({ + id: race.race.id, + name: `${race.race.track} - ${race.race.car}`, + date: race.race.scheduledAt.toISOString(), + ...(leagueName ? { leagueName } : {}), })), }; } @@ -25,19 +26,19 @@ export class LeagueSchedulePresenter { } } -export class LeagueRacesPresenter { +export class LeagueRacesPresenter implements UseCaseOutputPort { private result: RaceDTO[] | null = null; reset() { this.result = null; } - present(output: GetLeagueScheduleOutputPort, leagueName?: string) { - this.result = output.races.map(race => ({ - id: race.id, - name: race.name, - date: race.scheduledAt.toISOString(), - leagueName, + present(result: GetLeagueScheduleResult, leagueName?: string) { + this.result = result.races.map(race => ({ + id: race.race.id, + name: `${race.race.track} - ${race.race.car}`, + date: race.race.scheduledAt.toISOString(), + ...(leagueName ? { leagueName } : {}), })); } diff --git a/apps/api/src/domain/league/presenters/LeagueScoringConfigPresenter.ts b/apps/api/src/domain/league/presenters/LeagueScoringConfigPresenter.ts index b375e13ee..481abcf10 100644 --- a/apps/api/src/domain/league/presenters/LeagueScoringConfigPresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueScoringConfigPresenter.ts @@ -1,7 +1,7 @@ +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { GetLeagueScoringConfigResult } from '@core/racing/application/use-cases/GetLeagueScoringConfigUseCase'; import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig'; import type { BonusRule } from '@core/racing/domain/types/BonusRule'; -import type { LeagueScoringConfigOutputPort } from '@core/racing/application/ports/output/LeagueScoringConfigOutputPort'; -import type { LeagueScoringPresetOutputPort } from '@core/racing/application/ports/output/LeagueScoringPresetOutputPort'; export interface LeagueScoringChampionshipViewModel { id: string; @@ -24,33 +24,31 @@ export interface LeagueScoringConfigViewModel { championships: LeagueScoringChampionshipViewModel[]; } -export class LeagueScoringConfigPresenter { +export class LeagueScoringConfigPresenter implements UseCaseOutputPort { private viewModel: LeagueScoringConfigViewModel | null = null; reset(): void { this.viewModel = null; } - present(data: LeagueScoringConfigOutputPort): LeagueScoringConfigViewModel { + present(result: GetLeagueScoringConfigResult): void { const championships: LeagueScoringChampionshipViewModel[] = - data.championships.map((champ) => this.mapChampionship(champ)); + result.scoringConfig.championships.map((champ) => this.mapChampionship(champ)); const dropPolicySummary = - data.preset?.dropPolicySummary ?? - this.deriveDropPolicyDescriptionFromChampionships(data.championships); + result.preset?.dropPolicySummary ?? + this.deriveDropPolicyDescriptionFromChampionships(result.scoringConfig.championships); this.viewModel = { - leagueId: data.leagueId, - seasonId: data.seasonId, - gameId: data.gameId, - gameName: data.gameName, - scoringPresetId: data.scoringPresetId ?? 'custom', - scoringPresetName: data.preset?.name ?? 'Custom', + leagueId: result.league.id.toString(), + seasonId: result.season.id, + gameId: result.game.id.toString(), + gameName: result.game.name.toString(), + scoringPresetId: result.scoringConfig.scoringPresetId?.toString() ?? 'custom', + scoringPresetName: result.preset?.name ?? 'Custom', dropPolicySummary, championships, }; - - return this.viewModel; } getViewModel(): LeagueScoringConfigViewModel | null { diff --git a/apps/api/src/domain/league/presenters/LeagueScoringPresetsPresenter.ts b/apps/api/src/domain/league/presenters/LeagueScoringPresetsPresenter.ts index 2341a15a8..8552826ee 100644 --- a/apps/api/src/domain/league/presenters/LeagueScoringPresetsPresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueScoringPresetsPresenter.ts @@ -1,22 +1,22 @@ -import type { LeagueScoringPresetsOutputPort } from '@core/racing/application/ports/output/LeagueScoringPresetsOutputPort'; -import type { LeagueScoringPresetOutputPort } from '@core/racing/application/ports/output/LeagueScoringPresetOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { ListLeagueScoringPresetsResult, LeagueScoringPreset } from '@core/racing/application/use-cases/ListLeagueScoringPresetsUseCase'; export interface LeagueScoringPresetsViewModel { - presets: LeagueScoringPresetOutputPort[]; + presets: LeagueScoringPreset[]; totalCount: number; } -export class LeagueScoringPresetsPresenter { +export class LeagueScoringPresetsPresenter implements UseCaseOutputPort { private viewModel: LeagueScoringPresetsViewModel | null = null; reset(): void { this.viewModel = null; } - present(output: LeagueScoringPresetsOutputPort): void { + present(result: ListLeagueScoringPresetsResult): void { this.viewModel = { - presets: output.presets, - totalCount: output.presets.length, + presets: result.presets, + totalCount: result.presets.length, }; } diff --git a/apps/api/src/domain/league/presenters/LeagueStandingsPresenter.ts b/apps/api/src/domain/league/presenters/LeagueStandingsPresenter.ts index 35f1e1a1f..129e15cc4 100644 --- a/apps/api/src/domain/league/presenters/LeagueStandingsPresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueStandingsPresenter.ts @@ -1,21 +1,24 @@ -import { LeagueStandingsOutputPort } from '@core/racing/application/ports/output/LeagueStandingsOutputPort'; +import type { GetLeagueStandingsResult } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase'; import { LeagueStandingsDTO } from '../dtos/LeagueStandingsDTO'; import type { Presenter } from '@core/shared/presentation'; -export class LeagueStandingsPresenter implements Presenter { +export class LeagueStandingsPresenter implements Presenter { private result: LeagueStandingsDTO | null = null; reset() { this.result = null; } - present(dto: LeagueStandingsOutputPort) { + present(dto: GetLeagueStandingsResult) { const standings = dto.standings.map(standing => ({ driverId: standing.driverId, driver: { id: standing.driver.id, - name: standing.driver.name, - // Add other DriverDto fields if needed, but for now just id and name + iracingId: standing.driver.iracingId.toString(), + name: standing.driver.name.toString(), + country: standing.driver.country.toString(), + ...(standing.driver.bio ? { bio: standing.driver.bio.toString() } : {}), + joinedAt: standing.driver.joinedAt.toString(), }, points: standing.points, rank: standing.rank, @@ -23,7 +26,7 @@ export class LeagueStandingsPresenter implements Presenter { +export class LeagueStatsPresenter implements Presenter { private result: LeagueStatsDTO | null = null; reset() { this.result = null; } - present(dto: LeagueStatsOutputPort) { - this.result = dto; + present(dto: GetLeagueStatsResult) { + this.result = { + totalMembers: dto.driverCount, + totalRaces: dto.raceCount, + averageRating: dto.averageRating, + }; } - getViewModel(): LeagueStatsDTO | null { + getResponseModel(): LeagueStatsDTO | null { return this.result; } } \ No newline at end of file diff --git a/apps/api/src/domain/league/presenters/RejectLeagueJoinRequestPresenter.ts b/apps/api/src/domain/league/presenters/RejectLeagueJoinRequestPresenter.ts index df8229b61..6b3877b58 100644 --- a/apps/api/src/domain/league/presenters/RejectLeagueJoinRequestPresenter.ts +++ b/apps/api/src/domain/league/presenters/RejectLeagueJoinRequestPresenter.ts @@ -1,17 +1,18 @@ -import type { RejectLeagueJoinRequestOutputPort } from '@core/racing/application/ports/output/RejectLeagueJoinRequestOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { RejectLeagueJoinRequestResult } from '@core/racing/application/use-cases/RejectLeagueJoinRequestUseCase'; import type { RejectJoinRequestOutputDTO } from '../dtos/RejectJoinRequestOutputDTO'; -export class RejectLeagueJoinRequestPresenter { +export class RejectLeagueJoinRequestPresenter implements UseCaseOutputPort { private result: RejectJoinRequestOutputDTO | null = null; reset() { this.result = null; } - present(output: RejectLeagueJoinRequestOutputPort) { + present(_result: RejectLeagueJoinRequestResult): void { this.result = { - success: output.success, - message: output.message, + success: true, + message: 'Join request rejected successfully', }; } diff --git a/apps/api/src/domain/league/presenters/RemoveLeagueMemberPresenter.ts b/apps/api/src/domain/league/presenters/RemoveLeagueMemberPresenter.ts index bbcde12f3..09255f82f 100644 --- a/apps/api/src/domain/league/presenters/RemoveLeagueMemberPresenter.ts +++ b/apps/api/src/domain/league/presenters/RemoveLeagueMemberPresenter.ts @@ -1,16 +1,17 @@ -import type { RemoveLeagueMemberOutputPort } from '@core/racing/application/ports/output/RemoveLeagueMemberOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { RemoveLeagueMemberResult } from '@core/racing/application/use-cases/RemoveLeagueMemberUseCase'; import type { RemoveLeagueMemberOutputDTO } from '../dtos/RemoveLeagueMemberOutputDTO'; -export class RemoveLeagueMemberPresenter { +export class RemoveLeagueMemberPresenter implements UseCaseOutputPort { private result: RemoveLeagueMemberOutputDTO | null = null; reset() { this.result = null; } - present(output: RemoveLeagueMemberOutputPort) { + present(_result: RemoveLeagueMemberResult): void { this.result = { - success: output.success, + success: true, }; } diff --git a/apps/api/src/domain/league/presenters/TotalLeaguesPresenter.ts b/apps/api/src/domain/league/presenters/TotalLeaguesPresenter.ts index 573be4856..204ef10ef 100644 --- a/apps/api/src/domain/league/presenters/TotalLeaguesPresenter.ts +++ b/apps/api/src/domain/league/presenters/TotalLeaguesPresenter.ts @@ -1,4 +1,4 @@ -import { GetTotalLeaguesOutputPort } from '@core/racing/application/ports/output/GetTotalLeaguesOutputPort'; +import type { GetTotalLeaguesResult } from '@core/racing/application/use-cases/GetTotalLeaguesUseCase'; import { TotalLeaguesDTO } from '../dtos/TotalLeaguesDTO'; export class TotalLeaguesPresenter { @@ -8,13 +8,13 @@ export class TotalLeaguesPresenter { this.result = null; } - present(output: GetTotalLeaguesOutputPort) { + present(output: GetTotalLeaguesResult) { this.result = { totalLeagues: output.totalLeagues, }; } - getViewModel(): TotalLeaguesDTO | null { + getResponseModel(): TotalLeaguesDTO | null { return this.result; } } \ No newline at end of file diff --git a/apps/api/src/domain/league/presenters/TransferLeagueOwnershipPresenter.ts b/apps/api/src/domain/league/presenters/TransferLeagueOwnershipPresenter.ts index 81c9a0dd4..064e55f69 100644 --- a/apps/api/src/domain/league/presenters/TransferLeagueOwnershipPresenter.ts +++ b/apps/api/src/domain/league/presenters/TransferLeagueOwnershipPresenter.ts @@ -1,16 +1,17 @@ -import type { TransferLeagueOwnershipOutputPort } from '@core/racing/application/ports/output/TransferLeagueOwnershipOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { TransferLeagueOwnershipResult } from '@core/racing/application/use-cases/TransferLeagueOwnershipUseCase'; import type { TransferLeagueOwnershipOutputDTO } from '../dtos/TransferLeagueOwnershipOutputDTO'; -export class TransferLeagueOwnershipPresenter { +export class TransferLeagueOwnershipPresenter implements UseCaseOutputPort { private result: TransferLeagueOwnershipOutputDTO | null = null; reset() { this.result = null; } - present(output: TransferLeagueOwnershipOutputPort) { + present(_result: TransferLeagueOwnershipResult): void { this.result = { - success: output.success, + success: true, }; } diff --git a/apps/api/src/domain/league/presenters/UpdateLeagueMemberRolePresenter.ts b/apps/api/src/domain/league/presenters/UpdateLeagueMemberRolePresenter.ts index 0376d4c6b..64877ee55 100644 --- a/apps/api/src/domain/league/presenters/UpdateLeagueMemberRolePresenter.ts +++ b/apps/api/src/domain/league/presenters/UpdateLeagueMemberRolePresenter.ts @@ -1,16 +1,17 @@ -import type { UpdateLeagueMemberRoleOutputPort } from '@core/racing/application/ports/output/UpdateLeagueMemberRoleOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { UpdateLeagueMemberRoleResult } from '@core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase'; import type { UpdateLeagueMemberRoleOutputDTO } from '../dtos/UpdateLeagueMemberRoleOutputDTO'; -export class UpdateLeagueMemberRolePresenter { +export class UpdateLeagueMemberRolePresenter implements UseCaseOutputPort { private result: UpdateLeagueMemberRoleOutputDTO | null = null; reset() { this.result = null; } - present(output: UpdateLeagueMemberRoleOutputPort) { + present(_result: UpdateLeagueMemberRoleResult): void { this.result = { - success: output.success, + success: true, }; } diff --git a/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts b/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts index d6a698302..2204efbeb 100644 --- a/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts +++ b/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts @@ -18,11 +18,11 @@ export interface ApproveLeagueJoinRequestResult { export class ApproveLeagueJoinRequestUseCase { constructor( private readonly leagueMembershipRepository: ILeagueMembershipRepository, - private readonly output: UseCaseOutputPort, ) {} async execute( input: ApproveLeagueJoinRequestInput, + output: UseCaseOutputPort, ): Promise>> { const requests = await this.leagueMembershipRepository.getJoinRequests(input.leagueId); const request = requests.find(r => r.id === input.requestId); @@ -41,7 +41,7 @@ export class ApproveLeagueJoinRequestUseCase { }); const result: ApproveLeagueJoinRequestResult = { success: true, message: 'Join request approved.' }; - this.output.present(result); + output.present(result); return Result.ok(undefined); }