From 9da528d5bdb62bd35021854cab5fcc7b66b6434a Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 22 Dec 2025 10:24:40 +0100 Subject: [PATCH] refactor driver module (wip) --- apps/api/src/domain/driver/DriverProviders.ts | 4 +- .../dtos/DriverProfileAchievementDTO.ts | 28 +++ .../dtos/DriverProfileDriverSummaryDTO.ts | 36 +++ .../dtos/DriverProfileExtendedProfileDTO.ts | 34 +++ .../DriverProfileFinishDistributionDTO.ts | 21 ++ .../DriverProfileSocialFriendSummaryDTO.ts | 15 ++ .../dtos/DriverProfileSocialHandleDTO.ts | 14 ++ .../dtos/DriverProfileSocialSummaryDTO.ts | 11 + .../driver/dtos/DriverProfileStatsDTO.ts | 45 ++++ .../dtos/DriverProfileTeamMembershipDTO.ts | 21 ++ .../driver/dtos/GetDriverProfileOutputDTO.ts | 227 +----------------- .../presenters/DriverProfilePresenter.ts | 3 +- .../presenters/DriverStatsPresenter.test.ts | 25 +- .../DriversLeaderboardPresenter.test.ts | 147 ++---------- apps/api/src/domain/league/LeagueProviders.ts | 19 +- apps/api/src/domain/league/LeagueService.ts | 8 +- .../AllLeaguesWithCapacityAndScoringDTO.ts | 4 +- .../league/dtos/AllLeaguesWithCapacityDTO.ts | 24 +- .../league/dtos/ApproveJoinRequestInputDTO.ts | 4 +- .../dtos/ApproveJoinRequestOutputDTO.ts | 2 +- .../league/dtos/CreateLeagueInputDTO.ts | 8 +- .../league/dtos/CreateLeagueOutputDTO.ts | 4 +- .../dtos/GetLeagueAdminConfigOutputDTO.ts | 2 +- .../dtos/GetLeagueAdminConfigQueryDTO.ts | 2 +- .../dtos/GetLeagueAdminPermissionsInputDTO.ts | 4 +- .../dtos/GetLeagueJoinRequestsQueryDTO.ts | 2 +- .../dtos/GetLeagueOwnerSummaryQueryDTO.ts | 4 +- .../league/dtos/GetLeagueProtestsQueryDTO.ts | 2 +- .../league/dtos/GetLeagueRacesOutputDTO.ts | 2 +- .../league/dtos/GetLeagueSeasonsQueryDTO.ts | 2 +- .../league/dtos/GetLeagueWalletOutputDTO.ts | 32 +-- .../dtos/GetSeasonSponsorshipsOutputDTO.ts | 2 +- .../league/dtos/LeagueAdminConfigDTO.ts | 2 +- .../src/domain/league/dtos/LeagueAdminDTO.ts | 10 +- .../league/dtos/LeagueAdminPermissionsDTO.ts | 4 +- .../league/dtos/LeagueAdminProtestsDTO.ts | 20 +- .../dtos/LeagueConfigFormModelBasicsDTO.ts | 6 +- .../league/dtos/LeagueConfigFormModelDTO.ts | 16 +- .../LeagueConfigFormModelDropPolicyDTO.ts | 2 +- .../dtos/LeagueConfigFormModelScoringDTO.ts | 4 +- .../LeagueConfigFormModelStewardingDTO.ts | 16 +- .../dtos/LeagueConfigFormModelStructureDTO.ts | 2 +- .../dtos/LeagueConfigFormModelTimingsDTO.ts | 6 +- .../league/dtos/LeagueJoinRequestDTO.ts | 18 +- .../src/domain/league/dtos/LeagueMemberDTO.ts | 14 +- .../domain/league/dtos/LeagueMembershipDTO.ts | 12 +- .../league/dtos/LeagueMembershipsDTO.ts | 2 +- .../league/dtos/LeagueOwnerSummaryDTO.ts | 12 +- .../src/domain/league/dtos/LeagueRoleDTO.ts | 2 +- .../domain/league/dtos/LeagueScheduleDTO.ts | 8 +- .../league/dtos/LeagueScoringPresetDTO.ts | 14 +- .../league/dtos/LeagueSeasonSummaryDTO.ts | 10 +- .../domain/league/dtos/LeagueStandingDTO.ts | 14 +- .../domain/league/dtos/LeagueStandingsDTO.ts | 2 +- .../src/domain/league/dtos/LeagueStatsDTO.ts | 6 +- .../domain/league/dtos/LeagueSummaryDTO.ts | 12 +- .../league/dtos/LeagueWithCapacityDTO.ts | 12 +- .../domain/league/dtos/MembershipRoleDTO.ts | 2 +- .../domain/league/dtos/MembershipStatusDTO.ts | 2 +- apps/api/src/domain/league/dtos/ProtestDTO.ts | 16 +- .../league/dtos/RejectJoinRequestInputDTO.ts | 4 +- .../league/dtos/RejectJoinRequestOutputDTO.ts | 2 +- .../league/dtos/RemoveLeagueMemberInputDTO.ts | 6 +- .../dtos/RemoveLeagueMemberOutputDTO.ts | 2 +- apps/api/src/domain/league/dtos/SeasonDTO.ts | 10 +- .../src/domain/league/dtos/TotalLeaguesDTO.ts | 2 +- .../dtos/UpdateLeagueMemberRoleInputDTO.ts | 8 +- .../dtos/UpdateLeagueMemberRoleOutputDTO.ts | 2 +- .../dtos/WithdrawFromLeagueWalletInputDTO.ts | 8 +- .../dtos/WithdrawFromLeagueWalletOutputDTO.ts | 2 +- .../src/domain/league/dtos/WizardStepDTO.ts | 2 +- .../GetLeagueOwnerSummaryPresenter.ts | 28 +-- .../presenters/GetLeagueProtestsPresenter.ts | 71 +++--- .../presenters/GetLeagueSeasonsPresenter.ts | 25 +- .../src/domain/media/MediaController.test.ts | 6 +- apps/api/src/domain/media/MediaService.ts | 2 +- .../media/presenters/UpdateAvatarPresenter.ts | 2 +- .../src/domain/payments/PaymentsService.ts | 6 - .../presenters/GetPaymentsPresenter.ts | 2 +- .../src/domain/protests/ProtestsService.ts | 5 + .../presenters/ReviewProtestPresenter.ts | 10 +- apps/api/src/domain/race/RaceController.ts | 4 +- apps/api/src/domain/race/RaceService.ts | 6 +- .../presenters/AllRacesPageDataPresenter.ts | 4 + .../presenters/GetAllRacesPresenter.test.ts | 34 +-- .../race/presenters/GetAllRacesPresenter.ts | 4 + .../race/presenters/GetTotalRacesPresenter.ts | 4 + .../ImportRaceResultsApiPresenter.ts | 4 + .../race/presenters/RaceDetailPresenter.ts | 66 ++--- .../race/presenters/RacePenaltiesPresenter.ts | 4 + .../race/presenters/RaceProtestsPresenter.ts | 4 + .../presenters/RaceResultsDetailPresenter.ts | 45 +--- .../race/presenters/RaceWithSOFPresenter.ts | 4 + .../race/presenters/RacesPageDataPresenter.ts | 4 + .../src/domain/sponsor/SponsorProviders.ts | 2 +- apps/api/src/domain/team/TeamService.test.ts | 12 +- apps/api/src/domain/team/TeamService.ts | 135 +++++------ .../team/presenters/AllTeamsPresenter.ts | 30 +-- .../team/presenters/CreateTeamPresenter.ts | 29 +-- .../team/presenters/DriverTeamPresenter.ts | 33 +-- .../team/presenters/TeamDetailsPresenter.ts | 33 +-- .../presenters/TeamJoinRequestsPresenter.ts | 23 +- .../team/presenters/TeamMembersPresenter.ts | 41 ++-- .../presenters/TeamMembershipPresenter.ts | 23 +- .../team/presenters/UpdateTeamPresenter.ts | 16 +- .../use-cases/GetLeagueProtestsUseCase.ts | 4 + .../use-cases/GetLeagueSeasonsUseCase.ts | 2 +- .../use-cases/GetRaceDetailUseCase.ts | 10 +- 108 files changed, 842 insertions(+), 947 deletions(-) create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileAchievementDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileDriverSummaryDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileExtendedProfileDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileFinishDistributionDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileSocialFriendSummaryDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileSocialHandleDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileSocialSummaryDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileStatsDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverProfileTeamMembershipDTO.ts diff --git a/apps/api/src/domain/driver/DriverProviders.ts b/apps/api/src/domain/driver/DriverProviders.ts index 197e40907..afa38684e 100644 --- a/apps/api/src/domain/driver/DriverProviders.ts +++ b/apps/api/src/domain/driver/DriverProviders.ts @@ -128,7 +128,7 @@ export const DriverProviders: Provider[] = [ driverStatsService: IDriverStatsService, imageService: IImageServicePort, logger: Logger, - ) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, imageService, logger), + ) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, (driverId: string) => Promise.resolve(imageService.getDriverAvatar(driverId)), logger), inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, IMAGE_SERVICE_PORT_TOKEN, LOGGER_TOKEN], }, { @@ -169,7 +169,7 @@ export const DriverProviders: Provider[] = [ teamRepository, teamMembershipRepository, socialRepository, - (driverId: string) => Promise.resolve(imageService.getDriverAvatar(driverId)), + imageService, driverExtendedProfileProvider, (driverId: string) => { const stats = driverStatsService.getDriverStats(driverId); diff --git a/apps/api/src/domain/driver/dtos/DriverProfileAchievementDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileAchievementDTO.ts new file mode 100644 index 000000000..1a8821ad2 --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileAchievementDTO.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export enum DriverProfileAchievementRarity { + COMMON = 'common', + RARE = 'rare', + EPIC = 'epic', + LEGENDARY = 'legendary', +} + +export class DriverProfileAchievementDTO { + @ApiProperty() + id!: string; + + @ApiProperty() + title!: string; + + @ApiProperty() + description!: string; + + @ApiProperty({ enum: ['trophy', 'medal', 'star', 'crown', 'target', 'zap'] }) + icon!: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap'; + + @ApiProperty({ enum: DriverProfileAchievementRarity }) + rarity!: DriverProfileAchievementRarity; + + @ApiProperty() + earnedAt!: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/DriverProfileDriverSummaryDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileDriverSummaryDTO.ts new file mode 100644 index 000000000..993cfae0a --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileDriverSummaryDTO.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DriverProfileDriverSummaryDTO { + @ApiProperty() + id!: string; + + @ApiProperty() + name!: string; + + @ApiProperty() + country!: string; + + @ApiProperty() + avatarUrl!: string; + + @ApiProperty({ nullable: true }) + iracingId!: string | null; + + @ApiProperty() + joinedAt!: string; + + @ApiProperty({ nullable: true }) + rating!: number | null; + + @ApiProperty({ nullable: true }) + globalRank!: number | null; + + @ApiProperty({ nullable: true }) + consistency!: number | null; + + @ApiProperty({ nullable: true }) + bio!: string | null; + + @ApiProperty({ nullable: true }) + totalDrivers!: number | null; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/DriverProfileExtendedProfileDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileExtendedProfileDTO.ts new file mode 100644 index 000000000..e673d2fff --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileExtendedProfileDTO.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { DriverProfileSocialHandleDTO } from './DriverProfileSocialHandleDTO'; + +import { DriverProfileAchievementDTO } from './DriverProfileAchievementDTO'; + +export class DriverProfileExtendedProfileDTO { + @ApiProperty({ type: [DriverProfileSocialHandleDTO] }) + socialHandles!: DriverProfileSocialHandleDTO[]; + + @ApiProperty({ type: [DriverProfileAchievementDTO] }) + achievements!: DriverProfileAchievementDTO[]; + + @ApiProperty() + racingStyle!: string; + + @ApiProperty() + favoriteTrack!: string; + + @ApiProperty() + favoriteCar!: string; + + @ApiProperty() + timezone!: string; + + @ApiProperty() + availableHours!: string; + + @ApiProperty() + lookingForTeam!: boolean; + + @ApiProperty() + openToRequests!: boolean; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/DriverProfileFinishDistributionDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileFinishDistributionDTO.ts new file mode 100644 index 000000000..9693a587e --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileFinishDistributionDTO.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DriverProfileFinishDistributionDTO { + @ApiProperty() + totalRaces!: number; + + @ApiProperty() + wins!: number; + + @ApiProperty() + podiums!: number; + + @ApiProperty() + topTen!: number; + + @ApiProperty() + dnfs!: number; + + @ApiProperty() + other!: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/DriverProfileSocialFriendSummaryDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileSocialFriendSummaryDTO.ts new file mode 100644 index 000000000..242c18cfb --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileSocialFriendSummaryDTO.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DriverProfileSocialFriendSummaryDTO { + @ApiProperty() + id!: string; + + @ApiProperty() + name!: string; + + @ApiProperty() + country!: string; + + @ApiProperty() + avatarUrl!: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/DriverProfileSocialHandleDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileSocialHandleDTO.ts new file mode 100644 index 000000000..715ee8f03 --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileSocialHandleDTO.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export type DriverProfileSocialPlatform = 'twitter' | 'youtube' | 'twitch' | 'discord'; + +export class DriverProfileSocialHandleDTO { + @ApiProperty({ enum: ['twitter', 'youtube', 'twitch', 'discord'] }) + platform!: DriverProfileSocialPlatform; + + @ApiProperty() + handle!: string; + + @ApiProperty() + url!: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/DriverProfileSocialSummaryDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileSocialSummaryDTO.ts new file mode 100644 index 000000000..971ef7072 --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileSocialSummaryDTO.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { DriverProfileSocialFriendSummaryDTO } from './DriverProfileSocialFriendSummaryDTO'; + +export class DriverProfileSocialSummaryDTO { + @ApiProperty() + friendsCount!: number; + + @ApiProperty({ type: [DriverProfileSocialFriendSummaryDTO] }) + friends!: DriverProfileSocialFriendSummaryDTO[]; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/DriverProfileStatsDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileStatsDTO.ts new file mode 100644 index 000000000..67246abc5 --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileStatsDTO.ts @@ -0,0 +1,45 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DriverProfileStatsDTO { + @ApiProperty() + totalRaces!: number; + + @ApiProperty() + wins!: number; + + @ApiProperty() + podiums!: number; + + @ApiProperty() + dnfs!: number; + + @ApiProperty({ nullable: true }) + avgFinish!: number | null; + + @ApiProperty({ nullable: true }) + bestFinish!: number | null; + + @ApiProperty({ nullable: true }) + worstFinish!: number | null; + + @ApiProperty({ nullable: true }) + finishRate!: number | null; + + @ApiProperty({ nullable: true }) + winRate!: number | null; + + @ApiProperty({ nullable: true }) + podiumRate!: number | null; + + @ApiProperty({ nullable: true }) + percentile!: number | null; + + @ApiProperty({ nullable: true }) + rating!: number | null; + + @ApiProperty({ nullable: true }) + consistency!: number | null; + + @ApiProperty({ nullable: true }) + overallRank!: number | null; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/DriverProfileTeamMembershipDTO.ts b/apps/api/src/domain/driver/dtos/DriverProfileTeamMembershipDTO.ts new file mode 100644 index 000000000..de906e156 --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverProfileTeamMembershipDTO.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DriverProfileTeamMembershipDTO { + @ApiProperty() + teamId!: string; + + @ApiProperty() + teamName!: string; + + @ApiProperty({ nullable: true }) + teamTag!: string | null; + + @ApiProperty() + role!: string; + + @ApiProperty() + joinedAt!: string; + + @ApiProperty() + isCurrent!: boolean; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/GetDriverProfileOutputDTO.ts b/apps/api/src/domain/driver/dtos/GetDriverProfileOutputDTO.ts index 8ffa26507..bfcc39f3f 100644 --- a/apps/api/src/domain/driver/dtos/GetDriverProfileOutputDTO.ts +++ b/apps/api/src/domain/driver/dtos/GetDriverProfileOutputDTO.ts @@ -1,231 +1,28 @@ import { ApiProperty } from '@nestjs/swagger'; -export class DriverProfileDriverSummaryDTO { - @ApiProperty() - id!: string; - - @ApiProperty() - name!: string; - - @ApiProperty() - country!: string; - - @ApiProperty() - avatarUrl!: string; - - @ApiProperty({ nullable: true }) - iracingId!: string | null; - - @ApiProperty() - joinedAt!: string; - - @ApiProperty({ nullable: true }) - rating!: number | null; - - @ApiProperty({ nullable: true }) - globalRank!: number | null; - - @ApiProperty({ nullable: true }) - consistency!: number | null; - - @ApiProperty({ nullable: true }) - bio!: string | null; - - @ApiProperty({ nullable: true }) - totalDrivers!: number | null; -} - -export class DriverProfileStatsDTO { - @ApiProperty() - totalRaces!: number; - - @ApiProperty() - wins!: number; - - @ApiProperty() - podiums!: number; - - @ApiProperty() - dnfs!: number; - - @ApiProperty({ nullable: true }) - avgFinish!: number | null; - - @ApiProperty({ nullable: true }) - bestFinish!: number | null; - - @ApiProperty({ nullable: true }) - worstFinish!: number | null; - - @ApiProperty({ nullable: true }) - finishRate!: number | null; - - @ApiProperty({ nullable: true }) - winRate!: number | null; - - @ApiProperty({ nullable: true }) - podiumRate!: number | null; - - @ApiProperty({ nullable: true }) - percentile!: number | null; - - @ApiProperty({ nullable: true }) - rating!: number | null; - - @ApiProperty({ nullable: true }) - consistency!: number | null; - - @ApiProperty({ nullable: true }) - overallRank!: number | null; -} - -export class DriverProfileFinishDistributionDTO { - @ApiProperty() - totalRaces: number; - - @ApiProperty() - wins: number; - - @ApiProperty() - podiums: number; - - @ApiProperty() - topTen: number; - - @ApiProperty() - dnfs: number; - - @ApiProperty() - other: number; -} - -export class DriverProfileTeamMembershipDTO { - @ApiProperty() - teamId: string; - - @ApiProperty() - teamName: string; - - @ApiProperty({ nullable: true }) - teamTag: string | null; - - @ApiProperty() - role: string; - - @ApiProperty() - joinedAt: string; - - @ApiProperty() - isCurrent: boolean; -} - -export class DriverProfileSocialFriendSummaryDTO { - @ApiProperty() - id: string; - - @ApiProperty() - name: string; - - @ApiProperty() - country: string; - - @ApiProperty() - avatarUrl: string; -} - -export class DriverProfileSocialSummaryDTO { - @ApiProperty() - friendsCount: number; - - @ApiProperty({ type: [DriverProfileSocialFriendSummaryDTO] }) - friends: DriverProfileSocialFriendSummaryDTO[]; -} - -export type DriverProfileSocialPlatform = 'twitter' | 'youtube' | 'twitch' | 'discord'; - -export enum DriverProfileAchievementRarity { - COMMON = 'common', - RARE = 'rare', - EPIC = 'epic', - LEGENDARY = 'legendary', -} - -export class DriverProfileAchievementDTO { - @ApiProperty() - id: string; - - @ApiProperty() - title: string; - - @ApiProperty() - description: string; - - @ApiProperty({ enum: ['trophy', 'medal', 'star', 'crown', 'target', 'zap'] }) - icon: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap'; - - @ApiProperty({ enum: DriverProfileAchievementRarity }) - rarity: DriverProfileAchievementRarity; - - @ApiProperty() - earnedAt: string; -} - -export class DriverProfileSocialHandleDTO { - @ApiProperty({ enum: DriverProfileSocialPlatform }) - platform: DriverProfileSocialPlatform; - - @ApiProperty() - handle: string; - - @ApiProperty() - url: string; -} - -export class DriverProfileExtendedProfileDTO { - @ApiProperty({ type: [DriverProfileSocialHandleDTO] }) - socialHandles: DriverProfileSocialHandleDTO[]; - - @ApiProperty({ type: [DriverProfileAchievementDTO] }) - achievements: DriverProfileAchievementDTO[]; - - @ApiProperty() - racingStyle: string; - - @ApiProperty() - favoriteTrack: string; - - @ApiProperty() - favoriteCar: string; - - @ApiProperty() - timezone: string; - - @ApiProperty() - availableHours: string; - - @ApiProperty() - lookingForTeam: boolean; - - @ApiProperty() - openToRequests: boolean; -} +import { DriverProfileDriverSummaryDTO } from './DriverProfileDriverSummaryDTO'; +import { DriverProfileStatsDTO } from './DriverProfileStatsDTO'; +import { DriverProfileFinishDistributionDTO } from './DriverProfileFinishDistributionDTO'; +import { DriverProfileTeamMembershipDTO } from './DriverProfileTeamMembershipDTO'; +import { DriverProfileSocialSummaryDTO } from './DriverProfileSocialSummaryDTO'; +import { DriverProfileExtendedProfileDTO } from './DriverProfileExtendedProfileDTO'; export class GetDriverProfileOutputDTO { @ApiProperty({ type: DriverProfileDriverSummaryDTO, nullable: true }) - currentDriver: DriverProfileDriverSummaryDTO | null; + currentDriver!: DriverProfileDriverSummaryDTO | null; @ApiProperty({ type: DriverProfileStatsDTO, nullable: true }) - stats: DriverProfileStatsDTO | null; + stats!: DriverProfileStatsDTO | null; @ApiProperty({ type: DriverProfileFinishDistributionDTO, nullable: true }) - finishDistribution: DriverProfileFinishDistributionDTO | null; + finishDistribution!: DriverProfileFinishDistributionDTO | null; @ApiProperty({ type: [DriverProfileTeamMembershipDTO] }) - teamMemberships: DriverProfileTeamMembershipDTO[]; + teamMemberships!: DriverProfileTeamMembershipDTO[]; @ApiProperty({ type: DriverProfileSocialSummaryDTO }) - socialSummary: DriverProfileSocialSummaryDTO; + socialSummary!: DriverProfileSocialSummaryDTO; @ApiProperty({ type: DriverProfileExtendedProfileDTO, nullable: true }) - extendedProfile: DriverProfileExtendedProfileDTO | null; + extendedProfile!: DriverProfileExtendedProfileDTO | null; } \ No newline at end of file diff --git a/apps/api/src/domain/driver/presenters/DriverProfilePresenter.ts b/apps/api/src/domain/driver/presenters/DriverProfilePresenter.ts index bc3eca1f1..8bd035c45 100644 --- a/apps/api/src/domain/driver/presenters/DriverProfilePresenter.ts +++ b/apps/api/src/domain/driver/presenters/DriverProfilePresenter.ts @@ -1,7 +1,8 @@ import type { GetProfileOverviewResult, } from '@core/racing/application/use-cases/GetProfileOverviewUseCase'; -import type { GetDriverProfileOutputDTO, DriverProfileExtendedProfileDTO } from '../dtos/GetDriverProfileOutputDTO'; +import type { GetDriverProfileOutputDTO } from '../dtos/GetDriverProfileOutputDTO'; +import type { DriverProfileExtendedProfileDTO } from '../dtos/DriverProfileExtendedProfileDTO'; export class DriverProfilePresenter { diff --git a/apps/api/src/domain/driver/presenters/DriverStatsPresenter.test.ts b/apps/api/src/domain/driver/presenters/DriverStatsPresenter.test.ts index b15805cae..37520970a 100644 --- a/apps/api/src/domain/driver/presenters/DriverStatsPresenter.test.ts +++ b/apps/api/src/domain/driver/presenters/DriverStatsPresenter.test.ts @@ -1,7 +1,6 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { Result } from '@core/shared/application/Result'; import { DriverStatsPresenter } from './DriverStatsPresenter'; -import type { GetTotalDriversResult } from '../../../../../core/racing/application/use-cases/GetTotalDriversUseCase'; +import type { GetTotalDriversResult } from '@core/racing/application/use-cases/GetTotalDriversUseCase'; describe('DriverStatsPresenter', () => { let presenter: DriverStatsPresenter; @@ -16,31 +15,13 @@ describe('DriverStatsPresenter', () => { totalDrivers: 42, }; - const result = Result.ok(output); + presenter.present(output); - presenter.present(result); - - const response = presenter.responseModel; + const response = presenter.getResponseModel(); expect(response).toEqual({ totalDrivers: 42, }); }); }); - - describe('reset', () => { - it('should reset the result', () => { - const output: GetTotalDriversResult = { - totalDrivers: 10, - }; - - const result = Result.ok(output); - - presenter.present(result); - expect(presenter.responseModel).toBeDefined(); - - presenter.reset(); - expect(() => presenter.responseModel).toThrow('Presenter not presented'); - }); - }); }); \ No newline at end of file diff --git a/apps/api/src/domain/driver/presenters/DriversLeaderboardPresenter.test.ts b/apps/api/src/domain/driver/presenters/DriversLeaderboardPresenter.test.ts index c484855f1..e872f196c 100644 --- a/apps/api/src/domain/driver/presenters/DriversLeaderboardPresenter.test.ts +++ b/apps/api/src/domain/driver/presenters/DriversLeaderboardPresenter.test.ts @@ -2,6 +2,8 @@ import { GetDriversLeaderboardResult } from '@core/racing/application/use-cases/ import { Result } from '@core/shared/application/Result'; import { beforeEach, describe, expect, it } from 'vitest'; import { DriversLeaderboardPresenter } from './DriversLeaderboardPresenter'; +import type { Driver } from '@core/racing/domain/entities/Driver'; +import type { SkillLevel } from '@core/racing/domain/services/SkillLevelService'; // TODO fix eslint issues @@ -19,11 +21,11 @@ describe('DriversLeaderboardPresenter', () => { { driver: { id: 'driver-1', - name: 'Driver One' as unknown, - country: 'US' as unknown, - } as unknown, + name: 'Driver One', + country: 'US', + } as unknown as Driver, rating: 2500, - skillLevel: 'advanced' as unknown, + skillLevel: 'advanced' as unknown as SkillLevel, racesCompleted: 50, wins: 10, podiums: 20, @@ -34,11 +36,11 @@ describe('DriversLeaderboardPresenter', () => { { driver: { id: 'driver-2', - name: 'Driver Two' as unknown, - country: 'DE' as unknown, - } as unknown, + name: 'Driver Two', + country: 'DE', + } as unknown as Driver, rating: 2400, - skillLevel: 'intermediate' as unknown, + skillLevel: 'intermediate' as unknown as SkillLevel, racesCompleted: 40, wins: 5, podiums: 15, @@ -56,9 +58,10 @@ describe('DriversLeaderboardPresenter', () => { presenter.present(result); + const output = presenter.getResponseModel(); - expect(result.drivers).toHaveLength(2); - expect(result.drivers[0]).toEqual({ + expect(output.drivers).toHaveLength(2); + expect(output.drivers[0]).toEqual({ id: 'driver-1', name: 'Driver One', rating: 2500, @@ -71,7 +74,7 @@ describe('DriversLeaderboardPresenter', () => { rank: 1, avatarUrl: 'https://example.com/avatar1.png', }); - expect(result.drivers[1]).toEqual({ + expect(output.drivers[1]).toEqual({ id: 'driver-2', name: 'Driver Two', rating: 2400, @@ -84,126 +87,10 @@ describe('DriversLeaderboardPresenter', () => { rank: 2, avatarUrl: 'https://example.com/avatar2.png', }); - expect(result.totalRaces).toBe(90); - expect(result.totalWins).toBe(15); - expect(result.activeCount).toBe(2); + expect(output.totalRaces).toBe(90); + expect(output.totalWins).toBe(15); + expect(output.activeCount).toBe(2); }); - it('should sort drivers by rating descending', () => { - const dto: DriversLeaderboardResultDTO = { - drivers: [ - { - id: 'driver-1', - name: 'Driver One', - country: 'US', - iracingId: '12345', - joinedAt: new Date(), - }, - { - id: 'driver-2', - name: 'Driver Two', - country: 'DE', - iracingId: '67890', - joinedAt: new Date(), - }, - ], - rankings: [ - { driverId: 'driver-1', rating: 2400, overallRank: 2 }, - { driverId: 'driver-2', rating: 2500, overallRank: 1 }, - ], - stats: {}, - avatarUrls: {}, - }; - - presenter.present(dto); - - const result = presenter.viewModel; - - expect(result.drivers[0].id).toBe('driver-2'); // Higher rating first - expect(result.drivers[1].id).toBe('driver-1'); - }); - - it('should handle missing stats gracefully', () => { - const dto: DriversLeaderboardResultDTO = { - drivers: [ - { - id: 'driver-1', - name: 'Driver One', - country: 'US', - iracingId: '12345', - joinedAt: new Date(), - }, - ], - rankings: [ - { driverId: 'driver-1', rating: 2500, overallRank: 1 }, - ], - stats: {}, // No stats - avatarUrls: {}, - }; - - presenter.present(dto); - - const result = presenter.viewModel; - - expect(result.drivers[0].racesCompleted).toBe(0); - expect(result.drivers[0].wins).toBe(0); - expect(result.drivers[0].podiums).toBe(0); - expect(result.drivers[0].isActive).toBe(false); - }); - - it('should derive skill level from rating bands', () => { - const dto: DriversLeaderboardResultDTO = { - drivers: [ - { id: 'd1', name: 'Beginner', country: 'US', iracingId: '1', joinedAt: new Date() }, - { id: 'd2', name: 'Intermediate', country: 'US', iracingId: '2', joinedAt: new Date() }, - { id: 'd3', name: 'Advanced', country: 'US', iracingId: '3', joinedAt: new Date() }, - { id: 'd4', name: 'Pro', country: 'US', iracingId: '4', joinedAt: new Date() }, - ], - rankings: [ - { driverId: 'd1', rating: 1700, overallRank: 4 }, - { driverId: 'd2', rating: 2000, overallRank: 3 }, - { driverId: 'd3', rating: 2600, overallRank: 2 }, - { driverId: 'd4', rating: 3100, overallRank: 1 }, - ], - stats: { - d1: { racesCompleted: 5, wins: 0, podiums: 0 }, - d2: { racesCompleted: 5, wins: 0, podiums: 0 }, - d3: { racesCompleted: 5, wins: 0, podiums: 0 }, - d4: { racesCompleted: 5, wins: 0, podiums: 0 }, - }, - avatarUrls: { - d1: 'avatar-1', - d2: 'avatar-2', - d3: 'avatar-3', - d4: 'avatar-4', - }, - }; - - presenter.present(dto); - const result = presenter.viewModel; - - const levels = result.drivers - .sort((a, b) => a.rating - b.rating) - .map(d => d.skillLevel); - - expect(levels).toEqual(['beginner', 'intermediate', 'advanced', 'pro']); - }); - }); - - describe('reset', () => { - it('should reset the result', () => { - const dto: DriversLeaderboardResultDTO = { - drivers: [], - rankings: [], - stats: {}, - avatarUrls: {}, - }; - - presenter.present(dto); - expect(presenter.viewModel).toBeDefined(); - - presenter.reset(); - expect(() => presenter.viewModel).toThrow('Presenter not presented'); - }); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/league/LeagueProviders.ts b/apps/api/src/domain/league/LeagueProviders.ts index f499bf456..c33cdc36c 100644 --- a/apps/api/src/domain/league/LeagueProviders.ts +++ b/apps/api/src/domain/league/LeagueProviders.ts @@ -8,6 +8,8 @@ import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeason 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 concrete in-memory implementations import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository'; @@ -52,6 +54,7 @@ import { WithdrawFromLeagueWalletUseCase } from '@core/racing/application/use-ca // Import presenters import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter'; +import { GetLeagueProtestsPresenter } from './presenters/GetLeagueProtestsPresenter'; // Define injection tokens export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository'; @@ -145,6 +148,10 @@ export const LeagueProviders: Provider[] = [ provide: 'AllLeaguesWithCapacityPresenter', useClass: AllLeaguesWithCapacityPresenter, }, + { + provide: 'GetLeagueProtestsPresenter', + useClass: GetLeagueProtestsPresenter, + }, // Use cases { provide: GetAllLeaguesWithCapacityUseCase, @@ -167,7 +174,17 @@ export const LeagueProviders: Provider[] = [ RemoveLeagueMemberUseCase, UpdateLeagueMemberRoleUseCase, GetLeagueOwnerSummaryUseCase, - GetLeagueProtestsUseCase, + { + provide: GetLeagueProtestsUseCase, + useFactory: ( + raceRepo: IRaceRepository, + protestRepo: IProtestRepository, + driverRepo: IDriverRepository, + leagueRepo: ILeagueRepository, + presenter: GetLeagueProtestsPresenter, + ) => new GetLeagueProtestsUseCase(raceRepo, protestRepo, driverRepo, leagueRepo, presenter), + inject: [RACE_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, 'GetLeagueProtestsPresenter'], + }, GetLeagueSeasonsUseCase, GetLeagueMembershipsUseCase, GetLeagueScheduleUseCase, diff --git a/apps/api/src/domain/league/LeagueService.ts b/apps/api/src/domain/league/LeagueService.ts index a07ddffab..6feb67787 100644 --- a/apps/api/src/domain/league/LeagueService.ts +++ b/apps/api/src/domain/league/LeagueService.ts @@ -257,9 +257,7 @@ export class LeagueService { if (result.isErr()) { throw new Error(result.unwrapErr().code); } - const presenter = new GetLeagueProtestsPresenter(); - presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return (this.getLeagueProtestsUseCase.outputPort as GetLeagueProtestsPresenter).getResponseModel()!; } async getLeagueSeasons(query: GetLeagueSeasonsQueryDTO): Promise { @@ -268,9 +266,7 @@ export class LeagueService { if (result.isErr()) { throw new Error(result.unwrapErr().code); } - const presenter = new GetLeagueSeasonsPresenter(); - presenter.present(result.unwrap()); - return presenter.getViewModel()!; + return (this.getLeagueSeasonsUseCase.output as GetLeagueSeasonsPresenter).getResponseModel()!; } async getLeagueMemberships(leagueId: string): Promise { diff --git a/apps/api/src/domain/league/dtos/AllLeaguesWithCapacityAndScoringDTO.ts b/apps/api/src/domain/league/dtos/AllLeaguesWithCapacityAndScoringDTO.ts index 6a95b5120..5dec87d5b 100644 --- a/apps/api/src/domain/league/dtos/AllLeaguesWithCapacityAndScoringDTO.ts +++ b/apps/api/src/domain/league/dtos/AllLeaguesWithCapacityAndScoringDTO.ts @@ -8,9 +8,9 @@ export class AllLeaguesWithCapacityAndScoringDTO { @IsArray() @ValidateNested({ each: true }) @Type(() => LeagueSummaryDTO) - leagues: LeagueSummaryDTO[]; + leagues!: LeagueSummaryDTO[]; @ApiProperty() @IsNumber() - totalCount: number; + totalCount!: number; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/AllLeaguesWithCapacityDTO.ts b/apps/api/src/domain/league/dtos/AllLeaguesWithCapacityDTO.ts index 2a5bc0b7c..7127eff49 100644 --- a/apps/api/src/domain/league/dtos/AllLeaguesWithCapacityDTO.ts +++ b/apps/api/src/domain/league/dtos/AllLeaguesWithCapacityDTO.ts @@ -1,5 +1,17 @@ import { ApiProperty } from '@nestjs/swagger'; +export interface LeagueSettings { + maxDrivers: number; + sessionDuration?: number; + visibility?: string; +} + +export interface SocialLinks { + discordUrl?: string; + youtubeUrl?: string; + websiteUrl?: string; +} + export class LeagueWithCapacityDTO { @ApiProperty() id!: string; @@ -14,21 +26,13 @@ export class LeagueWithCapacityDTO { ownerId!: string; @ApiProperty() - settings!: { - maxDrivers: number; - sessionDuration?: number; - visibility?: string; - }; + settings!: LeagueSettings; @ApiProperty() createdAt!: string; @ApiProperty({ nullable: true }) - socialLinks?: { - discordUrl?: string; - youtubeUrl?: string; - websiteUrl?: string; - }; + socialLinks?: SocialLinks; @ApiProperty() usedSlots!: number; diff --git a/apps/api/src/domain/league/dtos/ApproveJoinRequestInputDTO.ts b/apps/api/src/domain/league/dtos/ApproveJoinRequestInputDTO.ts index 5249b7b67..ca86922ec 100644 --- a/apps/api/src/domain/league/dtos/ApproveJoinRequestInputDTO.ts +++ b/apps/api/src/domain/league/dtos/ApproveJoinRequestInputDTO.ts @@ -4,9 +4,9 @@ import { IsString } from 'class-validator'; export class ApproveJoinRequestInputDTO { @ApiProperty() @IsString() - requestId: string; + requestId!: string; @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/ApproveJoinRequestOutputDTO.ts b/apps/api/src/domain/league/dtos/ApproveJoinRequestOutputDTO.ts index b29262b26..8cfd63559 100644 --- a/apps/api/src/domain/league/dtos/ApproveJoinRequestOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/ApproveJoinRequestOutputDTO.ts @@ -4,7 +4,7 @@ import { IsString, IsBoolean } from 'class-validator'; export class ApproveJoinRequestOutputDTO { @ApiProperty() @IsBoolean() - success: boolean; + success!: boolean; @ApiProperty({ required: false }) @IsString() diff --git a/apps/api/src/domain/league/dtos/CreateLeagueInputDTO.ts b/apps/api/src/domain/league/dtos/CreateLeagueInputDTO.ts index 2795b7d34..cecc26549 100644 --- a/apps/api/src/domain/league/dtos/CreateLeagueInputDTO.ts +++ b/apps/api/src/domain/league/dtos/CreateLeagueInputDTO.ts @@ -4,17 +4,17 @@ import { IsString, IsEnum } from 'class-validator'; export class CreateLeagueInputDTO { @ApiProperty() @IsString() - name: string; + name!: string; @ApiProperty() @IsString() - description: string; + description!: string; @ApiProperty({ enum: ['public', 'private'] }) @IsEnum(['public', 'private']) - visibility: 'public' | 'private'; + visibility!: 'public' | 'private'; @ApiProperty() @IsString() - ownerId: string; + ownerId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/CreateLeagueOutputDTO.ts b/apps/api/src/domain/league/dtos/CreateLeagueOutputDTO.ts index 914e92293..08f44db53 100644 --- a/apps/api/src/domain/league/dtos/CreateLeagueOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/CreateLeagueOutputDTO.ts @@ -4,9 +4,9 @@ import { IsString, IsBoolean } from 'class-validator'; export class CreateLeagueOutputDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty() @IsBoolean() - success: boolean; + success!: boolean; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueAdminConfigOutputDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueAdminConfigOutputDTO.ts index 22a2bad70..a032adbb9 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueAdminConfigOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueAdminConfigOutputDTO.ts @@ -8,5 +8,5 @@ export class GetLeagueAdminConfigOutputDTO { @IsOptional() @ValidateNested() @Type(() => LeagueConfigFormModelDTO) - form: LeagueConfigFormModelDTO | null; + form!: LeagueConfigFormModelDTO | null; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueAdminConfigQueryDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueAdminConfigQueryDTO.ts index a8aadccba..717af9447 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueAdminConfigQueryDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueAdminConfigQueryDTO.ts @@ -4,5 +4,5 @@ import { IsString } from 'class-validator'; export class GetLeagueAdminConfigQueryDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueAdminPermissionsInputDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueAdminPermissionsInputDTO.ts index 1927fd508..6a901fd94 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueAdminPermissionsInputDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueAdminPermissionsInputDTO.ts @@ -4,9 +4,9 @@ import { IsString } from 'class-validator'; export class GetLeagueAdminPermissionsInputDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty() @IsString() - performerDriverId: string; + performerDriverId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueJoinRequestsQueryDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueJoinRequestsQueryDTO.ts index ae2357a15..30706dee9 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueJoinRequestsQueryDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueJoinRequestsQueryDTO.ts @@ -4,5 +4,5 @@ import { IsString } from 'class-validator'; export class GetLeagueJoinRequestsQueryDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueOwnerSummaryQueryDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueOwnerSummaryQueryDTO.ts index 0c31ae3ff..b4507d305 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueOwnerSummaryQueryDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueOwnerSummaryQueryDTO.ts @@ -4,9 +4,9 @@ import { IsString } from 'class-validator'; export class GetLeagueOwnerSummaryQueryDTO { @ApiProperty() @IsString() - ownerId: string; + ownerId!: string; @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueProtestsQueryDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueProtestsQueryDTO.ts index 2ef063d4e..f435765f0 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueProtestsQueryDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueProtestsQueryDTO.ts @@ -4,5 +4,5 @@ import { IsString } from 'class-validator'; export class GetLeagueProtestsQueryDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueRacesOutputDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueRacesOutputDTO.ts index 189b55214..abc594634 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueRacesOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueRacesOutputDTO.ts @@ -3,5 +3,5 @@ import { RaceDTO } from '../../race/dtos/RaceDTO'; export class GetLeagueRacesOutputDTO { @ApiProperty({ type: [RaceDTO] }) - races: RaceDTO[]; + races!: RaceDTO[]; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueSeasonsQueryDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueSeasonsQueryDTO.ts index c352e1a14..bb6cb1cca 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueSeasonsQueryDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueSeasonsQueryDTO.ts @@ -4,5 +4,5 @@ import { IsString } from 'class-validator'; export class GetLeagueSeasonsQueryDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetLeagueWalletOutputDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueWalletOutputDTO.ts index f58939db2..ec04f7e2b 100644 --- a/apps/api/src/domain/league/dtos/GetLeagueWalletOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/GetLeagueWalletOutputDTO.ts @@ -2,28 +2,28 @@ import { ApiProperty } from '@nestjs/swagger'; export class WalletTransactionDTO { @ApiProperty() - id: string; + id!: string; @ApiProperty({ enum: ['sponsorship', 'membership', 'withdrawal', 'prize'] }) - type: 'sponsorship' | 'membership' | 'withdrawal' | 'prize'; + type!: 'sponsorship' | 'membership' | 'withdrawal' | 'prize'; @ApiProperty() - description: string; + description!: string; @ApiProperty() - amount: number; + amount!: number; @ApiProperty() - fee: number; + fee!: number; @ApiProperty() - netAmount: number; + netAmount!: number; @ApiProperty() - date: string; + date!: string; @ApiProperty({ enum: ['completed', 'pending', 'failed'] }) - status: 'completed' | 'pending' | 'failed'; + status!: 'completed' | 'pending' | 'failed'; @ApiProperty({ required: false }) reference?: string; @@ -31,29 +31,29 @@ export class WalletTransactionDTO { export class GetLeagueWalletOutputDTO { @ApiProperty() - balance: number; + balance!: number; @ApiProperty() - currency: string; + currency!: string; @ApiProperty() - totalRevenue: number; + totalRevenue!: number; @ApiProperty() - totalFees: number; + totalFees!: number; @ApiProperty() - totalWithdrawals: number; + totalWithdrawals!: number; @ApiProperty() - pendingPayouts: number; + pendingPayouts!: number; @ApiProperty() - canWithdraw: boolean; + canWithdraw!: boolean; @ApiProperty({ required: false }) withdrawalBlockReason?: string; @ApiProperty({ type: [WalletTransactionDTO] }) - transactions: WalletTransactionDTO[]; + transactions!: WalletTransactionDTO[]; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/GetSeasonSponsorshipsOutputDTO.ts b/apps/api/src/domain/league/dtos/GetSeasonSponsorshipsOutputDTO.ts index 4633e2de4..caaa2e4f7 100644 --- a/apps/api/src/domain/league/dtos/GetSeasonSponsorshipsOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/GetSeasonSponsorshipsOutputDTO.ts @@ -3,5 +3,5 @@ import { SponsorshipDetailDTO } from '../../sponsor/dtos/SponsorshipDetailDTO'; export class GetSeasonSponsorshipsOutputDTO { @ApiProperty({ type: [SponsorshipDetailDTO] }) - sponsorships: SponsorshipDetailDTO[]; + sponsorships!: SponsorshipDetailDTO[]; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueAdminConfigDTO.ts b/apps/api/src/domain/league/dtos/LeagueAdminConfigDTO.ts index 9832a0b8c..eb07284ed 100644 --- a/apps/api/src/domain/league/dtos/LeagueAdminConfigDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueAdminConfigDTO.ts @@ -8,5 +8,5 @@ export class LeagueAdminConfigDTO { @IsOptional() @ValidateNested() @Type(() => LeagueConfigFormModelDTO) - form: LeagueConfigFormModelDTO | null; + form!: LeagueConfigFormModelDTO | null; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueAdminDTO.ts b/apps/api/src/domain/league/dtos/LeagueAdminDTO.ts index e9811d442..795f629cb 100644 --- a/apps/api/src/domain/league/dtos/LeagueAdminDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueAdminDTO.ts @@ -12,27 +12,27 @@ export class LeagueAdminDTO { @IsArray() @ValidateNested({ each: true }) @Type(() => LeagueJoinRequestDTO) - joinRequests: LeagueJoinRequestDTO[]; + joinRequests!: LeagueJoinRequestDTO[]; @ApiProperty({ type: () => LeagueOwnerSummaryDTO, nullable: true }) @IsOptional() @ValidateNested() @Type(() => LeagueOwnerSummaryDTO) - ownerSummary: LeagueOwnerSummaryDTO | null; + ownerSummary!: LeagueOwnerSummaryDTO | null; @ApiProperty({ type: () => LeagueAdminConfigDTO }) @ValidateNested() @Type(() => LeagueAdminConfigDTO) - config: LeagueAdminConfigDTO; + config!: LeagueAdminConfigDTO; @ApiProperty({ type: () => LeagueAdminProtestsDTO }) @ValidateNested() @Type(() => LeagueAdminProtestsDTO) - protests: LeagueAdminProtestsDTO; + protests!: LeagueAdminProtestsDTO; @ApiProperty({ type: [LeagueSeasonSummaryDTO] }) @IsArray() @ValidateNested({ each: true }) @Type(() => LeagueSeasonSummaryDTO) - seasons: LeagueSeasonSummaryDTO[]; + seasons!: LeagueSeasonSummaryDTO[]; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueAdminPermissionsDTO.ts b/apps/api/src/domain/league/dtos/LeagueAdminPermissionsDTO.ts index 1bc0c5c87..15629155a 100644 --- a/apps/api/src/domain/league/dtos/LeagueAdminPermissionsDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueAdminPermissionsDTO.ts @@ -4,9 +4,9 @@ import { IsBoolean } from 'class-validator'; export class LeagueAdminPermissionsDTO { @ApiProperty() @IsBoolean() - canRemoveMember: boolean; + canRemoveMember!: boolean; @ApiProperty() @IsBoolean() - canUpdateRoles: boolean; + canUpdateRoles!: boolean; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueAdminProtestsDTO.ts b/apps/api/src/domain/league/dtos/LeagueAdminProtestsDTO.ts index cb2f766f9..a354caaaa 100644 --- a/apps/api/src/domain/league/dtos/LeagueAdminProtestsDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueAdminProtestsDTO.ts @@ -1,24 +1,24 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsArray, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; -import { DriverDto } from '../../driver/dto/DriverDto'; -import { RaceDto } from '../../race/dto/RaceDto'; +import { DriverDTO } from '../../driver/dtos/DriverDTO'; +import { RaceDTO } from '../../race/dtos/RaceDTO'; import { ProtestDTO } from './ProtestDTO'; export class LeagueAdminProtestsDTO { - @ApiProperty({ type: [ProtestDTO] }) + @ApiProperty({ type: [ProtestDTO] }) @IsArray() @ValidateNested({ each: true }) @Type(() => ProtestDTO) - protests: ProtestDTO[]; + protests!: ProtestDTO[]; - @ApiProperty({ type: () => RaceDto }) + @ApiProperty({ type: () => RaceDTO }) @ValidateNested() - @Type(() => RaceDto) - racesById: { [raceId: string]: RaceDto }; + @Type(() => RaceDTO) + racesById!: { [raceId: string]: RaceDTO }; - @ApiProperty({ type: () => DriverDto }) + @ApiProperty({ type: () => DriverDTO }) @ValidateNested() - @Type(() => DriverDto) - driversById: { [driverId: string]: DriverDto }; + @Type(() => DriverDTO) + driversById!: { [driverId: string]: DriverDTO }; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueConfigFormModelBasicsDTO.ts b/apps/api/src/domain/league/dtos/LeagueConfigFormModelBasicsDTO.ts index 1bacccbb5..147623900 100644 --- a/apps/api/src/domain/league/dtos/LeagueConfigFormModelBasicsDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueConfigFormModelBasicsDTO.ts @@ -4,13 +4,13 @@ import { IsString, IsEnum } from 'class-validator'; export class LeagueConfigFormModelBasicsDTO { @ApiProperty() @IsString() - name: string; + name!: string; @ApiProperty() @IsString() - description: string; + description!: string; @ApiProperty({ enum: ['public', 'private'] }) @IsEnum(['public', 'private']) - visibility: 'public' | 'private'; + visibility!: 'public' | 'private'; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueConfigFormModelDTO.ts b/apps/api/src/domain/league/dtos/LeagueConfigFormModelDTO.ts index 6087368df..3c5d3d344 100644 --- a/apps/api/src/domain/league/dtos/LeagueConfigFormModelDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueConfigFormModelDTO.ts @@ -11,39 +11,39 @@ import { LeagueConfigFormModelTimingsDTO } from './LeagueConfigFormModelTimingsD export class LeagueConfigFormModelDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty({ type: LeagueConfigFormModelBasicsDTO }) @ValidateNested() @Type(() => LeagueConfigFormModelBasicsDTO) - basics: LeagueConfigFormModelBasicsDTO; + basics!: LeagueConfigFormModelBasicsDTO; @ApiProperty({ type: LeagueConfigFormModelStructureDTO }) @ValidateNested() @Type(() => LeagueConfigFormModelStructureDTO) - structure: LeagueConfigFormModelStructureDTO; + structure!: LeagueConfigFormModelStructureDTO; @ApiProperty({ type: [Object] }) @IsArray() - championships: Object[]; + championships!: Object[]; @ApiProperty({ type: LeagueConfigFormModelScoringDTO }) @ValidateNested() @Type(() => LeagueConfigFormModelScoringDTO) - scoring: LeagueConfigFormModelScoringDTO; + scoring!: LeagueConfigFormModelScoringDTO; @ApiProperty({ type: LeagueConfigFormModelDropPolicyDTO }) @ValidateNested() @Type(() => LeagueConfigFormModelDropPolicyDTO) - dropPolicy: LeagueConfigFormModelDropPolicyDTO; + dropPolicy!: LeagueConfigFormModelDropPolicyDTO; @ApiProperty({ type: LeagueConfigFormModelTimingsDTO }) @ValidateNested() @Type(() => LeagueConfigFormModelTimingsDTO) - timings: LeagueConfigFormModelTimingsDTO; + timings!: LeagueConfigFormModelTimingsDTO; @ApiProperty({ type: LeagueConfigFormModelStewardingDTO }) @ValidateNested() @Type(() => LeagueConfigFormModelStewardingDTO) - stewarding: LeagueConfigFormModelStewardingDTO; + stewarding!: LeagueConfigFormModelStewardingDTO; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueConfigFormModelDropPolicyDTO.ts b/apps/api/src/domain/league/dtos/LeagueConfigFormModelDropPolicyDTO.ts index a91948475..8b95c80bf 100644 --- a/apps/api/src/domain/league/dtos/LeagueConfigFormModelDropPolicyDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueConfigFormModelDropPolicyDTO.ts @@ -4,7 +4,7 @@ import { IsNumber, IsOptional, IsEnum } from 'class-validator'; export class LeagueConfigFormModelDropPolicyDTO { @ApiProperty({ enum: ['none', 'worst_n'] }) @IsEnum(['none', 'worst_n']) - strategy: 'none' | 'worst_n'; + strategy!: 'none' | 'worst_n'; @ApiProperty({ required: false }) @IsOptional() diff --git a/apps/api/src/domain/league/dtos/LeagueConfigFormModelScoringDTO.ts b/apps/api/src/domain/league/dtos/LeagueConfigFormModelScoringDTO.ts index 911fc763c..ebbaafd09 100644 --- a/apps/api/src/domain/league/dtos/LeagueConfigFormModelScoringDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueConfigFormModelScoringDTO.ts @@ -4,9 +4,9 @@ import { IsString, IsNumber } from 'class-validator'; export class LeagueConfigFormModelScoringDTO { @ApiProperty() @IsString() - type: string; + type!: string; @ApiProperty() @IsNumber() - points: number; + points!: number; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueConfigFormModelStewardingDTO.ts b/apps/api/src/domain/league/dtos/LeagueConfigFormModelStewardingDTO.ts index d710012a9..c7db5251d 100644 --- a/apps/api/src/domain/league/dtos/LeagueConfigFormModelStewardingDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueConfigFormModelStewardingDTO.ts @@ -4,7 +4,7 @@ import { IsNumber, IsBoolean, IsOptional, IsEnum } from 'class-validator'; export class LeagueConfigFormModelStewardingDTO { @ApiProperty({ enum: ['single_steward', 'committee_vote'] }) @IsEnum(['single_steward', 'committee_vote']) - decisionMode: 'single_steward' | 'committee_vote'; + decisionMode!: 'single_steward' | 'committee_vote'; @ApiProperty({ required: false }) @IsOptional() @@ -13,29 +13,29 @@ export class LeagueConfigFormModelStewardingDTO { @ApiProperty() @IsBoolean() - requireDefense: boolean; + requireDefense!: boolean; @ApiProperty() @IsNumber() - defenseTimeLimit: number; + defenseTimeLimit!: number; @ApiProperty() @IsNumber() - voteTimeLimit: number; + voteTimeLimit!: number; @ApiProperty() @IsNumber() - protestDeadlineHours: number; + protestDeadlineHours!: number; @ApiProperty() @IsNumber() - stewardingClosesHours: number; + stewardingClosesHours!: number; @ApiProperty() @IsBoolean() - notifyAccusedOnProtest: boolean; + notifyAccusedOnProtest!: boolean; @ApiProperty() @IsBoolean() - notifyOnVoteRequired: boolean; + notifyOnVoteRequired!: boolean; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueConfigFormModelStructureDTO.ts b/apps/api/src/domain/league/dtos/LeagueConfigFormModelStructureDTO.ts index 03025442c..da82ce0a2 100644 --- a/apps/api/src/domain/league/dtos/LeagueConfigFormModelStructureDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueConfigFormModelStructureDTO.ts @@ -5,5 +5,5 @@ export class LeagueConfigFormModelStructureDTO { @ApiProperty() @IsString() @IsEnum(['solo', 'team']) - mode: 'solo' | 'team'; + mode!: 'solo' | 'team'; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueConfigFormModelTimingsDTO.ts b/apps/api/src/domain/league/dtos/LeagueConfigFormModelTimingsDTO.ts index e3e06ef70..240f6f5db 100644 --- a/apps/api/src/domain/league/dtos/LeagueConfigFormModelTimingsDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueConfigFormModelTimingsDTO.ts @@ -4,13 +4,13 @@ import { IsString, IsNumber } from 'class-validator'; export class LeagueConfigFormModelTimingsDTO { @ApiProperty() @IsString() - raceDayOfWeek: string; + raceDayOfWeek!: string; @ApiProperty() @IsNumber() - raceTimeHour: number; + raceTimeHour!: number; @ApiProperty() @IsNumber() - raceTimeMinute: number; + raceTimeMinute!: number; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueJoinRequestDTO.ts b/apps/api/src/domain/league/dtos/LeagueJoinRequestDTO.ts index fa9baa3d6..1ca6dd00e 100644 --- a/apps/api/src/domain/league/dtos/LeagueJoinRequestDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueJoinRequestDTO.ts @@ -2,23 +2,28 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsDate, IsOptional } from 'class-validator'; import { Type } from 'class-transformer'; +export interface DriverInfo { + id: string; + name: string; +} + export class LeagueJoinRequestDTO { @ApiProperty() @IsString() - id: string; + id!: string; @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty() @IsString() - driverId: string; + driverId!: string; @ApiProperty() @IsDate() @Type(() => Date) - requestedAt: Date; + requestedAt!: Date; @ApiProperty({ required: false }) @IsOptional() @@ -30,8 +35,5 @@ export class LeagueJoinRequestDTO { type: () => Object, }) @IsOptional() - driver?: { - id: string; - name: string; - }; + driver?: DriverInfo; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueMemberDTO.ts b/apps/api/src/domain/league/dtos/LeagueMemberDTO.ts index 57421047a..e5a544b44 100644 --- a/apps/api/src/domain/league/dtos/LeagueMemberDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueMemberDTO.ts @@ -1,24 +1,24 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsDate, IsEnum, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; -import { DriverDto } from '../../driver/dto/DriverDto'; +import { DriverDTO } from '../../driver/dtos/DriverDTO'; export class LeagueMemberDTO { @ApiProperty() @IsString() - driverId: string; + driverId!: string; - @ApiProperty({ type: () => DriverDto }) + @ApiProperty({ type: () => DriverDTO }) @ValidateNested() - @Type(() => DriverDto) - driver: DriverDto; + @Type(() => DriverDTO) + driver!: DriverDTO; @ApiProperty({ enum: ['owner', 'manager', 'member'] }) @IsEnum(['owner', 'manager', 'member']) - role: 'owner' | 'manager' | 'member'; + role!: 'owner' | 'manager' | 'member'; @ApiProperty() @IsDate() @Type(() => Date) - joinedAt: Date; + joinedAt!: Date; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueMembershipDTO.ts b/apps/api/src/domain/league/dtos/LeagueMembershipDTO.ts index 0c7a1e48f..c146e15cf 100644 --- a/apps/api/src/domain/league/dtos/LeagueMembershipDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueMembershipDTO.ts @@ -4,25 +4,25 @@ import { IsString, IsEnum } from 'class-validator'; export class LeagueMembershipDTO { @ApiProperty() @IsString() - id: string; + id!: string; @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty() @IsString() - driverId: string; + driverId!: string; @ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] }) @IsEnum(['owner', 'admin', 'steward', 'member']) - role: 'owner' | 'admin' | 'steward' | 'member'; + role!: 'owner' | 'admin' | 'steward' | 'member'; @ApiProperty({ enum: ['active', 'inactive', 'pending'] }) @IsEnum(['active', 'inactive', 'pending']) - status: 'active' | 'inactive' | 'pending'; + status!: 'active' | 'inactive' | 'pending'; @ApiProperty() @IsString() - joinedAt: string; + joinedAt!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueMembershipsDTO.ts b/apps/api/src/domain/league/dtos/LeagueMembershipsDTO.ts index 090ea7e34..caacadff7 100644 --- a/apps/api/src/domain/league/dtos/LeagueMembershipsDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueMembershipsDTO.ts @@ -8,5 +8,5 @@ export class LeagueMembershipsDTO { @IsArray() @ValidateNested({ each: true }) @Type(() => LeagueMemberDTO) - members: LeagueMemberDTO[]; + members!: LeagueMemberDTO[]; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueOwnerSummaryDTO.ts b/apps/api/src/domain/league/dtos/LeagueOwnerSummaryDTO.ts index 62ad5a8e2..bc64e5f9c 100644 --- a/apps/api/src/domain/league/dtos/LeagueOwnerSummaryDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueOwnerSummaryDTO.ts @@ -1,21 +1,21 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNumber, IsOptional, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; -import { DriverDto } from '../../driver/dto/DriverDto'; +import { DriverDTO } from '../../driver/dtos/DriverDTO'; export class LeagueOwnerSummaryDTO { - @ApiProperty({ type: () => DriverDto }) + @ApiProperty({ type: () => DriverDTO }) @ValidateNested() - @Type(() => DriverDto) - driver: DriverDto; + @Type(() => DriverDTO) + driver!: DriverDTO; @ApiProperty({ nullable: true }) @IsOptional() @IsNumber() - rating: number | null; + rating!: number | null; @ApiProperty({ nullable: true }) @IsOptional() @IsNumber() - rank: number | null; + rank!: number | null; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueRoleDTO.ts b/apps/api/src/domain/league/dtos/LeagueRoleDTO.ts index cb6008932..5bdad4060 100644 --- a/apps/api/src/domain/league/dtos/LeagueRoleDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueRoleDTO.ts @@ -4,5 +4,5 @@ import { IsEnum } from 'class-validator'; export class LeagueRoleDTO { @ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] }) @IsEnum(['owner', 'admin', 'steward', 'member']) - value: 'owner' | 'admin' | 'steward' | 'member'; + value!: 'owner' | 'admin' | 'steward' | 'member'; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueScheduleDTO.ts b/apps/api/src/domain/league/dtos/LeagueScheduleDTO.ts index 07a420a12..c8a91400a 100644 --- a/apps/api/src/domain/league/dtos/LeagueScheduleDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueScheduleDTO.ts @@ -1,12 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsArray, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; -import { RaceDto } from '../../race/dto/RaceDto'; +import { RaceDTO } from '../../race/dtos/RaceDTO'; export class LeagueScheduleDTO { - @ApiProperty({ type: [RaceDto] }) + @ApiProperty({ type: [RaceDTO] }) @IsArray() @ValidateNested({ each: true }) - @Type(() => RaceDto) - races: RaceDto[]; + @Type(() => RaceDTO) + races!: RaceDTO[]; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueScoringPresetDTO.ts b/apps/api/src/domain/league/dtos/LeagueScoringPresetDTO.ts index b80c334b5..c1495a149 100644 --- a/apps/api/src/domain/league/dtos/LeagueScoringPresetDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueScoringPresetDTO.ts @@ -4,29 +4,29 @@ import { IsString, IsEnum } from 'class-validator'; export class LeagueScoringPresetDTO { @ApiProperty() @IsString() - id: string; + id!: string; @ApiProperty() @IsString() - name: string; + name!: string; @ApiProperty() @IsString() - description: string; + description!: string; @ApiProperty({ enum: ['driver', 'team', 'nations', 'trophy'] }) @IsEnum(['driver', 'team', 'nations', 'trophy']) - primaryChampionshipType: 'driver' | 'team' | 'nations' | 'trophy'; + primaryChampionshipType!: 'driver' | 'team' | 'nations' | 'trophy'; @ApiProperty() @IsString() - sessionSummary: string; + sessionSummary!: string; @ApiProperty() @IsString() - bonusSummary: string; + bonusSummary!: string; @ApiProperty() @IsString() - dropPolicySummary: string; + dropPolicySummary!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueSeasonSummaryDTO.ts b/apps/api/src/domain/league/dtos/LeagueSeasonSummaryDTO.ts index fb91dd8e0..fccc6a8ee 100644 --- a/apps/api/src/domain/league/dtos/LeagueSeasonSummaryDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueSeasonSummaryDTO.ts @@ -5,15 +5,15 @@ import { Type } from 'class-transformer'; export class LeagueSeasonSummaryDTO { @ApiProperty() @IsString() - seasonId: string; + seasonId!: string; @ApiProperty() @IsString() - name: string; + name!: string; @ApiProperty() @IsString() - status: string; + status!: string; @ApiProperty({ required: false }) @IsOptional() @@ -29,9 +29,9 @@ export class LeagueSeasonSummaryDTO { @ApiProperty() @IsBoolean() - isPrimary: boolean; + isPrimary!: boolean; @ApiProperty() @IsBoolean() - isParallelActive: boolean; + isParallelActive!: boolean; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueStandingDTO.ts b/apps/api/src/domain/league/dtos/LeagueStandingDTO.ts index 2045b4501..e20e09b20 100644 --- a/apps/api/src/domain/league/dtos/LeagueStandingDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueStandingDTO.ts @@ -1,23 +1,23 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsNumber, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; -import { DriverDto } from '../../driver/dto/DriverDto'; +import { DriverDTO } from '../../driver/dtos/DriverDTO'; export class LeagueStandingDTO { @ApiProperty() @IsString() - driverId: string; + driverId!: string; - @ApiProperty({ type: () => DriverDto }) + @ApiProperty({ type: () => DriverDTO }) @ValidateNested() - @Type(() => DriverDto) - driver: DriverDto; + @Type(() => DriverDTO) + driver!: DriverDTO; @ApiProperty() @IsNumber() - points: number; + points!: number; @ApiProperty() @IsNumber() - rank: number; + rank!: number; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueStandingsDTO.ts b/apps/api/src/domain/league/dtos/LeagueStandingsDTO.ts index 9d8700316..22aaf826c 100644 --- a/apps/api/src/domain/league/dtos/LeagueStandingsDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueStandingsDTO.ts @@ -8,5 +8,5 @@ export class LeagueStandingsDTO { @IsArray() @ValidateNested({ each: true }) @Type(() => LeagueStandingDTO) - standings: LeagueStandingDTO[]; + standings!: LeagueStandingDTO[]; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueStatsDTO.ts b/apps/api/src/domain/league/dtos/LeagueStatsDTO.ts index 66489b843..9e39f395a 100644 --- a/apps/api/src/domain/league/dtos/LeagueStatsDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueStatsDTO.ts @@ -4,13 +4,13 @@ import { IsNumber } from 'class-validator'; export class LeagueStatsDTO { @ApiProperty() @IsNumber() - totalMembers: number; + totalMembers!: number; @ApiProperty() @IsNumber() - totalRaces: number; + totalRaces!: number; @ApiProperty() @IsNumber() - averageRating: number; + averageRating!: number; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/LeagueSummaryDTO.ts b/apps/api/src/domain/league/dtos/LeagueSummaryDTO.ts index 55e5fccb2..eca077393 100644 --- a/apps/api/src/domain/league/dtos/LeagueSummaryDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueSummaryDTO.ts @@ -4,11 +4,11 @@ import { IsString, IsNumber, IsBoolean, IsOptional } from 'class-validator'; export class LeagueSummaryDTO { @ApiProperty() @IsString() - id: string; + id!: string; @ApiProperty() @IsString() - name: string; + name!: string; @ApiProperty({ nullable: true }) @IsOptional() @@ -27,19 +27,19 @@ export class LeagueSummaryDTO { @ApiProperty() @IsNumber() - memberCount: number; + memberCount!: number; @ApiProperty() @IsNumber() - maxMembers: number; + maxMembers!: number; @ApiProperty() @IsBoolean() - isPublic: boolean; + isPublic!: boolean; @ApiProperty() @IsString() - ownerId: string; + ownerId!: string; @ApiProperty({ nullable: true }) @IsOptional() diff --git a/apps/api/src/domain/league/dtos/LeagueWithCapacityDTO.ts b/apps/api/src/domain/league/dtos/LeagueWithCapacityDTO.ts index bf95af5b2..ff56c4d7d 100644 --- a/apps/api/src/domain/league/dtos/LeagueWithCapacityDTO.ts +++ b/apps/api/src/domain/league/dtos/LeagueWithCapacityDTO.ts @@ -6,11 +6,11 @@ import { LeagueSettingsDTO } from './LeagueSettingsDTO'; export class LeagueWithCapacityDTO { @ApiProperty() @IsString() - id: string; + id!: string; @ApiProperty() @IsString() - name: string; + name!: string; // ... other properties of LeagueWithCapacityDTO @ApiProperty({ nullable: true }) @@ -20,20 +20,20 @@ export class LeagueWithCapacityDTO { @ApiProperty() @IsString() - ownerId: string; + ownerId!: string; @ApiProperty({ type: () => LeagueSettingsDTO }) @ValidateNested() @Type(() => LeagueSettingsDTO) - settings: LeagueSettingsDTO; + settings!: LeagueSettingsDTO; @ApiProperty() @IsString() - createdAt: string; + createdAt!: string; @ApiProperty() @IsNumber() - usedSlots: number; + usedSlots!: number; @ApiProperty({ type: () => Object, nullable: true }) // Using Object for generic social links @IsOptional() diff --git a/apps/api/src/domain/league/dtos/MembershipRoleDTO.ts b/apps/api/src/domain/league/dtos/MembershipRoleDTO.ts index 58bde2186..64d0c9fc7 100644 --- a/apps/api/src/domain/league/dtos/MembershipRoleDTO.ts +++ b/apps/api/src/domain/league/dtos/MembershipRoleDTO.ts @@ -4,5 +4,5 @@ import { IsEnum } from 'class-validator'; export class MembershipRoleDTO { @ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] }) @IsEnum(['owner', 'admin', 'steward', 'member']) - value: 'owner' | 'admin' | 'steward' | 'member'; + value!: 'owner' | 'admin' | 'steward' | 'member'; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/MembershipStatusDTO.ts b/apps/api/src/domain/league/dtos/MembershipStatusDTO.ts index 804c631de..9d852ab91 100644 --- a/apps/api/src/domain/league/dtos/MembershipStatusDTO.ts +++ b/apps/api/src/domain/league/dtos/MembershipStatusDTO.ts @@ -4,5 +4,5 @@ import { IsEnum } from 'class-validator'; export class MembershipStatusDTO { @ApiProperty({ enum: ['active', 'inactive', 'pending'] }) @IsEnum(['active', 'inactive', 'pending']) - value: 'active' | 'inactive' | 'pending'; + value!: 'active' | 'inactive' | 'pending'; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/ProtestDTO.ts b/apps/api/src/domain/league/dtos/ProtestDTO.ts index 78252ef1b..5e4c81084 100644 --- a/apps/api/src/domain/league/dtos/ProtestDTO.ts +++ b/apps/api/src/domain/league/dtos/ProtestDTO.ts @@ -13,34 +13,34 @@ import { Type } from 'class-transformer'; export class ProtestDTO { @ApiProperty() @IsString() - id: string; + id!: string; @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty() @IsString() - raceId: string; + raceId!: string; @ApiProperty() @IsString() - protestingDriverId: string; + protestingDriverId!: string; @ApiProperty() @IsString() - accusedDriverId: string; + accusedDriverId!: string; @ApiProperty() @IsDate() @Type(() => Date) - submittedAt: Date; + submittedAt!: Date; @ApiProperty() @IsString() - description: string; + description!: string; @ApiProperty({ enum: ['pending', 'accepted', 'rejected'] }) @IsEnum(['pending', 'accepted', 'rejected']) - status: 'pending' | 'accepted' | 'rejected'; + status!: 'pending' | 'accepted' | 'rejected'; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/RejectJoinRequestInputDTO.ts b/apps/api/src/domain/league/dtos/RejectJoinRequestInputDTO.ts index 4110afb74..130d7c05e 100644 --- a/apps/api/src/domain/league/dtos/RejectJoinRequestInputDTO.ts +++ b/apps/api/src/domain/league/dtos/RejectJoinRequestInputDTO.ts @@ -4,9 +4,9 @@ import { IsString } from 'class-validator'; export class RejectJoinRequestInputDTO { @ApiProperty() @IsString() - requestId: string; + requestId!: string; @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/RejectJoinRequestOutputDTO.ts b/apps/api/src/domain/league/dtos/RejectJoinRequestOutputDTO.ts index c873dc91a..fa758d91b 100644 --- a/apps/api/src/domain/league/dtos/RejectJoinRequestOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/RejectJoinRequestOutputDTO.ts @@ -4,7 +4,7 @@ import { IsString, IsBoolean } from 'class-validator'; export class RejectJoinRequestOutputDTO { @ApiProperty() @IsBoolean() - success: boolean; + success!: boolean; @ApiProperty({ required: false }) @IsString() diff --git a/apps/api/src/domain/league/dtos/RemoveLeagueMemberInputDTO.ts b/apps/api/src/domain/league/dtos/RemoveLeagueMemberInputDTO.ts index b1350b40a..e04cc8d46 100644 --- a/apps/api/src/domain/league/dtos/RemoveLeagueMemberInputDTO.ts +++ b/apps/api/src/domain/league/dtos/RemoveLeagueMemberInputDTO.ts @@ -4,13 +4,13 @@ import { IsString } from 'class-validator'; export class RemoveLeagueMemberInputDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty() @IsString() - performerDriverId: string; + performerDriverId!: string; @ApiProperty() @IsString() - targetDriverId: string; + targetDriverId!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/RemoveLeagueMemberOutputDTO.ts b/apps/api/src/domain/league/dtos/RemoveLeagueMemberOutputDTO.ts index 4a1ad8483..2235f925d 100644 --- a/apps/api/src/domain/league/dtos/RemoveLeagueMemberOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/RemoveLeagueMemberOutputDTO.ts @@ -4,7 +4,7 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator'; export class RemoveLeagueMemberOutputDTO { @ApiProperty() @IsBoolean() - success: boolean; + success!: boolean; @ApiProperty({ required: false }) @IsOptional() diff --git a/apps/api/src/domain/league/dtos/SeasonDTO.ts b/apps/api/src/domain/league/dtos/SeasonDTO.ts index ab0d7fe96..cee25bbc8 100644 --- a/apps/api/src/domain/league/dtos/SeasonDTO.ts +++ b/apps/api/src/domain/league/dtos/SeasonDTO.ts @@ -5,15 +5,15 @@ import { Type } from 'class-transformer'; export class SeasonDTO { @ApiProperty() @IsString() - seasonId: string; + seasonId!: string; @ApiProperty() @IsString() - name: string; + name!: string; @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty({ required: false }) @IsOptional() @@ -29,11 +29,11 @@ export class SeasonDTO { @ApiProperty({ enum: ['planned', 'active', 'completed'] }) @IsEnum(['planned', 'active', 'completed']) - status: 'planned' | 'active' | 'completed'; + status!: 'planned' | 'active' | 'completed'; @ApiProperty() @IsBoolean() - isPrimary: boolean; + isPrimary!: boolean; @ApiProperty({ required: false }) @IsOptional() diff --git a/apps/api/src/domain/league/dtos/TotalLeaguesDTO.ts b/apps/api/src/domain/league/dtos/TotalLeaguesDTO.ts index 07535fc0c..713e6ba1f 100644 --- a/apps/api/src/domain/league/dtos/TotalLeaguesDTO.ts +++ b/apps/api/src/domain/league/dtos/TotalLeaguesDTO.ts @@ -4,5 +4,5 @@ import { IsNumber } from 'class-validator'; export class TotalLeaguesDTO { @ApiProperty() @IsNumber() - totalLeagues: number; + totalLeagues!: number; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/UpdateLeagueMemberRoleInputDTO.ts b/apps/api/src/domain/league/dtos/UpdateLeagueMemberRoleInputDTO.ts index 2b55ef26e..ab793d759 100644 --- a/apps/api/src/domain/league/dtos/UpdateLeagueMemberRoleInputDTO.ts +++ b/apps/api/src/domain/league/dtos/UpdateLeagueMemberRoleInputDTO.ts @@ -4,17 +4,17 @@ import { IsString, IsEnum } from 'class-validator'; export class UpdateLeagueMemberRoleInputDTO { @ApiProperty() @IsString() - leagueId: string; + leagueId!: string; @ApiProperty() @IsString() - performerDriverId: string; + performerDriverId!: string; @ApiProperty() @IsString() - targetDriverId: string; + targetDriverId!: string; @ApiProperty({ enum: ['owner', 'manager', 'member'] }) @IsEnum(['owner', 'manager', 'member']) - newRole: 'owner' | 'manager' | 'member'; + newRole!: 'owner' | 'manager' | 'member'; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/UpdateLeagueMemberRoleOutputDTO.ts b/apps/api/src/domain/league/dtos/UpdateLeagueMemberRoleOutputDTO.ts index 290770c84..5a042d601 100644 --- a/apps/api/src/domain/league/dtos/UpdateLeagueMemberRoleOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/UpdateLeagueMemberRoleOutputDTO.ts @@ -4,7 +4,7 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator'; export class UpdateLeagueMemberRoleOutputDTO { @ApiProperty() @IsBoolean() - success: boolean; + success!: boolean; @ApiProperty({ required: false }) @IsOptional() diff --git a/apps/api/src/domain/league/dtos/WithdrawFromLeagueWalletInputDTO.ts b/apps/api/src/domain/league/dtos/WithdrawFromLeagueWalletInputDTO.ts index 9fdf43b0d..37edd2df8 100644 --- a/apps/api/src/domain/league/dtos/WithdrawFromLeagueWalletInputDTO.ts +++ b/apps/api/src/domain/league/dtos/WithdrawFromLeagueWalletInputDTO.ts @@ -2,14 +2,14 @@ import { ApiProperty } from '@nestjs/swagger'; export class WithdrawFromLeagueWalletInputDTO { @ApiProperty() - amount: number; + amount!: number; @ApiProperty() - currency: string; + currency!: string; @ApiProperty() - seasonId: string; + seasonId!: string; @ApiProperty() - destinationAccount: string; + destinationAccount!: string; } \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/WithdrawFromLeagueWalletOutputDTO.ts b/apps/api/src/domain/league/dtos/WithdrawFromLeagueWalletOutputDTO.ts index 74d947be5..08c020777 100644 --- a/apps/api/src/domain/league/dtos/WithdrawFromLeagueWalletOutputDTO.ts +++ b/apps/api/src/domain/league/dtos/WithdrawFromLeagueWalletOutputDTO.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; export class WithdrawFromLeagueWalletOutputDTO { @ApiProperty() - success: boolean; + success!: boolean; @ApiProperty({ required: false }) message?: string; diff --git a/apps/api/src/domain/league/dtos/WizardStepDTO.ts b/apps/api/src/domain/league/dtos/WizardStepDTO.ts index 95bb68020..3e3e31257 100644 --- a/apps/api/src/domain/league/dtos/WizardStepDTO.ts +++ b/apps/api/src/domain/league/dtos/WizardStepDTO.ts @@ -4,5 +4,5 @@ import { IsEnum } from 'class-validator'; export class WizardStepDTO { @ApiProperty({ enum: [1, 2, 3, 4, 5, 6, 7] }) @IsEnum([1, 2, 3, 4, 5, 6, 7]) - value: 1 | 2 | 3 | 4 | 5 | 6 | 7; + value!: 1 | 2 | 3 | 4 | 5 | 6 | 7; } \ No newline at end of file diff --git a/apps/api/src/domain/league/presenters/GetLeagueOwnerSummaryPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueOwnerSummaryPresenter.ts index c6e3fb76e..69d3e7155 100644 --- a/apps/api/src/domain/league/presenters/GetLeagueOwnerSummaryPresenter.ts +++ b/apps/api/src/domain/league/presenters/GetLeagueOwnerSummaryPresenter.ts @@ -1,30 +1,26 @@ -import { GetLeagueOwnerSummaryOutputPort } from '@core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application'; +import type { GetLeagueOwnerSummaryResult } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase'; import { LeagueOwnerSummaryDTO } from '../dtos/LeagueOwnerSummaryDTO'; -export class GetLeagueOwnerSummaryPresenter { +export class GetLeagueOwnerSummaryPresenter implements UseCaseOutputPort { private result: LeagueOwnerSummaryDTO | null = null; reset() { this.result = null; } - present(output: GetLeagueOwnerSummaryOutputPort) { - if (!output.summary) { - this.result = null; - return; - } - + present(output: 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: output.owner.id, + iracingId: output.owner.iracingId.toString(), + name: output.owner.name.toString(), + country: output.owner.country.toString(), + joinedAt: output.owner.joinedAt.toDate().toISOString(), + ...(output.owner.bio ? { bio: output.owner.bio.toString() } : {}), }, - rating: output.summary.rating, - rank: output.summary.rank, + rating: output.rating, + rank: output.rank, }; } diff --git a/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts index 5768d52ee..a22440f31 100644 --- a/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts +++ b/apps/api/src/domain/league/presenters/GetLeagueProtestsPresenter.ts @@ -1,10 +1,11 @@ -import { GetLeagueProtestsOutputPort, type ProtestOutputPort } from '@core/racing/application/ports/output/GetLeagueProtestsOutputPort'; +import { Presenter } from '@core/shared/presentation'; +import type { GetLeagueProtestsResult } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase'; import { LeagueAdminProtestsDTO } from '../dtos/LeagueAdminProtestsDTO'; import { ProtestDTO } from '../dtos/ProtestDTO'; import { RaceDTO } from '../../race/dtos/RaceDTO'; import { DriverDTO } from '../../driver/dtos/DriverDTO'; -function mapProtestStatus(status: ProtestOutputPort['status']): ProtestDTO['status'] { +function mapProtestStatus(status: string): ProtestDTO['status'] { switch (status) { case 'pending': case 'awaiting_defense': @@ -20,53 +21,63 @@ function mapProtestStatus(status: ProtestOutputPort['status']): ProtestDTO['stat } } -export class GetLeagueProtestsPresenter { +export class GetLeagueProtestsPresenter implements Presenter { private result: LeagueAdminProtestsDTO | null = null; reset() { this.result = null; } - present(output: GetLeagueProtestsOutputPort, leagueName?: string) { - const protests: ProtestDTO[] = output.protests.map((protest) => { - const race = output.racesById[protest.raceId]; + present(input: GetLeagueProtestsResult) { + const protests: ProtestDTO[] = input.protests.map((protestWithEntities) => { + const { protest, race } = protestWithEntities; return { - id: protest.id, - leagueId: race?.leagueId || '', - raceId: protest.raceId, - protestingDriverId: protest.protestingDriverId, - accusedDriverId: protest.accusedDriverId, + id: protest.id.toString(), + leagueId: race?.leagueId.toString() || '', + raceId: protest.raceId.toString(), + protestingDriverId: protest.protestingDriverId.toString(), + accusedDriverId: protest.accusedDriverId.toString(), submittedAt: new Date(protest.filedAt), - description: protest.incident.description, - status: mapProtestStatus(protest.status), + description: protest.incident.description.toString(), + status: mapProtestStatus(protest.status.toString()), }; }); const racesById: { [raceId: string]: RaceDTO } = {}; - for (const raceId in output.racesById) { - const race = output.racesById[raceId]; + for (const protestWithEntities of input.protests) { + const { race } = protestWithEntities; if (race) { - racesById[raceId] = { - id: race.id, - name: race.track, + racesById[race.id.toString()] = { + id: race.id.toString(), + name: race.track.toString(), date: race.scheduledAt.toISOString(), - leagueName, + leagueName: input.league.name.toString(), }; } } const driversById: { [driverId: string]: DriverDTO } = {}; - for (const driverId in output.driversById) { - const driver = output.driversById[driverId]; - if (driver) { - driversById[driverId] = { - id: driver.id, - iracingId: driver.iracingId, - name: driver.name, - country: driver.country, - bio: driver.bio, - joinedAt: driver.joinedAt, + for (const protestWithEntities of input.protests) { + const { protestingDriver, accusedDriver } = protestWithEntities; + if (protestingDriver) { + driversById[protestingDriver.id.toString()] = { + id: protestingDriver.id.toString(), + iracingId: protestingDriver.iracingId.toString(), + name: protestingDriver.name.toString(), + country: protestingDriver.country.toString(), + bio: protestingDriver.bio?.toString(), + joinedAt: protestingDriver.joinedAt.toDate().toISOString(), + }; + } + if (accusedDriver) { + driversById[accusedDriver.id.toString()] = { + id: accusedDriver.id.toString(), + iracingId: accusedDriver.iracingId.toString(), + name: accusedDriver.name.toString(), + country: accusedDriver.country.toString(), + bio: accusedDriver.bio?.toString(), + joinedAt: accusedDriver.joinedAt.toISOString(), }; } } @@ -78,7 +89,7 @@ export class GetLeagueProtestsPresenter { }; } - getViewModel(): LeagueAdminProtestsDTO | null { + getResponseModel(): LeagueAdminProtestsDTO | null { return this.result; } } \ No newline at end of file diff --git a/apps/api/src/domain/league/presenters/GetLeagueSeasonsPresenter.ts b/apps/api/src/domain/league/presenters/GetLeagueSeasonsPresenter.ts index cf75262b9..512923eb1 100644 --- a/apps/api/src/domain/league/presenters/GetLeagueSeasonsPresenter.ts +++ b/apps/api/src/domain/league/presenters/GetLeagueSeasonsPresenter.ts @@ -1,26 +1,27 @@ -import { GetLeagueSeasonsOutputPort } from '@core/racing/application/ports/output/GetLeagueSeasonsOutputPort'; +import { Presenter } from '@core/shared/presentation'; +import type { GetLeagueSeasonsResult } from '@core/racing/application/use-cases/GetLeagueSeasonsUseCase'; import { LeagueSeasonSummaryDTO } from '../dtos/LeagueSeasonSummaryDTO'; -export class GetLeagueSeasonsPresenter { +export class GetLeagueSeasonsPresenter implements Presenter { private result: LeagueSeasonSummaryDTO[] | null = null; reset() { this.result = null; } - present(output: GetLeagueSeasonsOutputPort) { - this.result = output.seasons.map(season => ({ - seasonId: season.seasonId, - name: season.name, - status: season.status, - startDate: season.startDate, - endDate: season.endDate, - isPrimary: season.isPrimary, - isParallelActive: season.isParallelActive, + present(input: GetLeagueSeasonsResult) { + this.result = input.seasons.map(seasonSummary => ({ + seasonId: seasonSummary.season.id.toString(), + name: seasonSummary.season.name.toString(), + status: seasonSummary.season.status.toString(), + startDate: seasonSummary.season.startDate.toISOString(), + endDate: seasonSummary.season.endDate?.toISOString(), + isPrimary: seasonSummary.isPrimary, + isParallelActive: seasonSummary.isParallelActive, })); } - getViewModel(): LeagueSeasonSummaryDTO[] | null { + getResponseModel(): LeagueSeasonSummaryDTO[] | null { return this.result; } } \ No newline at end of file diff --git a/apps/api/src/domain/media/MediaController.test.ts b/apps/api/src/domain/media/MediaController.test.ts index 7b8f6716f..95b53b3da 100644 --- a/apps/api/src/domain/media/MediaController.test.ts +++ b/apps/api/src/domain/media/MediaController.test.ts @@ -217,7 +217,7 @@ describe('MediaController', () => { describe('updateAvatar', () => { it('should update avatar and return result', async () => { const driverId = 'driver-123'; - const input = { mediaUrl: 'https://example.com/new-avatar.png' } as UpdateAvatarOutputDTO; + const input: UpdateAvatarInputDTO = { avatarUrl: 'https://example.com/new-avatar.png' }; const dto: UpdateAvatarOutputDTO = { success: true, }; @@ -225,9 +225,9 @@ describe('MediaController', () => { const res = createMockResponse(); - await controller.updateAvatar(driverId, input as any, res); + await controller.updateAvatar(driverId, input, res); - expect(service.updateAvatar).toHaveBeenCalledWith(driverId, input as any); + expect(service.updateAvatar).toHaveBeenCalledWith(driverId, input); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith(dto); }); diff --git a/apps/api/src/domain/media/MediaService.ts b/apps/api/src/domain/media/MediaService.ts index c539f4ec8..a371f75d1 100644 --- a/apps/api/src/domain/media/MediaService.ts +++ b/apps/api/src/domain/media/MediaService.ts @@ -92,7 +92,7 @@ export class MediaService { } async uploadMedia( - input: UploadMediaInput & { file: Express.Multer.File } & { userId?: string; metadata?: Record }, + input: UploadMediaInput & { file: Express.Multer.File } & { userId?: string; metadata?: Record }, ): Promise { this.logger.debug('[MediaService] Uploading media.'); diff --git a/apps/api/src/domain/media/presenters/UpdateAvatarPresenter.ts b/apps/api/src/domain/media/presenters/UpdateAvatarPresenter.ts index 76ae97453..f3865085d 100644 --- a/apps/api/src/domain/media/presenters/UpdateAvatarPresenter.ts +++ b/apps/api/src/domain/media/presenters/UpdateAvatarPresenter.ts @@ -11,7 +11,7 @@ export class UpdateAvatarPresenter implements UseCaseOutputPort { const presenter = await this.raceService.getRaceDetail({ raceId, driverId }); - return presenter.viewModel; + return await presenter.viewModel; } @Get(':raceId/results') @@ -74,7 +74,7 @@ export class RaceController { @ApiResponse({ status: 200, description: 'Race results detail', type: RaceResultsDetailDTO }) async getRaceResultsDetail(@Param('raceId') raceId: string): Promise { const presenter = await this.raceService.getRaceResultsDetail(raceId); - return presenter.viewModel; + return await presenter.viewModel; } @Get(':raceId/sof') diff --git a/apps/api/src/domain/race/RaceService.ts b/apps/api/src/domain/race/RaceService.ts index 08044c77b..608256969 100644 --- a/apps/api/src/domain/race/RaceService.ts +++ b/apps/api/src/domain/race/RaceService.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import type { RaceDetailOutputPort } from '@core/racing/application/ports/output/RaceDetailOutputPort'; import type { RacesPageOutputPort } from '@core/racing/application/ports/output/RacesPageOutputPort'; import type { RaceResultsDetailOutputPort } from '@core/racing/application/ports/output/RaceResultsDetailOutputPort'; import type { RaceWithSOFOutputPort } from '@core/racing/application/ports/output/RaceWithSOFOutputPort'; @@ -130,14 +129,15 @@ export class RaceService { async getRaceDetail(params: GetRaceDetailParamsDTO): Promise { this.logger.debug('[RaceService] Fetching race detail:', params); + const presenter = new RaceDetailPresenter(this.driverRatingProvider, this.imageService, params); + this.getRaceDetailUseCase.setOutput(presenter); + const result = await this.getRaceDetailUseCase.execute(params); if (result.isErr()) { throw new Error('Failed to get race detail'); } - const presenter = new RaceDetailPresenter(this.driverRatingProvider, this.imageService); - await presenter.present(result.value as RaceDetailOutputPort, params); return presenter; } diff --git a/apps/api/src/domain/race/presenters/AllRacesPageDataPresenter.ts b/apps/api/src/domain/race/presenters/AllRacesPageDataPresenter.ts index d2ea19981..f076074e8 100644 --- a/apps/api/src/domain/race/presenters/AllRacesPageDataPresenter.ts +++ b/apps/api/src/domain/race/presenters/AllRacesPageDataPresenter.ts @@ -47,4 +47,8 @@ export class AllRacesPageDataPresenter { return this.model; } + + get viewModel(): AllRacesPageDataResponseModel { + return this.responseModel; + } } diff --git a/apps/api/src/domain/race/presenters/GetAllRacesPresenter.test.ts b/apps/api/src/domain/race/presenters/GetAllRacesPresenter.test.ts index 083fe9ac0..e68ed96b7 100644 --- a/apps/api/src/domain/race/presenters/GetAllRacesPresenter.test.ts +++ b/apps/api/src/domain/race/presenters/GetAllRacesPresenter.test.ts @@ -1,4 +1,3 @@ -import { Result } from '@core/shared/application/Result'; import { GetAllRacesPresenter } from './GetAllRacesPresenter'; import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase'; @@ -6,7 +5,7 @@ describe('GetAllRacesPresenter', () => { it('should map races and distinct leagues into the DTO', async () => { const presenter = new GetAllRacesPresenter(); - const output: GetAllRacesOutputPort = { + const output: GetAllRacesResult = { races: [ { id: 'race-1', @@ -14,9 +13,8 @@ describe('GetAllRacesPresenter', () => { track: 'Track A', car: 'Car A', status: 'scheduled', - scheduledAt: '2025-01-01T10:00:00.000Z', + scheduledAt: new Date('2025-01-01T10:00:00.000Z'), strengthOfField: 1500, - leagueName: 'League One', }, { id: 'race-2', @@ -24,9 +22,8 @@ describe('GetAllRacesPresenter', () => { track: 'Track B', car: 'Car B', status: 'completed', - scheduledAt: '2025-01-02T10:00:00.000Z', - strengthOfField: null, - leagueName: 'League One', + scheduledAt: new Date('2025-01-02T10:00:00.000Z'), + strengthOfField: undefined, }, { id: 'race-3', @@ -34,16 +31,22 @@ describe('GetAllRacesPresenter', () => { track: 'Track C', car: 'Car C', status: 'running', - scheduledAt: '2025-01-03T10:00:00.000Z', + scheduledAt: new Date('2025-01-03T10:00:00.000Z'), strengthOfField: 1800, - leagueName: 'League Two', }, - ], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ] as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + leagues: [ + { id: 'league-1', name: 'League One' }, + { id: 'league-2', name: 'League Two' }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ] as any, totalCount: 3, }; await presenter.present(output); - const viewModel = presenter.getViewModel(); + const viewModel = presenter.getResponseModel(); expect(viewModel).not.toBeNull(); expect(viewModel!.races).toHaveLength(3); @@ -61,13 +64,16 @@ describe('GetAllRacesPresenter', () => { it('should handle empty races by returning empty leagues', async () => { const presenter = new GetAllRacesPresenter(); - const output: GetAllRacesOutputPort = { - races: [], + const output: GetAllRacesResult = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + races: [] as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + leagues: [] as any, totalCount: 0, }; await presenter.present(output); - const viewModel = presenter.getViewModel(); + const viewModel = presenter.getResponseModel(); expect(viewModel).not.toBeNull(); expect(viewModel!.races).toHaveLength(0); diff --git a/apps/api/src/domain/race/presenters/GetAllRacesPresenter.ts b/apps/api/src/domain/race/presenters/GetAllRacesPresenter.ts index 5b5bee1ef..214736886 100644 --- a/apps/api/src/domain/race/presenters/GetAllRacesPresenter.ts +++ b/apps/api/src/domain/race/presenters/GetAllRacesPresenter.ts @@ -53,4 +53,8 @@ export class GetAllRacesPresenter implements UseCaseOutputPort; - -export class RaceDetailPresenter { - private model: GetRaceDetailResponseModel | null = null; +export class RaceDetailPresenter implements UseCaseOutputPort { + private result: GetRaceDetailResult | null = null; constructor( private readonly driverRatingProvider: DriverRatingProvider, private readonly imageService: IImageServicePort, + private readonly params: GetRaceDetailParamsDTO, ) {} - reset(): void { - this.model = null; + present(result: GetRaceDetailResult): void { + this.result = result; } - async present( - result: Result, - params: GetRaceDetailParamsDTO, - ): Promise { - if (result.isErr()) { - const error = result.unwrapErr(); - if (error.code === 'RACE_NOT_FOUND') { - this.model = { - race: null, - league: null, - entryList: [], - registration: { - isUserRegistered: false, - canRegister: false, - }, - userResult: null, - } as RaceDetailDTO; - return; - } - - throw new Error(error.details?.message ?? 'Failed to get race detail'); + async getResponseModel(): Promise { + if (!this.result) { + return null; } - const output = result.unwrap(); + const output = this.result; + const params = this.params; const raceDTO: RaceDetailRaceDTO | null = output.race ? { @@ -118,7 +93,7 @@ export class RaceDetailPresenter { } : null; - this.model = { + return { race: raceDTO, league: leagueDTO, entryList: entryListDTO, @@ -127,16 +102,11 @@ export class RaceDetailPresenter { } as RaceDetailDTO; } - getResponseModel(): GetRaceDetailResponseModel | null { - return this.model; - } - - get responseModel(): GetRaceDetailResponseModel { - if (!this.model) { - throw new Error('Presenter not presented'); - } - - return this.model; + get viewModel(): Promise { + return this.getResponseModel().then(model => { + if (!model) throw new Error('Presenter not presented'); + return model; + }); } private calculateRatingChange(position: number): number { diff --git a/apps/api/src/domain/race/presenters/RacePenaltiesPresenter.ts b/apps/api/src/domain/race/presenters/RacePenaltiesPresenter.ts index 4d1861a87..ce5e9370a 100644 --- a/apps/api/src/domain/race/presenters/RacePenaltiesPresenter.ts +++ b/apps/api/src/domain/race/presenters/RacePenaltiesPresenter.ts @@ -62,4 +62,8 @@ export class RacePenaltiesPresenter { return this.model; } + + get viewModel(): GetRacePenaltiesResponseModel { + return this.responseModel; + } } diff --git a/apps/api/src/domain/race/presenters/RaceProtestsPresenter.ts b/apps/api/src/domain/race/presenters/RaceProtestsPresenter.ts index 67808d6f2..a10cf2de5 100644 --- a/apps/api/src/domain/race/presenters/RaceProtestsPresenter.ts +++ b/apps/api/src/domain/race/presenters/RaceProtestsPresenter.ts @@ -63,4 +63,8 @@ export class RaceProtestsPresenter { return this.model; } + + get viewModel(): GetRaceProtestsResponseModel { + return this.responseModel; + } } diff --git a/apps/api/src/domain/race/presenters/RaceResultsDetailPresenter.ts b/apps/api/src/domain/race/presenters/RaceResultsDetailPresenter.ts index 691280515..9f9e5532e 100644 --- a/apps/api/src/domain/race/presenters/RaceResultsDetailPresenter.ts +++ b/apps/api/src/domain/race/presenters/RaceResultsDetailPresenter.ts @@ -1,4 +1,3 @@ -import type { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { GetRaceResultsDetailResult, @@ -16,33 +15,20 @@ export type GetRaceResultsDetailApplicationError = ApplicationErrorCode< >; export class RaceResultsDetailPresenter { - private model: GetRaceResultsDetailResponseModel | null = null; + private result: GetRaceResultsDetailResult | null = null; constructor(private readonly imageService: IImageServicePort) {} - reset(): void { - this.model = null; + present(result: GetRaceResultsDetailResult): void { + this.result = result; } - async present( - result: Result, - ): Promise { - if (result.isErr()) { - const error = result.unwrapErr(); - - if (error.code === 'RACE_NOT_FOUND') { - this.model = { - raceId: '', - track: '', - results: [], - } as RaceResultsDetailDTO; - return; - } - - throw new Error(error.details?.message ?? 'Failed to get race results detail'); + async getResponseModel(): Promise { + if (!this.result) { + return null; } - const output = result.unwrap(); + const output = this.result; const driverMap = new Map(output.drivers.map(driver => [driver.id, driver])); @@ -70,22 +56,17 @@ export class RaceResultsDetailPresenter { }), ); - this.model = { + return { raceId: output.race.id, track: output.race.track, results, } as RaceResultsDetailDTO; } - getResponseModel(): GetRaceResultsDetailResponseModel | null { - return this.model; - } - - get responseModel(): GetRaceResultsDetailResponseModel { - if (!this.model) { - throw new Error('Presenter not presented'); - } - - return this.model; + get viewModel(): Promise { + return this.getResponseModel().then(model => { + if (!model) throw new Error('Presenter not presented'); + return model; + }); } } diff --git a/apps/api/src/domain/race/presenters/RaceWithSOFPresenter.ts b/apps/api/src/domain/race/presenters/RaceWithSOFPresenter.ts index a9021a278..89402fdf7 100644 --- a/apps/api/src/domain/race/presenters/RaceWithSOFPresenter.ts +++ b/apps/api/src/domain/race/presenters/RaceWithSOFPresenter.ts @@ -55,4 +55,8 @@ export class RaceWithSOFPresenter { return this.model; } + + get viewModel(): GetRaceWithSOFResponseModel { + return this.responseModel; + } } diff --git a/apps/api/src/domain/race/presenters/RacesPageDataPresenter.ts b/apps/api/src/domain/race/presenters/RacesPageDataPresenter.ts index 02fd790e9..f002161f9 100644 --- a/apps/api/src/domain/race/presenters/RacesPageDataPresenter.ts +++ b/apps/api/src/domain/race/presenters/RacesPageDataPresenter.ts @@ -59,4 +59,8 @@ export class RacesPageDataPresenter { return this.model; } + + get viewModel(): GetRacesPageDataResponseModel { + return this.responseModel; + } } diff --git a/apps/api/src/domain/sponsor/SponsorProviders.ts b/apps/api/src/domain/sponsor/SponsorProviders.ts index 8016c59a8..f48f1e21c 100644 --- a/apps/api/src/domain/sponsor/SponsorProviders.ts +++ b/apps/api/src/domain/sponsor/SponsorProviders.ts @@ -5,7 +5,7 @@ import { SponsorService } from './SponsorService'; import { NotificationService } from '@core/notifications/application/ports/NotificationService'; import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository'; import { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository'; -import { IPaymentGateway } from '@core/racing/application/ports/IPaymentGateway'; +import { IPaymentGateway } from '@core/payments/domain/ports/IPaymentGateway'; import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository'; import { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository'; import { ILeagueWalletRepository } from '@core/racing/domain/repositories/ILeagueWalletRepository'; diff --git a/apps/api/src/domain/team/TeamService.test.ts b/apps/api/src/domain/team/TeamService.test.ts index b3137b8dd..f97a37bfa 100644 --- a/apps/api/src/domain/team/TeamService.test.ts +++ b/apps/api/src/domain/team/TeamService.test.ts @@ -2,15 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing'; import { TeamService } from './TeamService'; import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase'; -import type { Logger } from '@core/shared/application/Logger'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { AllTeamsPresenter } from './presenters/AllTeamsPresenter'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { DriverTeamPresenter } from './presenters/DriverTeamPresenter'; +import { DriverTeamViewModel } from './dtos/TeamDto'; describe('TeamService', () => { let service: TeamService; let getAllTeamsUseCase: jest.Mocked; let getDriverTeamUseCase: jest.Mocked; - let logger: jest.Mocked; beforeEach(async () => { const mockGetAllTeamsUseCase = { @@ -46,7 +48,6 @@ describe('TeamService', () => { service = module.get(TeamService); getAllTeamsUseCase = module.get(GetAllTeamsUseCase); getDriverTeamUseCase = module.get(GetDriverTeamUseCase); - logger = module.get('Logger'); }); it('should be defined', () => { @@ -56,12 +57,14 @@ describe('TeamService', () => { describe('getAll', () => { it('should call use case and return result', async () => { const mockResult = { isOk: () => true, value: { teams: [], totalCount: 0 } }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any getAllTeamsUseCase.execute.mockResolvedValue(mockResult as any); const mockPresenter = { present: jest.fn(), getViewModel: jest.fn().mockReturnValue({ teams: [], totalCount: 0 }), }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any (AllTeamsPresenter as any) = jest.fn().mockImplementation(() => mockPresenter); const result = await service.getAll(); @@ -74,12 +77,14 @@ describe('TeamService', () => { describe('getDriverTeam', () => { it('should call use case and return result', async () => { const mockResult = { isOk: () => true, value: {} }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any getDriverTeamUseCase.execute.mockResolvedValue(mockResult as any); const mockPresenter = { present: jest.fn(), getViewModel: jest.fn().mockReturnValue({} as DriverTeamViewModel), }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any (DriverTeamPresenter as any) = jest.fn().mockImplementation(() => mockPresenter); const result = await service.getDriverTeam('driver1'); @@ -90,6 +95,7 @@ describe('TeamService', () => { it('should return null on error', async () => { const mockResult = { isErr: () => true, error: {} }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any getDriverTeamUseCase.execute.mockResolvedValue(mockResult as any); const result = await service.getDriverTeam('driver1'); diff --git a/apps/api/src/domain/team/TeamService.ts b/apps/api/src/domain/team/TeamService.ts index 2fbe059c0..f0ce50384 100644 --- a/apps/api/src/domain/team/TeamService.ts +++ b/apps/api/src/domain/team/TeamService.ts @@ -12,14 +12,18 @@ import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO'; // Core imports import type { Logger } from '@core/shared/application/Logger'; +import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository'; +import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository'; +import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository'; +import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort'; // Use cases import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase'; import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase'; import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase'; -import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase'; -import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase'; +import { CreateTeamUseCase, CreateTeamInput } from '@core/racing/application/use-cases/CreateTeamUseCase'; +import { UpdateTeamUseCase, UpdateTeamInput } from '@core/racing/application/use-cases/UpdateTeamUseCase'; import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase'; import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase'; @@ -34,97 +38,90 @@ import { CreateTeamPresenter } from './presenters/CreateTeamPresenter'; import { UpdateTeamPresenter } from './presenters/UpdateTeamPresenter'; // Tokens -import { LOGGER_TOKEN } from './TeamProviders'; +import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, LOGGER_TOKEN } from './TeamProviders'; @Injectable() export class TeamService { constructor( - private readonly getAllTeamsUseCase: GetAllTeamsUseCase, - private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase, - private readonly getTeamMembersUseCase: GetTeamMembersUseCase, - private readonly getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase, - private readonly createTeamUseCase: CreateTeamUseCase, - private readonly updateTeamUseCase: UpdateTeamUseCase, - private readonly getDriverTeamUseCase: GetDriverTeamUseCase, - private readonly getTeamMembershipUseCase: GetTeamMembershipUseCase, + @Inject(TEAM_REPOSITORY_TOKEN) private readonly teamRepository: ITeamRepository, + @Inject(TEAM_MEMBERSHIP_REPOSITORY_TOKEN) private readonly membershipRepository: ITeamMembershipRepository, + @Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository, + @Inject(IMAGE_SERVICE_TOKEN) private readonly imageService: IImageServicePort, @Inject(LOGGER_TOKEN) private readonly logger: Logger, ) {} - async getAll(): Promise { // TODO: type + async getAll(): Promise { this.logger.debug('[TeamService] Fetching all teams.'); - const result = await this.getAllTeamsUseCase.execute(); const presenter = new AllTeamsPresenter(); + const useCase = new GetAllTeamsUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter); + const result = await useCase.execute(); if (result.isErr()) { - this.logger.error('Error fetching all teams', result.error); - await presenter.present({ teams: [], totalCount: 0 }); - return presenter.responseModel; + this.logger.error('Error fetching all teams', result.error?.details?.message || 'Unknown error'); + return { teams: [], totalCount: 0 }; } - await presenter.present(result.value); return presenter.responseModel; } - async getDetails(teamId: string, userId?: string): Promise { + async getDetails(teamId: string, userId?: string): Promise { this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`); const presenter = new TeamDetailsPresenter(); - const result = await this.getTeamDetailsUseCase.execute({ teamId, driverId: userId || '' }); + const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository, presenter); + const result = await useCase.execute({ teamId, driverId: userId || '' }); if (result.isErr()) { - this.logger.error(`Error fetching team details for teamId: ${teamId}`, result.error); - return presenter; + this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`); + return null; } - await presenter.present(result.value); - return presenter; + return presenter.getResponseModel(); } - async getMembers(teamId: string): Promise { + async getMembers(teamId: string): Promise { this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`); const presenter = new TeamMembersPresenter(); - const result = await this.getTeamMembersUseCase.execute({ teamId }); + const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.imageService, this.logger, presenter); + const result = await useCase.execute({ teamId }); if (result.isErr()) { - this.logger.error(`Error fetching team members for teamId: ${teamId}`, result.error); - await presenter.present({ + this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`); + return { members: [], totalCount: 0, ownerCount: 0, managerCount: 0, memberCount: 0, - } as unknown as any); - return presenter; + }; } - await presenter.present(result.value); - return presenter; + return presenter.getResponseModel()!; } - async getJoinRequests(teamId: string): Promise { + async getJoinRequests(teamId: string): Promise { this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`); const presenter = new TeamJoinRequestsPresenter(); - const result = await this.getTeamJoinRequestsUseCase.execute({ teamId }); + const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, presenter); + const result = await useCase.execute({ teamId }); if (result.isErr()) { - this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, result.error); - await presenter.present({ + this.logger.error(new Error(`Error fetching team join requests for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`)); + return { requests: [], pendingCount: 0, totalCount: 0, - } as unknown as any); - return presenter; + }; } - await presenter.present(result.value); - return presenter; + return presenter.getResponseModel()!; } - async create(input: CreateTeamInputDTO, userId?: string): Promise { + async create(input: CreateTeamInputDTO, userId?: string): Promise { this.logger.debug('[TeamService] Creating team', { input, userId }); const presenter = new CreateTeamPresenter(); - const command = { + const command: CreateTeamInput = { name: input.name, tag: input.tag, description: input.description ?? '', @@ -132,68 +129,66 @@ export class TeamService { leagues: [], }; - const result = await this.createTeamUseCase.execute(command as any); + const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter); + const result = await useCase.execute(command); if (result.isErr()) { - this.logger.error('Error creating team', result.error); - presenter.presentError(); - return presenter; + this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`); + return { id: '', success: false }; } - presenter.presentSuccess(result.value); - return presenter; + return presenter.responseModel; } - async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise { + async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise { this.logger.debug(`[TeamService] Updating team ${teamId}`, { input, userId }); const presenter = new UpdateTeamPresenter(); - const command = { + const command: UpdateTeamInput = { teamId, updates: { - name: input.name, - tag: input.tag, - description: input.description, + ...(input.name !== undefined && { name: input.name }), + ...(input.tag !== undefined && { tag: input.tag }), + ...(input.description !== undefined && { description: input.description }), }, updatedBy: userId || '', }; - const result = await this.updateTeamUseCase.execute(command as any); + const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository, presenter); + const result = await useCase.execute(command); if (result.isErr()) { - this.logger.error(`Error updating team ${teamId}`, result.error); - presenter.presentError(); - return presenter; + this.logger.error(`Error updating team ${teamId}: ${result.error?.details?.message || 'Unknown error'}`); + return { success: false }; } - presenter.presentSuccess(); - return presenter; + return presenter.responseModel; } - async getDriverTeam(driverId: string): Promise { + async getDriverTeam(driverId: string): Promise { this.logger.debug(`[TeamService] Fetching driver team for driverId: ${driverId}`); const presenter = new DriverTeamPresenter(); - const result = await this.getDriverTeamUseCase.execute({ driverId }); + const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter); + const result = await useCase.execute({ driverId }); if (result.isErr()) { - this.logger.error(`Error fetching driver team for driverId: ${driverId}`, result.error); - return presenter; + this.logger.error(`Error fetching driver team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`); + return null; } - await presenter.present(result.value); - return presenter; + return presenter.getResponseModel(); } - async getMembership(teamId: string, driverId: string): Promise { + async getMembership(teamId: string, driverId: string): Promise { this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`); const presenter = new TeamMembershipPresenter(); - const result = await this.getTeamMembershipUseCase.execute({ teamId, driverId }); + const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger, presenter); + const result = await useCase.execute({ teamId, driverId }); if (result.isErr()) { - this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}`, result.error); - return presenter; + this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`); + return null; } - presenter.present(result.value as any); - return presenter; + return presenter.responseModel; } } \ No newline at end of file diff --git a/apps/api/src/domain/team/presenters/AllTeamsPresenter.ts b/apps/api/src/domain/team/presenters/AllTeamsPresenter.ts index 70a916820..066d64aed 100644 --- a/apps/api/src/domain/team/presenters/AllTeamsPresenter.ts +++ b/apps/api/src/domain/team/presenters/AllTeamsPresenter.ts @@ -1,36 +1,26 @@ -import type { GetAllTeamsErrorCode, GetAllTeamsResult } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; -import { Result } from '@core/shared/application/Result'; -import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { GetAllTeamsResult } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; import { GetAllTeamsOutputDTO } from '../dtos/GetAllTeamsOutputDTO'; -export type GetAllTeamsError = ApplicationErrorCode; - -export class AllTeamsPresenter { +export class AllTeamsPresenter implements UseCaseOutputPort { private model: GetAllTeamsOutputDTO | null = null; reset(): void { this.model = null; } - present(result: Result): void { - if (result.isErr()) { - const error = result.unwrapErr(); - throw new Error(error.details?.message ?? 'Failed to get teams'); - } - - const output = result.unwrap(); - + present(result: GetAllTeamsResult): void { this.model = { - teams: output.teams.map(team => ({ + teams: result.teams.map(team => ({ id: team.id, - name: team.name, - tag: team.tag, - description: team.description, + name: team.name.toString(), + tag: team.tag.toString(), + description: team.description?.toString() || '', memberCount: team.memberCount, - leagues: team.leagues || [], + leagues: team.leagues?.map(l => l.toString()) || [], // Note: specialization, region, languages not available in output })), - totalCount: output.totalCount ?? output.teams.length, + totalCount: result.totalCount ?? result.teams.length, }; } diff --git a/apps/api/src/domain/team/presenters/CreateTeamPresenter.ts b/apps/api/src/domain/team/presenters/CreateTeamPresenter.ts index 0d57a1425..31c82fdf3 100644 --- a/apps/api/src/domain/team/presenters/CreateTeamPresenter.ts +++ b/apps/api/src/domain/team/presenters/CreateTeamPresenter.ts @@ -1,36 +1,17 @@ -import type { Result } from '@core/shared/application/Result'; -import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { CreateTeamErrorCode, CreateTeamResult } from '@core/racing/application/use-cases/CreateTeamUseCase'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { CreateTeamResult } from '@core/racing/application/use-cases/CreateTeamUseCase'; import type { CreateTeamOutputDTO } from '../dtos/CreateTeamOutputDTO'; -export type CreateTeamError = ApplicationErrorCode; - -export class CreateTeamPresenter { +export class CreateTeamPresenter implements UseCaseOutputPort { private model: CreateTeamOutputDTO | null = null; reset(): void { this.model = null; } - present(result: Result): void { - if (result.isErr()) { - const error = result.unwrapErr(); - // Validation and expected domain errors map to an unsuccessful DTO - if (error.code === 'VALIDATION_ERROR' || error.code === 'LEAGUE_NOT_FOUND') { - this.model = { - id: '', - success: false, - }; - return; - } - - throw new Error(error.details?.message ?? 'Failed to create team'); - } - - const output = result.unwrap(); - + present(result: CreateTeamResult): void { this.model = { - id: output.team.id, + id: result.team.id, success: true, }; } diff --git a/apps/api/src/domain/team/presenters/DriverTeamPresenter.ts b/apps/api/src/domain/team/presenters/DriverTeamPresenter.ts index e44e0f4b4..f40d0a62b 100644 --- a/apps/api/src/domain/team/presenters/DriverTeamPresenter.ts +++ b/apps/api/src/domain/team/presenters/DriverTeamPresenter.ts @@ -1,38 +1,39 @@ -import { DriverTeamOutputPort } from '@core/racing/application/ports/output/DriverTeamOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { GetDriverTeamResult } from '@core/racing/application/use-cases/GetDriverTeamUseCase'; import { GetDriverTeamOutputDTO } from '../dtos/GetDriverTeamOutputDTO'; -export class DriverTeamPresenter { +export class DriverTeamPresenter implements UseCaseOutputPort { private result: GetDriverTeamOutputDTO | null = null; reset() { this.result = null; } - async present(output: DriverTeamOutputPort) { - const isOwner = output.team.ownerId === output.driverId; - const canManage = isOwner || output.membership.role === 'owner' || output.membership.role === 'manager'; + present(result: GetDriverTeamResult): void { + const isOwner = result.team.ownerId.toString() === result.driverId; + const canManage = isOwner || result.membership.role === 'owner' || result.membership.role === 'manager'; this.result = { team: { - id: output.team.id, - name: output.team.name, - tag: output.team.tag, - description: output.team.description || '', - ownerId: output.team.ownerId, - leagues: output.team.leagues || [], - createdAt: output.team.createdAt.toISOString(), + id: result.team.id, + name: result.team.name.toString(), + tag: result.team.tag.toString(), + description: result.team.description?.toString() || '', + ownerId: result.team.ownerId.toString(), + leagues: result.team.leagues?.map(l => l.toString()) || [], + createdAt: result.team.createdAt.toDate().toISOString(), }, membership: { - role: output.membership.role === 'driver' ? 'member' : output.membership.role, - joinedAt: output.membership.joinedAt.toISOString(), - isActive: output.membership.status === 'active', + role: result.membership.role === 'driver' ? 'member' : result.membership.role, + joinedAt: result.membership.joinedAt.toISOString(), + isActive: result.membership.status === 'active', }, isOwner, canManage, }; } - getViewModel(): GetDriverTeamOutputDTO | null { + getResponseModel(): GetDriverTeamOutputDTO | null { return this.result; } } \ No newline at end of file diff --git a/apps/api/src/domain/team/presenters/TeamDetailsPresenter.ts b/apps/api/src/domain/team/presenters/TeamDetailsPresenter.ts index 0b9a9c30d..637f8874c 100644 --- a/apps/api/src/domain/team/presenters/TeamDetailsPresenter.ts +++ b/apps/api/src/domain/team/presenters/TeamDetailsPresenter.ts @@ -1,36 +1,37 @@ -import type { GetTeamDetailsOutputPort } from '@core/racing/application/ports/output/GetTeamDetailsOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { GetTeamDetailsResult } from '@core/racing/application/use-cases/GetTeamDetailsUseCase'; import type { GetTeamDetailsOutputDTO } from '../dtos/GetTeamDetailsOutputDTO'; -export class TeamDetailsPresenter { +export class TeamDetailsPresenter implements UseCaseOutputPort { private result: GetTeamDetailsOutputDTO | null = null; reset() { this.result = null; } - async present(outputPort: GetTeamDetailsOutputPort): Promise { + present(result: GetTeamDetailsResult): void { this.result = { team: { - id: outputPort.team.id, - name: outputPort.team.name, - tag: outputPort.team.tag, - description: outputPort.team.description, - ownerId: outputPort.team.ownerId, - leagues: outputPort.team.leagues, - createdAt: outputPort.team.createdAt.toISOString(), + id: result.team.id, + name: result.team.name.toString(), + tag: result.team.tag.toString(), + description: result.team.description?.toString() || '', + ownerId: result.team.ownerId.toString(), + leagues: result.team.leagues?.map(l => l.toString()) || [], + createdAt: result.team.createdAt.toDate().toISOString(), }, - membership: outputPort.membership + membership: result.membership ? { - role: outputPort.membership.role, - joinedAt: outputPort.membership.joinedAt.toISOString(), - isActive: outputPort.membership.isActive, + role: result.membership.role === 'driver' ? 'member' : result.membership.role, + joinedAt: result.membership.joinedAt.toISOString(), + isActive: result.membership.status === 'active', } : null, - canManage: outputPort.canManage, + canManage: result.canManage, }; } - getViewModel(): GetTeamDetailsOutputDTO | null { + getResponseModel(): GetTeamDetailsOutputDTO | null { return this.result; } } \ No newline at end of file diff --git a/apps/api/src/domain/team/presenters/TeamJoinRequestsPresenter.ts b/apps/api/src/domain/team/presenters/TeamJoinRequestsPresenter.ts index cd6096bcb..dea3e9efd 100644 --- a/apps/api/src/domain/team/presenters/TeamJoinRequestsPresenter.ts +++ b/apps/api/src/domain/team/presenters/TeamJoinRequestsPresenter.ts @@ -1,30 +1,31 @@ -import type { TeamJoinRequestsOutputPort } from '@core/racing/application/ports/output/TeamJoinRequestsOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { GetTeamJoinRequestsResult } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase'; import type { GetTeamJoinRequestsOutputDTO } from '../dtos/GetTeamJoinRequestsOutputDTO'; -export class TeamJoinRequestsPresenter { +export class TeamJoinRequestsPresenter implements UseCaseOutputPort { private result: GetTeamJoinRequestsOutputDTO | null = null; reset() { this.result = null; } - async present(outputPort: TeamJoinRequestsOutputPort): Promise { + present(result: GetTeamJoinRequestsResult): void { this.result = { - requests: outputPort.requests.map(request => ({ - requestId: request.requestId, + requests: result.joinRequests.map(request => ({ + requestId: request.id, driverId: request.driverId, - driverName: request.driverName, + driverName: request.driver.name.toString(), teamId: request.teamId, - status: request.status, + status: 'pending', requestedAt: request.requestedAt.toISOString(), - avatarUrl: request.avatarUrl, + avatarUrl: '', // TODO: get avatar })), - pendingCount: outputPort.pendingCount, - totalCount: outputPort.totalCount, + pendingCount: result.joinRequests.length, + totalCount: result.joinRequests.length, }; } - getViewModel(): GetTeamJoinRequestsOutputDTO | null { + getResponseModel(): GetTeamJoinRequestsOutputDTO | null { return this.result; } } \ No newline at end of file diff --git a/apps/api/src/domain/team/presenters/TeamMembersPresenter.ts b/apps/api/src/domain/team/presenters/TeamMembersPresenter.ts index b87d11fb3..81b2b6580 100644 --- a/apps/api/src/domain/team/presenters/TeamMembersPresenter.ts +++ b/apps/api/src/domain/team/presenters/TeamMembersPresenter.ts @@ -1,31 +1,40 @@ -import type { TeamMembersOutputPort } from '@core/racing/application/ports/output/TeamMembersOutputPort'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { GetTeamMembersResult } from '@core/racing/application/use-cases/GetTeamMembersUseCase'; import type { GetTeamMembersOutputDTO } from '../dtos/GetTeamMembersOutputDTO'; -export class TeamMembersPresenter { +export class TeamMembersPresenter implements UseCaseOutputPort { private result: GetTeamMembersOutputDTO | null = null; reset() { this.result = null; } - async present(outputPort: TeamMembersOutputPort): Promise { + present(result: GetTeamMembersResult): void { + const members = result.members + .filter(member => member.driver !== null) + .map(member => ({ + driverId: member.membership.driverId, + driverName: member.driver!.name.toString(), + role: (member.membership.role === 'driver' ? 'member' : member.membership.role) as 'owner' | 'manager' | 'member', + joinedAt: member.membership.joinedAt.toISOString(), + isActive: member.membership.status === 'active', + avatarUrl: '', // TODO: get avatar + })); + + const ownerCount = result.members.filter(m => m.membership.role === 'owner').length; + const managerCount = result.members.filter(m => m.membership.role === 'manager').length; + const memberCount = result.members.filter(m => m.membership.role === 'driver').length; + this.result = { - members: outputPort.members.map(member => ({ - driverId: member.driverId, - driverName: member.driverName, - role: member.role, - joinedAt: member.joinedAt.toISOString(), - isActive: member.isActive, - avatarUrl: member.avatarUrl, - })), - totalCount: outputPort.totalCount, - ownerCount: outputPort.ownerCount, - managerCount: outputPort.managerCount, - memberCount: outputPort.memberCount, + members, + totalCount: members.length, + ownerCount, + managerCount, + memberCount, }; } - getViewModel(): GetTeamMembersOutputDTO | null { + getResponseModel(): GetTeamMembersOutputDTO | null { return this.result; } } \ No newline at end of file diff --git a/apps/api/src/domain/team/presenters/TeamMembershipPresenter.ts b/apps/api/src/domain/team/presenters/TeamMembershipPresenter.ts index ae35cd317..1b1e44b97 100644 --- a/apps/api/src/domain/team/presenters/TeamMembershipPresenter.ts +++ b/apps/api/src/domain/team/presenters/TeamMembershipPresenter.ts @@ -1,30 +1,19 @@ +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { GetTeamMembershipResult } from '@core/racing/application/use-cases/GetTeamMembershipUseCase'; import type { GetTeamMembershipOutputDTO } from '../dtos/GetTeamMembershipOutputDTO'; -export class TeamMembershipPresenter { +export class TeamMembershipPresenter implements UseCaseOutputPort { private result: GetTeamMembershipOutputDTO | null = null; reset(): void { this.result = null; } - present(membership: GetTeamMembershipOutputDTO | null): void { - if (!membership) { - this.result = null; - return; - } - - this.result = { - role: membership.role, - joinedAt: membership.joinedAt, - isActive: membership.isActive, - }; + present(result: GetTeamMembershipResult): void { + this.result = result.membership; } - getViewModel(): GetTeamMembershipOutputDTO | null { - return this.result; - } - - get viewModel(): GetTeamMembershipOutputDTO | null { + getResponseModel(): GetTeamMembershipOutputDTO | null { return this.result; } } diff --git a/apps/api/src/domain/team/presenters/UpdateTeamPresenter.ts b/apps/api/src/domain/team/presenters/UpdateTeamPresenter.ts index d8d53523e..4b3fc366c 100644 --- a/apps/api/src/domain/team/presenters/UpdateTeamPresenter.ts +++ b/apps/api/src/domain/team/presenters/UpdateTeamPresenter.ts @@ -1,29 +1,25 @@ +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { UpdateTeamResult } from '@core/racing/application/use-cases/UpdateTeamUseCase'; import type { UpdateTeamOutputDTO } from '../dtos/UpdateTeamOutputDTO'; -export class UpdateTeamPresenter { +export class UpdateTeamPresenter implements UseCaseOutputPort { private result: UpdateTeamOutputDTO | null = null; reset(): void { this.result = null; } - presentSuccess(): void { + present(_result: UpdateTeamResult): void { this.result = { success: true, }; } - presentError(): void { - this.result = { - success: false, - }; - } - - getViewModel(): UpdateTeamOutputDTO | null { + getResponseModel(): UpdateTeamOutputDTO | null { return this.result; } - get viewModel(): UpdateTeamOutputDTO { + get responseModel(): UpdateTeamOutputDTO { if (!this.result) { throw new Error('Presenter not presented'); } diff --git a/core/racing/application/use-cases/GetLeagueProtestsUseCase.ts b/core/racing/application/use-cases/GetLeagueProtestsUseCase.ts index 3dc831317..fda9ba0fd 100644 --- a/core/racing/application/use-cases/GetLeagueProtestsUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueProtestsUseCase.ts @@ -37,6 +37,10 @@ export class GetLeagueProtestsUseCase { private readonly output: UseCaseOutputPort, ) {} + get outputPort(): UseCaseOutputPort { + return this.output; + } + async execute( input: GetLeagueProtestsInput, ): Promise>> { diff --git a/core/racing/application/use-cases/GetLeagueSeasonsUseCase.ts b/core/racing/application/use-cases/GetLeagueSeasonsUseCase.ts index 59138c29b..1bcfa5102 100644 --- a/core/racing/application/use-cases/GetLeagueSeasonsUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueSeasonsUseCase.ts @@ -27,7 +27,7 @@ export class GetLeagueSeasonsUseCase { constructor( private readonly seasonRepository: ISeasonRepository, private readonly leagueRepository: ILeagueRepository, - private readonly output: UseCaseOutputPort, + readonly output: UseCaseOutputPort, ) {} async execute( diff --git a/core/racing/application/use-cases/GetRaceDetailUseCase.ts b/core/racing/application/use-cases/GetRaceDetailUseCase.ts index 59454786b..9245e9ce7 100644 --- a/core/racing/application/use-cases/GetRaceDetailUseCase.ts +++ b/core/racing/application/use-cases/GetRaceDetailUseCase.ts @@ -33,6 +33,8 @@ export type GetRaceDetailResult = { }; export class GetRaceDetailUseCase { + private output: UseCaseOutputPort | null = null; + constructor( private readonly raceRepository: IRaceRepository, private readonly leagueRepository: ILeagueRepository, @@ -40,9 +42,12 @@ export class GetRaceDetailUseCase { private readonly raceRegistrationRepository: IRaceRegistrationRepository, private readonly resultRepository: IResultRepository, private readonly leagueMembershipRepository: ILeagueMembershipRepository, - private readonly output: UseCaseOutputPort, ) {} + setOutput(output: UseCaseOutputPort) { + this.output = output; + } + async execute( input: GetRaceDetailInput, ): Promise>> { @@ -90,6 +95,9 @@ export class GetRaceDetailUseCase { canRegister, }; + if (!this.output) { + throw new Error('Output not set'); + } this.output.present(result); return Result.ok(undefined);