From d617654928a5c51940b3848fbac4f42f7701b98d Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Thu, 18 Dec 2025 22:19:40 +0100 Subject: [PATCH] resolve manual DTOs --- apps/api/openapi.json | 733 +++++++++++++++--- apps/api/src/app.module.ts | 4 + .../domain/analytics/AnalyticsController.ts | 28 +- .../src/domain/analytics/AnalyticsService.ts | 24 + .../dtos/GetAnalyticsMetricsOutputDTO.ts | 20 + .../dtos/GetDashboardDataOutputDTO.ts | 20 + apps/api/src/domain/auth/AuthController.ts | 14 +- .../domain/dashboard/DashboardController.ts | 18 + .../src/domain/dashboard/DashboardModule.ts | 11 + .../domain/dashboard/DashboardProviders.ts | 23 + .../src/domain/dashboard/DashboardService.ts | 28 + .../dtos/DashboardDriverSummaryDTO.ts | 47 ++ .../dtos/DashboardFeedItemSummaryDTO.ts | 53 ++ .../dashboard/dtos/DashboardFeedSummaryDTO.ts | 12 + .../dtos/DashboardFriendSummaryDTO.ts | 20 + .../dtos/DashboardLeagueStandingSummaryDTO.ts | 24 + .../dashboard/dtos/DashboardOverviewDTO.ts | 41 + .../dashboard/dtos/DashboardRaceSummaryDTO.ts | 36 + .../dtos/DashboardRecentResultDTO.ts | 32 + .../api/src/domain/driver/DriverController.ts | 26 +- apps/api/src/domain/driver/DriverService.ts | 46 +- apps/api/src/domain/driver/dtos/DriverDTO.ts | 21 + .../domain/driver/dtos/GetDriverOutputDTO.ts | 21 + .../driver/dtos/GetDriverProfileOutputDTO.ts | 226 ++++++ .../api/src/domain/league/LeagueController.ts | 16 + apps/api/src/domain/league/LeagueService.ts | 22 + .../league/dtos/GetLeagueRacesOutputDTO.ts | 7 + .../dtos/GetSeasonSponsorshipsOutputDTO.ts | 7 + .../domain/league/dtos/LeagueMembershipDTO.ts | 28 + .../src/domain/league/dtos/LeagueRoleDTO.ts | 8 + .../league/dtos/LeagueScoringPresetDTO.ts | 32 + .../domain/league/dtos/MembershipRoleDTO.ts | 8 + .../domain/league/dtos/MembershipStatusDTO.ts | 8 + .../src/domain/league/dtos/WizardErrorsDTO.ts | 92 +++ .../src/domain/league/dtos/WizardStepDTO.ts | 8 + apps/api/src/domain/media/MediaController.ts | 94 ++- apps/api/src/domain/media/MediaService.ts | 52 ++ .../domain/media/dtos/DeleteMediaOutputDTO.ts | 13 + .../domain/media/dtos/GetAvatarOutputDTO.ts | 8 + .../domain/media/dtos/GetMediaOutputDTO.ts | 29 + .../domain/media/dtos/UpdateAvatarInputDTO.ts | 12 + .../media/dtos/UpdateAvatarOutputDTO.ts | 13 + .../domain/media/dtos/UploadMediaInputDTO.ts | 16 + .../domain/media/dtos/UploadMediaOutputDTO.ts | 23 + .../src/domain/payments/PaymentsController.ts | 2 +- .../src/domain/payments/PaymentsService.ts | 2 +- .../src/domain/protests/ProtestsController.ts | 22 + .../api/src/domain/protests/ProtestsModule.ts | 9 + apps/api/src/domain/race/RaceController.ts | 8 - apps/api/src/domain/race/RaceProviders.ts | 8 +- apps/api/src/domain/race/RaceService.ts | 27 +- .../race/dtos/ReviewProtestCommandDTO.ts | 25 + .../src/domain/sponsor/SponsorController.ts | 41 +- .../src/domain/sponsor/SponsorProviders.ts | 31 + apps/api/src/domain/sponsor/SponsorService.ts | 58 +- .../dtos/AcceptSponsorshipRequestInputDTO.ts | 9 + .../GetPendingSponsorshipRequestsOutputDTO.ts | 16 + .../sponsor/dtos/GetSponsorOutputDTO.ts | 7 + .../dtos/RejectSponsorshipRequestInputDTO.ts | 14 + .../api/src/domain/sponsor/dtos/SponsorDTO.ts | 15 + .../sponsor/dtos/SponsorshipRequestDTO.ts | 39 + apps/api/src/domain/team/TeamController.ts | 112 ++- apps/api/src/domain/team/TeamModule.ts | 2 +- apps/api/src/domain/team/TeamProviders.ts | 164 +--- apps/api/src/domain/team/TeamService.ts | 217 ++---- .../domain/team/dtos/CreateTeamInputDTO.ts | 19 + .../domain/team/dtos/CreateTeamOutputDTO.ts | 9 + .../domain/team/dtos/GetAllTeamsOutputDTO.ts | 38 + .../team/dtos/GetDriverTeamOutputDTO.ts | 58 ++ .../team/dtos/GetTeamDetailsOutputDTO.ts | 55 ++ .../team/dtos/GetTeamJoinRequestsOutputDTO.ts | 35 + .../team/dtos/GetTeamMembersOutputDTO.ts | 38 + .../team/dtos/GetTeamMembershipOutputDTO.ts | 12 + .../domain/team/dtos/UpdateTeamInputDTO.ts | 19 + .../domain/team/dtos/UpdateTeamOutputDTO.ts | 6 + apps/api/src/main.ts | 1 + apps/website/app/leaderboards/page.tsx | 7 +- .../app/leagues/[id]/settings/page.tsx | 2 +- apps/website/app/profile/leagues/page.tsx | 2 +- .../lib/api/analytics/AnalyticsApiClient.ts | 34 +- apps/website/lib/api/auth/AuthApiClient.ts | 30 +- .../lib/api/dashboard/DashboardApiClient.ts | 74 +- .../lib/api/drivers/DriversApiClient.ts | 23 +- apps/website/lib/api/index.ts | 6 + .../lib/api/leagues/LeaguesApiClient.ts | 10 +- apps/website/lib/api/media/MediaApiClient.ts | 43 +- .../lib/api/payments/PaymentsApiClient.ts | 122 ++- .../lib/api/penalties/PenaltiesApiClient.ts | 8 +- .../lib/api/protests/ProtestsApiClient.ts | 10 +- apps/website/lib/api/races/RacesApiClient.ts | 69 +- .../lib/api/sponsors/SponsorsApiClient.ts | 24 +- apps/website/lib/api/teams/TeamsApiClient.ts | 53 +- apps/website/lib/auth/AuthApiClient.ts | 50 -- .../leagues/LeagueWizardCommandModel.ts | 26 +- .../lib/display-objects/LeagueRoleDisplay.ts | 3 +- .../services/analytics/AnalyticsService.ts | 14 +- .../leagues/LeagueMembershipService.ts | 6 +- .../lib/services/leagues/LeagueService.ts | 1 - .../services/leagues/LeagueSettingsService.ts | 10 +- .../services/leagues/LeagueWizardService.ts | 11 +- .../lib/services/media/MediaService.test.ts | 12 +- .../payments/MembershipFeeService.test.ts | 32 +- .../services/payments/MembershipFeeService.ts | 12 +- .../services/payments/PaymentService.test.ts | 12 +- .../lib/services/payments/PaymentService.ts | 22 +- .../services/payments/WalletService.test.ts | 6 +- .../services/races/RaceResultsService.test.ts | 14 +- .../lib/services/races/RaceResultsService.ts | 25 +- .../services/sponsors/SponsorService.test.ts | 4 + .../lib/services/teams/TeamService.test.ts | 71 +- .../website/lib/services/teams/TeamService.ts | 59 +- .../website/lib/types/CreateLeagueInputDTO.ts | 7 - apps/website/lib/types/CreateLeagueResult.ts | 5 - .../lib/types/LeagueConfigFormModel.ts | 50 -- apps/website/lib/types/LeagueMembership.ts | 14 - apps/website/lib/types/LeagueRole.ts | 3 - .../lib/types/LeagueScoringPresetDTO.ts | 15 - apps/website/lib/types/MembershipRole.ts | 1 - apps/website/lib/types/MembershipStatus.ts | 1 - apps/website/lib/types/WizardErrors.ts | 21 - apps/website/lib/types/WizardStep.ts | 1 - .../AcceptSponsorshipRequestInputDTO.ts | 9 + .../types/generated/ApplyPenaltyCommandDTO.ts | 6 +- .../lib/types/generated/CreateTeamInputDTO.ts | 10 + .../types/generated/CreateTeamOutputDTO.ts | 10 + .../types/generated/DeleteMediaOutputDTO.ts | 9 + .../lib/types/{ => generated}/DriverDTO.ts | 7 +- .../generated/DriverProfileAchievementDTO.ts | 11 + .../DriverProfileDriverSummaryDTO.ts} | 4 +- .../DriverProfileFinishDistributionDTO.ts | 14 + .../DriverProfileSocialFriendSummaryDTO.ts | 12 + .../DriverProfileSocialSummaryDTO.ts | 9 + .../types/generated/DriverProfileStatsDTO.ts | 12 + .../DriverProfileTeamMembershipDTO.ts | 10 + .../types/generated/GetAllTeamsOutputDTO.ts | 22 + .../generated/GetAnalyticsMetricsOutputDTO.ts | 12 + .../GetAvatarOutputDTO.ts} | 4 +- .../generated/GetDashboardDataOutputDTO.ts | 12 + .../lib/types/generated/GetDriverOutputDTO.ts | 14 + .../types/generated/GetDriverTeamOutputDTO.ts | 31 + .../lib/types/generated/GetMediaOutputDTO.ts | 15 + .../GetPendingSponsorshipRequestsOutputDTO.ts | 10 + .../types/generated/GetSponsorOutputDTO.ts | 8 + .../generated/GetTeamDetailsOutputDTO.ts | 30 + .../generated/GetTeamJoinRequestsOutputDTO.ts | 21 + .../generated/GetTeamMembersOutputDTO.ts | 22 + .../generated/GetTeamMembershipOutputDTO.ts | 11 + .../generated/IracingAuthRedirectResult.ts | 10 + .../generated/LeagueConfigFormModelDTO.ts | 14 + .../lib/types/generated/LoginParams.ts | 10 + .../LoginWithIracingCallbackParams.ts | 11 + .../lib/types/generated/RacePenaltiesDTO.ts | 12 + .../generated/RecordEngagementInputDTO.ts | 5 + .../types/generated/RecordPageViewInputDTO.ts | 4 + .../RejectSponsorshipRequestInputDTO.ts | 9 + .../generated/ReviewProtestCommandDTO.ts | 11 + .../lib/types/generated/SignupParams.ts | 14 + .../website/lib/types/generated/SponsorDTO.ts | 10 + .../types/generated/SponsorshipRequestDTO.ts | 11 + .../types/generated/UpdateAvatarInputDTO.ts | 10 + .../types/generated/UpdateAvatarOutputDTO.ts | 9 + .../lib/types/generated/UpdateTeamInputDTO.ts | 11 + .../types/generated/UpdateTeamOutputDTO.ts | 9 + .../types/generated/UploadMediaOutputDTO.ts | 9 + .../lib/utilities/LeagueMembershipUtility.ts | 2 + .../lib/utilities/LeagueRoleUtility.ts | 3 +- .../lib/view-models/DriverSummaryViewModel.ts | 6 +- .../lib/view-models/DriverTeamViewModel.ts | 21 +- .../ImportRaceResultsSummaryViewModel.ts | 19 +- .../view-models/LeagueDetailPageViewModel.ts | 12 +- .../LeagueScoringPresetsViewModel.ts | 2 +- .../view-models/LeagueSettingsViewModel.ts | 4 +- .../view-models/LeagueStandingsViewModel.ts | 6 +- .../lib/view-models/MediaViewModel.test.ts | 28 +- .../website/lib/view-models/MediaViewModel.ts | 18 +- .../lib/view-models/TeamDetailsViewModel.ts | 81 +- .../lib/view-models/TeamMemberViewModel.ts | 31 +- .../lib/view-models/TeamSummaryViewModel.ts | 29 +- .../use-cases/GetSponsorUseCase.ts | 40 + 179 files changed, 3716 insertions(+), 1257 deletions(-) create mode 100644 apps/api/src/domain/analytics/dtos/GetAnalyticsMetricsOutputDTO.ts create mode 100644 apps/api/src/domain/analytics/dtos/GetDashboardDataOutputDTO.ts create mode 100644 apps/api/src/domain/dashboard/DashboardController.ts create mode 100644 apps/api/src/domain/dashboard/DashboardModule.ts create mode 100644 apps/api/src/domain/dashboard/DashboardProviders.ts create mode 100644 apps/api/src/domain/dashboard/DashboardService.ts create mode 100644 apps/api/src/domain/dashboard/dtos/DashboardDriverSummaryDTO.ts create mode 100644 apps/api/src/domain/dashboard/dtos/DashboardFeedItemSummaryDTO.ts create mode 100644 apps/api/src/domain/dashboard/dtos/DashboardFeedSummaryDTO.ts create mode 100644 apps/api/src/domain/dashboard/dtos/DashboardFriendSummaryDTO.ts create mode 100644 apps/api/src/domain/dashboard/dtos/DashboardLeagueStandingSummaryDTO.ts create mode 100644 apps/api/src/domain/dashboard/dtos/DashboardOverviewDTO.ts create mode 100644 apps/api/src/domain/dashboard/dtos/DashboardRaceSummaryDTO.ts create mode 100644 apps/api/src/domain/dashboard/dtos/DashboardRecentResultDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/DriverDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/GetDriverOutputDTO.ts create mode 100644 apps/api/src/domain/driver/dtos/GetDriverProfileOutputDTO.ts create mode 100644 apps/api/src/domain/league/dtos/GetLeagueRacesOutputDTO.ts create mode 100644 apps/api/src/domain/league/dtos/GetSeasonSponsorshipsOutputDTO.ts create mode 100644 apps/api/src/domain/league/dtos/LeagueMembershipDTO.ts create mode 100644 apps/api/src/domain/league/dtos/LeagueRoleDTO.ts create mode 100644 apps/api/src/domain/league/dtos/LeagueScoringPresetDTO.ts create mode 100644 apps/api/src/domain/league/dtos/MembershipRoleDTO.ts create mode 100644 apps/api/src/domain/league/dtos/MembershipStatusDTO.ts create mode 100644 apps/api/src/domain/league/dtos/WizardErrorsDTO.ts create mode 100644 apps/api/src/domain/league/dtos/WizardStepDTO.ts create mode 100644 apps/api/src/domain/media/dtos/DeleteMediaOutputDTO.ts create mode 100644 apps/api/src/domain/media/dtos/GetAvatarOutputDTO.ts create mode 100644 apps/api/src/domain/media/dtos/GetMediaOutputDTO.ts create mode 100644 apps/api/src/domain/media/dtos/UpdateAvatarInputDTO.ts create mode 100644 apps/api/src/domain/media/dtos/UpdateAvatarOutputDTO.ts create mode 100644 apps/api/src/domain/media/dtos/UploadMediaInputDTO.ts create mode 100644 apps/api/src/domain/media/dtos/UploadMediaOutputDTO.ts create mode 100644 apps/api/src/domain/protests/ProtestsController.ts create mode 100644 apps/api/src/domain/protests/ProtestsModule.ts create mode 100644 apps/api/src/domain/race/dtos/ReviewProtestCommandDTO.ts create mode 100644 apps/api/src/domain/sponsor/dtos/AcceptSponsorshipRequestInputDTO.ts create mode 100644 apps/api/src/domain/sponsor/dtos/GetPendingSponsorshipRequestsOutputDTO.ts create mode 100644 apps/api/src/domain/sponsor/dtos/GetSponsorOutputDTO.ts create mode 100644 apps/api/src/domain/sponsor/dtos/RejectSponsorshipRequestInputDTO.ts create mode 100644 apps/api/src/domain/sponsor/dtos/SponsorDTO.ts create mode 100644 apps/api/src/domain/sponsor/dtos/SponsorshipRequestDTO.ts create mode 100644 apps/api/src/domain/team/dtos/CreateTeamInputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/CreateTeamOutputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/GetAllTeamsOutputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/GetDriverTeamOutputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/GetTeamDetailsOutputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/GetTeamJoinRequestsOutputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/GetTeamMembersOutputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/GetTeamMembershipOutputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/UpdateTeamInputDTO.ts create mode 100644 apps/api/src/domain/team/dtos/UpdateTeamOutputDTO.ts delete mode 100644 apps/website/lib/auth/AuthApiClient.ts delete mode 100644 apps/website/lib/types/CreateLeagueInputDTO.ts delete mode 100644 apps/website/lib/types/CreateLeagueResult.ts delete mode 100644 apps/website/lib/types/LeagueConfigFormModel.ts delete mode 100644 apps/website/lib/types/LeagueMembership.ts delete mode 100644 apps/website/lib/types/LeagueRole.ts delete mode 100644 apps/website/lib/types/LeagueScoringPresetDTO.ts delete mode 100644 apps/website/lib/types/MembershipRole.ts delete mode 100644 apps/website/lib/types/MembershipStatus.ts delete mode 100644 apps/website/lib/types/WizardErrors.ts delete mode 100644 apps/website/lib/types/WizardStep.ts create mode 100644 apps/website/lib/types/generated/AcceptSponsorshipRequestInputDTO.ts create mode 100644 apps/website/lib/types/generated/CreateTeamInputDTO.ts create mode 100644 apps/website/lib/types/generated/CreateTeamOutputDTO.ts create mode 100644 apps/website/lib/types/generated/DeleteMediaOutputDTO.ts rename apps/website/lib/types/{ => generated}/DriverDTO.ts (78%) create mode 100644 apps/website/lib/types/generated/DriverProfileAchievementDTO.ts rename apps/website/lib/types/{RaceDetailEntryDTO.ts => generated/DriverProfileDriverSummaryDTO.ts} (73%) create mode 100644 apps/website/lib/types/generated/DriverProfileFinishDistributionDTO.ts create mode 100644 apps/website/lib/types/generated/DriverProfileSocialFriendSummaryDTO.ts create mode 100644 apps/website/lib/types/generated/DriverProfileSocialSummaryDTO.ts create mode 100644 apps/website/lib/types/generated/DriverProfileStatsDTO.ts create mode 100644 apps/website/lib/types/generated/DriverProfileTeamMembershipDTO.ts create mode 100644 apps/website/lib/types/generated/GetAllTeamsOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetAnalyticsMetricsOutputDTO.ts rename apps/website/lib/types/{GetAvatarOutputDto.ts => generated/GetAvatarOutputDTO.ts} (83%) create mode 100644 apps/website/lib/types/generated/GetDashboardDataOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetDriverOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetDriverTeamOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetMediaOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetSponsorOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetTeamDetailsOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetTeamJoinRequestsOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetTeamMembersOutputDTO.ts create mode 100644 apps/website/lib/types/generated/GetTeamMembershipOutputDTO.ts create mode 100644 apps/website/lib/types/generated/IracingAuthRedirectResult.ts create mode 100644 apps/website/lib/types/generated/LoginParams.ts create mode 100644 apps/website/lib/types/generated/LoginWithIracingCallbackParams.ts create mode 100644 apps/website/lib/types/generated/RacePenaltiesDTO.ts create mode 100644 apps/website/lib/types/generated/RecordEngagementInputDTO.ts create mode 100644 apps/website/lib/types/generated/RecordPageViewInputDTO.ts create mode 100644 apps/website/lib/types/generated/RejectSponsorshipRequestInputDTO.ts create mode 100644 apps/website/lib/types/generated/ReviewProtestCommandDTO.ts create mode 100644 apps/website/lib/types/generated/SignupParams.ts create mode 100644 apps/website/lib/types/generated/SponsorDTO.ts create mode 100644 apps/website/lib/types/generated/SponsorshipRequestDTO.ts create mode 100644 apps/website/lib/types/generated/UpdateAvatarInputDTO.ts create mode 100644 apps/website/lib/types/generated/UpdateAvatarOutputDTO.ts create mode 100644 apps/website/lib/types/generated/UpdateTeamInputDTO.ts create mode 100644 apps/website/lib/types/generated/UpdateTeamOutputDTO.ts create mode 100644 apps/website/lib/types/generated/UploadMediaOutputDTO.ts create mode 100644 core/racing/application/use-cases/GetSponsorUseCase.ts diff --git a/apps/api/openapi.json b/apps/api/openapi.json index 4f1381f24..38bfc5e89 100644 --- a/apps/api/openapi.json +++ b/apps/api/openapi.json @@ -8,6 +8,85 @@ "paths": {}, "components": { "schemas": { + "UpdateTeamOutputDTO": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ] + }, + "GetTeamMembershipOutputDTO": { + "type": "object", + "properties": { + "role": { + "type": "string" + }, + "joinedAt": { + "type": "string" + }, + "isActive": { + "type": "boolean" + } + }, + "required": [ + "role", + "joinedAt", + "isActive" + ] + }, + "CreateTeamOutputDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "id", + "success" + ] + }, + "CreateTeamInputDTO": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "required": [ + "name", + "tag" + ] + }, + "SponsorshipRequestDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sponsorId": { + "type": "string" + }, + "sponsorName": { + "type": "string" + } + }, + "required": [ + "id", + "sponsorId", + "sponsorName" + ] + }, "SponsorshipPricingItemDTO": { "type": "object", "properties": { @@ -161,6 +240,32 @@ "sponsorName" ] }, + "SponsorDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "RejectSponsorshipRequestInputDTO": { + "type": "object", + "properties": { + "respondedBy": { + "type": "string" + } + }, + "required": [ + "respondedBy" + ] + }, "GetSponsorSponsorshipsQueryParamsDTO": { "type": "object", "properties": { @@ -183,6 +288,21 @@ "sponsorId" ] }, + "GetPendingSponsorshipRequestsOutputDTO": { + "type": "object", + "properties": { + "entityType": { + "type": "string" + }, + "entityId": { + "type": "string" + } + }, + "required": [ + "entityType", + "entityId" + ] + }, "CreateSponsorInputDTO": { "type": "object", "properties": { @@ -198,6 +318,17 @@ "contactEmail" ] }, + "AcceptSponsorshipRequestInputDTO": { + "type": "object", + "properties": { + "respondedBy": { + "type": "string" + } + }, + "required": [ + "respondedBy" + ] + }, "WithdrawFromRaceParamsDTO": { "type": "object", "properties": { @@ -213,6 +344,25 @@ "driverId" ] }, + "ReviewProtestCommandDTO": { + "type": "object", + "properties": { + "protestId": { + "type": "string" + }, + "stewardId": { + "type": "string" + }, + "enum": { + "type": "string" + } + }, + "required": [ + "protestId", + "stewardId", + "enum" + ] + }, "RequestProtestDefenseCommandDTO": { "type": "object", "properties": { @@ -874,25 +1024,6 @@ "enum" ] }, - "RequestAvatarGenerationInputDTO": { - "type": "object", - "properties": { - "userId": { - "type": "string" - }, - "facePhotoData": { - "type": "string" - }, - "suitColor": { - "type": "string" - } - }, - "required": [ - "userId", - "facePhotoData", - "suitColor" - ] - }, "UpdatePaymentStatusInputDTO": { "type": "object", "properties": { @@ -1058,99 +1189,7 @@ "id" ] }, - "GetDriverRegistrationStatusQueryDTO": { - "type": "object", - "properties": { - "raceId": { - "type": "string" - }, - "driverId": { - "type": "string" - } - }, - "required": [ - "raceId", - "driverId" - ] - }, - "DriverStatsDTO": { - "type": "object", - "properties": { - "totalDrivers": { - "type": "number" - } - }, - "required": [ - "totalDrivers" - ] - }, - "DriverRegistrationStatusDTO": { - "type": "object", - "properties": { - "isRegistered": { - "type": "boolean" - }, - "raceId": { - "type": "string" - }, - "driverId": { - "type": "string" - } - }, - "required": [ - "isRegistered", - "raceId", - "driverId" - ] - }, - "DriverLeaderboardItemDTO": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "rating": { - "type": "number" - }, - "skillLevel": { - "type": "string" - }, - "nationality": { - "type": "string" - }, - "racesCompleted": { - "type": "number" - }, - "wins": { - "type": "number" - }, - "podiums": { - "type": "number" - }, - "isActive": { - "type": "boolean" - }, - "rank": { - "type": "number" - } - }, - "required": [ - "id", - "name", - "rating", - "skillLevel", - "nationality", - "racesCompleted", - "wins", - "podiums", - "isActive", - "rank" - ] - }, - "CompleteOnboardingOutputDTO": { + "UploadMediaOutputDTO": { "type": "object", "properties": { "success": { @@ -1161,27 +1200,101 @@ "success" ] }, - "CompleteOnboardingInputDTO": { + "UpdateAvatarOutputDTO": { "type": "object", "properties": { - "firstName": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ] + }, + "UpdateAvatarInputDTO": { + "type": "object", + "properties": { + "driverId": { "type": "string" }, - "lastName": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "country": { + "avatarUrl": { "type": "string" } }, "required": [ - "firstName", - "lastName", - "displayName", - "country" + "driverId", + "avatarUrl" + ] + }, + "RequestAvatarGenerationInputDTO": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "facePhotoData": { + "type": "string" + }, + "suitColor": { + "type": "string" + } + }, + "required": [ + "userId", + "facePhotoData", + "suitColor" + ] + }, + "GetMediaOutputDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "type": { + "type": "string" + }, + "category": { + "type": "string" + }, + "uploadedAt": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "number" + } + }, + "required": [ + "id", + "url", + "type", + "uploadedAt" + ] + }, + "GetAvatarOutputDTO": { + "type": "object", + "properties": { + "avatarUrl": { + "type": "string" + } + }, + "required": [ + "avatarUrl" + ] + }, + "DeleteMediaOutputDTO": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" ] }, "UpdateLeagueMemberRoleOutputDTO": { @@ -1651,6 +1764,330 @@ "leagueId" ] }, + "GetDriverRegistrationStatusQueryDTO": { + "type": "object", + "properties": { + "raceId": { + "type": "string" + }, + "driverId": { + "type": "string" + } + }, + "required": [ + "raceId", + "driverId" + ] + }, + "DriverProfileDriverSummaryDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "country": { + "type": "string" + }, + "avatarUrl": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "country", + "avatarUrl" + ] + }, + "DriverProfileStatsDTO": { + "type": "object", + "properties": { + "totalRaces": { + "type": "number" + }, + "wins": { + "type": "number" + }, + "podiums": { + "type": "number" + }, + "dnfs": { + "type": "number" + } + }, + "required": [ + "totalRaces", + "wins", + "podiums", + "dnfs" + ] + }, + "DriverProfileFinishDistributionDTO": { + "type": "object", + "properties": { + "totalRaces": { + "type": "number" + }, + "wins": { + "type": "number" + }, + "podiums": { + "type": "number" + }, + "topTen": { + "type": "number" + }, + "dnfs": { + "type": "number" + }, + "other": { + "type": "number" + } + }, + "required": [ + "totalRaces", + "wins", + "podiums", + "topTen", + "dnfs", + "other" + ] + }, + "DriverProfileTeamMembershipDTO": { + "type": "object", + "properties": { + "teamId": { + "type": "string" + }, + "teamName": { + "type": "string" + } + }, + "required": [ + "teamId", + "teamName" + ] + }, + "DriverProfileSocialFriendSummaryDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "country": { + "type": "string" + }, + "avatarUrl": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "country", + "avatarUrl" + ] + }, + "DriverProfileSocialSummaryDTO": { + "type": "object", + "properties": { + "friendsCount": { + "type": "number" + } + }, + "required": [ + "friendsCount" + ] + }, + "DriverProfileAchievementDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "id", + "title", + "description" + ] + }, + "GetDriverOutputDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "iracingId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "country": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "joinedAt": { + "type": "string" + } + }, + "required": [ + "id", + "iracingId", + "name", + "country", + "joinedAt" + ] + }, + "DriverStatsDTO": { + "type": "object", + "properties": { + "totalDrivers": { + "type": "number" + } + }, + "required": [ + "totalDrivers" + ] + }, + "DriverRegistrationStatusDTO": { + "type": "object", + "properties": { + "isRegistered": { + "type": "boolean" + }, + "raceId": { + "type": "string" + }, + "driverId": { + "type": "string" + } + }, + "required": [ + "isRegistered", + "raceId", + "driverId" + ] + }, + "DriverLeaderboardItemDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "rating": { + "type": "number" + }, + "skillLevel": { + "type": "string" + }, + "nationality": { + "type": "string" + }, + "racesCompleted": { + "type": "number" + }, + "wins": { + "type": "number" + }, + "podiums": { + "type": "number" + }, + "isActive": { + "type": "boolean" + }, + "rank": { + "type": "number" + } + }, + "required": [ + "id", + "name", + "rating", + "skillLevel", + "nationality", + "racesCompleted", + "wins", + "podiums", + "isActive", + "rank" + ] + }, + "DriverDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "iracingId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "country": { + "type": "string" + } + }, + "required": [ + "id", + "iracingId", + "name", + "country" + ] + }, + "CompleteOnboardingOutputDTO": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ] + }, + "CompleteOnboardingInputDTO": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "country": { + "type": "string" + } + }, + "required": [ + "firstName", + "lastName", + "displayName", + "country" + ] + }, "AuthenticatedUserDTO": { "type": "object", "properties": { @@ -1710,6 +2147,52 @@ "eventId", "engagementWeight" ] + }, + "GetDashboardDataOutputDTO": { + "type": "object", + "properties": { + "totalUsers": { + "type": "number" + }, + "activeUsers": { + "type": "number" + }, + "totalRaces": { + "type": "number" + }, + "totalLeagues": { + "type": "number" + } + }, + "required": [ + "totalUsers", + "activeUsers", + "totalRaces", + "totalLeagues" + ] + }, + "GetAnalyticsMetricsOutputDTO": { + "type": "object", + "properties": { + "pageViews": { + "type": "number" + }, + "uniqueVisitors": { + "type": "number" + }, + "averageSessionDuration": { + "type": "number" + }, + "bounceRate": { + "type": "number" + } + }, + "required": [ + "pageViews", + "uniqueVisitors", + "averageSessionDuration", + "bounceRate" + ] } } } diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index f4369e674..c6fef1758 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -7,8 +7,10 @@ import { DatabaseModule } from './infrastructure/database/database.module'; import { LoggingModule } from './infrastructure/logging/LoggingModule'; import { BootstrapModule } from './infrastructure/bootstrap/BootstrapModule'; import { AuthModule } from './domain/auth/AuthModule'; +import { DashboardModule } from './domain/dashboard/DashboardModule'; import { LeagueModule } from './domain/league/LeagueModule'; import { RaceModule } from './domain/race/RaceModule'; +import { ProtestsModule } from './domain/protests/ProtestsModule'; import { TeamModule } from './domain/team/TeamModule'; import { SponsorModule } from './domain/sponsor/SponsorModule'; import { DriverModule } from './domain/driver/DriverModule'; @@ -22,8 +24,10 @@ import { PaymentsModule } from './domain/payments/PaymentsModule'; BootstrapModule, AnalyticsModule, AuthModule, + DashboardModule, LeagueModule, RaceModule, + ProtestsModule, TeamModule, SponsorModule, DriverModule, diff --git a/apps/api/src/domain/analytics/AnalyticsController.ts b/apps/api/src/domain/analytics/AnalyticsController.ts index c4a07da91..5aa2b3f79 100644 --- a/apps/api/src/domain/analytics/AnalyticsController.ts +++ b/apps/api/src/domain/analytics/AnalyticsController.ts @@ -1,16 +1,22 @@ -import { Controller, Post, Body, Res, HttpStatus } from '@nestjs/common'; +import { Controller, Get, Post, Body, Res, HttpStatus } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBody, ApiResponse } from '@nestjs/swagger'; import type { Response } from 'express'; import type { RecordPageViewInputDTO } from './dtos/RecordPageViewInputDTO'; import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO'; import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO'; import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO'; +import type { GetDashboardDataOutputDTO } from './dtos/GetDashboardDataOutputDTO'; +import type { GetAnalyticsMetricsOutputDTO } from './dtos/GetAnalyticsMetricsOutputDTO'; import { AnalyticsService } from './AnalyticsService'; type RecordPageViewInput = RecordPageViewInputDTO; type RecordPageViewOutput = RecordPageViewOutputDTO; type RecordEngagementInput = RecordEngagementInputDTO; type RecordEngagementOutput = RecordEngagementOutputDTO; +type GetDashboardDataOutput = GetDashboardDataOutputDTO; +type GetAnalyticsMetricsOutput = GetAnalyticsMetricsOutputDTO; +@ApiTags('analytics') @Controller('analytics') export class AnalyticsController { constructor( @@ -18,6 +24,9 @@ export class AnalyticsController { ) {} @Post('page-view') + @ApiOperation({ summary: 'Record a page view' }) + @ApiBody({ type: RecordPageViewInputDTO }) + @ApiResponse({ status: 201, description: 'Page view recorded', type: RecordPageViewOutputDTO }) async recordPageView( @Body() input: RecordPageViewInput, @Res() res: Response, @@ -27,6 +36,9 @@ export class AnalyticsController { } @Post('engagement') + @ApiOperation({ summary: 'Record an engagement event' }) + @ApiBody({ type: RecordEngagementInputDTO }) + @ApiResponse({ status: 201, description: 'Engagement recorded', type: RecordEngagementOutputDTO }) async recordEngagement( @Body() input: RecordEngagementInput, @Res() res: Response, @@ -34,4 +46,18 @@ export class AnalyticsController { const output: RecordEngagementOutput = await this.analyticsService.recordEngagement(input); res.status(HttpStatus.CREATED).json(output); } + + @Get('dashboard') + @ApiOperation({ summary: 'Get analytics dashboard data' }) + @ApiResponse({ status: 200, description: 'Dashboard data', type: GetDashboardDataOutputDTO }) + async getDashboardData(): Promise { + return await this.analyticsService.getDashboardData(); + } + + @Get('metrics') + @ApiOperation({ summary: 'Get analytics metrics' }) + @ApiResponse({ status: 200, description: 'Analytics metrics', type: GetAnalyticsMetricsOutputDTO }) + async getAnalyticsMetrics(): Promise { + return await this.analyticsService.getAnalyticsMetrics(); + } } diff --git a/apps/api/src/domain/analytics/AnalyticsService.ts b/apps/api/src/domain/analytics/AnalyticsService.ts index 1b3bb4e16..ec4f7ea42 100644 --- a/apps/api/src/domain/analytics/AnalyticsService.ts +++ b/apps/api/src/domain/analytics/AnalyticsService.ts @@ -3,6 +3,8 @@ import type { RecordPageViewInputDTO } from './dtos/RecordPageViewInputDTO'; import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO'; import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO'; import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO'; +import type { GetDashboardDataOutputDTO } from './dtos/GetDashboardDataOutputDTO'; +import type { GetAnalyticsMetricsOutputDTO } from './dtos/GetAnalyticsMetricsOutputDTO'; import type { Logger } from '@core/shared/application'; import { RecordPageViewUseCase } from './use-cases/RecordPageViewUseCase'; import { RecordEngagementUseCase } from './use-cases/RecordEngagementUseCase'; @@ -11,6 +13,8 @@ type RecordPageViewInput = RecordPageViewInputDTO; type RecordPageViewOutput = RecordPageViewOutputDTO; type RecordEngagementInput = RecordEngagementInputDTO; type RecordEngagementOutput = RecordEngagementOutputDTO; +type GetDashboardDataOutput = GetDashboardDataOutputDTO; +type GetAnalyticsMetricsOutput = GetAnalyticsMetricsOutputDTO; const Logger_TOKEN = 'Logger_TOKEN'; const RECORD_PAGE_VIEW_USE_CASE_TOKEN = 'RecordPageViewUseCase_TOKEN'; @@ -31,4 +35,24 @@ export class AnalyticsService { async recordEngagement(input: RecordEngagementInput): Promise { return await this.recordEngagementUseCase.execute(input); } + + async getDashboardData(): Promise { + // TODO: Implement actual dashboard data retrieval + return { + totalUsers: 0, + activeUsers: 0, + totalRaces: 0, + totalLeagues: 0, + }; + } + + async getAnalyticsMetrics(): Promise { + // TODO: Implement actual analytics metrics retrieval + return { + pageViews: 0, + uniqueVisitors: 0, + averageSessionDuration: 0, + bounceRate: 0, + }; + } } diff --git a/apps/api/src/domain/analytics/dtos/GetAnalyticsMetricsOutputDTO.ts b/apps/api/src/domain/analytics/dtos/GetAnalyticsMetricsOutputDTO.ts new file mode 100644 index 000000000..b1570c3cd --- /dev/null +++ b/apps/api/src/domain/analytics/dtos/GetAnalyticsMetricsOutputDTO.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class GetAnalyticsMetricsOutputDTO { + @ApiProperty() + @IsNumber() + pageViews!: number; + + @ApiProperty() + @IsNumber() + uniqueVisitors!: number; + + @ApiProperty() + @IsNumber() + averageSessionDuration!: number; + + @ApiProperty() + @IsNumber() + bounceRate!: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/analytics/dtos/GetDashboardDataOutputDTO.ts b/apps/api/src/domain/analytics/dtos/GetDashboardDataOutputDTO.ts new file mode 100644 index 000000000..4200f8491 --- /dev/null +++ b/apps/api/src/domain/analytics/dtos/GetDashboardDataOutputDTO.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class GetDashboardDataOutputDTO { + @ApiProperty() + @IsNumber() + totalUsers!: number; + + @ApiProperty() + @IsNumber() + activeUsers!: number; + + @ApiProperty() + @IsNumber() + totalRaces!: number; + + @ApiProperty() + @IsNumber() + totalLeagues!: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/auth/AuthController.ts b/apps/api/src/domain/auth/AuthController.ts index 7b34a0615..3dbf7af96 100644 --- a/apps/api/src/domain/auth/AuthController.ts +++ b/apps/api/src/domain/auth/AuthController.ts @@ -1,34 +1,34 @@ import { Controller, Get, Post, Body, Query, Res, Redirect, HttpStatus } from '@nestjs/common'; import { Response } from 'express'; import { AuthService } from './AuthService'; -import { LoginParams, SignupParams, LoginWithIracingCallbackParams } from './dto/AuthDto'; +import { LoginParams, SignupParams, LoginWithIracingCallbackParams, AuthSessionDTO, IracingAuthRedirectResult } from './dto/AuthDto'; @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} @Post('signup') - async signup(@Body() params: SignupParams) { + async signup(@Body() params: SignupParams): Promise { return this.authService.signupWithEmail(params); } @Post('login') - async login(@Body() params: LoginParams) { + async login(@Body() params: LoginParams): Promise { return this.authService.loginWithEmail(params); } @Get('session') - async getSession() { + async getSession(): Promise { return this.authService.getCurrentSession(); } @Post('logout') - async logout() { + async logout(): Promise { return this.authService.logout(); } @Get('iracing/start') - async startIracingAuthRedirect(@Query('returnTo') returnTo?: string, @Res() res?: Response) { + async startIracingAuthRedirect(@Query('returnTo') returnTo?: string, @Res() res?: Response): Promise { const { redirectUrl, state } = await this.authService.startIracingAuthRedirect(returnTo); // In real application, you might want to store 'state' in a secure cookie or session. // For this example, we'll just redirect. @@ -36,7 +36,7 @@ export class AuthController { } @Get('iracing/callback') - async loginWithIracingCallback(@Query('code') code: string, @Query('state') state: string, @Query('returnTo') returnTo?: string) { + async loginWithIracingCallback(@Query('code') code: string, @Query('state') state: string, @Query('returnTo') returnTo?: string): Promise { return this.authService.loginWithIracingCallback({ code, state, returnTo }); } } diff --git a/apps/api/src/domain/dashboard/DashboardController.ts b/apps/api/src/domain/dashboard/DashboardController.ts new file mode 100644 index 000000000..cbc7a2bfc --- /dev/null +++ b/apps/api/src/domain/dashboard/DashboardController.ts @@ -0,0 +1,18 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiTags, ApiResponse, ApiOperation, ApiQuery } from '@nestjs/swagger'; +import { DashboardService } from './DashboardService'; +import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO'; + +@ApiTags('dashboard') +@Controller('dashboard') +export class DashboardController { + constructor(private readonly dashboardService: DashboardService) {} + + @Get('overview') + @ApiOperation({ summary: 'Get dashboard overview' }) + @ApiQuery({ name: 'driverId', description: 'Driver ID' }) + @ApiResponse({ status: 200, description: 'Dashboard overview', type: DashboardOverviewDTO }) + async getDashboardOverview(@Query('driverId') driverId: string): Promise { + return this.dashboardService.getDashboardOverview(driverId); + } +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/DashboardModule.ts b/apps/api/src/domain/dashboard/DashboardModule.ts new file mode 100644 index 000000000..092b26a57 --- /dev/null +++ b/apps/api/src/domain/dashboard/DashboardModule.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { DashboardService } from './DashboardService'; +import { DashboardController } from './DashboardController'; +import { DashboardProviders } from './DashboardProviders'; + +@Module({ + controllers: [DashboardController], + providers: DashboardProviders, + exports: [DashboardService], +}) +export class DashboardModule {} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/DashboardProviders.ts b/apps/api/src/domain/dashboard/DashboardProviders.ts new file mode 100644 index 000000000..79b624eb1 --- /dev/null +++ b/apps/api/src/domain/dashboard/DashboardProviders.ts @@ -0,0 +1,23 @@ +import { Provider } from '@nestjs/common'; +import { DashboardService } from './DashboardService'; + +// Import core interfaces +import type { Logger } from '@core/shared/application/Logger'; + +// Import concrete implementations +import { ConsoleLogger } from '@adapters/logging/ConsoleLogger'; + +// Import use cases +import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase'; + +// Define injection tokens +export const LOGGER_TOKEN = 'Logger'; + +export const DashboardProviders: Provider[] = [ + DashboardService, + { + provide: LOGGER_TOKEN, + useClass: ConsoleLogger, + }, + DashboardOverviewUseCase, +]; \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/DashboardService.ts b/apps/api/src/domain/dashboard/DashboardService.ts new file mode 100644 index 000000000..88a0731c1 --- /dev/null +++ b/apps/api/src/domain/dashboard/DashboardService.ts @@ -0,0 +1,28 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase'; + +// Core imports +import type { Logger } from '@core/shared/application/Logger'; + +// Tokens +import { LOGGER_TOKEN } from './DashboardProviders'; + +@Injectable() +export class DashboardService { + constructor( + private readonly dashboardOverviewUseCase: DashboardOverviewUseCase, + @Inject(LOGGER_TOKEN) private readonly logger: Logger, + ) {} + + async getDashboardOverview(driverId: string): Promise { + this.logger.debug('[DashboardService] Getting dashboard overview:', { driverId }); + + const result = await this.dashboardOverviewUseCase.execute({ driverId }); + + if (result.isErr()) { + throw new Error(result.error.details.message || 'Failed to get dashboard overview'); + } + + return result.value; + } +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/dtos/DashboardDriverSummaryDTO.ts b/apps/api/src/domain/dashboard/dtos/DashboardDriverSummaryDTO.ts new file mode 100644 index 000000000..ced95cee9 --- /dev/null +++ b/apps/api/src/domain/dashboard/dtos/DashboardDriverSummaryDTO.ts @@ -0,0 +1,47 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNumber, IsOptional } from 'class-validator'; + +export class DashboardDriverSummaryDTO { + @ApiProperty() + @IsString() + id!: string; + + @ApiProperty() + @IsString() + name!: string; + + @ApiProperty() + @IsString() + country!: string; + + @ApiProperty() + @IsString() + avatarUrl!: string; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + rating?: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + globalRank?: number | null; + + @ApiProperty() + @IsNumber() + totalRaces!: number; + + @ApiProperty() + @IsNumber() + wins!: number; + + @ApiProperty() + @IsNumber() + podiums!: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + consistency?: number | null; +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/dtos/DashboardFeedItemSummaryDTO.ts b/apps/api/src/domain/dashboard/dtos/DashboardFeedItemSummaryDTO.ts new file mode 100644 index 000000000..2df53f238 --- /dev/null +++ b/apps/api/src/domain/dashboard/dtos/DashboardFeedItemSummaryDTO.ts @@ -0,0 +1,53 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional } from 'class-validator'; + +export type DashboardFeedItemType = + | 'friend-joined-league' + | 'friend-joined-team' + | 'friend-finished-race' + | 'friend-new-personal-best' + | 'new-race-scheduled' + | 'new-result-posted' + | 'league-highlight'; + +export class DashboardFeedItemSummaryDTO { + @ApiProperty() + @IsString() + id!: string; + + @ApiProperty({ + enum: [ + 'friend-joined-league', + 'friend-joined-team', + 'friend-finished-race', + 'friend-new-personal-best', + 'new-race-scheduled', + 'new-result-posted', + 'league-highlight', + ], + }) + type!: DashboardFeedItemType; + + @ApiProperty() + @IsString() + headline!: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + body?: string; + + @ApiProperty() + @IsString() + timestamp!: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + ctaLabel?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + ctaHref?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/dtos/DashboardFeedSummaryDTO.ts b/apps/api/src/domain/dashboard/dtos/DashboardFeedSummaryDTO.ts new file mode 100644 index 000000000..0ef464782 --- /dev/null +++ b/apps/api/src/domain/dashboard/dtos/DashboardFeedSummaryDTO.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; +import { DashboardFeedItemSummaryDTO } from './DashboardFeedItemSummaryDTO'; + +export class DashboardFeedSummaryDTO { + @ApiProperty() + @IsNumber() + notificationCount!: number; + + @ApiProperty({ type: [DashboardFeedItemSummaryDTO] }) + items!: DashboardFeedItemSummaryDTO[]; +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/dtos/DashboardFriendSummaryDTO.ts b/apps/api/src/domain/dashboard/dtos/DashboardFriendSummaryDTO.ts new file mode 100644 index 000000000..02cdb8b14 --- /dev/null +++ b/apps/api/src/domain/dashboard/dtos/DashboardFriendSummaryDTO.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class DashboardFriendSummaryDTO { + @ApiProperty() + @IsString() + id!: string; + + @ApiProperty() + @IsString() + name!: string; + + @ApiProperty() + @IsString() + country!: string; + + @ApiProperty() + @IsString() + avatarUrl!: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/dtos/DashboardLeagueStandingSummaryDTO.ts b/apps/api/src/domain/dashboard/dtos/DashboardLeagueStandingSummaryDTO.ts new file mode 100644 index 000000000..99966f01c --- /dev/null +++ b/apps/api/src/domain/dashboard/dtos/DashboardLeagueStandingSummaryDTO.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNumber } from 'class-validator'; + +export class DashboardLeagueStandingSummaryDTO { + @ApiProperty() + @IsString() + leagueId!: string; + + @ApiProperty() + @IsString() + leagueName!: string; + + @ApiProperty() + @IsNumber() + position!: number; + + @ApiProperty() + @IsNumber() + totalDrivers!: number; + + @ApiProperty() + @IsNumber() + points!: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/dtos/DashboardOverviewDTO.ts b/apps/api/src/domain/dashboard/dtos/DashboardOverviewDTO.ts new file mode 100644 index 000000000..36706f79c --- /dev/null +++ b/apps/api/src/domain/dashboard/dtos/DashboardOverviewDTO.ts @@ -0,0 +1,41 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; +import { DashboardDriverSummaryDTO } from '../../../race/dtos/DashboardDriverSummaryDTO'; +import { DashboardRaceSummaryDTO } from '../../../race/dtos/DashboardRaceSummaryDTO'; +import { DashboardRecentResultDTO } from '../../../race/dtos/DashboardRecentResultDTO'; +import { DashboardLeagueStandingSummaryDTO } from '../../../race/dtos/DashboardLeagueStandingSummaryDTO'; +import { DashboardFeedSummaryDTO } from '../../../race/dtos/DashboardFeedSummaryDTO'; +import { DashboardFriendSummaryDTO } from '../../../race/dtos/DashboardFriendSummaryDTO'; + +export class DashboardOverviewDTO { + @ApiProperty({ nullable: true }) + currentDriver!: DashboardDriverSummaryDTO | null; + + @ApiProperty({ type: [DashboardRaceSummaryDTO] }) + myUpcomingRaces!: DashboardRaceSummaryDTO[]; + + @ApiProperty({ type: [DashboardRaceSummaryDTO] }) + otherUpcomingRaces!: DashboardRaceSummaryDTO[]; + + @ApiProperty({ type: [DashboardRaceSummaryDTO] }) + upcomingRaces!: DashboardRaceSummaryDTO[]; + + @ApiProperty() + @IsNumber() + activeLeaguesCount!: number; + + @ApiProperty({ nullable: true }) + nextRace!: DashboardRaceSummaryDTO | null; + + @ApiProperty({ type: [DashboardRecentResultDTO] }) + recentResults!: DashboardRecentResultDTO[]; + + @ApiProperty({ type: [DashboardLeagueStandingSummaryDTO] }) + leagueStandingsSummaries!: DashboardLeagueStandingSummaryDTO[]; + + @ApiProperty() + feedSummary!: DashboardFeedSummaryDTO; + + @ApiProperty({ type: [DashboardFriendSummaryDTO] }) + friends!: DashboardFriendSummaryDTO[]; +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/dtos/DashboardRaceSummaryDTO.ts b/apps/api/src/domain/dashboard/dtos/DashboardRaceSummaryDTO.ts new file mode 100644 index 000000000..4c870af7c --- /dev/null +++ b/apps/api/src/domain/dashboard/dtos/DashboardRaceSummaryDTO.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsBoolean } from 'class-validator'; + +export class DashboardRaceSummaryDTO { + @ApiProperty() + @IsString() + id!: string; + + @ApiProperty() + @IsString() + leagueId!: string; + + @ApiProperty() + @IsString() + leagueName!: string; + + @ApiProperty() + @IsString() + track!: string; + + @ApiProperty() + @IsString() + car!: string; + + @ApiProperty() + @IsString() + scheduledAt!: string; + + @ApiProperty({ enum: ['scheduled', 'running', 'completed', 'cancelled'] }) + @IsString() + status!: 'scheduled' | 'running' | 'completed' | 'cancelled'; + + @ApiProperty() + @IsBoolean() + isMyLeague!: boolean; +} \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/dtos/DashboardRecentResultDTO.ts b/apps/api/src/domain/dashboard/dtos/DashboardRecentResultDTO.ts new file mode 100644 index 000000000..5cec87cd8 --- /dev/null +++ b/apps/api/src/domain/dashboard/dtos/DashboardRecentResultDTO.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNumber } from 'class-validator'; + +export class DashboardRecentResultDTO { + @ApiProperty() + @IsString() + raceId!: string; + + @ApiProperty() + @IsString() + raceName!: string; + + @ApiProperty() + @IsString() + leagueId!: string; + + @ApiProperty() + @IsString() + leagueName!: string; + + @ApiProperty() + @IsString() + finishedAt!: string; + + @ApiProperty() + @IsNumber() + position!: number; + + @ApiProperty() + @IsNumber() + incidents!: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/DriverController.ts b/apps/api/src/domain/driver/DriverController.ts index 1d71c645b..93a6794c8 100644 --- a/apps/api/src/domain/driver/DriverController.ts +++ b/apps/api/src/domain/driver/DriverController.ts @@ -9,6 +9,8 @@ import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO' import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO'; import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO'; import { DriverDTO } from './dtos/DriverDTO'; +import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO'; +import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO'; @ApiTags('drivers') @Controller('drivers') @@ -31,9 +33,9 @@ export class DriverController { @Get('current') @ApiOperation({ summary: 'Get current authenticated driver' }) - @ApiResponse({ status: 200, description: 'Current driver data', type: DriverDTO }) + @ApiResponse({ status: 200, description: 'Current driver data', type: GetDriverOutputDTO }) @ApiResponse({ status: 404, description: 'Driver not found' }) - async getCurrentDriver(@Req() req: Request): Promise { + async getCurrentDriver(@Req() req: Request): Promise { // Assuming userId is available from the request (e.g., via auth middleware) const userId = req['user']?.userId; if (!userId) { @@ -64,13 +66,29 @@ export class DriverController { return this.driverService.getDriverRegistrationStatus({ driverId, raceId }); } + @Get(':driverId') + @ApiOperation({ summary: 'Get driver by ID' }) + @ApiResponse({ status: 200, description: 'Driver data', type: GetDriverOutputDTO }) + @ApiResponse({ status: 404, description: 'Driver not found' }) + async getDriver(@Param('driverId') driverId: string): Promise { + return this.driverService.getDriver(driverId); + } + + @Get(':driverId/profile') + @ApiOperation({ summary: 'Get driver profile with full details' }) + @ApiResponse({ status: 200, description: 'Driver profile data', type: GetDriverProfileOutputDTO }) + @ApiResponse({ status: 404, description: 'Driver not found' }) + async getDriverProfile(@Param('driverId') driverId: string): Promise { + return this.driverService.getDriverProfile(driverId); + } + @Put(':driverId/profile') @ApiOperation({ summary: 'Update driver profile' }) - @ApiResponse({ status: 200, description: 'Driver profile updated', type: DriverDTO }) + @ApiResponse({ status: 200, description: 'Driver profile updated', type: GetDriverOutputDTO }) async updateDriverProfile( @Param('driverId') driverId: string, @Body() body: { bio?: string; country?: string }, - ): Promise { + ): Promise { return this.driverService.updateDriverProfile(driverId, body.bio, body.country); } diff --git a/apps/api/src/domain/driver/DriverService.ts b/apps/api/src/domain/driver/DriverService.ts index 00a9eeb8e..bd6bac889 100644 --- a/apps/api/src/domain/driver/DriverService.ts +++ b/apps/api/src/domain/driver/DriverService.ts @@ -5,6 +5,8 @@ import { CompleteOnboardingInputDTO } from './dtos/CompleteOnboardingInputDTO'; import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO'; import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO'; import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO'; +import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO'; +import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO'; // Use cases import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase'; @@ -76,7 +78,7 @@ export class DriverService { return presenter.viewModel; } - async getCurrentDriver(userId: string): Promise { + async getCurrentDriver(userId: string): Promise { this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`); const driver = await this.driverRepository.findById(userId); @@ -86,11 +88,15 @@ export class DriverService { return { id: driver.id, + iracingId: driver.iracingId.value, name: driver.name.value, + country: driver.country.value, + bio: driver.bio?.value, + joinedAt: driver.joinedAt.toISOString(), }; } - async updateDriverProfile(driverId: string, bio?: string, country?: string): Promise { + async updateDriverProfile(driverId: string, bio?: string, country?: string): Promise { this.logger.debug(`[DriverService] Updating driver profile for driverId: ${driverId}`); const result = await this.updateDriverProfileUseCase.execute({ driverId, bio, country }); @@ -101,4 +107,40 @@ export class DriverService { return result.value; } + + async getDriver(driverId: string): Promise { + this.logger.debug(`[DriverService] Fetching driver for driverId: ${driverId}`); + + const driver = await this.driverRepository.findById(driverId); + if (!driver) { + return null; + } + + return { + id: driver.id, + iracingId: driver.iracingId.value, + name: driver.name.value, + country: driver.country.value, + bio: driver.bio?.value, + joinedAt: driver.joinedAt.toISOString(), + }; + } + + async getDriverProfile(driverId: string): Promise { + this.logger.debug(`[DriverService] Fetching driver profile for driverId: ${driverId}`); + + // TODO: Implement proper driver profile fetching with all the detailed data + // For now, return a placeholder structure + return { + currentDriver: null, + stats: null, + finishDistribution: null, + teamMemberships: [], + socialSummary: { + friendsCount: 0, + friends: [], + }, + extendedProfile: null, + }; + } } diff --git a/apps/api/src/domain/driver/dtos/DriverDTO.ts b/apps/api/src/domain/driver/dtos/DriverDTO.ts new file mode 100644 index 000000000..947befe75 --- /dev/null +++ b/apps/api/src/domain/driver/dtos/DriverDTO.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DriverDTO { + @ApiProperty() + id: string; + + @ApiProperty() + iracingId: string; + + @ApiProperty() + name: string; + + @ApiProperty() + country: string; + + @ApiProperty({ required: false }) + bio?: string; + + @ApiProperty() + joinedAt: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/driver/dtos/GetDriverOutputDTO.ts b/apps/api/src/domain/driver/dtos/GetDriverOutputDTO.ts new file mode 100644 index 000000000..9632d9c27 --- /dev/null +++ b/apps/api/src/domain/driver/dtos/GetDriverOutputDTO.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class GetDriverOutputDTO { + @ApiProperty() + id: string; + + @ApiProperty() + iracingId: string; + + @ApiProperty() + name: string; + + @ApiProperty() + country: string; + + @ApiProperty() + bio?: string; + + @ApiProperty() + joinedAt: string; +} \ 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 new file mode 100644 index 000000000..26ed7e9c4 --- /dev/null +++ b/apps/api/src/domain/driver/dtos/GetDriverProfileOutputDTO.ts @@ -0,0 +1,226 @@ +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 type DriverProfileAchievementRarity = 'common' | 'rare' | 'epic' | '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; +} + +export class GetDriverProfileOutputDTO { + @ApiProperty({ type: DriverProfileDriverSummaryDTO, nullable: true }) + currentDriver: DriverProfileDriverSummaryDTO | null; + + @ApiProperty({ type: DriverProfileStatsDTO, nullable: true }) + stats: DriverProfileStatsDTO | null; + + @ApiProperty({ type: DriverProfileFinishDistributionDTO, nullable: true }) + finishDistribution: DriverProfileFinishDistributionDTO | null; + + @ApiProperty({ type: [DriverProfileTeamMembershipDTO] }) + teamMemberships: DriverProfileTeamMembershipDTO[]; + + @ApiProperty({ type: DriverProfileSocialSummaryDTO }) + socialSummary: DriverProfileSocialSummaryDTO; + + @ApiProperty({ type: DriverProfileExtendedProfileDTO, nullable: true }) + extendedProfile: DriverProfileExtendedProfileDTO | null; +} \ No newline at end of file diff --git a/apps/api/src/domain/league/LeagueController.ts b/apps/api/src/domain/league/LeagueController.ts index f2977c9c0..7e606e872 100644 --- a/apps/api/src/domain/league/LeagueController.ts +++ b/apps/api/src/domain/league/LeagueController.ts @@ -30,6 +30,8 @@ import { GetLeagueProtestsQueryDTO } from './dtos/GetLeagueProtestsQueryDTO'; import { GetLeagueSeasonsQueryDTO } from './dtos/GetLeagueSeasonsQueryDTO'; import { GetLeagueAdminConfigQueryDTO } from './dtos/GetLeagueAdminConfigQueryDTO'; import { GetLeagueOwnerSummaryQueryDTO } from './dtos/GetLeagueOwnerSummaryQueryDTO'; +import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO'; +import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO'; @ApiTags('leagues') @Controller('leagues') @@ -262,4 +264,18 @@ export class LeagueController { async transferLeagueOwnership(@Param('leagueId') leagueId: string, @Body() body: { currentOwnerId: string, newOwnerId: string }) { return this.leagueService.transferLeagueOwnership(leagueId, body.currentOwnerId, body.newOwnerId); } + + @Get('seasons/:seasonId/sponsorships') + @ApiOperation({ summary: 'Get season sponsorships' }) + @ApiResponse({ status: 200, description: 'Season sponsorships', type: GetSeasonSponsorshipsOutputDTO }) + async getSeasonSponsorships(@Param('seasonId') seasonId: string): Promise { + return this.leagueService.getSeasonSponsorships(seasonId); + } + + @Get(':leagueId/races') + @ApiOperation({ summary: 'Get league races' }) + @ApiResponse({ status: 200, description: 'League races', type: GetLeagueRacesOutputDTO }) + async getRaces(@Param('leagueId') leagueId: string): Promise { + return this.leagueService.getRaces(leagueId); + } } diff --git a/apps/api/src/domain/league/LeagueService.ts b/apps/api/src/domain/league/LeagueService.ts index a09179648..384f93e58 100644 --- a/apps/api/src/domain/league/LeagueService.ts +++ b/apps/api/src/domain/league/LeagueService.ts @@ -27,6 +27,8 @@ import { LeagueStatsDTO } from './dtos/LeagueStatsDTO'; import { LeagueAdminDTO } from './dtos/LeagueAdminDTO'; import { CreateLeagueInputDTO } from './dtos/CreateLeagueInputDTO'; import { CreateLeagueOutputDTO } from './dtos/CreateLeagueOutputDTO'; +import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO'; +import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO'; // Core imports import type { Logger } from '@core/shared/application/Logger'; @@ -328,4 +330,24 @@ export class LeagueService { success: true, }; } + + async getSeasonSponsorships(seasonId: string): Promise { + this.logger.debug('Getting season sponsorships', { seasonId }); + + // TODO: Implement actual logic to fetch season sponsorships + // For now, return empty array as placeholder + return { + sponsorships: [], + }; + } + + async getRaces(leagueId: string): Promise { + this.logger.debug('Getting league races', { leagueId }); + + // TODO: Implement actual logic to fetch league races + // For now, return empty array as placeholder + return { + races: [], + }; + } } diff --git a/apps/api/src/domain/league/dtos/GetLeagueRacesOutputDTO.ts b/apps/api/src/domain/league/dtos/GetLeagueRacesOutputDTO.ts new file mode 100644 index 000000000..189b55214 --- /dev/null +++ b/apps/api/src/domain/league/dtos/GetLeagueRacesOutputDTO.ts @@ -0,0 +1,7 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { RaceDTO } from '../../race/dtos/RaceDTO'; + +export class GetLeagueRacesOutputDTO { + @ApiProperty({ type: [RaceDTO] }) + races: RaceDTO[]; +} \ 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 new file mode 100644 index 000000000..4633e2de4 --- /dev/null +++ b/apps/api/src/domain/league/dtos/GetSeasonSponsorshipsOutputDTO.ts @@ -0,0 +1,7 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { SponsorshipDetailDTO } from '../../sponsor/dtos/SponsorshipDetailDTO'; + +export class GetSeasonSponsorshipsOutputDTO { + @ApiProperty({ type: [SponsorshipDetailDTO] }) + sponsorships: SponsorshipDetailDTO[]; +} \ 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 new file mode 100644 index 000000000..0c7a1e48f --- /dev/null +++ b/apps/api/src/domain/league/dtos/LeagueMembershipDTO.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsEnum } from 'class-validator'; + +export class LeagueMembershipDTO { + @ApiProperty() + @IsString() + id: string; + + @ApiProperty() + @IsString() + leagueId: string; + + @ApiProperty() + @IsString() + driverId: string; + + @ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] }) + @IsEnum(['owner', 'admin', 'steward', 'member']) + role: 'owner' | 'admin' | 'steward' | 'member'; + + @ApiProperty({ enum: ['active', 'inactive', 'pending'] }) + @IsEnum(['active', 'inactive', 'pending']) + status: 'active' | 'inactive' | 'pending'; + + @ApiProperty() + @IsString() + joinedAt: string; +} \ 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 new file mode 100644 index 000000000..cb6008932 --- /dev/null +++ b/apps/api/src/domain/league/dtos/LeagueRoleDTO.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; + +export class LeagueRoleDTO { + @ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] }) + @IsEnum(['owner', 'admin', 'steward', 'member']) + value: 'owner' | 'admin' | 'steward' | 'member'; +} \ 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 new file mode 100644 index 000000000..b80c334b5 --- /dev/null +++ b/apps/api/src/domain/league/dtos/LeagueScoringPresetDTO.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsEnum } from 'class-validator'; + +export class LeagueScoringPresetDTO { + @ApiProperty() + @IsString() + id: string; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsString() + description: string; + + @ApiProperty({ enum: ['driver', 'team', 'nations', 'trophy'] }) + @IsEnum(['driver', 'team', 'nations', 'trophy']) + primaryChampionshipType: 'driver' | 'team' | 'nations' | 'trophy'; + + @ApiProperty() + @IsString() + sessionSummary: string; + + @ApiProperty() + @IsString() + bonusSummary: string; + + @ApiProperty() + @IsString() + dropPolicySummary: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/MembershipRoleDTO.ts b/apps/api/src/domain/league/dtos/MembershipRoleDTO.ts new file mode 100644 index 000000000..58bde2186 --- /dev/null +++ b/apps/api/src/domain/league/dtos/MembershipRoleDTO.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; + +export class MembershipRoleDTO { + @ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] }) + @IsEnum(['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 new file mode 100644 index 000000000..804c631de --- /dev/null +++ b/apps/api/src/domain/league/dtos/MembershipStatusDTO.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; + +export class MembershipStatusDTO { + @ApiProperty({ enum: ['active', 'inactive', 'pending'] }) + @IsEnum(['active', 'inactive', 'pending']) + value: 'active' | 'inactive' | 'pending'; +} \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/WizardErrorsDTO.ts b/apps/api/src/domain/league/dtos/WizardErrorsDTO.ts new file mode 100644 index 000000000..015131064 --- /dev/null +++ b/apps/api/src/domain/league/dtos/WizardErrorsDTO.ts @@ -0,0 +1,92 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; + +class WizardErrorsBasicsDTO { + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + name?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + description?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + visibility?: string; +} + +class WizardErrorsStructureDTO { + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + maxDrivers?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + maxTeams?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + driversPerTeam?: string; +} + +class WizardErrorsTimingsDTO { + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + qualifyingMinutes?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + mainRaceMinutes?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + roundsPlanned?: string; +} + +class WizardErrorsScoringDTO { + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + patternId?: string; +} + +export class WizardErrorsDTO { + @ApiProperty({ type: WizardErrorsBasicsDTO, required: false }) + @IsOptional() + @ValidateNested() + @Type(() => WizardErrorsBasicsDTO) + basics?: WizardErrorsBasicsDTO; + + @ApiProperty({ type: WizardErrorsStructureDTO, required: false }) + @IsOptional() + @ValidateNested() + @Type(() => WizardErrorsStructureDTO) + structure?: WizardErrorsStructureDTO; + + @ApiProperty({ type: WizardErrorsTimingsDTO, required: false }) + @IsOptional() + @ValidateNested() + @Type(() => WizardErrorsTimingsDTO) + timings?: WizardErrorsTimingsDTO; + + @ApiProperty({ type: WizardErrorsScoringDTO, required: false }) + @IsOptional() + @ValidateNested() + @Type(() => WizardErrorsScoringDTO) + scoring?: WizardErrorsScoringDTO; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + submit?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/league/dtos/WizardStepDTO.ts b/apps/api/src/domain/league/dtos/WizardStepDTO.ts new file mode 100644 index 000000000..95bb68020 --- /dev/null +++ b/apps/api/src/domain/league/dtos/WizardStepDTO.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +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; +} \ No newline at end of file diff --git a/apps/api/src/domain/media/MediaController.ts b/apps/api/src/domain/media/MediaController.ts index 372476859..4aaea3e34 100644 --- a/apps/api/src/domain/media/MediaController.ts +++ b/apps/api/src/domain/media/MediaController.ts @@ -1,12 +1,27 @@ -import { Controller, Post, Body, HttpStatus, Res } from '@nestjs/common'; -import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; +import { Controller, Post, Get, Delete, Put, Body, HttpStatus, Res, Param, UseInterceptors, UploadedFile } from '@nestjs/common'; +import { ApiTags, ApiResponse, ApiOperation, ApiParam, ApiConsumes } from '@nestjs/swagger'; import { Response } from 'express'; +import { FileInterceptor } from '@nestjs/platform-express'; import { MediaService } from './MediaService'; import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO'; import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO'; +import type { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO'; +import type { UploadMediaOutputDTO } from './dtos/UploadMediaOutputDTO'; +import type { GetMediaOutputDTO } from './dtos/GetMediaOutputDTO'; +import type { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO'; +import type { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO'; +import type { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO'; +import type { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO'; type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO; type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO; +type UploadMediaInput = UploadMediaInputDTO; +type UploadMediaOutput = UploadMediaOutputDTO; +type GetMediaOutput = GetMediaOutputDTO; +type DeleteMediaOutput = DeleteMediaOutputDTO; +type GetAvatarOutput = GetAvatarOutputDTO; +type UpdateAvatarInput = UpdateAvatarInputDTO; +type UpdateAvatarOutput = UpdateAvatarOutputDTO; @ApiTags('media') @Controller('media') @@ -27,4 +42,79 @@ export class MediaController { res.status(HttpStatus.BAD_REQUEST).json(result); } } + + @Post('upload') + @UseInterceptors(FileInterceptor('file')) + @ApiOperation({ summary: 'Upload media file' }) + @ApiConsumes('multipart/form-data') + @ApiResponse({ status: 201, description: 'Media uploaded successfully', type: UploadMediaOutput }) + async uploadMedia( + @UploadedFile() file: Express.Multer.File, + @Body() input: UploadMediaInput, + @Res() res: Response, + ): Promise { + const result = await this.mediaService.uploadMedia({ ...input, file }); + if (result.success) { + res.status(HttpStatus.CREATED).json(result); + } else { + res.status(HttpStatus.BAD_REQUEST).json(result); + } + } + + @Get(':mediaId') + @ApiOperation({ summary: 'Get media by ID' }) + @ApiParam({ name: 'mediaId', description: 'Media ID' }) + @ApiResponse({ status: 200, description: 'Media details', type: GetMediaOutput }) + async getMedia( + @Param('mediaId') mediaId: string, + @Res() res: Response, + ): Promise { + const result = await this.mediaService.getMedia(mediaId); + if (result) { + res.status(HttpStatus.OK).json(result); + } else { + res.status(HttpStatus.NOT_FOUND).json({ error: 'Media not found' }); + } + } + + @Delete(':mediaId') + @ApiOperation({ summary: 'Delete media by ID' }) + @ApiParam({ name: 'mediaId', description: 'Media ID' }) + @ApiResponse({ status: 200, description: 'Media deleted', type: DeleteMediaOutput }) + async deleteMedia( + @Param('mediaId') mediaId: string, + @Res() res: Response, + ): Promise { + const result = await this.mediaService.deleteMedia(mediaId); + res.status(HttpStatus.OK).json(result); + } + + @Get('avatar/:driverId') + @ApiOperation({ summary: 'Get avatar for driver' }) + @ApiParam({ name: 'driverId', description: 'Driver ID' }) + @ApiResponse({ status: 200, description: 'Avatar details', type: GetAvatarOutput }) + async getAvatar( + @Param('driverId') driverId: string, + @Res() res: Response, + ): Promise { + const result = await this.mediaService.getAvatar(driverId); + if (result) { + res.status(HttpStatus.OK).json(result); + } else { + res.status(HttpStatus.NOT_FOUND).json({ error: 'Avatar not found' }); + } + } + + @Put('avatar/:driverId') + @ApiOperation({ summary: 'Update avatar for driver' }) + @ApiParam({ name: 'driverId', description: 'Driver ID' }) + @ApiResponse({ status: 200, description: 'Avatar updated', type: UpdateAvatarOutput }) + async updateAvatar( + @Param('driverId') driverId: string, + @Body() input: UpdateAvatarInput, + @Res() res: Response, + ): Promise { + const result = await this.mediaService.updateAvatar(driverId, input); + res.status(HttpStatus.OK).json(result); + } } diff --git a/apps/api/src/domain/media/MediaService.ts b/apps/api/src/domain/media/MediaService.ts index 013b14d5e..ddda239b0 100644 --- a/apps/api/src/domain/media/MediaService.ts +++ b/apps/api/src/domain/media/MediaService.ts @@ -1,9 +1,23 @@ import { Injectable, Inject } from '@nestjs/common'; import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO'; import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO'; +import type { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO'; +import type { UploadMediaOutputDTO } from './dtos/UploadMediaOutputDTO'; +import type { GetMediaOutputDTO } from './dtos/GetMediaOutputDTO'; +import type { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO'; +import type { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO'; +import type { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO'; +import type { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO'; type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO; type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO; +type UploadMediaInput = UploadMediaInputDTO; +type UploadMediaOutput = UploadMediaOutputDTO; +type GetMediaOutput = GetMediaOutputDTO; +type DeleteMediaOutput = DeleteMediaOutputDTO; +type GetAvatarOutput = GetAvatarOutputDTO; +type UpdateAvatarInput = UpdateAvatarInputDTO; +type UpdateAvatarOutput = UpdateAvatarOutputDTO; // Use cases import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase'; @@ -33,4 +47,42 @@ export class MediaService { }, presenter); return presenter.viewModel; } + + async uploadMedia(input: UploadMediaInput & { file: Express.Multer.File }): Promise { + this.logger.debug('[MediaService] Uploading media.'); + // TODO: Implement media upload logic + return { + success: true, + mediaId: 'placeholder-media-id', + url: 'placeholder-url', + }; + } + + async getMedia(mediaId: string): Promise { + this.logger.debug(`[MediaService] Getting media: ${mediaId}`); + // TODO: Implement get media logic + return null; + } + + async deleteMedia(mediaId: string): Promise { + this.logger.debug(`[MediaService] Deleting media: ${mediaId}`); + // TODO: Implement delete media logic + return { + success: true, + }; + } + + async getAvatar(driverId: string): Promise { + this.logger.debug(`[MediaService] Getting avatar for driver: ${driverId}`); + // TODO: Implement get avatar logic + return null; + } + + async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise { + this.logger.debug(`[MediaService] Updating avatar for driver: ${driverId}`); + // TODO: Implement update avatar logic + return { + success: true, + }; + } } diff --git a/apps/api/src/domain/media/dtos/DeleteMediaOutputDTO.ts b/apps/api/src/domain/media/dtos/DeleteMediaOutputDTO.ts new file mode 100644 index 000000000..9b1ce702c --- /dev/null +++ b/apps/api/src/domain/media/dtos/DeleteMediaOutputDTO.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsString, IsOptional } from 'class-validator'; + +export class DeleteMediaOutputDTO { + @ApiProperty() + @IsBoolean() + success: boolean; + + @ApiProperty({ required: false }) + @IsString() + @IsOptional() + error?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/media/dtos/GetAvatarOutputDTO.ts b/apps/api/src/domain/media/dtos/GetAvatarOutputDTO.ts new file mode 100644 index 000000000..b9c4f5d4a --- /dev/null +++ b/apps/api/src/domain/media/dtos/GetAvatarOutputDTO.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class GetAvatarOutputDTO { + @ApiProperty() + @IsString() + avatarUrl: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/media/dtos/GetMediaOutputDTO.ts b/apps/api/src/domain/media/dtos/GetMediaOutputDTO.ts new file mode 100644 index 000000000..ac186bda0 --- /dev/null +++ b/apps/api/src/domain/media/dtos/GetMediaOutputDTO.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsNumber } from 'class-validator'; + +export class GetMediaOutputDTO { + @ApiProperty() + @IsString() + id: string; + + @ApiProperty() + @IsString() + url: string; + + @ApiProperty() + @IsString() + type: string; + + @ApiProperty() + @IsString() + @IsOptional() + category?: string; + + @ApiProperty() + uploadedAt: Date; + + @ApiProperty() + @IsNumber() + @IsOptional() + size?: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/media/dtos/UpdateAvatarInputDTO.ts b/apps/api/src/domain/media/dtos/UpdateAvatarInputDTO.ts new file mode 100644 index 000000000..647c13d0b --- /dev/null +++ b/apps/api/src/domain/media/dtos/UpdateAvatarInputDTO.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class UpdateAvatarInputDTO { + @ApiProperty() + @IsString() + driverId: string; + + @ApiProperty() + @IsString() + avatarUrl: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/media/dtos/UpdateAvatarOutputDTO.ts b/apps/api/src/domain/media/dtos/UpdateAvatarOutputDTO.ts new file mode 100644 index 000000000..5f5b46d58 --- /dev/null +++ b/apps/api/src/domain/media/dtos/UpdateAvatarOutputDTO.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsString, IsOptional } from 'class-validator'; + +export class UpdateAvatarOutputDTO { + @ApiProperty() + @IsBoolean() + success: boolean; + + @ApiProperty({ required: false }) + @IsString() + @IsOptional() + error?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/media/dtos/UploadMediaInputDTO.ts b/apps/api/src/domain/media/dtos/UploadMediaInputDTO.ts new file mode 100644 index 000000000..4d3a76dd1 --- /dev/null +++ b/apps/api/src/domain/media/dtos/UploadMediaInputDTO.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional } from 'class-validator'; + +export class UploadMediaInputDTO { + @ApiProperty({ type: 'string', format: 'binary' }) + file: any; // File upload handled by multer + + @ApiProperty() + @IsString() + type: string; + + @ApiProperty({ required: false }) + @IsString() + @IsOptional() + category?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/media/dtos/UploadMediaOutputDTO.ts b/apps/api/src/domain/media/dtos/UploadMediaOutputDTO.ts new file mode 100644 index 000000000..153bd6751 --- /dev/null +++ b/apps/api/src/domain/media/dtos/UploadMediaOutputDTO.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsBoolean, IsOptional } from 'class-validator'; + +export class UploadMediaOutputDTO { + @ApiProperty() + @IsBoolean() + success: boolean; + + @ApiProperty({ required: false }) + @IsString() + @IsOptional() + mediaId?: string; + + @ApiProperty({ required: false }) + @IsString() + @IsOptional() + url?: string; + + @ApiProperty({ required: false }) + @IsString() + @IsOptional() + error?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/payments/PaymentsController.ts b/apps/api/src/domain/payments/PaymentsController.ts index e89fa785f..f9b56ced3 100644 --- a/apps/api/src/domain/payments/PaymentsController.ts +++ b/apps/api/src/domain/payments/PaymentsController.ts @@ -1,7 +1,7 @@ import { Controller, Get, Post, Patch, Delete, Body, Query, HttpCode, HttpStatus } from '@nestjs/common'; import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; import { PaymentsService } from './PaymentsService'; -import { CreatePaymentInput, CreatePaymentOutput, UpdatePaymentStatusInput, UpdatePaymentStatusOutput, GetPaymentsQuery, GetPaymentsOutput, GetMembershipFeesQuery, GetMembershipFeesOutput, UpsertMembershipFeeInput, UpsertMembershipFeeOutput, UpdateMemberPaymentInput, UpdateMemberPaymentOutput, GetPrizesQuery, GetPrizesOutput, CreatePrizeInput, CreatePrizeOutput, AwardPrizeInput, AwardPrizeOutput, DeletePrizeInput, DeletePrizeOutput, GetWalletQuery, GetWalletOutput, ProcessWalletTransactionInput, ProcessWalletTransactionOutput } from './dto/PaymentsDto'; +import { CreatePaymentInput, CreatePaymentOutput, UpdatePaymentStatusInput, UpdatePaymentStatusOutput, GetPaymentsQuery, GetPaymentsOutput, GetMembershipFeesQuery, GetMembershipFeesOutput, UpsertMembershipFeeInput, UpsertMembershipFeeOutput, UpdateMemberPaymentInput, UpdateMemberPaymentOutput, GetPrizesQuery, GetPrizesOutput, CreatePrizeInput, CreatePrizeOutput, AwardPrizeInput, AwardPrizeOutput, DeletePrizeInput, DeletePrizeOutput, GetWalletQuery, GetWalletOutput, ProcessWalletTransactionInput, ProcessWalletTransactionOutput } from './dtos/PaymentsDto'; @ApiTags('payments') @Controller('payments') diff --git a/apps/api/src/domain/payments/PaymentsService.ts b/apps/api/src/domain/payments/PaymentsService.ts index e7e7b9da1..a4ce02ee6 100644 --- a/apps/api/src/domain/payments/PaymentsService.ts +++ b/apps/api/src/domain/payments/PaymentsService.ts @@ -55,7 +55,7 @@ import type { GetWalletOutput, ProcessWalletTransactionInput, ProcessWalletTransactionOutput, -} from './dto/PaymentsDto'; +} from './dtos/PaymentsDto'; // Injection tokens import { diff --git a/apps/api/src/domain/protests/ProtestsController.ts b/apps/api/src/domain/protests/ProtestsController.ts new file mode 100644 index 000000000..861b6b1c8 --- /dev/null +++ b/apps/api/src/domain/protests/ProtestsController.ts @@ -0,0 +1,22 @@ +import { Controller, Post, Body, HttpCode, HttpStatus, Param } from '@nestjs/common'; +import { ApiTags, ApiResponse, ApiOperation, ApiParam } from '@nestjs/swagger'; +import { RaceService } from '../race/RaceService'; +import { ReviewProtestCommandDTO } from '../race/dtos/ReviewProtestCommandDTO'; + +@ApiTags('protests') +@Controller('protests') +export class ProtestsController { + constructor(private readonly raceService: RaceService) {} + + @Post(':protestId/review') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Review a protest' }) + @ApiParam({ name: 'protestId', description: 'Protest ID' }) + @ApiResponse({ status: 200, description: 'Protest reviewed successfully' }) + async reviewProtest( + @Param('protestId') protestId: string, + @Body() body: Omit, + ): Promise { + return this.raceService.reviewProtest({ protestId, ...body }); + } +} \ No newline at end of file diff --git a/apps/api/src/domain/protests/ProtestsModule.ts b/apps/api/src/domain/protests/ProtestsModule.ts new file mode 100644 index 000000000..6c1e96cc5 --- /dev/null +++ b/apps/api/src/domain/protests/ProtestsModule.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ProtestsController } from './ProtestsController'; +import { RaceModule } from '../race/RaceModule'; + +@Module({ + imports: [RaceModule], + controllers: [ProtestsController], +}) +export class ProtestsModule {} \ No newline at end of file diff --git a/apps/api/src/domain/race/RaceController.ts b/apps/api/src/domain/race/RaceController.ts index 3ab9c70de..21dd7d057 100644 --- a/apps/api/src/domain/race/RaceController.ts +++ b/apps/api/src/domain/race/RaceController.ts @@ -15,7 +15,6 @@ import { WithdrawFromRaceParamsDTO } from './dtos/WithdrawFromRaceParamsDTO'; import { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO'; import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO'; import { ImportRaceResultsSummaryDTO } from './dtos/ImportRaceResultsSummaryDTO'; -import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO'; import { FileProtestCommandDTO } from './dtos/FileProtestCommandDTO'; import { QuickPenaltyCommandDTO } from './dtos/QuickPenaltyCommandDTO'; import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO'; @@ -152,13 +151,6 @@ export class RaceController { return this.raceService.importRaceResults({ raceId, ...body }); } - @Get('dashboard/overview') - @ApiOperation({ summary: 'Get dashboard overview' }) - @ApiQuery({ name: 'driverId', description: 'Driver ID' }) - @ApiResponse({ status: 200, description: 'Dashboard overview', type: DashboardOverviewDTO }) - async getDashboardOverview(@Query('driverId') driverId: string): Promise { - return this.raceService.getDashboardOverview(driverId); - } @Post('protests/file') @HttpCode(HttpStatus.OK) diff --git a/apps/api/src/domain/race/RaceProviders.ts b/apps/api/src/domain/race/RaceProviders.ts index 0a7a747ce..03706ace0 100644 --- a/apps/api/src/domain/race/RaceProviders.ts +++ b/apps/api/src/domain/race/RaceProviders.ts @@ -43,11 +43,11 @@ import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/With import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase'; import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase'; import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase'; -import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase'; import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase'; import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase'; import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase'; import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase'; +import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase'; // Define injection tokens export const RACE_REPOSITORY_TOKEN = 'IRaceRepository'; @@ -239,4 +239,10 @@ export const RaceProviders: Provider[] = [ new RequestProtestDefenseUseCase(protestRepo, raceRepo, leagueMembershipRepo), inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN], }, + { + provide: ReviewProtestUseCase, + useFactory: (protestRepo: IProtestRepository, raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository) => + new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo), + inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN], + }, ]; diff --git a/apps/api/src/domain/race/RaceService.ts b/apps/api/src/domain/race/RaceService.ts index f91c1b62e..ddd59e2ed 100644 --- a/apps/api/src/domain/race/RaceService.ts +++ b/apps/api/src/domain/race/RaceService.ts @@ -35,11 +35,11 @@ import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/With import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase'; import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase'; import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase'; -import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase'; import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase'; import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase'; import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase'; import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase'; +import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase'; // Presenters import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter'; @@ -67,11 +67,11 @@ export class RaceService { private readonly cancelRaceUseCase: CancelRaceUseCase, private readonly completeRaceUseCase: CompleteRaceUseCase, private readonly importRaceResultsUseCase: ImportRaceResultsUseCase, - private readonly dashboardOverviewUseCase: DashboardOverviewUseCase, private readonly fileProtestUseCase: FileProtestUseCase, private readonly quickPenaltyUseCase: QuickPenaltyUseCase, private readonly applyPenaltyUseCase: ApplyPenaltyUseCase, private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase, + private readonly reviewProtestUseCase: ReviewProtestUseCase, @Inject(LOGGER_TOKEN) private readonly logger: Logger, ) {} @@ -235,17 +235,6 @@ export class RaceService { } } - async getDashboardOverview(driverId: string): Promise { - this.logger.debug('[RaceService] Getting dashboard overview:', { driverId }); - - const result = await this.dashboardOverviewUseCase.execute({ driverId }); - - if (result.isErr()) { - throw new Error(result.error.details.message || 'Failed to get dashboard overview'); - } - - return result.value; - } async fileProtest(command: any): Promise { this.logger.debug('[RaceService] Filing protest:', command); @@ -294,4 +283,16 @@ export class RaceService { return result.value; } + + async reviewProtest(command: any): Promise { + this.logger.debug('[RaceService] Reviewing protest:', command); + + const result = await this.reviewProtestUseCase.execute(command); + + if (result.isErr()) { + throw new Error(result.error.details.message || 'Failed to review protest'); + } + + return result.value; + } } diff --git a/apps/api/src/domain/race/dtos/ReviewProtestCommandDTO.ts b/apps/api/src/domain/race/dtos/ReviewProtestCommandDTO.ts new file mode 100644 index 000000000..00ada1504 --- /dev/null +++ b/apps/api/src/domain/race/dtos/ReviewProtestCommandDTO.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty, IsEnum } from 'class-validator'; + +export class ReviewProtestCommandDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + protestId!: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + stewardId!: string; + + @ApiProperty({ + enum: ['uphold', 'dismiss'], + }) + @IsEnum(['uphold', 'dismiss']) + decision!: 'uphold' | 'dismiss'; + + @ApiProperty() + @IsString() + @IsNotEmpty() + decisionNotes!: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/sponsor/SponsorController.ts b/apps/api/src/domain/sponsor/SponsorController.ts index 5ea567597..2e75b4996 100644 --- a/apps/api/src/domain/sponsor/SponsorController.ts +++ b/apps/api/src/domain/sponsor/SponsorController.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post, Body, HttpCode, HttpStatus, Param } from '@nestjs/common'; +import { Controller, Get, Post, Body, HttpCode, HttpStatus, Param, Query } from '@nestjs/common'; import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; import { SponsorService } from './SponsorService'; import { GetEntitySponsorshipPricingResultDTO } from './dtos/GetEntitySponsorshipPricingResultDTO'; @@ -9,6 +9,10 @@ import { GetSponsorDashboardQueryParamsDTO } from './dtos/GetSponsorDashboardQue import { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO'; import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO'; import { SponsorSponsorshipsDTO } from './dtos/SponsorSponsorshipsDTO'; +import { GetSponsorOutputDTO } from './dtos/GetSponsorOutputDTO'; +import { GetPendingSponsorshipRequestsOutputDTO } from './dtos/GetPendingSponsorshipRequestsOutputDTO'; +import { AcceptSponsorshipRequestInputDTO } from './dtos/AcceptSponsorshipRequestInputDTO'; +import { RejectSponsorshipRequestInputDTO } from './dtos/RejectSponsorshipRequestInputDTO'; @ApiTags('sponsors') @Controller('sponsors') @@ -52,4 +56,39 @@ export class SponsorController { async getSponsorSponsorships(@Param('sponsorId') sponsorId: string): Promise { return this.sponsorService.getSponsorSponsorships({ sponsorId } as GetSponsorSponsorshipsQueryParamsDTO); } + + @Get(':sponsorId') + @ApiOperation({ summary: 'Get a sponsor by ID' }) + @ApiResponse({ status: 200, description: 'Sponsor data', type: GetSponsorOutputDTO }) + @ApiResponse({ status: 404, description: 'Sponsor not found' }) + async getSponsor(@Param('sponsorId') sponsorId: string): Promise { + return this.sponsorService.getSponsor(sponsorId); + } + + @Get('requests') + @ApiOperation({ summary: 'Get pending sponsorship requests' }) + @ApiResponse({ status: 200, description: 'List of pending sponsorship requests', type: GetPendingSponsorshipRequestsOutputDTO }) + async getPendingSponsorshipRequests(@Query() query: { entityType: string; entityId: string }): Promise { + return this.sponsorService.getPendingSponsorshipRequests(query); + } + + @Post('requests/:requestId/accept') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Accept a sponsorship request' }) + @ApiResponse({ status: 200, description: 'Sponsorship request accepted' }) + @ApiResponse({ status: 400, description: 'Invalid request' }) + @ApiResponse({ status: 404, description: 'Request not found' }) + async acceptSponsorshipRequest(@Param('requestId') requestId: string, @Body() input: AcceptSponsorshipRequestInputDTO): Promise { + return this.sponsorService.acceptSponsorshipRequest(requestId, input.respondedBy); + } + + @Post('requests/:requestId/reject') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Reject a sponsorship request' }) + @ApiResponse({ status: 200, description: 'Sponsorship request rejected' }) + @ApiResponse({ status: 400, description: 'Invalid request' }) + @ApiResponse({ status: 404, description: 'Request not found' }) + async rejectSponsorshipRequest(@Param('requestId') requestId: string, @Body() input: RejectSponsorshipRequestInputDTO): Promise { + return this.sponsorService.rejectSponsorshipRequest(requestId, input.respondedBy, input.reason); + } } diff --git a/apps/api/src/domain/sponsor/SponsorProviders.ts b/apps/api/src/domain/sponsor/SponsorProviders.ts index aef6f0991..e7a07d858 100644 --- a/apps/api/src/domain/sponsor/SponsorProviders.ts +++ b/apps/api/src/domain/sponsor/SponsorProviders.ts @@ -19,6 +19,10 @@ import { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateS import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase'; import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase'; import { GetEntitySponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase'; +import { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase'; +import { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase'; +import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase'; +import { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase'; // Import concrete in-memory implementations import { InMemorySponsorRepository } from '@adapters/racing/persistence/inmemory/InMemorySponsorRepository'; @@ -49,6 +53,10 @@ export const CREATE_SPONSOR_USE_CASE_TOKEN = 'CreateSponsorUseCase'; export const GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN = 'GetSponsorDashboardUseCase'; export const GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN = 'GetSponsorSponsorshipsUseCase'; export const GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetEntitySponsorshipPricingUseCase'; +export const GET_SPONSOR_USE_CASE_TOKEN = 'GetSponsorUseCase'; +export const GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN = 'GetPendingSponsorshipRequestsUseCase'; +export const ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'AcceptSponsorshipRequestUseCase'; +export const REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'RejectSponsorshipRequestUseCase'; export const SponsorProviders: Provider[] = [ SponsorService, @@ -131,4 +139,27 @@ export const SponsorProviders: Provider[] = [ new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, sponsorshipRequestRepo, seasonSponsorshipRepo, logger), inject: [SPONSORSHIP_PRICING_REPOSITORY_TOKEN, SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN], }, + { + provide: GET_SPONSOR_USE_CASE_TOKEN, + useFactory: (sponsorRepo: ISponsorRepository) => new GetSponsorUseCase(sponsorRepo), + inject: [SPONSOR_REPOSITORY_TOKEN], + }, + { + provide: GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN, + useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, sponsorRepo: ISponsorRepository) => + new GetPendingSponsorshipRequestsUseCase(sponsorshipRequestRepo, sponsorRepo), + inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SPONSOR_REPOSITORY_TOKEN], + }, + { + provide: ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN, + useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository, seasonRepo: ISeasonRepository, notificationService: any, paymentGateway: any, walletRepository: any, leagueWalletRepository: any, logger: Logger) => + new AcceptSponsorshipRequestUseCase(sponsorshipRequestRepo, seasonSponsorshipRepo, seasonRepo, notificationService, paymentGateway, walletRepository, leagueWalletRepository, logger), + inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, SEASON_REPOSITORY_TOKEN, 'INotificationService', 'IPaymentGateway', 'IWalletRepository', 'ILeagueWalletRepository', LOGGER_TOKEN], + }, + { + provide: REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN, + useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, logger: Logger) => + new RejectSponsorshipRequestUseCase(sponsorshipRequestRepo, logger), + inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, LOGGER_TOKEN], + }, ]; diff --git a/apps/api/src/domain/sponsor/SponsorService.ts b/apps/api/src/domain/sponsor/SponsorService.ts index 0458dace1..ce48742ed 100644 --- a/apps/api/src/domain/sponsor/SponsorService.ts +++ b/apps/api/src/domain/sponsor/SponsorService.ts @@ -7,6 +7,10 @@ import { GetSponsorDashboardQueryParamsDTO } from './dtos/GetSponsorDashboardQue import { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO'; import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO'; import { SponsorSponsorshipsDTO } from './dtos/SponsorSponsorshipsDTO'; +import { GetSponsorOutputDTO } from './dtos/GetSponsorOutputDTO'; +import { GetPendingSponsorshipRequestsOutputDTO } from './dtos/GetPendingSponsorshipRequestsOutputDTO'; +import { AcceptSponsorshipRequestInputDTO } from './dtos/AcceptSponsorshipRequestInputDTO'; +import { RejectSponsorshipRequestInputDTO } from './dtos/RejectSponsorshipRequestInputDTO'; import { SponsorDTO } from './dtos/SponsorDTO'; import { SponsorDashboardMetricsDTO } from './dtos/SponsorDashboardMetricsDTO'; import { SponsoredLeagueDTO } from './dtos/SponsoredLeagueDTO'; @@ -19,6 +23,10 @@ import { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponso import { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase'; import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase'; import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase'; +import { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase'; +import { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase'; +import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase'; +import { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase'; // Presenters import { GetSponsorshipPricingPresenter } from './presenters/GetSponsorshipPricingPresenter'; @@ -28,7 +36,7 @@ import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPr import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter'; // Tokens -import { GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN, GET_SPONSORS_USE_CASE_TOKEN, CREATE_SPONSOR_USE_CASE_TOKEN, GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN, GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN, LOGGER_TOKEN } from './SponsorProviders'; +import { GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN, GET_SPONSORS_USE_CASE_TOKEN, CREATE_SPONSOR_USE_CASE_TOKEN, GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN, GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN, GET_SPONSOR_USE_CASE_TOKEN, GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN, ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN, REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN, LOGGER_TOKEN } from './SponsorProviders'; import type { Logger } from '@core/shared/application'; @Injectable() @@ -39,6 +47,10 @@ export class SponsorService { @Inject(CREATE_SPONSOR_USE_CASE_TOKEN) private readonly createSponsorUseCase: CreateSponsorUseCase, @Inject(GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN) private readonly getSponsorDashboardUseCase: GetSponsorDashboardUseCase, @Inject(GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN) private readonly getSponsorSponsorshipsUseCase: GetSponsorSponsorshipsUseCase, + @Inject(GET_SPONSOR_USE_CASE_TOKEN) private readonly getSponsorUseCase: GetSponsorUseCase, + @Inject(GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN) private readonly getPendingSponsorshipRequestsUseCase: GetPendingSponsorshipRequestsUseCase, + @Inject(ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN) private readonly acceptSponsorshipRequestUseCase: AcceptSponsorshipRequestUseCase, + @Inject(REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN) private readonly rejectSponsorshipRequestUseCase: RejectSponsorshipRequestUseCase, @Inject(LOGGER_TOKEN) private readonly logger: Logger, ) {} @@ -81,4 +93,48 @@ export class SponsorService { await this.getSponsorSponsorshipsUseCase.execute(params, presenter); return presenter.viewModel as SponsorSponsorshipsDTO | null; } + + async getSponsor(sponsorId: string): Promise { + this.logger.debug('[SponsorService] Fetching sponsor.', { sponsorId }); + + const result = await this.getSponsorUseCase.execute({ sponsorId }); + if (result.isErr()) { + this.logger.error('[SponsorService] Failed to fetch sponsor.', result.error); + return null; + } + return result.value as GetSponsorOutputDTO | null; + } + + async getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise { + this.logger.debug('[SponsorService] Fetching pending sponsorship requests.', { params }); + + const result = await this.getPendingSponsorshipRequestsUseCase.execute(params as any); + if (result.isErr()) { + this.logger.error('[SponsorService] Failed to fetch pending sponsorship requests.', result.error); + return { entityType: params.entityType as any, entityId: params.entityId, requests: [], totalCount: 0 }; + } + return result.value as GetPendingSponsorshipRequestsOutputDTO; + } + + async acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<{ requestId: string; sponsorshipId: string; status: string; acceptedAt: Date; platformFee: number; netAmount: number } | null> { + this.logger.debug('[SponsorService] Accepting sponsorship request.', { requestId, respondedBy }); + + const result = await this.acceptSponsorshipRequestUseCase.execute({ requestId, respondedBy }); + if (result.isErr()) { + this.logger.error('[SponsorService] Failed to accept sponsorship request.', result.error); + return null; + } + return result.value; + } + + async rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise<{ requestId: string; status: string; rejectedAt: Date } | null> { + this.logger.debug('[SponsorService] Rejecting sponsorship request.', { requestId, respondedBy, reason }); + + const result = await this.rejectSponsorshipRequestUseCase.execute({ requestId, respondedBy, reason }); + if (result.isErr()) { + this.logger.error('[SponsorService] Failed to reject sponsorship request.', result.error); + return null; + } + return result.value; + } } diff --git a/apps/api/src/domain/sponsor/dtos/AcceptSponsorshipRequestInputDTO.ts b/apps/api/src/domain/sponsor/dtos/AcceptSponsorshipRequestInputDTO.ts new file mode 100644 index 000000000..5c209c4ba --- /dev/null +++ b/apps/api/src/domain/sponsor/dtos/AcceptSponsorshipRequestInputDTO.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty } from 'class-validator'; + +export class AcceptSponsorshipRequestInputDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + respondedBy: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/sponsor/dtos/GetPendingSponsorshipRequestsOutputDTO.ts b/apps/api/src/domain/sponsor/dtos/GetPendingSponsorshipRequestsOutputDTO.ts new file mode 100644 index 000000000..73dbb6598 --- /dev/null +++ b/apps/api/src/domain/sponsor/dtos/GetPendingSponsorshipRequestsOutputDTO.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { SponsorshipRequestDTO } from './SponsorshipRequestDTO'; + +export class GetPendingSponsorshipRequestsOutputDTO { + @ApiProperty() + entityType: string; + + @ApiProperty() + entityId: string; + + @ApiProperty({ type: [SponsorshipRequestDTO] }) + requests: SponsorshipRequestDTO[]; + + @ApiProperty() + totalCount: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/sponsor/dtos/GetSponsorOutputDTO.ts b/apps/api/src/domain/sponsor/dtos/GetSponsorOutputDTO.ts new file mode 100644 index 000000000..85d00d651 --- /dev/null +++ b/apps/api/src/domain/sponsor/dtos/GetSponsorOutputDTO.ts @@ -0,0 +1,7 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { SponsorDTO } from './SponsorDTO'; + +export class GetSponsorOutputDTO { + @ApiProperty({ type: SponsorDTO }) + sponsor: SponsorDTO; +} \ No newline at end of file diff --git a/apps/api/src/domain/sponsor/dtos/RejectSponsorshipRequestInputDTO.ts b/apps/api/src/domain/sponsor/dtos/RejectSponsorshipRequestInputDTO.ts new file mode 100644 index 000000000..d8f8a5179 --- /dev/null +++ b/apps/api/src/domain/sponsor/dtos/RejectSponsorshipRequestInputDTO.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty, IsOptional } from 'class-validator'; + +export class RejectSponsorshipRequestInputDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + respondedBy: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + reason?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/sponsor/dtos/SponsorDTO.ts b/apps/api/src/domain/sponsor/dtos/SponsorDTO.ts new file mode 100644 index 000000000..cd40fbe33 --- /dev/null +++ b/apps/api/src/domain/sponsor/dtos/SponsorDTO.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class SponsorDTO { + @ApiProperty() + id: string; + + @ApiProperty() + name: string; + + @ApiProperty({ required: false }) + logoUrl?: string; + + @ApiProperty({ required: false }) + websiteUrl?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/sponsor/dtos/SponsorshipRequestDTO.ts b/apps/api/src/domain/sponsor/dtos/SponsorshipRequestDTO.ts new file mode 100644 index 000000000..c09d97250 --- /dev/null +++ b/apps/api/src/domain/sponsor/dtos/SponsorshipRequestDTO.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class SponsorshipRequestDTO { + @ApiProperty() + id: string; + + @ApiProperty() + sponsorId: string; + + @ApiProperty() + sponsorName: string; + + @ApiProperty({ required: false }) + sponsorLogo?: string; + + @ApiProperty() + tier: string; + + @ApiProperty() + offeredAmount: number; + + @ApiProperty() + currency: string; + + @ApiProperty() + formattedAmount: string; + + @ApiProperty({ required: false }) + message?: string; + + @ApiProperty() + createdAt: Date; + + @ApiProperty() + platformFee: number; + + @ApiProperty() + netAmount: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/TeamController.ts b/apps/api/src/domain/team/TeamController.ts index e07453be8..db0502915 100644 --- a/apps/api/src/domain/team/TeamController.ts +++ b/apps/api/src/domain/team/TeamController.ts @@ -1,7 +1,17 @@ -import { Controller, Get, Post, Patch, Body, Param } from '@nestjs/common'; -import { ApiTags, ApiResponse, ApiOperation, ApiBody } from '@nestjs/swagger'; +import { Controller, Get, Post, Patch, Body, Req, Param } from '@nestjs/common'; +import { Request } from 'express'; +import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; import { TeamService } from './TeamService'; -import { AllTeamsViewModel, DriverTeamViewModel, TeamDetailsViewModel, TeamMembersViewModel, TeamJoinRequestsViewModel, CreateTeamInput, CreateTeamOutput, UpdateTeamInput, UpdateTeamOutput, ApproveTeamJoinRequestInput, ApproveTeamJoinRequestOutput, RejectTeamJoinRequestInput, RejectTeamJoinRequestOutput } from './dto/TeamDto'; +import { GetAllTeamsOutputDTO } from './dtos/GetAllTeamsOutputDTO'; +import { GetTeamDetailsOutputDTO } from './dtos/GetTeamDetailsOutputDTO'; +import { GetTeamMembersOutputDTO } from './dtos/GetTeamMembersOutputDTO'; +import { GetTeamJoinRequestsOutputDTO } from './dtos/GetTeamJoinRequestsOutputDTO'; +import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO'; +import { CreateTeamOutputDTO } from './dtos/CreateTeamOutputDTO'; +import { UpdateTeamInputDTO } from './dtos/UpdateTeamInputDTO'; +import { UpdateTeamOutputDTO } from './dtos/UpdateTeamOutputDTO'; +import { GetDriverTeamOutputDTO } from './dtos/GetDriverTeamOutputDTO'; +import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO'; @ApiTags('teams') @Controller('teams') @@ -10,91 +20,63 @@ export class TeamController { @Get('all') @ApiOperation({ summary: 'Get all teams' }) - @ApiResponse({ status: 200, description: 'List of all teams', type: AllTeamsViewModel }) - async getAllTeams(): Promise { - return this.teamService.getAllTeams(); + @ApiResponse({ status: 200, description: 'List of all teams', type: GetAllTeamsOutputDTO }) + async getAll(): Promise { + return this.teamService.getAll(); } @Get(':teamId') @ApiOperation({ summary: 'Get team details' }) - @ApiResponse({ status: 200, description: 'Team details', type: TeamDetailsViewModel }) + @ApiResponse({ status: 200, description: 'Team details', type: GetTeamDetailsOutputDTO }) @ApiResponse({ status: 404, description: 'Team not found' }) - async getTeamDetails( - @Param('teamId') teamId: string, - ): Promise { - return this.teamService.getTeamDetails(teamId); + async getDetails(@Param('teamId') teamId: string, @Req() req: Request): Promise { + const userId = req['user']?.userId; + return this.teamService.getDetails(teamId, userId); } @Get(':teamId/members') @ApiOperation({ summary: 'Get team members' }) - @ApiResponse({ status: 200, description: 'Team members', type: TeamMembersViewModel }) - async getTeamMembers(@Param('teamId') teamId: string): Promise { - return this.teamService.getTeamMembers(teamId); + @ApiResponse({ status: 200, description: 'Team members', type: GetTeamMembersOutputDTO }) + async getMembers(@Param('teamId') teamId: string): Promise { + return this.teamService.getMembers(teamId); } @Get(':teamId/join-requests') @ApiOperation({ summary: 'Get team join requests' }) - @ApiResponse({ status: 200, description: 'Team join requests', type: TeamJoinRequestsViewModel }) - async getTeamJoinRequests(@Param('teamId') teamId: string): Promise { - return this.teamService.getTeamJoinRequests(teamId); - } - - @Post(':teamId/join-requests/approve') - @ApiOperation({ summary: 'Approve a team join request' }) - @ApiBody({ type: ApproveTeamJoinRequestInput }) - @ApiResponse({ status: 200, description: 'Join request approved', type: ApproveTeamJoinRequestOutput }) - @ApiResponse({ status: 404, description: 'Join request not found' }) - async approveJoinRequest( - @Param('teamId') teamId: string, - @Body() input: ApproveTeamJoinRequestInput, - ): Promise { - return this.teamService.approveTeamJoinRequest({ ...input, teamId }); - } - - @Post(':teamId/join-requests/reject') - @ApiOperation({ summary: 'Reject a team join request' }) - @ApiBody({ type: RejectTeamJoinRequestInput }) - @ApiResponse({ status: 200, description: 'Join request rejected', type: RejectTeamJoinRequestOutput }) - @ApiResponse({ status: 404, description: 'Join request not found' }) - async rejectJoinRequest( - @Param('teamId') teamId: string, - @Body() input: RejectTeamJoinRequestInput, - ): Promise { - return this.teamService.rejectTeamJoinRequest({ ...input, teamId }); + @ApiResponse({ status: 200, description: 'Team join requests', type: GetTeamJoinRequestsOutputDTO }) + async getJoinRequests(@Param('teamId') teamId: string): Promise { + return this.teamService.getJoinRequests(teamId); } @Post() @ApiOperation({ summary: 'Create a new team' }) - @ApiBody({ type: CreateTeamInput }) - @ApiResponse({ status: 201, description: 'Team created successfully', type: CreateTeamOutput }) - async createTeam(@Body() input: CreateTeamInput): Promise { - return this.teamService.createTeam(input); + @ApiResponse({ status: 201, description: 'Team created', type: CreateTeamOutputDTO }) + async create(@Body() input: CreateTeamInputDTO, @Req() req: Request): Promise { + const userId = req['user']?.userId; + return this.teamService.create(input, userId); } @Patch(':teamId') - @ApiOperation({ summary: 'Update team details' }) - @ApiBody({ type: UpdateTeamInput }) - @ApiResponse({ status: 200, description: 'Team updated successfully', type: UpdateTeamOutput }) - @ApiResponse({ status: 404, description: 'Team not found' }) - async updateTeam( - @Param('teamId') teamId: string, - @Body() input: UpdateTeamInput, - ): Promise { - return this.teamService.updateTeam({ ...input, teamId }); + @ApiOperation({ summary: 'Update team' }) + @ApiResponse({ status: 200, description: 'Team updated', type: UpdateTeamOutputDTO }) + async update(@Param('teamId') teamId: string, @Body() input: UpdateTeamInputDTO, @Req() req: Request): Promise { + const userId = req['user']?.userId; + return this.teamService.update(teamId, input, userId); } @Get('driver/:driverId') - @ApiOperation({ summary: 'Get team for a driver' }) - @ApiResponse({ status: 200, description: 'Driver team membership', type: DriverTeamViewModel }) - @ApiResponse({ status: 404, description: 'Driver not in a team' }) - async getDriverTeam(@Param('driverId') driverId: string): Promise { - return this.teamService.getDriverTeam({ teamId: '', driverId }); + @ApiOperation({ summary: 'Get driver\'s team' }) + @ApiResponse({ status: 200, description: 'Driver\'s team', type: GetDriverTeamOutputDTO }) + @ApiResponse({ status: 404, description: 'Team not found' }) + async getDriverTeam(@Param('driverId') driverId: string): Promise { + return this.teamService.getDriverTeam(driverId); } - @Get('leaderboard') - @ApiOperation({ summary: 'Get teams leaderboard' }) - @ApiResponse({ status: 200, description: 'Teams leaderboard' }) - async getTeamsLeaderboard(): Promise { - return this.teamService.getTeamsLeaderboard(); + @Get(':teamId/members/:driverId') + @ApiOperation({ summary: 'Get team membership for a driver' }) + @ApiResponse({ status: 200, description: 'Team membership', type: GetTeamMembershipOutputDTO }) + @ApiResponse({ status: 404, description: 'Membership not found' }) + async getMembership(@Param('teamId') teamId: string, @Param('driverId') driverId: string): Promise { + return this.teamService.getMembership(teamId, driverId); } -} +} \ No newline at end of file diff --git a/apps/api/src/domain/team/TeamModule.ts b/apps/api/src/domain/team/TeamModule.ts index d1bfe12c0..84eeb9515 100644 --- a/apps/api/src/domain/team/TeamModule.ts +++ b/apps/api/src/domain/team/TeamModule.ts @@ -8,4 +8,4 @@ import { TeamProviders } from './TeamProviders'; providers: TeamProviders, exports: [TeamService], }) -export class TeamModule {} +export class TeamModule {} \ No newline at end of file diff --git a/apps/api/src/domain/team/TeamProviders.ts b/apps/api/src/domain/team/TeamProviders.ts index 28f1365e3..f98a9492f 100644 --- a/apps/api/src/domain/team/TeamProviders.ts +++ b/apps/api/src/domain/team/TeamProviders.ts @@ -1,166 +1,6 @@ import { Provider } from '@nestjs/common'; import { TeamService } from './TeamService'; -// Import core interfaces -import { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository'; -import { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository'; -import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository'; -import { IImageServicePort } from '@core/racing/application/ports/IImageServicePort'; -import type { Logger } from '@core/shared/application/Logger'; - -// Import concrete in-memory implementations -import { InMemoryTeamRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamRepository'; -import { InMemoryTeamMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository'; -import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository'; -import { ConsoleLogger } from '@adapters/logging/ConsoleLogger'; - -// Import use cases -import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; -import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase'; -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 { ApproveTeamJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveTeamJoinRequestUseCase'; -import { RejectTeamJoinRequestUseCase } from '@core/racing/application/use-cases/RejectTeamJoinRequestUseCase'; -import { GetTeamsLeaderboardUseCase } from '@core/racing/application/use-cases/GetTeamsLeaderboardUseCase'; - -// Import presenters for use case initialization -import { DriverTeamPresenter } from './presenters/DriverTeamPresenter'; -import { TeamMembersPresenter } from './presenters/TeamMembersPresenter'; -import { TeamJoinRequestsPresenter } from './presenters/TeamJoinRequestsPresenter'; -import { TeamsLeaderboardPresenter } from './presenters/TeamsLeaderboardPresenter'; - -// Tokens -export const TEAM_REPOSITORY_TOKEN = 'ITeamRepository'; -export const TEAM_MEMBERSHIP_REPOSITORY_TOKEN = 'ITeamMembershipRepository'; -export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository'; -export const IMAGE_SERVICE_TOKEN = 'IImageServicePort'; -export const TEAM_GET_ALL_USE_CASE_TOKEN = 'GetAllTeamsUseCase'; -export const TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN = 'GetDriverTeamUseCase'; -export const TEAM_GET_DETAILS_USE_CASE_TOKEN = 'GetTeamDetailsUseCase'; -export const TEAM_GET_MEMBERS_USE_CASE_TOKEN = 'GetTeamMembersUseCase'; -export const TEAM_GET_JOIN_REQUESTS_USE_CASE_TOKEN = 'GetTeamJoinRequestsUseCase'; -export const TEAM_CREATE_USE_CASE_TOKEN = 'CreateTeamUseCase'; -export const TEAM_UPDATE_USE_CASE_TOKEN = 'UpdateTeamUseCase'; -export const TEAM_APPROVE_JOIN_REQUEST_USE_CASE_TOKEN = 'ApproveTeamJoinRequestUseCase'; -export const TEAM_REJECT_JOIN_REQUEST_USE_CASE_TOKEN = 'RejectTeamJoinRequestUseCase'; -export const TEAM_GET_LEADERBOARD_USE_CASE_TOKEN = 'GetTeamsLeaderboardUseCase'; -export const TEAM_LOGGER_TOKEN = 'Logger'; - -// Simple image service implementation for team module -class SimpleImageService implements IImageServicePort { - getDriverAvatar(driverId: string): string { - return `/api/media/avatars/${driverId}`; - } - getTeamLogo(teamId: string): string { - return `/api/media/teams/${teamId}/logo`; - } - getLeagueCover(leagueId: string): string { - return `/api/media/leagues/${leagueId}/cover`; - } - getLeagueLogo(leagueId: string): string { - return `/api/media/leagues/${leagueId}/logo`; - } -} - export const TeamProviders: Provider[] = [ - TeamService, // Provide the service itself - { - provide: TEAM_REPOSITORY_TOKEN, - useFactory: (logger: Logger) => new InMemoryTeamRepository(logger), - inject: [TEAM_LOGGER_TOKEN], - }, - { - provide: TEAM_MEMBERSHIP_REPOSITORY_TOKEN, - useFactory: (logger: Logger) => new InMemoryTeamMembershipRepository(logger), - inject: [TEAM_LOGGER_TOKEN], - }, - { - provide: DRIVER_REPOSITORY_TOKEN, - useFactory: (logger: Logger) => new InMemoryDriverRepository(logger), - inject: [TEAM_LOGGER_TOKEN], - }, - { - provide: IMAGE_SERVICE_TOKEN, - useClass: SimpleImageService, - }, - { - provide: TEAM_LOGGER_TOKEN, - useClass: ConsoleLogger, - }, - // Use cases - { - provide: TEAM_GET_ALL_USE_CASE_TOKEN, - useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) => - new GetAllTeamsUseCase(teamRepo, membershipRepo, logger), - inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN], - }, - { - provide: TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN, - useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) => - new GetDriverTeamUseCase(teamRepo, membershipRepo, logger, new DriverTeamPresenter()), - inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN], - }, - { - provide: TEAM_GET_DETAILS_USE_CASE_TOKEN, - useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) => - new GetTeamDetailsUseCase(teamRepo, membershipRepo), - inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN], - }, - { - provide: TEAM_GET_MEMBERS_USE_CASE_TOKEN, - useFactory: ( - membershipRepo: ITeamMembershipRepository, - driverRepo: IDriverRepository, - imageService: IImageServicePort, - logger: Logger, - ) => new GetTeamMembersUseCase(membershipRepo, driverRepo, imageService, logger, new TeamMembersPresenter()), - inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, TEAM_LOGGER_TOKEN], - }, - { - provide: TEAM_GET_JOIN_REQUESTS_USE_CASE_TOKEN, - useFactory: ( - membershipRepo: ITeamMembershipRepository, - driverRepo: IDriverRepository, - imageService: IImageServicePort, - logger: Logger, - ) => new GetTeamJoinRequestsUseCase(membershipRepo, driverRepo, imageService, logger, new TeamJoinRequestsPresenter()), - inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, TEAM_LOGGER_TOKEN], - }, - { - provide: TEAM_CREATE_USE_CASE_TOKEN, - useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) => - new CreateTeamUseCase(teamRepo, membershipRepo), - inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN], - }, - { - provide: TEAM_UPDATE_USE_CASE_TOKEN, - useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) => - new UpdateTeamUseCase(teamRepo, membershipRepo), - inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN], - }, - { - provide: TEAM_APPROVE_JOIN_REQUEST_USE_CASE_TOKEN, - useFactory: (membershipRepo: ITeamMembershipRepository, logger: Logger) => - new ApproveTeamJoinRequestUseCase(membershipRepo, logger), - inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN], - }, - { - provide: TEAM_REJECT_JOIN_REQUEST_USE_CASE_TOKEN, - useFactory: (membershipRepo: ITeamMembershipRepository) => - new RejectTeamJoinRequestUseCase(membershipRepo), - inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN], - }, - { - provide: TEAM_GET_LEADERBOARD_USE_CASE_TOKEN, - useFactory: ( - teamRepo: ITeamRepository, - membershipRepo: ITeamMembershipRepository, - driverRepo: IDriverRepository, - logger: Logger, - ) => new GetTeamsLeaderboardUseCase(teamRepo, membershipRepo, driverRepo, () => null, logger), - inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN], - }, -]; + TeamService, +]; \ No newline at end of file diff --git a/apps/api/src/domain/team/TeamService.ts b/apps/api/src/domain/team/TeamService.ts index 05a8ff6f7..d9b9c1162 100644 --- a/apps/api/src/domain/team/TeamService.ts +++ b/apps/api/src/domain/team/TeamService.ts @@ -1,181 +1,72 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { AllTeamsViewModel, GetDriverTeamQuery, DriverTeamViewModel, TeamDetailsViewModel, TeamMembersViewModel, TeamJoinRequestsViewModel, CreateTeamInput, CreateTeamOutput, UpdateTeamInput, UpdateTeamOutput, ApproveTeamJoinRequestInput, ApproveTeamJoinRequestOutput, RejectTeamJoinRequestInput, RejectTeamJoinRequestOutput } from './dto/TeamDto'; - -// Use cases -import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; -import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase'; -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 { ApproveTeamJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveTeamJoinRequestUseCase'; -import { RejectTeamJoinRequestUseCase } from '@core/racing/application/use-cases/RejectTeamJoinRequestUseCase'; - -// Presenters -import { AllTeamsPresenter } from './presenters/AllTeamsPresenter'; -import { DriverTeamPresenter } from './presenters/DriverTeamPresenter'; -import { TeamDetailsPresenter } from './presenters/TeamDetailsPresenter'; -import { TeamMembersPresenter } from './presenters/TeamMembersPresenter'; -import { TeamJoinRequestsPresenter } from './presenters/TeamJoinRequestsPresenter'; - -// Logger -import type { Logger } from '@core/shared/application/Logger'; - -// Tokens -import { - TEAM_GET_ALL_USE_CASE_TOKEN, - TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN, - TEAM_GET_DETAILS_USE_CASE_TOKEN, - TEAM_GET_MEMBERS_USE_CASE_TOKEN, - TEAM_GET_JOIN_REQUESTS_USE_CASE_TOKEN, - TEAM_CREATE_USE_CASE_TOKEN, - TEAM_UPDATE_USE_CASE_TOKEN, - TEAM_APPROVE_JOIN_REQUEST_USE_CASE_TOKEN, - TEAM_REJECT_JOIN_REQUEST_USE_CASE_TOKEN, - TEAM_GET_LEADERBOARD_USE_CASE_TOKEN, - TEAM_LOGGER_TOKEN -} from './TeamProviders'; +import { Injectable } from '@nestjs/common'; +import { GetAllTeamsOutputDTO } from './dtos/GetAllTeamsOutputDTO'; +import { GetTeamDetailsOutputDTO } from './dtos/GetTeamDetailsOutputDTO'; +import { GetTeamMembersOutputDTO } from './dtos/GetTeamMembersOutputDTO'; +import { GetTeamJoinRequestsOutputDTO } from './dtos/GetTeamJoinRequestsOutputDTO'; +import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO'; +import { CreateTeamOutputDTO } from './dtos/CreateTeamOutputDTO'; +import { UpdateTeamInputDTO } from './dtos/UpdateTeamInputDTO'; +import { UpdateTeamOutputDTO } from './dtos/UpdateTeamOutputDTO'; +import { GetDriverTeamOutputDTO } from './dtos/GetDriverTeamOutputDTO'; +import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO'; @Injectable() export class TeamService { - constructor( - @Inject(TEAM_GET_ALL_USE_CASE_TOKEN) private readonly getAllTeamsUseCase: GetAllTeamsUseCase, - @Inject(TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN) private readonly getDriverTeamUseCase: GetDriverTeamUseCase, - @Inject(TEAM_GET_DETAILS_USE_CASE_TOKEN) private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase, - @Inject(TEAM_GET_MEMBERS_USE_CASE_TOKEN) private readonly getTeamMembersUseCase: GetTeamMembersUseCase, - @Inject(TEAM_GET_JOIN_REQUESTS_USE_CASE_TOKEN) private readonly getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase, - @Inject(TEAM_CREATE_USE_CASE_TOKEN) private readonly createTeamUseCase: CreateTeamUseCase, - @Inject(TEAM_UPDATE_USE_CASE_TOKEN) private readonly updateTeamUseCase: UpdateTeamUseCase, - @Inject(TEAM_APPROVE_JOIN_REQUEST_USE_CASE_TOKEN) private readonly approveTeamJoinRequestUseCase: ApproveTeamJoinRequestUseCase, - @Inject(TEAM_REJECT_JOIN_REQUEST_USE_CASE_TOKEN) private readonly rejectTeamJoinRequestUseCase: RejectTeamJoinRequestUseCase, - @Inject(TEAM_GET_LEADERBOARD_USE_CASE_TOKEN) private readonly getTeamsLeaderboardUseCase: GetTeamsLeaderboardUseCase, - @Inject(TEAM_LOGGER_TOKEN) private readonly logger: Logger, - ) {} - - async getAllTeams(): Promise { - this.logger.debug('[TeamService] Fetching all teams.'); - - const presenter = new AllTeamsPresenter(); - await this.getAllTeamsUseCase.execute(undefined, presenter); - return presenter.viewModel as unknown as AllTeamsViewModel; + async getAll(): Promise { + // TODO: Implement getAll teams logic + return { + teams: [], + totalCount: 0, + }; } - async getDriverTeam(query: GetDriverTeamQuery): Promise { - this.logger.debug(`[TeamService] Fetching driver team for driverId: ${query.driverId}`); - - const presenter = new DriverTeamPresenter(); - try { - await this.getDriverTeamUseCase.execute({ driverId: query.driverId }, presenter); - return presenter.viewModel as unknown as DriverTeamViewModel; - } catch (error) { - this.logger.error(`Error fetching driver team: ${error}`); - return null; - } + async getDetails(teamId: string, userId?: string): Promise { + // TODO: Implement get team details logic + return null; } - async getTeamDetails(teamId: string): Promise { - this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}`); - - const presenter = new TeamDetailsPresenter(); - try { - await this.getTeamDetailsUseCase.execute({ teamId, driverId: '' }, presenter); - return presenter.viewModel as unknown as TeamDetailsViewModel; - } catch (error) { - this.logger.error(`Error fetching team details: ${error}`); - return null; - } + async getMembers(teamId: string): Promise { + // TODO: Implement get team members logic + return { + members: [], + totalCount: 0, + ownerCount: 0, + managerCount: 0, + memberCount: 0, + }; } - async getTeamMembers(teamId: string): Promise { - this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`); - - const presenter = new TeamMembersPresenter(); - await this.getTeamMembersUseCase.execute({ teamId }, presenter); - return presenter.viewModel as unknown as TeamMembersViewModel; + async getJoinRequests(teamId: string): Promise { + // TODO: Implement get team join requests logic + return { + requests: [], + pendingCount: 0, + totalCount: 0, + }; } - async getTeamJoinRequests(teamId: string): Promise { - this.logger.debug(`[TeamService] Fetching join requests for teamId: ${teamId}`); - - const presenter = new TeamJoinRequestsPresenter(); - await this.getTeamJoinRequestsUseCase.execute({ teamId }, presenter); - return presenter.viewModel as unknown as TeamJoinRequestsViewModel; + async create(input: CreateTeamInputDTO, userId?: string): Promise { + // TODO: Implement create team logic + return { + id: 'placeholder-id', + success: true, + }; } - async createTeam(input: CreateTeamInput): Promise { - this.logger.debug('[TeamService] Creating team', input); - - try { - const result = await this.createTeamUseCase.execute({ - name: input.name, - tag: input.tag, - description: input.description, - ownerId: input.ownerId, - leagues: [], - }); - return { - teamId: result.team.id, - success: true, - }; - } catch (error) { - this.logger.error(`Error creating team: ${error}`); - throw error; - } + async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise { + // TODO: Implement update team logic + return { + success: true, + }; } - async updateTeam(input: UpdateTeamInput & { teamId: string }): Promise { - this.logger.debug('[TeamService] Updating team', input); - - try { - await this.updateTeamUseCase.execute({ - teamId: input.teamId, - updates: { - name: input.name, - tag: input.tag, - description: input.description, - }, - updatedBy: input.updatedBy, - }); - return { success: true }; - } catch (error) { - this.logger.error(`Error updating team: ${error}`); - throw error; - } + async getDriverTeam(driverId: string): Promise { + // TODO: Implement get driver team logic + return null; } - async approveTeamJoinRequest(input: ApproveTeamJoinRequestInput & { teamId: string }): Promise { - this.logger.debug('[TeamService] Approving team join request', input); - - try { - await this.approveTeamJoinRequestUseCase.execute({ requestId: input.requestId }); - return { success: true }; - } catch (error) { - this.logger.error(`Error approving join request: ${error}`); - throw error; - } + async getMembership(teamId: string, driverId: string): Promise { + // TODO: Implement get team membership logic + return null; } - - async rejectTeamJoinRequest(input: RejectTeamJoinRequestInput & { teamId: string }): Promise { - this.logger.debug('[TeamService] Rejecting team join request', input); - - try { - await this.rejectTeamJoinRequestUseCase.execute({ requestId: input.requestId }); - return { success: true }; - } catch (error) { - this.logger.error(`Error rejecting join request: ${error}`); - throw error; - } - } - - async getTeamsLeaderboard(): Promise { - this.logger.debug('[TeamService] Fetching teams leaderboard'); - - const result = await this.getTeamsLeaderboardUseCase.execute(); - if (result.isErr()) { - this.logger.error(`Error fetching teams leaderboard: ${result.error}`); - throw new Error('Failed to fetch teams leaderboard'); - } - return result.value; - } -} +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/CreateTeamInputDTO.ts b/apps/api/src/domain/team/dtos/CreateTeamInputDTO.ts new file mode 100644 index 000000000..2f489597e --- /dev/null +++ b/apps/api/src/domain/team/dtos/CreateTeamInputDTO.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty, IsOptional } from 'class-validator'; + +export class CreateTeamInputDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + tag: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + description?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/CreateTeamOutputDTO.ts b/apps/api/src/domain/team/dtos/CreateTeamOutputDTO.ts new file mode 100644 index 000000000..d7f5fad25 --- /dev/null +++ b/apps/api/src/domain/team/dtos/CreateTeamOutputDTO.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateTeamOutputDTO { + @ApiProperty() + id: string; + + @ApiProperty() + success: boolean; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/GetAllTeamsOutputDTO.ts b/apps/api/src/domain/team/dtos/GetAllTeamsOutputDTO.ts new file mode 100644 index 000000000..ed25016d3 --- /dev/null +++ b/apps/api/src/domain/team/dtos/GetAllTeamsOutputDTO.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; + +class TeamListItemDTO { + @ApiProperty() + id: string; + + @ApiProperty() + name: string; + + @ApiProperty() + tag: string; + + @ApiProperty() + description: string; + + @ApiProperty() + memberCount: number; + + @ApiProperty({ type: [String] }) + leagues: string[]; + + @ApiProperty({ required: false }) + specialization?: 'endurance' | 'sprint' | 'mixed'; + + @ApiProperty({ required: false }) + region?: string; + + @ApiProperty({ type: [String], required: false }) + languages?: string[]; +} + +export class GetAllTeamsOutputDTO { + @ApiProperty({ type: [TeamListItemDTO] }) + teams: TeamListItemDTO[]; + + @ApiProperty() + totalCount: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/GetDriverTeamOutputDTO.ts b/apps/api/src/domain/team/dtos/GetDriverTeamOutputDTO.ts new file mode 100644 index 000000000..97f14b3a8 --- /dev/null +++ b/apps/api/src/domain/team/dtos/GetDriverTeamOutputDTO.ts @@ -0,0 +1,58 @@ +import { ApiProperty } from '@nestjs/swagger'; + +class TeamDTO { + @ApiProperty() + id: string; + + @ApiProperty() + name: string; + + @ApiProperty() + tag: string; + + @ApiProperty() + description: string; + + @ApiProperty() + ownerId: string; + + @ApiProperty({ type: [String] }) + leagues: string[]; + + @ApiProperty({ required: false }) + createdAt?: string; + + @ApiProperty({ required: false }) + specialization?: 'endurance' | 'sprint' | 'mixed'; + + @ApiProperty({ required: false }) + region?: string; + + @ApiProperty({ type: [String], required: false }) + languages?: string[]; +} + +class MembershipDTO { + @ApiProperty() + role: 'owner' | 'manager' | 'member'; + + @ApiProperty() + joinedAt: string; + + @ApiProperty() + isActive: boolean; +} + +export class GetDriverTeamOutputDTO { + @ApiProperty({ type: TeamDTO }) + team: TeamDTO; + + @ApiProperty({ type: MembershipDTO }) + membership: MembershipDTO; + + @ApiProperty() + isOwner: boolean; + + @ApiProperty() + canManage: boolean; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/GetTeamDetailsOutputDTO.ts b/apps/api/src/domain/team/dtos/GetTeamDetailsOutputDTO.ts new file mode 100644 index 000000000..a630da8f7 --- /dev/null +++ b/apps/api/src/domain/team/dtos/GetTeamDetailsOutputDTO.ts @@ -0,0 +1,55 @@ +import { ApiProperty } from '@nestjs/swagger'; + +class TeamDTO { + @ApiProperty() + id: string; + + @ApiProperty() + name: string; + + @ApiProperty() + tag: string; + + @ApiProperty() + description: string; + + @ApiProperty() + ownerId: string; + + @ApiProperty({ type: [String] }) + leagues: string[]; + + @ApiProperty({ required: false }) + createdAt?: string; + + @ApiProperty({ required: false }) + specialization?: 'endurance' | 'sprint' | 'mixed'; + + @ApiProperty({ required: false }) + region?: string; + + @ApiProperty({ type: [String], required: false }) + languages?: string[]; +} + +class MembershipDTO { + @ApiProperty() + role: 'owner' | 'manager' | 'member'; + + @ApiProperty() + joinedAt: string; + + @ApiProperty() + isActive: boolean; +} + +export class GetTeamDetailsOutputDTO { + @ApiProperty({ type: TeamDTO }) + team: TeamDTO; + + @ApiProperty({ type: MembershipDTO, nullable: true }) + membership: MembershipDTO | null; + + @ApiProperty() + canManage: boolean; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/GetTeamJoinRequestsOutputDTO.ts b/apps/api/src/domain/team/dtos/GetTeamJoinRequestsOutputDTO.ts new file mode 100644 index 000000000..1869a3b0f --- /dev/null +++ b/apps/api/src/domain/team/dtos/GetTeamJoinRequestsOutputDTO.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; + +class TeamJoinRequestDTO { + @ApiProperty() + requestId: string; + + @ApiProperty() + driverId: string; + + @ApiProperty() + driverName: string; + + @ApiProperty() + teamId: string; + + @ApiProperty() + status: 'pending' | 'approved' | 'rejected'; + + @ApiProperty() + requestedAt: string; + + @ApiProperty() + avatarUrl: string; +} + +export class GetTeamJoinRequestsOutputDTO { + @ApiProperty({ type: [TeamJoinRequestDTO] }) + requests: TeamJoinRequestDTO[]; + + @ApiProperty() + pendingCount: number; + + @ApiProperty() + totalCount: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/GetTeamMembersOutputDTO.ts b/apps/api/src/domain/team/dtos/GetTeamMembersOutputDTO.ts new file mode 100644 index 000000000..24391f976 --- /dev/null +++ b/apps/api/src/domain/team/dtos/GetTeamMembersOutputDTO.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; + +class TeamMemberDTO { + @ApiProperty() + driverId: string; + + @ApiProperty() + driverName: string; + + @ApiProperty() + role: 'owner' | 'manager' | 'member'; + + @ApiProperty() + joinedAt: string; + + @ApiProperty() + isActive: boolean; + + @ApiProperty() + avatarUrl: string; +} + +export class GetTeamMembersOutputDTO { + @ApiProperty({ type: [TeamMemberDTO] }) + members: TeamMemberDTO[]; + + @ApiProperty() + totalCount: number; + + @ApiProperty() + ownerCount: number; + + @ApiProperty() + managerCount: number; + + @ApiProperty() + memberCount: number; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/GetTeamMembershipOutputDTO.ts b/apps/api/src/domain/team/dtos/GetTeamMembershipOutputDTO.ts new file mode 100644 index 000000000..f297e3255 --- /dev/null +++ b/apps/api/src/domain/team/dtos/GetTeamMembershipOutputDTO.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class GetTeamMembershipOutputDTO { + @ApiProperty() + role: 'owner' | 'manager' | 'member'; + + @ApiProperty() + joinedAt: string; + + @ApiProperty() + isActive: boolean; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/UpdateTeamInputDTO.ts b/apps/api/src/domain/team/dtos/UpdateTeamInputDTO.ts new file mode 100644 index 000000000..0b3489a4f --- /dev/null +++ b/apps/api/src/domain/team/dtos/UpdateTeamInputDTO.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional } from 'class-validator'; + +export class UpdateTeamInputDTO { + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + name?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + tag?: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + description?: string; +} \ No newline at end of file diff --git a/apps/api/src/domain/team/dtos/UpdateTeamOutputDTO.ts b/apps/api/src/domain/team/dtos/UpdateTeamOutputDTO.ts new file mode 100644 index 000000000..89f0caa00 --- /dev/null +++ b/apps/api/src/domain/team/dtos/UpdateTeamOutputDTO.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdateTeamOutputDTO { + @ApiProperty() + success: boolean; +} \ No newline at end of file diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 7c04058d8..6c9e8e2ef 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -15,6 +15,7 @@ async function bootstrap() { .setTitle('GridPilot API') .setDescription('GridPilot API documentation') .setVersion('1.0') + .addTag('dashboard', 'Dashboard endpoints') .addTag('races', 'Race management endpoints') .addTag('leagues', 'League management endpoints') .addTag('teams', 'Team management endpoints') diff --git a/apps/website/app/leaderboards/page.tsx b/apps/website/app/leaderboards/page.tsx index eb79167b9..1b40a3ebd 100644 --- a/apps/website/app/leaderboards/page.tsx +++ b/apps/website/app/leaderboards/page.tsx @@ -173,8 +173,7 @@ interface TeamLeaderboardPreviewProps { function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewProps) { const router = useRouter(); const top5 = [...teams] - .filter((t) => t.rating !== null) - .sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0)) + .sort((a, b) => b.memberCount - a.memberCount) .slice(0, 5); const getMedalColor = (position: number) => { @@ -257,8 +256,8 @@ function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewPr {/* Stats */}
-

{team.rating?.toLocaleString()}

-

Rating

+

{team.memberCount}

+

Members

{team.totalWins}

diff --git a/apps/website/app/leagues/[id]/settings/page.tsx b/apps/website/app/leagues/[id]/settings/page.tsx index e1a41183c..b377cea34 100644 --- a/apps/website/app/leagues/[id]/settings/page.tsx +++ b/apps/website/app/leagues/[id]/settings/page.tsx @@ -7,7 +7,7 @@ import Card from '@/components/ui/Card'; import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId'; import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles'; import { useServices } from '@/lib/services/ServiceProvider'; -import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel'; +import type { LeagueConfigFormModel } from '@core/racing/application'; import { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel'; import { AlertTriangle, Settings, UserCog } from 'lucide-react'; import { useParams, useRouter } from 'next/navigation'; diff --git a/apps/website/app/profile/leagues/page.tsx b/apps/website/app/profile/leagues/page.tsx index cd172b841..6ee3b18b7 100644 --- a/apps/website/app/profile/leagues/page.tsx +++ b/apps/website/app/profile/leagues/page.tsx @@ -4,7 +4,7 @@ import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId'; import { useServices } from '@/lib/services/ServiceProvider'; -import type { LeagueMembership } from '@/lib/types/LeagueMembership'; +import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership'; import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel'; import Link from 'next/link'; import { useEffect, useState } from 'react'; diff --git a/apps/website/lib/api/analytics/AnalyticsApiClient.ts b/apps/website/lib/api/analytics/AnalyticsApiClient.ts index 41fdd02d3..ef0f85429 100644 --- a/apps/website/lib/api/analytics/AnalyticsApiClient.ts +++ b/apps/website/lib/api/analytics/AnalyticsApiClient.ts @@ -1,24 +1,10 @@ import { BaseApiClient } from '../base/BaseApiClient'; import { RecordPageViewOutputDTO } from '../../types/generated/RecordPageViewOutputDTO'; import { RecordEngagementOutputDTO } from '../../types/generated/RecordEngagementOutputDTO'; - -// TODO: Move these types to apps/website/lib/types/generated when available -type RecordPageViewInputDto = { path: string; userId?: string }; -type RecordEngagementInputDto = { eventType: string; userId?: string; metadata?: Record }; - -// TODO: Move these types to apps/website/lib/types/generated when available -type AnalyticsDashboardDto = { - totalUsers: number; - activeUsers: number; - totalRaces: number; - totalLeagues: number; -}; -type AnalyticsMetricsDto = { - pageViews: number; - uniqueVisitors: number; - averageSessionDuration: number; - bounceRate: number; -}; +import { GetDashboardDataOutputDTO } from '../../types/generated/GetDashboardDataOutputDTO'; +import { GetAnalyticsMetricsOutputDTO } from '../../types/generated/GetAnalyticsMetricsOutputDTO'; +import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO'; +import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO'; /** * Analytics API Client @@ -27,22 +13,22 @@ type AnalyticsMetricsDto = { */ export class AnalyticsApiClient extends BaseApiClient { /** Record a page view */ - recordPageView(input: RecordPageViewInputDto): Promise { + recordPageView(input: RecordPageViewInputDTO): Promise { return this.post('/analytics/page-view', input); } /** Record an engagement event */ - recordEngagement(input: RecordEngagementInputDto): Promise { + recordEngagement(input: RecordEngagementInputDTO): Promise { return this.post('/analytics/engagement', input); } /** Get analytics dashboard data */ - getDashboardData(): Promise { - return this.get('/analytics/dashboard'); + getDashboardData(): Promise { + return this.get('/analytics/dashboard'); } /** Get analytics metrics */ - getAnalyticsMetrics(): Promise { - return this.get('/analytics/metrics'); + getAnalyticsMetrics(): Promise { + return this.get('/analytics/metrics'); } } \ No newline at end of file diff --git a/apps/website/lib/api/auth/AuthApiClient.ts b/apps/website/lib/api/auth/AuthApiClient.ts index aa278542e..2beaa466e 100644 --- a/apps/website/lib/api/auth/AuthApiClient.ts +++ b/apps/website/lib/api/auth/AuthApiClient.ts @@ -1,9 +1,9 @@ import { BaseApiClient } from '../base/BaseApiClient'; import { AuthSessionDTO } from '../../types/generated/AuthSessionDTO'; - -// TODO: Create DTOs for login/signup params in apps/website/lib/types/generated -type LoginParamsDto = { email: string; password: string }; -type SignupParamsDto = { email: string; password: string; displayName: string }; +import { LoginParams } from '../../types/generated/LoginParams'; +import { SignupParams } from '../../types/generated/SignupParams'; +import { LoginWithIracingCallbackParams } from '../../types/generated/LoginWithIracingCallbackParams'; +import { IracingAuthRedirectResult } from '../../types/generated/IracingAuthRedirectResult'; /** * Auth API Client @@ -12,12 +12,12 @@ type SignupParamsDto = { email: string; password: string; displayName: string }; */ export class AuthApiClient extends BaseApiClient { /** Sign up with email */ - signup(params: SignupParamsDto): Promise { + signup(params: SignupParams): Promise { return this.post('/auth/signup', params); } /** Login with email */ - login(params: LoginParamsDto): Promise { + login(params: LoginParams): Promise { return this.post('/auth/login', params); } @@ -32,9 +32,19 @@ export class AuthApiClient extends BaseApiClient { } /** Start iRacing auth redirect */ - getIracingAuthUrl(returnTo?: string): string { - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'; - const params = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : ''; - return `${baseUrl}/auth/iracing/start${params}`; + startIracingAuthRedirect(returnTo?: string): Promise { + const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : ''; + return this.get(`/auth/iracing/start${query}`); + } + + /** Login with iRacing callback */ + loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise { + const query = new URLSearchParams(); + query.append('code', params.code); + query.append('state', params.state); + if (params.returnTo) { + query.append('returnTo', params.returnTo); + } + return this.get(`/auth/iracing/callback?${query.toString()}`); } } \ No newline at end of file diff --git a/apps/website/lib/api/dashboard/DashboardApiClient.ts b/apps/website/lib/api/dashboard/DashboardApiClient.ts index 3dac992fa..fc1015584 100644 --- a/apps/website/lib/api/dashboard/DashboardApiClient.ts +++ b/apps/website/lib/api/dashboard/DashboardApiClient.ts @@ -1,61 +1,27 @@ import { BaseApiClient } from '../base/BaseApiClient'; +import { + DashboardDriverSummaryDTO, + DashboardRaceSummaryDTO, + DashboardLeagueStandingSummaryDTO, + DashboardFeedItemSummaryDTO, + DashboardFriendSummaryDTO, + DashboardRecentResultDTO, +} from '../../types/generated'; -// DTOs -export type DriverDto = { - id: string; - name: string; - avatarUrl: string; - country: string; - totalRaces: number; - wins: number; - podiums: number; - rating: number; - globalRank: number; - consistency: number; -}; - -export type RaceDto = { - id: string; - track: string; - car: string; - scheduledAt: string; // ISO date string - isMyLeague: boolean; - leagueName?: string; -}; - -export type LeagueStandingDto = { - leagueId: string; - leagueName: string; - position: number; - points: number; - totalDrivers: number; -}; - -export type FeedItemDto = { - id: string; - type: string; - headline: string; - body: string | null; - timestamp: string; // ISO date string - ctaHref?: string; - ctaLabel?: string; -}; - -export type FriendDto = { - id: string; - name: string; - avatarUrl: string; - country: string; -}; - +// Define DashboardOverviewDTO using generated types export type DashboardOverviewDto = { - currentDriver: DriverDto; - nextRace: RaceDto | null; - upcomingRaces: RaceDto[]; - leagueStandings: LeagueStandingDto[]; - feedItems: FeedItemDto[]; - friends: FriendDto[]; + currentDriver: DashboardDriverSummaryDTO | null; + myUpcomingRaces: DashboardRaceSummaryDTO[]; + otherUpcomingRaces: DashboardRaceSummaryDTO[]; + upcomingRaces: DashboardRaceSummaryDTO[]; activeLeaguesCount: number; + nextRace: DashboardRaceSummaryDTO | null; + recentResults: DashboardRecentResultDTO[]; + leagueStandingsSummaries: DashboardLeagueStandingSummaryDTO[]; + feedSummary: { + feedItems: DashboardFeedItemSummaryDTO[]; + }; + friends: DashboardFriendSummaryDTO[]; }; /** diff --git a/apps/website/lib/api/drivers/DriversApiClient.ts b/apps/website/lib/api/drivers/DriversApiClient.ts index 05439127d..2ddca9ed7 100644 --- a/apps/website/lib/api/drivers/DriversApiClient.ts +++ b/apps/website/lib/api/drivers/DriversApiClient.ts @@ -1,15 +1,6 @@ import { BaseApiClient } from '../base/BaseApiClient'; // Import generated types -import type { CompleteOnboardingInputDTO, CompleteOnboardingOutputDTO, DriverRegistrationStatusDTO, DriverLeaderboardItemDTO, DriverProfileDTO } from '../../types/generated'; - -// TODO: Create proper DriverDTO in generated types -type DriverDTO = { - id: string; - name: string; - avatarUrl?: string; - iracingId?: string; - rating?: number; -}; +import type { CompleteOnboardingInputDTO, CompleteOnboardingOutputDTO, DriverRegistrationStatusDTO, DriverLeaderboardItemDTO, DriverProfileDTO, GetDriverOutputDTO } from '../../types/generated'; type DriversLeaderboardDto = { drivers: DriverLeaderboardItemDTO[]; @@ -32,8 +23,8 @@ export class DriversApiClient extends BaseApiClient { } /** Get current driver (based on session) */ - getCurrent(): Promise { - return this.get('/drivers/current'); + getCurrent(): Promise { + return this.get('/drivers/current'); } /** Get driver registration status for a specific race */ @@ -42,8 +33,8 @@ export class DriversApiClient extends BaseApiClient { } /** Get driver by ID */ - getDriver(driverId: string): Promise { - return this.get(`/drivers/${driverId}`); + getDriver(driverId: string): Promise { + return this.get(`/drivers/${driverId}`); } /** Get driver profile with full details */ @@ -52,7 +43,7 @@ export class DriversApiClient extends BaseApiClient { } /** Update current driver profile */ - updateProfile(updates: { bio?: string; country?: string }): Promise { - return this.put('/drivers/profile', updates); + updateProfile(updates: { bio?: string; country?: string }): Promise { + return this.put('/drivers/profile', updates); } } \ No newline at end of file diff --git a/apps/website/lib/api/index.ts b/apps/website/lib/api/index.ts index 9fb450640..7ade7f3a8 100644 --- a/apps/website/lib/api/index.ts +++ b/apps/website/lib/api/index.ts @@ -8,6 +8,8 @@ import { AnalyticsApiClient } from './analytics/AnalyticsApiClient'; import { AuthApiClient } from './auth/AuthApiClient'; import { PaymentsApiClient } from './payments/PaymentsApiClient'; import { DashboardApiClient } from './dashboard/DashboardApiClient'; +import { PenaltiesApiClient } from './penalties/PenaltiesApiClient'; +import { ProtestsApiClient } from './protests/ProtestsApiClient'; /** * Main API Client @@ -25,6 +27,8 @@ export class ApiClient { public readonly auth: AuthApiClient; public readonly payments: PaymentsApiClient; public readonly dashboard: DashboardApiClient; + public readonly penalties: PenaltiesApiClient; + public readonly protests: ProtestsApiClient; constructor(baseUrl: string) { this.leagues = new LeaguesApiClient(baseUrl); @@ -37,6 +41,8 @@ export class ApiClient { this.auth = new AuthApiClient(baseUrl); this.payments = new PaymentsApiClient(baseUrl); this.dashboard = new DashboardApiClient(baseUrl); + this.penalties = new PenaltiesApiClient(baseUrl); + this.protests = new ProtestsApiClient(baseUrl); } } diff --git a/apps/website/lib/api/leagues/LeaguesApiClient.ts b/apps/website/lib/api/leagues/LeaguesApiClient.ts index 830a0f512..fa15e7cf8 100644 --- a/apps/website/lib/api/leagues/LeaguesApiClient.ts +++ b/apps/website/lib/api/leagues/LeaguesApiClient.ts @@ -7,6 +7,8 @@ import type { LeagueMembershipsDto, CreateLeagueInputDto, CreateLeagueOutputDto, + SponsorshipDetailDTO, + RaceDTO, } from '../../dtos'; /** @@ -61,8 +63,8 @@ export class LeaguesApiClient extends BaseApiClient { } /** Get season sponsorships */ - getSeasonSponsorships(seasonId: string): Promise<{ sponsorships: Array<{ sponsorId: string; tier: string; status: string }> }> { - return this.get<{ sponsorships: Array<{ sponsorId: string; tier: string; status: string }> }>(`/seasons/${seasonId}/sponsorships`); + getSeasonSponsorships(seasonId: string): Promise<{ sponsorships: SponsorshipDetailDTO[] }> { + return this.get<{ sponsorships: SponsorshipDetailDTO[] }>(`/leagues/seasons/${seasonId}/sponsorships`); } /** Get league config */ @@ -84,7 +86,7 @@ export class LeaguesApiClient extends BaseApiClient { } /** Get races for a league */ - getRaces(leagueId: string): Promise<{ races: any[] }> { - return this.get<{ races: any[] }>(`/leagues/${leagueId}/races`); + getRaces(leagueId: string): Promise<{ races: RaceDTO[] }> { + return this.get<{ races: RaceDTO[] }>(`/leagues/${leagueId}/races`); } } \ No newline at end of file diff --git a/apps/website/lib/api/media/MediaApiClient.ts b/apps/website/lib/api/media/MediaApiClient.ts index cc1b51fd9..c10309b43 100644 --- a/apps/website/lib/api/media/MediaApiClient.ts +++ b/apps/website/lib/api/media/MediaApiClient.ts @@ -1,14 +1,13 @@ import type { - DeleteMediaOutputDto, - GetMediaOutputDto, - RequestAvatarGenerationInputDto, - RequestAvatarGenerationOutputDto, - UpdateAvatarInputDto, - UpdateAvatarOutputDto, - UploadMediaInputDto, - UploadMediaOutputDto, -} from '../../dtos'; -import type { GetAvatarOutputDto } from '../../types/GetAvatarOutputDto'; + DeleteMediaOutputDTO, + GetMediaOutputDTO, + RequestAvatarGenerationInputDTO, + RequestAvatarGenerationOutputDTO, + UpdateAvatarInputDTO, + UpdateAvatarOutputDTO, + UploadMediaOutputDTO, +} from '../generated'; +import type { GetAvatarOutputDTO } from '../generated'; import { BaseApiClient } from '../base/BaseApiClient'; /** @@ -18,38 +17,38 @@ import { BaseApiClient } from '../base/BaseApiClient'; */ export class MediaApiClient extends BaseApiClient { /** Upload media file */ - uploadMedia(input: UploadMediaInputDto): Promise { + uploadMedia(input: { file: File; type: string; category?: string }): Promise { const formData = new FormData(); formData.append('file', input.file); formData.append('type', input.type); if (input.category) { formData.append('category', input.category); } - return this.post('/media/upload', formData); + return this.post('/media/upload', formData); } /** Get media by ID */ - getMedia(mediaId: string): Promise { - return this.get(`/media/${mediaId}`); + getMedia(mediaId: string): Promise { + return this.get(`/media/${mediaId}`); } /** Delete media by ID */ - deleteMedia(mediaId: string): Promise { - return this.delete(`/media/${mediaId}`); + deleteMedia(mediaId: string): Promise { + return this.delete(`/media/${mediaId}`); } /** Request avatar generation */ - requestAvatarGeneration(input: RequestAvatarGenerationInputDto): Promise { - return this.post('/media/avatar/generate', input); + requestAvatarGeneration(input: RequestAvatarGenerationInputDTO): Promise { + return this.post('/media/avatar/generate', input); } /** Get avatar for driver */ - getAvatar(driverId: string): Promise { - return this.get(`/media/avatar/${driverId}`); + getAvatar(driverId: string): Promise { + return this.get(`/media/avatar/${driverId}`); } /** Update avatar for driver */ - updateAvatar(input: UpdateAvatarInputDto): Promise { - return this.put(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl }); + updateAvatar(input: UpdateAvatarInputDTO): Promise { + return this.put(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl }); } } \ No newline at end of file diff --git a/apps/website/lib/api/payments/PaymentsApiClient.ts b/apps/website/lib/api/payments/PaymentsApiClient.ts index 99b1bd00d..5bfe26371 100644 --- a/apps/website/lib/api/payments/PaymentsApiClient.ts +++ b/apps/website/lib/api/payments/PaymentsApiClient.ts @@ -1,7 +1,8 @@ import { BaseApiClient } from '../base/BaseApiClient'; +import type { PaymentDto, MembershipFeeDto, MemberPaymentDto, PrizeDto, WalletDto, TransactionDto, UpdatePaymentStatusInputDTO } from '../types/generated'; -// TODO: Import these types from apps/website/lib/types/generated when available -type GetPaymentsOutputDto = { payments: import('../types/generated').PaymentDto[] }; +// Define missing types that are not fully generated +type GetPaymentsOutputDto = { payments: PaymentDto[] }; type CreatePaymentInputDto = { type: 'sponsorship' | 'membership_fee'; amount: number; @@ -10,15 +11,15 @@ type CreatePaymentInputDto = { leagueId: string; seasonId?: string; }; -type CreatePaymentOutputDto = { payment: import('../types/generated').PaymentDto }; +type CreatePaymentOutputDto = { payment: PaymentDto }; type GetMembershipFeesOutputDto = { - fee: import('../types/generated').MembershipFeeDto | null; - payments: import('../types/generated').MemberPaymentDto[] + fee: MembershipFeeDto | null; + payments: MemberPaymentDto[] }; -type GetPrizesOutputDto = { prizes: import('../types/generated').PrizeDto[] }; +type GetPrizesOutputDto = { prizes: PrizeDto[] }; type GetWalletOutputDto = { - wallet: import('../types/generated').WalletDto; - transactions: import('../types/generated').TransactionDto[] + wallet: WalletDto; + transactions: TransactionDto[] }; type ProcessWalletTransactionInputDto = { leagueId: string; @@ -29,8 +30,8 @@ type ProcessWalletTransactionInputDto = { referenceType?: 'sponsorship' | 'membership_fee' | 'prize'; }; type ProcessWalletTransactionOutputDto = { - wallet: import('../types/generated').WalletDto; - transaction: import('../types/generated').TransactionDto + wallet: WalletDto; + transaction: TransactionDto }; type UpdateMemberPaymentInputDto = { feeId: string; @@ -38,8 +39,33 @@ type UpdateMemberPaymentInputDto = { status?: 'pending' | 'paid' | 'overdue'; paidAt?: Date | string; }; -type UpdateMemberPaymentOutputDto = { payment: import('../types/generated').MemberPaymentDto }; -type GetWalletTransactionsOutputDto = { transactions: import('../types/generated').TransactionDto[] }; +type UpdateMemberPaymentOutputDto = { payment: MemberPaymentDto }; +type GetWalletTransactionsOutputDto = { transactions: TransactionDto[] }; +type UpdatePaymentStatusOutputDto = { payment: PaymentDto }; +type UpsertMembershipFeeInputDto = { + leagueId: string; + seasonId?: string; + type: 'season' | 'monthly' | 'per_race'; + amount: number; +}; +type UpsertMembershipFeeOutputDto = { fee: MembershipFeeDto }; +type CreatePrizeInputDto = { + leagueId: string; + seasonId: string; + position: number; + name: string; + amount: number; + type: 'cash' | 'merchandise' | 'other'; + description?: string; +}; +type CreatePrizeOutputDto = { prize: PrizeDto }; +type AwardPrizeInputDto = { + prizeId: string; + driverId: string; +}; +type AwardPrizeOutputDto = { prize: PrizeDto }; +type DeletePrizeInputDto = { prizeId: string }; +type DeletePrizeOutputDto = { success: boolean }; /** * Payments API Client @@ -48,12 +74,14 @@ type GetWalletTransactionsOutputDto = { transactions: import('../types/generated */ export class PaymentsApiClient extends BaseApiClient { /** Get payments */ - getPayments(leagueId?: string, driverId?: string): Promise { + getPayments(query?: { leagueId?: string; payerId?: string; type?: 'sponsorship' | 'membership_fee'; status?: 'pending' | 'completed' | 'failed' | 'refunded' }): Promise { const params = new URLSearchParams(); - if (leagueId) params.append('leagueId', leagueId); - if (driverId) params.append('driverId', driverId); - const query = params.toString(); - return this.get(`/payments${query ? `?${query}` : ''}`); + if (query?.leagueId) params.append('leagueId', query.leagueId); + if (query?.payerId) params.append('payerId', query.payerId); + if (query?.type) params.append('type', query.type); + if (query?.status) params.append('status', query.status); + const queryString = params.toString(); + return this.get(`/payments${queryString ? `?${queryString}` : ''}`); } /** Create a payment */ @@ -62,21 +90,63 @@ export class PaymentsApiClient extends BaseApiClient { } /** Get membership fees */ - getMembershipFees(leagueId: string): Promise { - return this.get(`/payments/membership-fees?leagueId=${leagueId}`); + getMembershipFees(query: { leagueId: string; driverId?: string }): Promise { + const params = new URLSearchParams(); + params.append('leagueId', query.leagueId); + if (query.driverId) params.append('driverId', query.driverId); + const queryString = params.toString(); + return this.get(`/payments/membership-fees?${queryString}`); } /** Get prizes */ - getPrizes(leagueId?: string, seasonId?: string): Promise { + getPrizes(query?: { leagueId?: string; seasonId?: string }): Promise { const params = new URLSearchParams(); - if (leagueId) params.append('leagueId', leagueId); - if (seasonId) params.append('seasonId', seasonId); - const query = params.toString(); - return this.get(`/payments/prizes${query ? `?${query}` : ''}`); + if (query?.leagueId) params.append('leagueId', query.leagueId); + if (query?.seasonId) params.append('seasonId', query.seasonId); + const queryString = params.toString(); + return this.get(`/payments/prizes${queryString ? `?${queryString}` : ''}`); } /** Get wallet */ - getWallet(driverId: string): Promise { - return this.get(`/payments/wallets?driverId=${driverId}`); + getWallet(query?: { leagueId?: string }): Promise { + const params = new URLSearchParams(); + if (query?.leagueId) params.append('leagueId', query.leagueId); + const queryString = params.toString(); + return this.get(`/payments/wallets${queryString ? `?${queryString}` : ''}`); + } + + /** Update payment status */ + updatePaymentStatus(input: UpdatePaymentStatusInputDTO): Promise { + return this.patch('/payments/status', input); + } + + /** Upsert membership fee */ + upsertMembershipFee(input: UpsertMembershipFeeInputDto): Promise { + return this.post('/payments/membership-fees', input); + } + + /** Update member payment */ + updateMemberPayment(input: UpdateMemberPaymentInputDto): Promise { + return this.patch('/payments/membership-fees/member-payment', input); + } + + /** Create prize */ + createPrize(input: CreatePrizeInputDto): Promise { + return this.post('/payments/prizes', input); + } + + /** Award prize */ + awardPrize(input: AwardPrizeInputDto): Promise { + return this.patch('/payments/prizes/award', input); + } + + /** Delete prize */ + deletePrize(prizeId: string): Promise { + return this.delete(`/payments/prizes?prizeId=${prizeId}`); + } + + /** Process wallet transaction */ + processWalletTransaction(input: ProcessWalletTransactionInputDto): Promise { + return this.post('/payments/wallets/transactions', input); } } \ No newline at end of file diff --git a/apps/website/lib/api/penalties/PenaltiesApiClient.ts b/apps/website/lib/api/penalties/PenaltiesApiClient.ts index 4b2916e1a..974beaff8 100644 --- a/apps/website/lib/api/penalties/PenaltiesApiClient.ts +++ b/apps/website/lib/api/penalties/PenaltiesApiClient.ts @@ -1,4 +1,6 @@ import { BaseApiClient } from '../base/BaseApiClient'; +import { RacePenaltiesDTO } from '../../types/generated/RacePenaltiesDTO'; +import { ApplyPenaltyCommandDTO } from '../../types/generated/ApplyPenaltyCommandDTO'; /** * Penalties API Client @@ -7,12 +9,12 @@ import { BaseApiClient } from '../base/BaseApiClient'; */ export class PenaltiesApiClient extends BaseApiClient { /** Get penalties for a race */ - getRacePenalties(raceId: string): Promise<{ penalties: any[] }> { - return this.get<{ penalties: any[] }>(`/races/${raceId}/penalties`); + getRacePenalties(raceId: string): Promise { + return this.get(`/races/${raceId}/penalties`); } /** Apply a penalty */ - applyPenalty(input: any): Promise { + applyPenalty(input: ApplyPenaltyCommandDTO): Promise { return this.post('/races/penalties/apply', input); } } \ No newline at end of file diff --git a/apps/website/lib/api/protests/ProtestsApiClient.ts b/apps/website/lib/api/protests/ProtestsApiClient.ts index 8b048a041..690e5d76c 100644 --- a/apps/website/lib/api/protests/ProtestsApiClient.ts +++ b/apps/website/lib/api/protests/ProtestsApiClient.ts @@ -3,7 +3,9 @@ import type { LeagueAdminProtestsDTO, ApplyPenaltyCommandDTO, RequestProtestDefenseCommandDTO, -} from '../../types'; + ReviewProtestCommandDTO, +} from '../../types/generated'; +import type { RaceProtestsDTO } from '../../types'; /** * Protests API Client @@ -32,12 +34,12 @@ export class ProtestsApiClient extends BaseApiClient { } /** Review protest */ - reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): Promise { + reviewProtest(input: ReviewProtestCommandDTO): Promise { return this.post(`/protests/${input.protestId}/review`, input); } /** Get protests for a race */ - getRaceProtests(raceId: string): Promise<{ protests: any[] }> { - return this.get<{ protests: any[] }>(`/races/${raceId}/protests`); + getRaceProtests(raceId: string): Promise { + return this.get(`/races/${raceId}/protests`); } } \ No newline at end of file diff --git a/apps/website/lib/api/races/RacesApiClient.ts b/apps/website/lib/api/races/RacesApiClient.ts index f97d71035..a7b390652 100644 --- a/apps/website/lib/api/races/RacesApiClient.ts +++ b/apps/website/lib/api/races/RacesApiClient.ts @@ -1,15 +1,34 @@ import { BaseApiClient } from '../base/BaseApiClient'; -import type { - RaceStatsDto, - RacesPageDataDto, - RaceDetailDto, - RaceResultsDetailDto, - RaceWithSOFDto, - RegisterForRaceInputDto, - ImportRaceResultsInputDto, - ImportRaceResultsSummaryDto, - WithdrawFromRaceInputDto, -} from '../../dtos'; +import type { RaceStatsDTO } from '../../types/generated/RaceStatsDTO'; +import type { RacesPageDataRaceDTO } from '../../types/generated/RacesPageDataRaceDTO'; +import type { RaceResultsDetailDTO } from '../../types/generated/RaceResultsDetailDTO'; +import type { RaceWithSOFDTO } from '../../types/generated/RaceWithSOFDTO'; +import type { RegisterForRaceParamsDTO } from '../../types/generated/RegisterForRaceParamsDTO'; +import type { ImportRaceResultsDTO } from '../../types/generated/ImportRaceResultsDTO'; +import type { WithdrawFromRaceParamsDTO } from '../../types/generated/WithdrawFromRaceParamsDTO'; +import type { RaceDetailRaceDTO } from '../../types/generated/RaceDetailRaceDTO'; +import type { RaceDetailLeagueDTO } from '../../types/generated/RaceDetailLeagueDTO'; +import type { RaceDetailEntryDTO } from '../../types/generated/RaceDetailEntryDTO'; +import type { RaceDetailRegistrationDTO } from '../../types/generated/RaceDetailRegistrationDTO'; +import type { RaceDetailUserResultDTO } from '../../types/generated/RaceDetailUserResultDTO'; + +// Define missing types +type RacesPageDataDTO = { races: RacesPageDataRaceDTO[] }; +type RaceDetailDTO = { + race: RaceDetailRaceDTO | null; + league: RaceDetailLeagueDTO | null; + entryList: RaceDetailEntryDTO[]; + registration: RaceDetailRegistrationDTO; + userResult: RaceDetailUserResultDTO | null; + error?: string; +}; +type ImportRaceResultsSummaryDTO = { + success: boolean; + raceId: string; + driversProcessed: number; + resultsRecorded: number; + errors?: string[]; +}; /** * Races API Client @@ -18,42 +37,42 @@ import type { */ export class RacesApiClient extends BaseApiClient { /** Get total number of races */ - getTotal(): Promise { - return this.get('/races/total-races'); + getTotal(): Promise { + return this.get('/races/total-races'); } /** Get races page data */ - getPageData(): Promise { - return this.get('/races/page-data'); + getPageData(): Promise { + return this.get('/races/page-data'); } /** Get race detail */ - getDetail(raceId: string, driverId: string): Promise { - return this.get(`/races/${raceId}?driverId=${driverId}`); + getDetail(raceId: string, driverId: string): Promise { + return this.get(`/races/${raceId}?driverId=${driverId}`); } /** Get race results detail */ - getResultsDetail(raceId: string): Promise { - return this.get(`/races/${raceId}/results`); + getResultsDetail(raceId: string): Promise { + return this.get(`/races/${raceId}/results`); } /** Get race with strength of field */ - getWithSOF(raceId: string): Promise { - return this.get(`/races/${raceId}/sof`); + getWithSOF(raceId: string): Promise { + return this.get(`/races/${raceId}/sof`); } /** Register for race */ - register(raceId: string, input: RegisterForRaceInputDto): Promise { + register(raceId: string, input: RegisterForRaceParamsDTO): Promise { return this.post(`/races/${raceId}/register`, input); } /** Import race results */ - importResults(raceId: string, input: ImportRaceResultsInputDto): Promise { - return this.post(`/races/${raceId}/import-results`, input); + importResults(raceId: string, input: ImportRaceResultsDTO): Promise { + return this.post(`/races/${raceId}/import-results`, input); } /** Withdraw from race */ - withdraw(raceId: string, input: WithdrawFromRaceInputDto): Promise { + withdraw(raceId: string, input: WithdrawFromRaceParamsDTO): Promise { return this.post(`/races/${raceId}/withdraw`, input); } diff --git a/apps/website/lib/api/sponsors/SponsorsApiClient.ts b/apps/website/lib/api/sponsors/SponsorsApiClient.ts index 32e51fef4..1e8c94d25 100644 --- a/apps/website/lib/api/sponsors/SponsorsApiClient.ts +++ b/apps/website/lib/api/sponsors/SponsorsApiClient.ts @@ -2,11 +2,15 @@ import { BaseApiClient } from '../base/BaseApiClient'; import type { CreateSponsorInputDTO } from '../../types/generated/CreateSponsorInputDTO'; import type { SponsorDashboardDTO } from '../../types/generated/SponsorDashboardDTO'; import type { SponsorSponsorshipsDTO } from '../../types/generated/SponsorSponsorshipsDTO'; +import type { GetPendingSponsorshipRequestsOutputDTO } from '../../types/generated/GetPendingSponsorshipRequestsOutputDTO'; +import type { AcceptSponsorshipRequestInputDTO } from '../../types/generated/AcceptSponsorshipRequestInputDTO'; +import type { RejectSponsorshipRequestInputDTO } from '../../types/generated/RejectSponsorshipRequestInputDTO'; +import type { GetSponsorOutputDTO } from '../../types/generated/GetSponsorOutputDTO'; +import type { SponsorDTO } from '../../types/generated/SponsorDTO'; -// TODO: Move these types to apps/website/lib/types/generated when available +// Types that are not yet generated export type CreateSponsorOutputDto = { id: string; name: string }; export type GetEntitySponsorshipPricingResultDto = { pricing: Array<{ entityType: string; price: number }> }; -export type SponsorDTO = { id: string; name: string; logoUrl?: string; websiteUrl?: string }; export type GetSponsorsOutputDto = { sponsors: SponsorDTO[] }; /** @@ -41,22 +45,22 @@ export class SponsorsApiClient extends BaseApiClient { } /** Get sponsor by ID */ - getSponsor(sponsorId: string): Promise { - return this.get(`/sponsors/${sponsorId}`); + getSponsor(sponsorId: string): Promise { + return this.get(`/sponsors/${sponsorId}`); } /** Get pending sponsorship requests for an entity */ - getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<{ requests: any[] }> { - return this.get<{ requests: any[] }>(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`); + getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise { + return this.get(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`); } /** Accept a sponsorship request */ - acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise { - return this.post(`/sponsors/requests/${requestId}/accept`, { respondedBy }); + acceptSponsorshipRequest(requestId: string, input: AcceptSponsorshipRequestInputDTO): Promise { + return this.post(`/sponsors/requests/${requestId}/accept`, input); } /** Reject a sponsorship request */ - rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise { - return this.post(`/sponsors/requests/${requestId}/reject`, { respondedBy, reason }); + rejectSponsorshipRequest(requestId: string, input: RejectSponsorshipRequestInputDTO): Promise { + return this.post(`/sponsors/requests/${requestId}/reject`, input); } } \ No newline at end of file diff --git a/apps/website/lib/api/teams/TeamsApiClient.ts b/apps/website/lib/api/teams/TeamsApiClient.ts index 45138093a..22e14c1d6 100644 --- a/apps/website/lib/api/teams/TeamsApiClient.ts +++ b/apps/website/lib/api/teams/TeamsApiClient.ts @@ -1,15 +1,14 @@ import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO'; -import type { - AllTeamsDto, - CreateTeamInputDto, - CreateTeamOutputDto, - DriverTeamDto, - TeamDetailsDto, - TeamJoinRequestsDto, - TeamMembersDto, - UpdateTeamInputDto, - UpdateTeamOutputDto, -} from '../../dtos'; +import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO'; +import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO'; +import type { GetTeamMembersOutputDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO'; +import type { GetTeamJoinRequestsOutputDTO } from '@/lib/types/generated/GetTeamJoinRequestsOutputDTO'; +import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO'; +import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO'; +import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO'; +import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO'; +import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO'; +import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO'; import { BaseApiClient } from '../base/BaseApiClient'; /** @@ -19,42 +18,42 @@ import { BaseApiClient } from '../base/BaseApiClient'; */ export class TeamsApiClient extends BaseApiClient { /** Get all teams */ - getAll(): Promise { - return this.get('/teams/all'); + getAll(): Promise { + return this.get('/teams/all'); } /** Get team details */ - getDetails(teamId: string): Promise { - return this.get(`/teams/${teamId}`); + getDetails(teamId: string): Promise { + return this.get(`/teams/${teamId}`); } /** Get team members */ - getMembers(teamId: string): Promise { - return this.get(`/teams/${teamId}/members`); + getMembers(teamId: string): Promise { + return this.get(`/teams/${teamId}/members`); } /** Get team join requests */ - getJoinRequests(teamId: string): Promise { - return this.get(`/teams/${teamId}/join-requests`); + getJoinRequests(teamId: string): Promise { + return this.get(`/teams/${teamId}/join-requests`); } /** Create a new team */ - create(input: CreateTeamInputDto): Promise { - return this.post('/teams', input); + create(input: CreateTeamInputDTO): Promise { + return this.post('/teams', input); } /** Update team */ - update(teamId: string, input: UpdateTeamInputDto): Promise { - return this.patch(`/teams/${teamId}`, input); + update(teamId: string, input: UpdateTeamInputDTO): Promise { + return this.patch(`/teams/${teamId}`, input); } /** Get driver's team */ - getDriverTeam(driverId: string): Promise { - return this.get(`/teams/driver/${driverId}`); + getDriverTeam(driverId: string): Promise { + return this.get(`/teams/driver/${driverId}`); } /** Get membership for a driver in a team */ - getMembership(teamId: string, driverId: string): Promise { - return this.get(`/teams/${teamId}/members/${driverId}`); + getMembership(teamId: string, driverId: string): Promise { + return this.get(`/teams/${teamId}/members/${driverId}`); } } \ No newline at end of file diff --git a/apps/website/lib/auth/AuthApiClient.ts b/apps/website/lib/auth/AuthApiClient.ts deleted file mode 100644 index 97f06c65a..000000000 --- a/apps/website/lib/auth/AuthApiClient.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { api } from '../apiClient'; -import type { - AuthenticatedUserDTO, - AuthSessionDTO, - SignupParams, - LoginParams, - IracingAuthRedirectResult, - LoginWithIracingCallbackParams, -} from '../../../apps/api/src/modules/auth/dto/AuthDto'; // Using generated API DTOs - -export class AuthApiClient { - async getCurrentSession(): Promise { - try { - return await api.get('/auth/session'); - } catch (error) { - // Handle error, e.g., if session is not found or API is down - console.error('Error fetching current session:', error); - return null; - } - } - - async signupWithEmail(params: SignupParams): Promise { - return api.post('/auth/signup', params); - } - - async loginWithEmail(params: LoginParams): Promise { - return api.post('/auth/login', params); - } - - async startIracingAuthRedirect(returnTo?: string): Promise { - const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : ''; - return api.get(`/auth/iracing/start${query}`); - } - - async loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise { - const query = new URLSearchParams(); - query.append('code', params.code); - query.append('state', params.state); - if (params.returnTo) { - query.append('returnTo', params.returnTo); - } - return await api.get(`/auth/iracing/callback?${query.toString()}`); - } - - async logout(): Promise { - return api.post('/auth/logout', {}); - } -} - -export const authApiClient = new AuthApiClient(); diff --git a/apps/website/lib/command-models/leagues/LeagueWizardCommandModel.ts b/apps/website/lib/command-models/leagues/LeagueWizardCommandModel.ts index b913f963e..f91f4dd98 100644 --- a/apps/website/lib/command-models/leagues/LeagueWizardCommandModel.ts +++ b/apps/website/lib/command-models/leagues/LeagueWizardCommandModel.ts @@ -1,9 +1,31 @@ -import { WizardStep } from '@/lib/types/WizardStep'; -import { WizardErrors } from '@/lib/types/WizardErrors'; import { CreateLeagueInputDTO } from '@/lib/types/CreateLeagueInputDTO'; import { LeagueWizardValidationMessages } from '@/lib/display-objects/LeagueWizardValidationMessages'; import { ScoringPresetApplier } from '@/lib/utilities/ScoringPresetApplier'; +export type WizardStep = 1 | 2 | 3 | 4 | 5 | 6 | 7; + +export interface WizardErrors { + basics?: { + name?: string; + description?: string; + visibility?: string; + }; + structure?: { + maxDrivers?: string; + maxTeams?: string; + driversPerTeam?: string; + }; + timings?: { + qualifyingMinutes?: string; + mainRaceMinutes?: string; + roundsPlanned?: string; + }; + scoring?: { + patternId?: string; + }; + submit?: string; +} + type LeagueWizardFormData = { leagueId: string | undefined; basics: { diff --git a/apps/website/lib/display-objects/LeagueRoleDisplay.ts b/apps/website/lib/display-objects/LeagueRoleDisplay.ts index e2ee6a100..269c50f94 100644 --- a/apps/website/lib/display-objects/LeagueRoleDisplay.ts +++ b/apps/website/lib/display-objects/LeagueRoleDisplay.ts @@ -1,4 +1,5 @@ -import { LeagueRole } from '@/lib/types/LeagueRole'; +import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole'; +type LeagueRole = MembershipRole; export interface LeagueRoleDisplayData { text: string; diff --git a/apps/website/lib/services/analytics/AnalyticsService.ts b/apps/website/lib/services/analytics/AnalyticsService.ts index e871d14c1..7ee1b8174 100644 --- a/apps/website/lib/services/analytics/AnalyticsService.ts +++ b/apps/website/lib/services/analytics/AnalyticsService.ts @@ -1,18 +1,8 @@ import { AnalyticsApiClient } from '../../api/analytics/AnalyticsApiClient'; import { RecordPageViewOutputViewModel } from '../../view-models/RecordPageViewOutputViewModel'; import { RecordEngagementOutputViewModel } from '../../view-models/RecordEngagementOutputViewModel'; - -// TODO: Create proper DTOs in generated types -interface RecordPageViewInputDTO { - path: string; - userId?: string; -} - -interface RecordEngagementInputDTO { - eventType: string; - userId?: string; - metadata?: Record; -} +import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO'; +import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO'; /** * Analytics Service diff --git a/apps/website/lib/services/leagues/LeagueMembershipService.ts b/apps/website/lib/services/leagues/LeagueMembershipService.ts index 28f175ec1..d567092a9 100644 --- a/apps/website/lib/services/leagues/LeagueMembershipService.ts +++ b/apps/website/lib/services/leagues/LeagueMembershipService.ts @@ -1,7 +1,7 @@ import { apiClient } from '@/lib/apiClient'; -import { LeagueMembership } from '@/lib/types/LeagueMembership'; -import { MembershipRole } from '@/lib/types/MembershipRole'; -import { MembershipStatus } from '@/lib/types/MembershipStatus'; +import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership'; +import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole'; +import type { MembershipStatus } from '@core/racing/domain/entities/MembershipStatus'; export class LeagueMembershipService { // In-memory cache for memberships (populated via API calls) diff --git a/apps/website/lib/services/leagues/LeagueService.ts b/apps/website/lib/services/leagues/LeagueService.ts index a6bcbb5cb..2fa3f5ebe 100644 --- a/apps/website/lib/services/leagues/LeagueService.ts +++ b/apps/website/lib/services/leagues/LeagueService.ts @@ -15,7 +15,6 @@ import { LeagueDetailViewModel } from "@/lib/view-models/LeagueDetailViewModel"; import { LeagueDetailPageViewModel, SponsorInfo } from "@/lib/view-models/LeagueDetailPageViewModel"; import { RaceViewModel } from "@/lib/view-models/RaceViewModel"; import { SubmitBlocker, ThrottleBlocker } from "@/lib/blockers"; -import { DriverDTO } from "@/lib/types/DriverDTO"; import { RaceDTO } from "@/lib/types/generated/RaceDTO"; import { LeagueScoringConfigDTO } from "@/lib/types/LeagueScoringConfigDTO"; import { LeagueStatsDTO } from "@/lib/types/generated/LeagueStatsDTO"; diff --git a/apps/website/lib/services/leagues/LeagueSettingsService.ts b/apps/website/lib/services/leagues/LeagueSettingsService.ts index 0cd7e7fb0..3b8677a9b 100644 --- a/apps/website/lib/services/leagues/LeagueSettingsService.ts +++ b/apps/website/lib/services/leagues/LeagueSettingsService.ts @@ -1,8 +1,8 @@ import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient"; import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient"; -import type { LeagueConfigFormModel } from "@/lib/types/LeagueConfigFormModel"; -import type { LeagueScoringPresetDTO } from "@/lib/types/LeagueScoringPresetDTO"; -import type { DriverDTO } from "@/lib/types/DriverDTO"; +import type { LeagueConfigFormModel } from "@core/racing/application"; +import type { LeagueScoringPresetDTO } from "@core/racing/application/ports/LeagueScoringPresetProvider"; +import type { GetDriverOutputDTO } from "@/lib/types/generated/GetDriverOutputDTO"; import { LeagueSettingsViewModel } from "@/lib/view-models/LeagueSettingsViewModel"; import { DriverSummaryViewModel } from "@/lib/view-models/DriverSummaryViewModel"; @@ -50,7 +50,7 @@ export class LeagueSettingsService { // TODO: get rating and rank from API owner = new DriverSummaryViewModel({ driver: ownerDriver, - rating: ownerDriver.rating ?? null, + rating: null, // TODO: get from API rank: null, // TODO: get from API }); } @@ -64,7 +64,7 @@ export class LeagueSettingsService { if (driver) { members.push(new DriverSummaryViewModel({ driver, - rating: driver.rating ?? null, + rating: null, // TODO: get from API rank: null, // TODO: get from API })); } diff --git a/apps/website/lib/services/leagues/LeagueWizardService.ts b/apps/website/lib/services/leagues/LeagueWizardService.ts index 034035a6c..0cdb0a9cc 100644 --- a/apps/website/lib/services/leagues/LeagueWizardService.ts +++ b/apps/website/lib/services/leagues/LeagueWizardService.ts @@ -1,26 +1,23 @@ import { apiClient } from '@/lib/apiClient'; import { LeagueWizardCommandModel } from '@/lib/command-models/leagues/LeagueWizardCommandModel'; -import { CreateLeagueResult } from '@/lib/types/CreateLeagueResult'; +import { CreateLeagueOutputDTO } from '@/lib/types/generated/CreateLeagueOutputDTO'; export class LeagueWizardService { static async createLeague( form: LeagueWizardCommandModel, ownerId: string, - ): Promise { + ): Promise { const command = form.toCreateLeagueCommand(ownerId); const result = await apiClient.leagues.create(command); - return { - leagueId: result.leagueId, - success: result.success, - }; + return result; } // Static method for backward compatibility static async createLeagueFromConfig( form: LeagueWizardCommandModel, ownerId: string, - ): Promise { + ): Promise { return this.createLeague(form, ownerId); } } \ No newline at end of file diff --git a/apps/website/lib/services/media/MediaService.test.ts b/apps/website/lib/services/media/MediaService.test.ts index 00555692b..1e97c4844 100644 --- a/apps/website/lib/services/media/MediaService.test.ts +++ b/apps/website/lib/services/media/MediaService.test.ts @@ -83,9 +83,9 @@ describe('MediaService', () => { const expectedOutput = { id: 'media-123', url: 'https://example.com/image.jpg', - type: 'image' as const, - category: 'avatar' as const, - uploadedAt: new Date('2023-01-15'), + type: 'image', + category: 'avatar', + uploadedAt: '2023-01-15T00:00:00.000Z', size: 2048000, }; mockApiClient.getMedia.mockResolvedValue(expectedOutput); @@ -98,7 +98,7 @@ describe('MediaService', () => { expect(result.url).toBe('https://example.com/image.jpg'); expect(result.type).toBe('image'); expect(result.category).toBe('avatar'); - expect(result.uploadedAt).toEqual(new Date('2023-01-15')); + expect(result.uploadedAt).toEqual(new Date('2023-01-15T00:00:00.000Z')); expect(result.size).toBe(2048000); expect(result.formattedSize).toBe('2000.00 KB'); }); @@ -109,8 +109,8 @@ describe('MediaService', () => { const expectedOutput = { id: 'media-456', url: 'https://example.com/video.mp4', - type: 'video' as const, - uploadedAt: new Date('2023-02-20'), + type: 'video', + uploadedAt: '2023-02-20T00:00:00.000Z', }; mockApiClient.getMedia.mockResolvedValue(expectedOutput); diff --git a/apps/website/lib/services/payments/MembershipFeeService.test.ts b/apps/website/lib/services/payments/MembershipFeeService.test.ts index bdd175ca8..598b20ebf 100644 --- a/apps/website/lib/services/payments/MembershipFeeService.test.ts +++ b/apps/website/lib/services/payments/MembershipFeeService.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, Mocked } from 'vitest'; -import { MembershipFeeService, GetMembershipFeesOutputDto } from './MembershipFeeService'; +import { MembershipFeeService } from './MembershipFeeService'; import { PaymentsApiClient } from '../../api/payments/PaymentsApiClient'; import { MembershipFeeViewModel } from '../../view-models'; import type { MembershipFeeDto } from '../../types/generated'; @@ -17,36 +17,30 @@ describe('MembershipFeeService', () => { }); describe('getMembershipFees', () => { - it('should call apiClient.getMembershipFees with correct leagueId and return mapped view models', async () => { + it('should call apiClient.getMembershipFees with correct leagueId and return fee and payments', async () => { const leagueId = 'league-123'; - const mockFees: MembershipFeeDto[] = [ - { id: 'fee-1', leagueId: 'league-123' }, - { id: 'fee-2', leagueId: 'league-123' }, - ]; - const mockOutput: GetMembershipFeesOutputDto = { fees: mockFees }; + const mockFee: MembershipFeeDto = { id: 'fee-1', leagueId: 'league-123', seasonId: undefined, type: 'season', amount: 100, enabled: true, createdAt: new Date(), updatedAt: new Date() }; + const mockPayments: any[] = []; + const mockOutput = { fee: mockFee, payments: mockPayments }; mockApiClient.getMembershipFees.mockResolvedValue(mockOutput); const result = await service.getMembershipFees(leagueId); - expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith(leagueId); - expect(result).toHaveLength(2); - expect(result[0]).toBeInstanceOf(MembershipFeeViewModel); - expect(result[0].id).toEqual('fee-1'); - expect(result[0].leagueId).toEqual('league-123'); - expect(result[1]).toBeInstanceOf(MembershipFeeViewModel); - expect(result[1].id).toEqual('fee-2'); - expect(result[1].leagueId).toEqual('league-123'); + expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith({ leagueId }); + expect(result.fee).toBeInstanceOf(MembershipFeeViewModel); + expect(result.fee!.id).toEqual('fee-1'); + expect(result.payments).toEqual([]); }); - it('should return empty array when no fees are returned', async () => { + it('should return null fee when no fee is returned', async () => { const leagueId = 'league-456'; - const mockOutput: GetMembershipFeesOutputDto = { fees: [] }; + const mockOutput = { fee: null, payments: [] }; mockApiClient.getMembershipFees.mockResolvedValue(mockOutput); const result = await service.getMembershipFees(leagueId); - expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith(leagueId); - expect(result).toEqual([]); + expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith({ leagueId }); + expect(result.fee).toBeNull(); }); }); }); \ No newline at end of file diff --git a/apps/website/lib/services/payments/MembershipFeeService.ts b/apps/website/lib/services/payments/MembershipFeeService.ts index 1e2d0a297..7eb4a3fd2 100644 --- a/apps/website/lib/services/payments/MembershipFeeService.ts +++ b/apps/website/lib/services/payments/MembershipFeeService.ts @@ -4,7 +4,8 @@ import { PaymentsApiClient } from '../../api/payments/PaymentsApiClient'; // TODO: This DTO should be generated from OpenAPI spec when the endpoint is added export interface GetMembershipFeesOutputDto { - fees: MembershipFeeDto[]; + fee: MembershipFeeDto | null; + payments: import('./MemberPaymentDto').MemberPaymentDto[]; } /** @@ -21,8 +22,11 @@ export class MembershipFeeService { /** * Get membership fees by league ID with view model transformation */ - async getMembershipFees(leagueId: string): Promise { - const dto = await this.apiClient.getMembershipFees(leagueId); - return dto.fees.map((fee: MembershipFeeDto) => new MembershipFeeViewModel(fee)); + async getMembershipFees(leagueId: string): Promise<{ fee: MembershipFeeViewModel | null; payments: any[] }> { + const dto = await this.apiClient.getMembershipFees({ leagueId }); + return { + fee: dto.fee ? new MembershipFeeViewModel(dto.fee) : null, + payments: dto.payments // TODO: map to view models if needed + }; } } \ No newline at end of file diff --git a/apps/website/lib/services/payments/PaymentService.test.ts b/apps/website/lib/services/payments/PaymentService.test.ts index c630609a0..6d7c2e71c 100644 --- a/apps/website/lib/services/payments/PaymentService.test.ts +++ b/apps/website/lib/services/payments/PaymentService.test.ts @@ -42,7 +42,7 @@ describe('PaymentService', () => { const result = await service.getPayments(); - expect(mockApiClient.getPayments).toHaveBeenCalledWith(undefined, undefined); + expect(mockApiClient.getPayments).toHaveBeenCalledWith(undefined); expect(result).toHaveLength(1); expect(result[0]).toBeInstanceOf(PaymentViewModel); expect(result[0].id).toBe('payment-1'); @@ -54,7 +54,7 @@ describe('PaymentService', () => { await service.getPayments('league-1', 'user-1'); - expect(mockApiClient.getPayments).toHaveBeenCalledWith('league-1', 'user-1'); + expect(mockApiClient.getPayments).toHaveBeenCalledWith({ leagueId: 'league-1', payerId: 'user-1' }); }); }); @@ -189,7 +189,7 @@ describe('PaymentService', () => { const result = await service.getPrizes(); - expect(mockApiClient.getPrizes).toHaveBeenCalledWith(undefined, undefined); + expect(mockApiClient.getPrizes).toHaveBeenCalledWith(undefined); expect(result).toHaveLength(1); expect(result[0]).toBeInstanceOf(PrizeViewModel); expect(result[0].id).toBe('prize-1'); @@ -201,7 +201,7 @@ describe('PaymentService', () => { await service.getPrizes('league-1', 'season-1'); - expect(mockApiClient.getPrizes).toHaveBeenCalledWith('league-1', 'season-1'); + expect(mockApiClient.getPrizes).toHaveBeenCalledWith({ leagueId: 'league-1', seasonId: 'season-1' }); }); }); @@ -232,9 +232,9 @@ describe('PaymentService', () => { mockApiClient.getWallet.mockResolvedValue(mockResponse); - const result = await service.getWallet('user-1'); + const result = await service.getWallet('league-1'); - expect(mockApiClient.getWallet).toHaveBeenCalledWith('user-1'); + expect(mockApiClient.getWallet).toHaveBeenCalledWith({ leagueId: 'league-1' }); expect(result).toBeInstanceOf(WalletViewModel); expect(result.id).toBe('wallet-1'); expect(result.balance).toBe(1000); diff --git a/apps/website/lib/services/payments/PaymentService.ts b/apps/website/lib/services/payments/PaymentService.ts index 079a50305..d71089fbb 100644 --- a/apps/website/lib/services/payments/PaymentService.ts +++ b/apps/website/lib/services/payments/PaymentService.ts @@ -31,8 +31,9 @@ export class PaymentService { /** * Get all payments with optional filters */ - async getPayments(leagueId?: string, driverId?: string): Promise { - const dto = await this.apiClient.getPayments(leagueId, driverId); + async getPayments(leagueId?: string, payerId?: string): Promise { + const query = leagueId || payerId ? { leagueId, payerId } : undefined; + const dto = await this.apiClient.getPayments(query); return dto.payments.map((payment: PaymentDTO) => new PaymentViewModel(payment)); } @@ -60,8 +61,8 @@ export class PaymentService { /** * Get membership fees for a league */ - async getMembershipFees(leagueId: string): Promise { - const dto = await this.apiClient.getMembershipFees(leagueId); + async getMembershipFees(leagueId: string, driverId?: string): Promise { + const dto = await this.apiClient.getMembershipFees({ leagueId, driverId }); return dto.fee ? new MembershipFeeViewModel(dto.fee) : null; } @@ -69,22 +70,23 @@ export class PaymentService { * Get prizes with optional filters */ async getPrizes(leagueId?: string, seasonId?: string): Promise { - const dto = await this.apiClient.getPrizes(leagueId, seasonId); + const query = leagueId || seasonId ? { leagueId, seasonId } : undefined; + const dto = await this.apiClient.getPrizes(query); return dto.prizes.map((prize: PrizeDto) => new PrizeViewModel(prize)); } /** - * Get wallet for a driver + * Get wallet for a league */ - async getWallet(driverId: string): Promise { - const dto = await this.apiClient.getWallet(driverId); + async getWallet(leagueId: string): Promise { + const dto = await this.apiClient.getWallet({ leagueId }); return new WalletViewModel({ ...dto.wallet, transactions: dto.transactions }); } /** * Get payment history for a user (driver) */ - async getPaymentHistory(driverId: string): Promise { - return await this.getPayments(undefined, driverId); + async getPaymentHistory(payerId: string): Promise { + return await this.getPayments(undefined, payerId); } } \ No newline at end of file diff --git a/apps/website/lib/services/payments/WalletService.test.ts b/apps/website/lib/services/payments/WalletService.test.ts index ea73b252c..d39ab7ef2 100644 --- a/apps/website/lib/services/payments/WalletService.test.ts +++ b/apps/website/lib/services/payments/WalletService.test.ts @@ -42,9 +42,9 @@ describe('WalletService', () => { mockApiClient.getWallet.mockResolvedValue(mockResponse); - const result = await service.getWallet('user-1'); + const result = await service.getWallet('league-1'); - expect(mockApiClient.getWallet).toHaveBeenCalledWith('user-1'); + expect(mockApiClient.getWallet).toHaveBeenCalledWith({ leagueId: 'league-1' }); expect(result).toBeInstanceOf(WalletViewModel); expect(result.id).toBe('wallet-1'); expect(result.balance).toBe(1000); @@ -69,7 +69,7 @@ describe('WalletService', () => { mockApiClient.getWallet.mockResolvedValue(mockResponse); - const result = await service.getWallet('user-1'); + const result = await service.getWallet('league-1'); expect(result.transactions).toHaveLength(0); }); diff --git a/apps/website/lib/services/races/RaceResultsService.test.ts b/apps/website/lib/services/races/RaceResultsService.test.ts index dd0785b89..eaaba13d6 100644 --- a/apps/website/lib/services/races/RaceResultsService.test.ts +++ b/apps/website/lib/services/races/RaceResultsService.test.ts @@ -102,8 +102,10 @@ describe('RaceResultsService', () => { const input = { raceId, results: [{ position: 1 }] }; const mockDto = { + success: true, raceId, - importedCount: 10, + driversProcessed: 10, + resultsRecorded: 10, errors: ['Error 1'], }; @@ -114,7 +116,8 @@ describe('RaceResultsService', () => { expect(mockApiClient.importResults).toHaveBeenCalledWith(raceId, input); expect(result).toBeInstanceOf(ImportRaceResultsSummaryViewModel); expect(result.raceId).toBe(raceId); - expect(result.importedCount).toBe(10); + expect(result.driversProcessed).toBe(10); + expect(result.resultsRecorded).toBe(10); expect(result.errors).toEqual(['Error 1']); }); @@ -123,8 +126,10 @@ describe('RaceResultsService', () => { const input = { raceId, results: [] }; const mockDto = { + success: true, raceId, - importedCount: 5, + driversProcessed: 5, + resultsRecorded: 5, errors: [], }; @@ -132,7 +137,8 @@ describe('RaceResultsService', () => { const result = await service.importResults(raceId, input); - expect(result.importedCount).toBe(5); + expect(result.driversProcessed).toBe(5); + expect(result.resultsRecorded).toBe(5); expect(result.errors).toEqual([]); }); diff --git a/apps/website/lib/services/races/RaceResultsService.ts b/apps/website/lib/services/races/RaceResultsService.ts index 1a630518c..690835abf 100644 --- a/apps/website/lib/services/races/RaceResultsService.ts +++ b/apps/website/lib/services/races/RaceResultsService.ts @@ -2,15 +2,16 @@ import { RacesApiClient } from '../../api/races/RacesApiClient'; import { RaceResultsDetailViewModel } from '../../view-models/RaceResultsDetailViewModel'; import { RaceWithSOFViewModel } from '../../view-models/RaceWithSOFViewModel'; import { ImportRaceResultsSummaryViewModel } from '../../view-models/ImportRaceResultsSummaryViewModel'; +import type { ImportRaceResultsDTO } from '../../types/generated/ImportRaceResultsDTO'; -// TODO: Move this type to apps/website/lib/types/generated when available -type ImportRaceResultsInputDto = { raceId: string; results: Array }; - -// TODO: Move this type to apps/website/lib/types/generated when available +// Define types +type ImportRaceResultsInputDto = ImportRaceResultsDTO; type ImportRaceResultsSummaryDto = { + success: boolean; raceId: string; - importedCount: number; - errors: string[]; + driversProcessed: number; + resultsRecorded: number; + errors?: string[]; }; /** @@ -41,10 +42,10 @@ export class RaceResultsService { } /** - * Import race results and get summary - */ - async importResults(raceId: string, input: ImportRaceResultsInputDto): Promise { - const dto = await this.apiClient.importResults(raceId, input) as ImportRaceResultsSummaryDto; - return new ImportRaceResultsSummaryViewModel(dto); - } + * Import race results and get summary + */ + async importResults(raceId: string, input: ImportRaceResultsInputDto): Promise { + const dto = await this.apiClient.importResults(raceId, input); + return new ImportRaceResultsSummaryViewModel(dto); + } } \ No newline at end of file diff --git a/apps/website/lib/services/sponsors/SponsorService.test.ts b/apps/website/lib/services/sponsors/SponsorService.test.ts index b363c3324..d40ed6c67 100644 --- a/apps/website/lib/services/sponsors/SponsorService.test.ts +++ b/apps/website/lib/services/sponsors/SponsorService.test.ts @@ -16,6 +16,10 @@ describe('SponsorService', () => { getSponsorships: vi.fn(), create: vi.fn(), getPricing: vi.fn(), + getSponsor: vi.fn(), + getPendingSponsorshipRequests: vi.fn(), + acceptSponsorshipRequest: vi.fn(), + rejectSponsorshipRequest: vi.fn(), } as Mocked; service = new SponsorService(mockApiClient); diff --git a/apps/website/lib/services/teams/TeamService.test.ts b/apps/website/lib/services/teams/TeamService.test.ts index e637c2068..f708853e7 100644 --- a/apps/website/lib/services/teams/TeamService.test.ts +++ b/apps/website/lib/services/teams/TeamService.test.ts @@ -29,11 +29,16 @@ describe('TeamService', () => { { id: 'team-1', name: 'Test Team', - logoUrl: 'https://example.com/logo.png', + tag: 'TT', + description: 'A test team', memberCount: 5, - rating: 1500, + leagues: ['league-1'], + specialization: 'endurance' as const, + region: 'EU', + languages: ['en'], }, ], + totalCount: 1, }; mockApiClient.getAll.mockResolvedValue(mockDto); @@ -45,6 +50,7 @@ describe('TeamService', () => { expect(result[0]).toBeInstanceOf(TeamSummaryViewModel); expect(result[0].id).toBe('team-1'); expect(result[0].name).toBe('Test Team'); + expect(result[0].tag).toBe('TT'); }); it('should throw error when apiClient.getAll fails', async () => { @@ -58,13 +64,24 @@ describe('TeamService', () => { describe('getTeamDetails', () => { it('should call apiClient.getDetails and return TeamDetailsViewModel when data exists', async () => { const mockDto = { - id: 'team-1', - name: 'Test Team', - description: 'A test team', - logoUrl: 'https://example.com/logo.png', - memberCount: 5, - ownerId: 'owner-1', - members: [], + team: { + id: 'team-1', + name: 'Test Team', + tag: 'TT', + description: 'A test team', + ownerId: 'owner-1', + leagues: ['league-1'], + createdAt: '2023-01-01T00:00:00Z', + specialization: 'endurance', + region: 'EU', + languages: ['en'], + }, + membership: { + role: 'member', + joinedAt: '2023-01-01T00:00:00Z', + isActive: true, + }, + canManage: false, }; mockApiClient.getDetails.mockResolvedValue(mockDto); @@ -75,6 +92,7 @@ describe('TeamService', () => { expect(result).toBeInstanceOf(TeamDetailsViewModel); expect(result?.id).toBe('team-1'); expect(result?.name).toBe('Test Team'); + expect(result?.tag).toBe('TT'); }); it('should return null when apiClient.getDetails returns null', async () => { @@ -100,11 +118,17 @@ describe('TeamService', () => { members: [ { driverId: 'driver-1', - driver: { id: 'driver-1', name: 'Driver One', avatarUrl: 'avatar.png', iracingId: '123', rating: 1400 }, - role: 'member', + driverName: 'Driver One', + role: 'member' as const, joinedAt: '2023-01-01T00:00:00Z', + isActive: true, + avatarUrl: 'avatar.png', }, ], + totalCount: 1, + ownerCount: 0, + managerCount: 0, + memberCount: 1, }; mockApiClient.getMembers.mockResolvedValue(mockDto); @@ -176,14 +200,35 @@ describe('TeamService', () => { describe('getDriverTeam', () => { it('should call apiClient.getDriverTeam and return the result', async () => { - const mockOutput = { teamId: 'team-1', teamName: 'Test Team', role: 'member' }; + const mockOutput = { + team: { + id: 'team-1', + name: 'Test Team', + tag: 'TT', + description: 'A test team', + ownerId: 'owner-1', + leagues: [], + specialization: 'endurance' as const, + region: 'EU', + languages: ['en'], + }, + membership: { + role: 'member', + joinedAt: '2023-01-01T00:00:00Z', + isActive: true, + }, + isOwner: false, + canManage: false, + }; mockApiClient.getDriverTeam.mockResolvedValue(mockOutput); const result = await service.getDriverTeam('driver-1'); expect(mockApiClient.getDriverTeam).toHaveBeenCalledWith('driver-1'); - expect(result).toEqual(mockOutput); + expect(result?.teamId).toBe('team-1'); + expect(result?.teamName).toBe('Test Team'); + expect(result?.role).toBe('member'); }); it('should return null when apiClient.getDriverTeam returns null', async () => { diff --git a/apps/website/lib/services/teams/TeamService.ts b/apps/website/lib/services/teams/TeamService.ts index 100fa36cd..106069978 100644 --- a/apps/website/lib/services/teams/TeamService.ts +++ b/apps/website/lib/services/teams/TeamService.ts @@ -6,16 +6,15 @@ import { UpdateTeamViewModel } from '@/lib/view-models/UpdateTeamViewModel'; import { DriverTeamViewModel } from '@/lib/view-models/DriverTeamViewModel'; import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO'; import type { TeamsApiClient } from '../../api/teams/TeamsApiClient'; - -// TODO: Move these types to apps/website/lib/types/generated when available -type DriverDTO = { id: string; name: string; avatarUrl?: string; iracingId?: string; rating?: number }; -type CreateTeamInputDto = { name: string; tag: string; description?: string }; -type CreateTeamOutputDto = { id: string; success: boolean }; -type UpdateTeamInputDto = { name?: string; tag?: string; description?: string }; -type UpdateTeamOutputDto = { success: boolean }; -type DriverTeamDto = { teamId: string; teamName: string; role: string }; -type TeamSummaryDTO = { id: string; name: string; logoUrl?: string; memberCount: number; rating: number }; -type TeamMemberDTO = { driverId: string; driver?: DriverDTO; role: string; joinedAt: string }; +import type { GetAllTeamsOutputDTO, TeamListItemDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO'; +import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO'; +import type { GetTeamMembersOutputDTO, TeamMemberDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO'; +import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO'; +import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO'; +import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO'; +import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO'; +import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO'; +import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO'; /** * Team Service @@ -32,15 +31,15 @@ export class TeamService { * Get all teams with view model transformation */ async getAllTeams(): Promise { - const dto = await this.apiClient.getAll(); - return dto.teams.map((team: TeamSummaryDTO) => new TeamSummaryViewModel(team)); + const dto: GetAllTeamsOutputDTO = await this.apiClient.getAll(); + return dto.teams.map((team: TeamListItemDTO) => new TeamSummaryViewModel(team)); } /** * Get team details with view model transformation */ async getTeamDetails(teamId: string, currentUserId: string): Promise { - const dto = await this.apiClient.getDetails(teamId); + const dto: GetTeamDetailsOutputDTO | null = await this.apiClient.getDetails(teamId); if (!dto) { return null; } @@ -51,40 +50,40 @@ export class TeamService { * Get team members with view model transformation */ async getTeamMembers(teamId: string, currentUserId: string, teamOwnerId: string): Promise { - const dto = await this.apiClient.getMembers(teamId); + const dto: GetTeamMembersOutputDTO = await this.apiClient.getMembers(teamId); return dto.members.map((member: TeamMemberDTO) => new TeamMemberViewModel(member, currentUserId, teamOwnerId)); } /** * Create a new team with view model transformation */ - async createTeam(input: CreateTeamInputDto): Promise { - const dto = await this.apiClient.create(input); + async createTeam(input: CreateTeamInputDTO): Promise { + const dto: CreateTeamOutputDTO = await this.apiClient.create(input); return new CreateTeamViewModel(dto); } /** * Update team with view model transformation */ - async updateTeam(teamId: string, input: UpdateTeamInputDto): Promise { - const dto = await this.apiClient.update(teamId, input); + async updateTeam(teamId: string, input: UpdateTeamInputDTO): Promise { + const dto: UpdateTeamOutputDTO = await this.apiClient.update(teamId, input); return new UpdateTeamViewModel(dto); } /** - * Get driver's team with view model transformation - */ - async getDriverTeam(driverId: string): Promise { - const dto = await this.apiClient.getDriverTeam(driverId); - return dto ? new DriverTeamViewModel(dto) : null; - } + * Get driver's team with view model transformation + */ + async getDriverTeam(driverId: string): Promise { + const dto: GetDriverTeamOutputDTO | null = await this.apiClient.getDriverTeam(driverId); + return dto ? new DriverTeamViewModel(dto) : null; + } - /** - * Get team membership for a driver - */ - async getMembership(teamId: string, driverId: string): Promise { - return this.apiClient.getMembership(teamId, driverId); - } + /** + * Get team membership for a driver + */ + async getMembership(teamId: string, driverId: string): Promise { + return this.apiClient.getMembership(teamId, driverId); + } /** * Remove a driver from the team diff --git a/apps/website/lib/types/CreateLeagueInputDTO.ts b/apps/website/lib/types/CreateLeagueInputDTO.ts deleted file mode 100644 index 661064e79..000000000 --- a/apps/website/lib/types/CreateLeagueInputDTO.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface CreateLeagueInputDTO { - name: string; - description: string; - isPublic: boolean; - maxMembers: number; - ownerId: string; -} \ No newline at end of file diff --git a/apps/website/lib/types/CreateLeagueResult.ts b/apps/website/lib/types/CreateLeagueResult.ts deleted file mode 100644 index d54bba56b..000000000 --- a/apps/website/lib/types/CreateLeagueResult.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface CreateLeagueResult { - leagueId: string; - seasonId?: string; - success: boolean; -} \ No newline at end of file diff --git a/apps/website/lib/types/LeagueConfigFormModel.ts b/apps/website/lib/types/LeagueConfigFormModel.ts deleted file mode 100644 index 56eaa97d2..000000000 --- a/apps/website/lib/types/LeagueConfigFormModel.ts +++ /dev/null @@ -1,50 +0,0 @@ -export interface LeagueConfigFormModel { - leagueId?: string; - basics: { - name: string; - description?: string; - visibility: 'public' | 'private' | 'unlisted'; - gameId: string; - }; - structure: { - mode: 'solo' | 'fixedTeams'; - maxDrivers?: number; - maxTeams?: number; - driversPerTeam?: number; - }; - championships: { - enableDriverChampionship: boolean; - enableTeamChampionship: boolean; - enableNationsChampionship: boolean; - enableTrophyChampionship: boolean; - }; - scoring: { - patternId?: string; - customScoringEnabled?: boolean; - }; - dropPolicy: { - strategy: 'none' | 'bestNResults' | 'dropWorstN'; - n?: number; - }; - timings: { - practiceMinutes?: number; - qualifyingMinutes?: number; - sprintRaceMinutes?: number; - mainRaceMinutes?: number; - sessionCount?: number; - roundsPlanned?: number; - raceDayOfWeek?: number; - raceTimeUtc?: string; - }; - stewarding: { - decisionMode: 'owner_only' | 'admin_vote' | 'steward_panel'; - requiredVotes?: number; - requireDefense: boolean; - defenseTimeLimit: number; - voteTimeLimit: number; - protestDeadlineHours: number; - stewardingClosesHours: number; - notifyAccusedOnProtest: boolean; - notifyOnVoteRequired: boolean; - }; -} \ No newline at end of file diff --git a/apps/website/lib/types/LeagueMembership.ts b/apps/website/lib/types/LeagueMembership.ts deleted file mode 100644 index be1044173..000000000 --- a/apps/website/lib/types/LeagueMembership.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MembershipRole } from './MembershipRole'; -import { MembershipStatus } from './MembershipStatus'; - -/** - * Lightweight league membership model for UI. - */ -export interface LeagueMembership { - id: string; - leagueId: string; - driverId: string; - role: MembershipRole; - status: MembershipStatus; - joinedAt: string; -} \ No newline at end of file diff --git a/apps/website/lib/types/LeagueRole.ts b/apps/website/lib/types/LeagueRole.ts deleted file mode 100644 index 080035cee..000000000 --- a/apps/website/lib/types/LeagueRole.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { MembershipRole } from './MembershipRole'; - -export type LeagueRole = MembershipRole; \ No newline at end of file diff --git a/apps/website/lib/types/LeagueScoringPresetDTO.ts b/apps/website/lib/types/LeagueScoringPresetDTO.ts deleted file mode 100644 index c0faea850..000000000 --- a/apps/website/lib/types/LeagueScoringPresetDTO.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type LeagueScoringPresetPrimaryChampionshipType = - | 'driver' - | 'team' - | 'nations' - | 'trophy'; - -export interface LeagueScoringPresetDTO { - id: string; - name: string; - description: string; - primaryChampionshipType: LeagueScoringPresetPrimaryChampionshipType; - sessionSummary: string; - bonusSummary: string; - dropPolicySummary: string; -} \ No newline at end of file diff --git a/apps/website/lib/types/MembershipRole.ts b/apps/website/lib/types/MembershipRole.ts deleted file mode 100644 index 3405cace0..000000000 --- a/apps/website/lib/types/MembershipRole.ts +++ /dev/null @@ -1 +0,0 @@ -export type MembershipRole = 'owner' | 'admin' | 'steward' | 'member'; \ No newline at end of file diff --git a/apps/website/lib/types/MembershipStatus.ts b/apps/website/lib/types/MembershipStatus.ts deleted file mode 100644 index a52b2a275..000000000 --- a/apps/website/lib/types/MembershipStatus.ts +++ /dev/null @@ -1 +0,0 @@ -export type MembershipStatus = 'active' | 'inactive' | 'pending'; \ No newline at end of file diff --git a/apps/website/lib/types/WizardErrors.ts b/apps/website/lib/types/WizardErrors.ts deleted file mode 100644 index 6eeab744a..000000000 --- a/apps/website/lib/types/WizardErrors.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface WizardErrors { - basics?: { - name?: string; - description?: string; - visibility?: string; - }; - structure?: { - maxDrivers?: string; - maxTeams?: string; - driversPerTeam?: string; - }; - timings?: { - qualifyingMinutes?: string; - mainRaceMinutes?: string; - roundsPlanned?: string; - }; - scoring?: { - patternId?: string; - }; - submit?: string; -} \ No newline at end of file diff --git a/apps/website/lib/types/WizardStep.ts b/apps/website/lib/types/WizardStep.ts deleted file mode 100644 index 027842b4c..000000000 --- a/apps/website/lib/types/WizardStep.ts +++ /dev/null @@ -1 +0,0 @@ -export type WizardStep = 1 | 2 | 3 | 4 | 5 | 6 | 7; \ No newline at end of file diff --git a/apps/website/lib/types/generated/AcceptSponsorshipRequestInputDTO.ts b/apps/website/lib/types/generated/AcceptSponsorshipRequestInputDTO.ts new file mode 100644 index 000000000..7ee338b02 --- /dev/null +++ b/apps/website/lib/types/generated/AcceptSponsorshipRequestInputDTO.ts @@ -0,0 +1,9 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface AcceptSponsorshipRequestInputDTO { + respondedBy: string; +} diff --git a/apps/website/lib/types/generated/ApplyPenaltyCommandDTO.ts b/apps/website/lib/types/generated/ApplyPenaltyCommandDTO.ts index 2fa2f5aae..0d03cc70f 100644 --- a/apps/website/lib/types/generated/ApplyPenaltyCommandDTO.ts +++ b/apps/website/lib/types/generated/ApplyPenaltyCommandDTO.ts @@ -8,9 +8,5 @@ export interface ApplyPenaltyCommandDTO { raceId: string; driverId: string; stewardId: string; - type: string; - value?: number; - reason: string; - protestId?: string; - notes?: string; + enum: string; } diff --git a/apps/website/lib/types/generated/CreateTeamInputDTO.ts b/apps/website/lib/types/generated/CreateTeamInputDTO.ts new file mode 100644 index 000000000..bed9c32d3 --- /dev/null +++ b/apps/website/lib/types/generated/CreateTeamInputDTO.ts @@ -0,0 +1,10 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface CreateTeamInputDTO { + name: string; + tag: string; +} diff --git a/apps/website/lib/types/generated/CreateTeamOutputDTO.ts b/apps/website/lib/types/generated/CreateTeamOutputDTO.ts new file mode 100644 index 000000000..d6e3178d5 --- /dev/null +++ b/apps/website/lib/types/generated/CreateTeamOutputDTO.ts @@ -0,0 +1,10 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface CreateTeamOutputDTO { + id: string; + success: boolean; +} diff --git a/apps/website/lib/types/generated/DeleteMediaOutputDTO.ts b/apps/website/lib/types/generated/DeleteMediaOutputDTO.ts new file mode 100644 index 000000000..d3294f301 --- /dev/null +++ b/apps/website/lib/types/generated/DeleteMediaOutputDTO.ts @@ -0,0 +1,9 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface DeleteMediaOutputDTO { + success: boolean; +} diff --git a/apps/website/lib/types/DriverDTO.ts b/apps/website/lib/types/generated/DriverDTO.ts similarity index 78% rename from apps/website/lib/types/DriverDTO.ts rename to apps/website/lib/types/generated/DriverDTO.ts index 3dd2c6f92..2d5860fa4 100644 --- a/apps/website/lib/types/DriverDTO.ts +++ b/apps/website/lib/types/generated/DriverDTO.ts @@ -6,8 +6,7 @@ export interface DriverDTO { id: string; + iracingId: string; name: string; - avatarUrl?: string; - iracingId?: string; - rating?: number; -} \ No newline at end of file + country: string; +} diff --git a/apps/website/lib/types/generated/DriverProfileAchievementDTO.ts b/apps/website/lib/types/generated/DriverProfileAchievementDTO.ts new file mode 100644 index 000000000..0840912e2 --- /dev/null +++ b/apps/website/lib/types/generated/DriverProfileAchievementDTO.ts @@ -0,0 +1,11 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface DriverProfileAchievementDTO { + id: string; + title: string; + description: string; +} diff --git a/apps/website/lib/types/RaceDetailEntryDTO.ts b/apps/website/lib/types/generated/DriverProfileDriverSummaryDTO.ts similarity index 73% rename from apps/website/lib/types/RaceDetailEntryDTO.ts rename to apps/website/lib/types/generated/DriverProfileDriverSummaryDTO.ts index 97afdb019..b3b9a880c 100644 --- a/apps/website/lib/types/RaceDetailEntryDTO.ts +++ b/apps/website/lib/types/generated/DriverProfileDriverSummaryDTO.ts @@ -4,11 +4,9 @@ * Do not edit manually - regenerate using: npm run api:sync-types */ -export interface RaceDetailEntryDTO { +export interface DriverProfileDriverSummaryDTO { id: string; name: string; country: string; avatarUrl: string; - rating: number | null; - isCurrentUser: boolean; } diff --git a/apps/website/lib/types/generated/DriverProfileFinishDistributionDTO.ts b/apps/website/lib/types/generated/DriverProfileFinishDistributionDTO.ts new file mode 100644 index 000000000..d65d13723 --- /dev/null +++ b/apps/website/lib/types/generated/DriverProfileFinishDistributionDTO.ts @@ -0,0 +1,14 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface DriverProfileFinishDistributionDTO { + totalRaces: number; + wins: number; + podiums: number; + topTen: number; + dnfs: number; + other: number; +} diff --git a/apps/website/lib/types/generated/DriverProfileSocialFriendSummaryDTO.ts b/apps/website/lib/types/generated/DriverProfileSocialFriendSummaryDTO.ts new file mode 100644 index 000000000..93277a222 --- /dev/null +++ b/apps/website/lib/types/generated/DriverProfileSocialFriendSummaryDTO.ts @@ -0,0 +1,12 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface DriverProfileSocialFriendSummaryDTO { + id: string; + name: string; + country: string; + avatarUrl: string; +} diff --git a/apps/website/lib/types/generated/DriverProfileSocialSummaryDTO.ts b/apps/website/lib/types/generated/DriverProfileSocialSummaryDTO.ts new file mode 100644 index 000000000..a52156e1f --- /dev/null +++ b/apps/website/lib/types/generated/DriverProfileSocialSummaryDTO.ts @@ -0,0 +1,9 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface DriverProfileSocialSummaryDTO { + friendsCount: number; +} diff --git a/apps/website/lib/types/generated/DriverProfileStatsDTO.ts b/apps/website/lib/types/generated/DriverProfileStatsDTO.ts new file mode 100644 index 000000000..357fea571 --- /dev/null +++ b/apps/website/lib/types/generated/DriverProfileStatsDTO.ts @@ -0,0 +1,12 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface DriverProfileStatsDTO { + totalRaces: number; + wins: number; + podiums: number; + dnfs: number; +} diff --git a/apps/website/lib/types/generated/DriverProfileTeamMembershipDTO.ts b/apps/website/lib/types/generated/DriverProfileTeamMembershipDTO.ts new file mode 100644 index 000000000..bed1b824d --- /dev/null +++ b/apps/website/lib/types/generated/DriverProfileTeamMembershipDTO.ts @@ -0,0 +1,10 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface DriverProfileTeamMembershipDTO { + teamId: string; + teamName: string; +} diff --git a/apps/website/lib/types/generated/GetAllTeamsOutputDTO.ts b/apps/website/lib/types/generated/GetAllTeamsOutputDTO.ts new file mode 100644 index 000000000..9ea45faba --- /dev/null +++ b/apps/website/lib/types/generated/GetAllTeamsOutputDTO.ts @@ -0,0 +1,22 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface TeamListItemDTO { + id: string; + name: string; + tag: string; + description: string; + memberCount: number; + leagues: string[]; + specialization?: 'endurance' | 'sprint' | 'mixed'; + region?: string; + languages?: string[]; +} + +export interface GetAllTeamsOutputDTO { + teams: TeamListItemDTO[]; + totalCount: number; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/GetAnalyticsMetricsOutputDTO.ts b/apps/website/lib/types/generated/GetAnalyticsMetricsOutputDTO.ts new file mode 100644 index 000000000..89464e655 --- /dev/null +++ b/apps/website/lib/types/generated/GetAnalyticsMetricsOutputDTO.ts @@ -0,0 +1,12 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface GetAnalyticsMetricsOutputDTO { + pageViews: number; + uniqueVisitors: number; + averageSessionDuration: number; + bounceRate: number; +} diff --git a/apps/website/lib/types/GetAvatarOutputDto.ts b/apps/website/lib/types/generated/GetAvatarOutputDTO.ts similarity index 83% rename from apps/website/lib/types/GetAvatarOutputDto.ts rename to apps/website/lib/types/generated/GetAvatarOutputDTO.ts index b1666ab1a..448bd1825 100644 --- a/apps/website/lib/types/GetAvatarOutputDto.ts +++ b/apps/website/lib/types/generated/GetAvatarOutputDTO.ts @@ -4,6 +4,6 @@ * Do not edit manually - regenerate using: npm run api:sync-types */ -export interface GetAvatarOutputDto { +export interface GetAvatarOutputDTO { avatarUrl: string; -} \ No newline at end of file +} diff --git a/apps/website/lib/types/generated/GetDashboardDataOutputDTO.ts b/apps/website/lib/types/generated/GetDashboardDataOutputDTO.ts new file mode 100644 index 000000000..76759886e --- /dev/null +++ b/apps/website/lib/types/generated/GetDashboardDataOutputDTO.ts @@ -0,0 +1,12 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface GetDashboardDataOutputDTO { + totalUsers: number; + activeUsers: number; + totalRaces: number; + totalLeagues: number; +} diff --git a/apps/website/lib/types/generated/GetDriverOutputDTO.ts b/apps/website/lib/types/generated/GetDriverOutputDTO.ts new file mode 100644 index 000000000..1192a319c --- /dev/null +++ b/apps/website/lib/types/generated/GetDriverOutputDTO.ts @@ -0,0 +1,14 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface GetDriverOutputDTO { + id: string; + iracingId: string; + name: string; + country: string; + bio?: string; + joinedAt: string; +} diff --git a/apps/website/lib/types/generated/GetDriverTeamOutputDTO.ts b/apps/website/lib/types/generated/GetDriverTeamOutputDTO.ts new file mode 100644 index 000000000..e4de6ca6e --- /dev/null +++ b/apps/website/lib/types/generated/GetDriverTeamOutputDTO.ts @@ -0,0 +1,31 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface TeamDTO { + id: string; + name: string; + tag: string; + description: string; + ownerId: string; + leagues: string[]; + createdAt?: string; + specialization?: 'endurance' | 'sprint' | 'mixed'; + region?: string; + languages?: string[]; +} + +export interface MembershipDTO { + role: 'owner' | 'manager' | 'member'; + joinedAt: string; + isActive: boolean; +} + +export interface GetDriverTeamOutputDTO { + team: TeamDTO; + membership: MembershipDTO; + isOwner: boolean; + canManage: boolean; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/GetMediaOutputDTO.ts b/apps/website/lib/types/generated/GetMediaOutputDTO.ts new file mode 100644 index 000000000..37bfa441e --- /dev/null +++ b/apps/website/lib/types/generated/GetMediaOutputDTO.ts @@ -0,0 +1,15 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface GetMediaOutputDTO { + id: string; + url: string; + type: string; + category?: string; + /** Format: date-time */ + uploadedAt: string; + size?: number; +} diff --git a/apps/website/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO.ts b/apps/website/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO.ts new file mode 100644 index 000000000..366536ac8 --- /dev/null +++ b/apps/website/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO.ts @@ -0,0 +1,10 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface GetPendingSponsorshipRequestsOutputDTO { + entityType: string; + entityId: string; +} diff --git a/apps/website/lib/types/generated/GetSponsorOutputDTO.ts b/apps/website/lib/types/generated/GetSponsorOutputDTO.ts new file mode 100644 index 000000000..cffd5c53b --- /dev/null +++ b/apps/website/lib/types/generated/GetSponsorOutputDTO.ts @@ -0,0 +1,8 @@ +export interface GetSponsorOutputDTO { + sponsor: { + id: string; + name: string; + logoUrl?: string; + websiteUrl?: string; + }; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/GetTeamDetailsOutputDTO.ts b/apps/website/lib/types/generated/GetTeamDetailsOutputDTO.ts new file mode 100644 index 000000000..9e92a0c53 --- /dev/null +++ b/apps/website/lib/types/generated/GetTeamDetailsOutputDTO.ts @@ -0,0 +1,30 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface TeamDTO { + id: string; + name: string; + tag: string; + description: string; + ownerId: string; + leagues: string[]; + createdAt?: string; + specialization?: 'endurance' | 'sprint' | 'mixed'; + region?: string; + languages?: string[]; +} + +export interface MembershipDTO { + role: 'owner' | 'manager' | 'member'; + joinedAt: string; + isActive: boolean; +} + +export interface GetTeamDetailsOutputDTO { + team: TeamDTO; + membership: MembershipDTO | null; + canManage: boolean; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/GetTeamJoinRequestsOutputDTO.ts b/apps/website/lib/types/generated/GetTeamJoinRequestsOutputDTO.ts new file mode 100644 index 000000000..6507e0e23 --- /dev/null +++ b/apps/website/lib/types/generated/GetTeamJoinRequestsOutputDTO.ts @@ -0,0 +1,21 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface TeamJoinRequestDTO { + requestId: string; + driverId: string; + driverName: string; + teamId: string; + status: 'pending' | 'approved' | 'rejected'; + requestedAt: string; + avatarUrl: string; +} + +export interface GetTeamJoinRequestsOutputDTO { + requests: TeamJoinRequestDTO[]; + pendingCount: number; + totalCount: number; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/GetTeamMembersOutputDTO.ts b/apps/website/lib/types/generated/GetTeamMembersOutputDTO.ts new file mode 100644 index 000000000..3f026cd32 --- /dev/null +++ b/apps/website/lib/types/generated/GetTeamMembersOutputDTO.ts @@ -0,0 +1,22 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface TeamMemberDTO { + driverId: string; + driverName: string; + role: 'owner' | 'manager' | 'member'; + joinedAt: string; + isActive: boolean; + avatarUrl: string; +} + +export interface GetTeamMembersOutputDTO { + members: TeamMemberDTO[]; + totalCount: number; + ownerCount: number; + managerCount: number; + memberCount: number; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/GetTeamMembershipOutputDTO.ts b/apps/website/lib/types/generated/GetTeamMembershipOutputDTO.ts new file mode 100644 index 000000000..2c2dd0bd8 --- /dev/null +++ b/apps/website/lib/types/generated/GetTeamMembershipOutputDTO.ts @@ -0,0 +1,11 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface GetTeamMembershipOutputDTO { + role: string; + joinedAt: string; + isActive: boolean; +} diff --git a/apps/website/lib/types/generated/IracingAuthRedirectResult.ts b/apps/website/lib/types/generated/IracingAuthRedirectResult.ts new file mode 100644 index 000000000..26b0d5897 --- /dev/null +++ b/apps/website/lib/types/generated/IracingAuthRedirectResult.ts @@ -0,0 +1,10 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface IracingAuthRedirectResult { + redirectUrl: string; + state: string; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/LeagueConfigFormModelDTO.ts b/apps/website/lib/types/generated/LeagueConfigFormModelDTO.ts index 74e172f74..322512cdc 100644 --- a/apps/website/lib/types/generated/LeagueConfigFormModelDTO.ts +++ b/apps/website/lib/types/generated/LeagueConfigFormModelDTO.ts @@ -4,6 +4,20 @@ * Do not edit manually - regenerate using: npm run api:sync-types */ +import type { LeagueConfigFormModelBasicsDTO } from './LeagueConfigFormModelBasicsDTO'; +import type { LeagueConfigFormModelStructureDTO } from './LeagueConfigFormModelStructureDTO'; +import type { LeagueConfigFormModelScoringDTO } from './LeagueConfigFormModelScoringDTO'; +import type { LeagueConfigFormModelDropPolicyDTO } from './LeagueConfigFormModelDropPolicyDTO'; +import type { LeagueConfigFormModelTimingsDTO } from './LeagueConfigFormModelTimingsDTO'; +import type { LeagueConfigFormModelStewardingDTO } from './LeagueConfigFormModelStewardingDTO'; + export interface LeagueConfigFormModelDTO { leagueId: string; + basics: LeagueConfigFormModelBasicsDTO; + structure: LeagueConfigFormModelStructureDTO; + championships: any[]; + scoring: LeagueConfigFormModelScoringDTO; + dropPolicy: LeagueConfigFormModelDropPolicyDTO; + timings: LeagueConfigFormModelTimingsDTO; + stewarding: LeagueConfigFormModelStewardingDTO; } diff --git a/apps/website/lib/types/generated/LoginParams.ts b/apps/website/lib/types/generated/LoginParams.ts new file mode 100644 index 000000000..aa7f438d9 --- /dev/null +++ b/apps/website/lib/types/generated/LoginParams.ts @@ -0,0 +1,10 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface LoginParams { + email: string; + password: string; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/LoginWithIracingCallbackParams.ts b/apps/website/lib/types/generated/LoginWithIracingCallbackParams.ts new file mode 100644 index 000000000..da801dfa2 --- /dev/null +++ b/apps/website/lib/types/generated/LoginWithIracingCallbackParams.ts @@ -0,0 +1,11 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface LoginWithIracingCallbackParams { + code: string; + state: string; + returnTo?: string; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/RacePenaltiesDTO.ts b/apps/website/lib/types/generated/RacePenaltiesDTO.ts new file mode 100644 index 000000000..926a7c978 --- /dev/null +++ b/apps/website/lib/types/generated/RacePenaltiesDTO.ts @@ -0,0 +1,12 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +import { RacePenaltyDTO } from './RacePenaltyDTO'; + +export interface RacePenaltiesDTO { + penalties: RacePenaltyDTO[]; + driverMap: Record; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/RecordEngagementInputDTO.ts b/apps/website/lib/types/generated/RecordEngagementInputDTO.ts new file mode 100644 index 000000000..011f0ad1f --- /dev/null +++ b/apps/website/lib/types/generated/RecordEngagementInputDTO.ts @@ -0,0 +1,5 @@ +export interface RecordEngagementInputDTO { + eventType: string; + userId?: string; + metadata?: Record; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/RecordPageViewInputDTO.ts b/apps/website/lib/types/generated/RecordPageViewInputDTO.ts new file mode 100644 index 000000000..141bdf30a --- /dev/null +++ b/apps/website/lib/types/generated/RecordPageViewInputDTO.ts @@ -0,0 +1,4 @@ +export interface RecordPageViewInputDTO { + path: string; + userId?: string; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/RejectSponsorshipRequestInputDTO.ts b/apps/website/lib/types/generated/RejectSponsorshipRequestInputDTO.ts new file mode 100644 index 000000000..b15cd433a --- /dev/null +++ b/apps/website/lib/types/generated/RejectSponsorshipRequestInputDTO.ts @@ -0,0 +1,9 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface RejectSponsorshipRequestInputDTO { + respondedBy: string; +} diff --git a/apps/website/lib/types/generated/ReviewProtestCommandDTO.ts b/apps/website/lib/types/generated/ReviewProtestCommandDTO.ts new file mode 100644 index 000000000..693fd2d32 --- /dev/null +++ b/apps/website/lib/types/generated/ReviewProtestCommandDTO.ts @@ -0,0 +1,11 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface ReviewProtestCommandDTO { + protestId: string; + stewardId: string; + enum: string; +} diff --git a/apps/website/lib/types/generated/SignupParams.ts b/apps/website/lib/types/generated/SignupParams.ts new file mode 100644 index 000000000..0eb63ffd9 --- /dev/null +++ b/apps/website/lib/types/generated/SignupParams.ts @@ -0,0 +1,14 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface SignupParams { + email: string; + password: string; + displayName: string; + iracingCustomerId?: string; + primaryDriverId?: string; + avatarUrl?: string; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/SponsorDTO.ts b/apps/website/lib/types/generated/SponsorDTO.ts new file mode 100644 index 000000000..0113fff90 --- /dev/null +++ b/apps/website/lib/types/generated/SponsorDTO.ts @@ -0,0 +1,10 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface SponsorDTO { + id: string; + name: string; +} diff --git a/apps/website/lib/types/generated/SponsorshipRequestDTO.ts b/apps/website/lib/types/generated/SponsorshipRequestDTO.ts new file mode 100644 index 000000000..04c999b18 --- /dev/null +++ b/apps/website/lib/types/generated/SponsorshipRequestDTO.ts @@ -0,0 +1,11 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface SponsorshipRequestDTO { + id: string; + sponsorId: string; + sponsorName: string; +} diff --git a/apps/website/lib/types/generated/UpdateAvatarInputDTO.ts b/apps/website/lib/types/generated/UpdateAvatarInputDTO.ts new file mode 100644 index 000000000..ef3839a88 --- /dev/null +++ b/apps/website/lib/types/generated/UpdateAvatarInputDTO.ts @@ -0,0 +1,10 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface UpdateAvatarInputDTO { + driverId: string; + avatarUrl: string; +} diff --git a/apps/website/lib/types/generated/UpdateAvatarOutputDTO.ts b/apps/website/lib/types/generated/UpdateAvatarOutputDTO.ts new file mode 100644 index 000000000..0dad0aeaa --- /dev/null +++ b/apps/website/lib/types/generated/UpdateAvatarOutputDTO.ts @@ -0,0 +1,9 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface UpdateAvatarOutputDTO { + success: boolean; +} diff --git a/apps/website/lib/types/generated/UpdateTeamInputDTO.ts b/apps/website/lib/types/generated/UpdateTeamInputDTO.ts new file mode 100644 index 000000000..d74ff0748 --- /dev/null +++ b/apps/website/lib/types/generated/UpdateTeamInputDTO.ts @@ -0,0 +1,11 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface UpdateTeamInputDTO { + name?: string; + tag?: string; + description?: string; +} \ No newline at end of file diff --git a/apps/website/lib/types/generated/UpdateTeamOutputDTO.ts b/apps/website/lib/types/generated/UpdateTeamOutputDTO.ts new file mode 100644 index 000000000..0149012ae --- /dev/null +++ b/apps/website/lib/types/generated/UpdateTeamOutputDTO.ts @@ -0,0 +1,9 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface UpdateTeamOutputDTO { + success: boolean; +} diff --git a/apps/website/lib/types/generated/UploadMediaOutputDTO.ts b/apps/website/lib/types/generated/UploadMediaOutputDTO.ts new file mode 100644 index 000000000..95844afca --- /dev/null +++ b/apps/website/lib/types/generated/UploadMediaOutputDTO.ts @@ -0,0 +1,9 @@ +/** + * Auto-generated DTO from OpenAPI spec + * This file is generated by scripts/generate-api-types.ts + * Do not edit manually - regenerate using: npm run api:sync-types + */ + +export interface UploadMediaOutputDTO { + success: boolean; +} diff --git a/apps/website/lib/utilities/LeagueMembershipUtility.ts b/apps/website/lib/utilities/LeagueMembershipUtility.ts index bf85b1918..80d1f283a 100644 --- a/apps/website/lib/utilities/LeagueMembershipUtility.ts +++ b/apps/website/lib/utilities/LeagueMembershipUtility.ts @@ -1,5 +1,7 @@ import { LeagueMembershipService } from '@/lib/services/leagues/LeagueMembershipService'; import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility'; +import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole'; +type LeagueRole = MembershipRole; export class LeagueMembershipUtility { /** diff --git a/apps/website/lib/utilities/LeagueRoleUtility.ts b/apps/website/lib/utilities/LeagueRoleUtility.ts index e8ec10504..ec3ab5a06 100644 --- a/apps/website/lib/utilities/LeagueRoleUtility.ts +++ b/apps/website/lib/utilities/LeagueRoleUtility.ts @@ -1,4 +1,5 @@ -import { LeagueRole } from '@/lib/types/LeagueRole'; +import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole'; +type LeagueRole = MembershipRole; export class LeagueRoleUtility { static isLeagueOwnerRole(role: LeagueRole): boolean { diff --git a/apps/website/lib/view-models/DriverSummaryViewModel.ts b/apps/website/lib/view-models/DriverSummaryViewModel.ts index c95902817..737188625 100644 --- a/apps/website/lib/view-models/DriverSummaryViewModel.ts +++ b/apps/website/lib/view-models/DriverSummaryViewModel.ts @@ -1,16 +1,16 @@ -import type { DriverDTO } from '../types/DriverDTO'; +import type { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO'; /** * View Model for driver summary with rating and rank * Transform from DTO to ViewModel with UI fields */ export class DriverSummaryViewModel { - driver: DriverDTO; + driver: GetDriverOutputDTO; rating: number | null; rank: number | null; constructor(dto: { - driver: DriverDTO; + driver: GetDriverOutputDTO; rating?: number | null; rank?: number | null; }) { diff --git a/apps/website/lib/view-models/DriverTeamViewModel.ts b/apps/website/lib/view-models/DriverTeamViewModel.ts index efaa35c5b..2ffbcf463 100644 --- a/apps/website/lib/view-models/DriverTeamViewModel.ts +++ b/apps/website/lib/view-models/DriverTeamViewModel.ts @@ -1,3 +1,5 @@ +import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO'; + /** * View Model for Driver's Team * @@ -6,21 +8,22 @@ export class DriverTeamViewModel { teamId: string; teamName: string; + tag: string; role: string; + isOwner: boolean; + canManage: boolean; - constructor(dto: { teamId: string; teamName: string; role: string }) { - this.teamId = dto.teamId; - this.teamName = dto.teamName; - this.role = dto.role; + constructor(dto: GetDriverTeamOutputDTO) { + this.teamId = dto.team.id; + this.teamName = dto.team.name; + this.tag = dto.team.tag; + this.role = dto.membership.role; + this.isOwner = dto.isOwner; + this.canManage = dto.canManage; } /** UI-specific: Display role */ get displayRole(): string { return this.role.charAt(0).toUpperCase() + this.role.slice(1); } - - /** UI-specific: Is owner */ - get isOwner(): boolean { - return this.role === 'owner'; - } } \ No newline at end of file diff --git a/apps/website/lib/view-models/ImportRaceResultsSummaryViewModel.ts b/apps/website/lib/view-models/ImportRaceResultsSummaryViewModel.ts index 2f94a3729..6e2a87258 100644 --- a/apps/website/lib/view-models/ImportRaceResultsSummaryViewModel.ts +++ b/apps/website/lib/view-models/ImportRaceResultsSummaryViewModel.ts @@ -1,20 +1,23 @@ -// TODO: Create ImportRaceResultsSummaryDTO in apps/website/lib/types/generated when available interface ImportRaceResultsSummaryDTO { + success: boolean; raceId: string; - importedCount: number; - errors: string[]; + driversProcessed: number; + resultsRecorded: number; + errors?: string[]; } export class ImportRaceResultsSummaryViewModel { + success: boolean; raceId: string; - importedCount: number; + driversProcessed: number; + resultsRecorded: number; errors: string[]; constructor(dto: ImportRaceResultsSummaryDTO) { + this.success = dto.success; this.raceId = dto.raceId; - this.importedCount = dto.importedCount; - this.errors = dto.errors; + this.driversProcessed = dto.driversProcessed; + this.resultsRecorded = dto.resultsRecorded; + this.errors = dto.errors || []; } - - // TODO: Add additional UI-specific fields when DTO is available } \ No newline at end of file diff --git a/apps/website/lib/view-models/LeagueDetailPageViewModel.ts b/apps/website/lib/view-models/LeagueDetailPageViewModel.ts index 6ddd23f5c..52d2261ed 100644 --- a/apps/website/lib/view-models/LeagueDetailPageViewModel.ts +++ b/apps/website/lib/view-models/LeagueDetailPageViewModel.ts @@ -3,7 +3,7 @@ import { LeagueStatsDTO } from '../types/generated/LeagueStatsDTO'; import { LeagueMembershipsDTO } from '../types/generated/LeagueMembershipsDTO'; import { LeagueScheduleDTO } from '../types/generated/LeagueScheduleDTO'; import { LeagueStandingsDTO } from '../types/generated/LeagueStandingsDTO'; -import { DriverDTO } from '../types/DriverDTO'; +import { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO'; import { RaceDTO } from '../types/generated/RaceDTO'; import { LeagueScoringConfigDTO } from '../types/LeagueScoringConfigDTO'; import { RaceViewModel } from './RaceViewModel'; @@ -20,7 +20,7 @@ export interface SponsorInfo { // Driver summary for management section export interface DriverSummary { - driver: DriverDTO; + driver: GetDriverOutputDTO; rating: number | null; rank: number | null; } @@ -50,13 +50,13 @@ export class LeagueDetailPageViewModel { }; // Owner info - owner: DriverDTO | null; + owner: GetDriverOutputDTO | null; // Scoring configuration scoringConfig: LeagueScoringConfigDTO | null; // Drivers and memberships - drivers: DriverDTO[]; + drivers: GetDriverOutputDTO[]; memberships: LeagueMembershipWithRole[]; // Races @@ -93,9 +93,9 @@ export class LeagueDetailPageViewModel { constructor( league: LeagueWithCapacityDTO, - owner: DriverDTO | null, + owner: GetDriverOutputDTO | null, scoringConfig: LeagueScoringConfigDTO | null, - drivers: DriverDTO[], + drivers: GetDriverOutputDTO[], memberships: LeagueMembershipsDTO, allRaces: RaceViewModel[], leagueStats: LeagueStatsDTO, diff --git a/apps/website/lib/view-models/LeagueScoringPresetsViewModel.ts b/apps/website/lib/view-models/LeagueScoringPresetsViewModel.ts index 6bcd72047..ee391e13d 100644 --- a/apps/website/lib/view-models/LeagueScoringPresetsViewModel.ts +++ b/apps/website/lib/view-models/LeagueScoringPresetsViewModel.ts @@ -1,4 +1,4 @@ -import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO'; +import type { LeagueScoringPresetDTO } from '@core/racing/application/ports/LeagueScoringPresetProvider'; /** * View Model for league scoring presets diff --git a/apps/website/lib/view-models/LeagueSettingsViewModel.ts b/apps/website/lib/view-models/LeagueSettingsViewModel.ts index d74d19be6..3021c0682 100644 --- a/apps/website/lib/view-models/LeagueSettingsViewModel.ts +++ b/apps/website/lib/view-models/LeagueSettingsViewModel.ts @@ -1,5 +1,5 @@ -import type { LeagueConfigFormModel } from '../types/LeagueConfigFormModel'; -import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO'; +import type { LeagueConfigFormModel } from '@core/racing/application'; +import type { LeagueScoringPresetDTO } from '@core/racing/application/ports/LeagueScoringPresetProvider'; import { LeagueScoringPresetsViewModel } from './LeagueScoringPresetsViewModel'; import { DriverSummaryViewModel } from './DriverSummaryViewModel'; diff --git a/apps/website/lib/view-models/LeagueStandingsViewModel.ts b/apps/website/lib/view-models/LeagueStandingsViewModel.ts index 2e5c2e550..a8708d1a9 100644 --- a/apps/website/lib/view-models/LeagueStandingsViewModel.ts +++ b/apps/website/lib/view-models/LeagueStandingsViewModel.ts @@ -1,14 +1,14 @@ import { LeagueStandingDTO } from '../types/generated/LeagueStandingDTO'; import { StandingEntryViewModel } from './StandingEntryViewModel'; -import { DriverDTO } from '../types/DriverDTO'; +import { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO'; import { LeagueMembership } from '../types/LeagueMembership'; export class LeagueStandingsViewModel { standings: StandingEntryViewModel[]; - drivers: DriverDTO[]; + drivers: GetDriverOutputDTO[]; memberships: LeagueMembership[]; - constructor(dto: { standings: LeagueStandingDTO[]; drivers: DriverDTO[]; memberships: LeagueMembership[] }, currentUserId: string, previousStandings?: LeagueStandingDTO[]) { + constructor(dto: { standings: LeagueStandingDTO[]; drivers: GetDriverOutputDTO[]; memberships: LeagueMembership[] }, currentUserId: string, previousStandings?: LeagueStandingDTO[]) { const leaderPoints = dto.standings[0]?.points || 0; this.standings = dto.standings.map((entry, index) => { const nextPoints = dto.standings[index + 1]?.points || entry.points; diff --git a/apps/website/lib/view-models/MediaViewModel.test.ts b/apps/website/lib/view-models/MediaViewModel.test.ts index 09cb74d54..e76c55300 100644 --- a/apps/website/lib/view-models/MediaViewModel.test.ts +++ b/apps/website/lib/view-models/MediaViewModel.test.ts @@ -6,9 +6,9 @@ describe('MediaViewModel', () => { const dto = { id: 'media-123', url: 'https://example.com/image.jpg', - type: 'image' as const, - category: 'avatar' as const, - uploadedAt: new Date('2023-01-15'), + type: 'image', + category: 'avatar', + uploadedAt: '2023-01-15T00:00:00.000Z', size: 2048000, }; @@ -26,8 +26,8 @@ describe('MediaViewModel', () => { const dto = { id: 'media-123', url: 'https://example.com/image.jpg', - type: 'image' as const, - uploadedAt: new Date('2023-01-15'), + type: 'image', + uploadedAt: '2023-01-15T00:00:00.000Z', }; const viewModel = new MediaViewModel(dto); @@ -41,7 +41,7 @@ describe('MediaViewModel', () => { id: 'media-123', url: 'https://example.com/image.jpg', type: 'image', - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), }); expect(viewModel.formattedSize).toBe('Unknown'); @@ -52,7 +52,7 @@ describe('MediaViewModel', () => { id: 'media-123', url: 'https://example.com/image.jpg', type: 'image', - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), size: 512000, // 500 KB }); @@ -64,7 +64,7 @@ describe('MediaViewModel', () => { id: 'media-123', url: 'https://example.com/image.jpg', type: 'image', - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), size: 2048000, // 2 MB }); @@ -76,7 +76,7 @@ describe('MediaViewModel', () => { id: 'media-123', url: 'https://example.com/image.jpg', type: 'image', - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), size: 1024, // 1 KB }); @@ -88,7 +88,7 @@ describe('MediaViewModel', () => { id: 'media-123', url: 'https://example.com/video.mp4', type: 'video', - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), size: 104857600, // 100 MB }); @@ -100,19 +100,19 @@ describe('MediaViewModel', () => { id: '1', url: 'image.jpg', type: 'image', - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), }); const videoVm = new MediaViewModel({ id: '2', url: 'video.mp4', type: 'video', - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), }); const docVm = new MediaViewModel({ id: '3', url: 'doc.pdf', type: 'document', - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), }); expect(imageVm.type).toBe('image'); @@ -129,7 +129,7 @@ describe('MediaViewModel', () => { url: 'https://example.com/image.jpg', type: 'image', category, - uploadedAt: new Date(), + uploadedAt: new Date().toISOString(), }); expect(viewModel.category).toBe(category); diff --git a/apps/website/lib/view-models/MediaViewModel.ts b/apps/website/lib/view-models/MediaViewModel.ts index ba5c27662..50c0b85d9 100644 --- a/apps/website/lib/view-models/MediaViewModel.ts +++ b/apps/website/lib/view-models/MediaViewModel.ts @@ -1,12 +1,4 @@ -// Note: No generated DTO available for Media yet -interface MediaDTO { - id: string; - url: string; - type: 'image' | 'video' | 'document'; - category?: 'avatar' | 'team-logo' | 'league-cover' | 'race-result'; - uploadedAt: Date; - size?: number; -} +import type { GetMediaOutputDTO } from '../types/generated'; /** * Media View Model @@ -21,12 +13,12 @@ export class MediaViewModel { uploadedAt: Date; size?: number; - constructor(dto: MediaDTO) { + constructor(dto: GetMediaOutputDTO) { this.id = dto.id; this.url = dto.url; - this.type = dto.type; - this.uploadedAt = dto.uploadedAt; - if (dto.category !== undefined) this.category = dto.category; + this.type = dto.type as 'image' | 'video' | 'document'; + this.uploadedAt = new Date(dto.uploadedAt); + if (dto.category !== undefined) this.category = dto.category as 'avatar' | 'team-logo' | 'league-cover' | 'race-result'; if (dto.size !== undefined) this.size = dto.size; } diff --git a/apps/website/lib/view-models/TeamDetailsViewModel.ts b/apps/website/lib/view-models/TeamDetailsViewModel.ts index 14def9b92..003d3e2c1 100644 --- a/apps/website/lib/view-models/TeamDetailsViewModel.ts +++ b/apps/website/lib/view-models/TeamDetailsViewModel.ts @@ -1,70 +1,53 @@ -import { TeamMemberViewModel } from './TeamMemberViewModel'; - -// Note: No generated DTO available for TeamDetails yet -interface DriverDTO { - id: string; - name: string; - avatarUrl?: string; - iracingId?: string; - rating?: number; -} -interface TeamMemberDTO { - driverId: string; - driver?: DriverDTO; - role: string; - joinedAt: string; -} -interface TeamDetailsDTO { - id: string; - name: string; - description?: string; - logoUrl?: string; - memberCount: number; - ownerId: string; - members: TeamMemberDTO[]; -} +import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO'; export class TeamDetailsViewModel { id: string; name: string; + tag: string; description?: string; - logoUrl?: string; - memberCount: number; ownerId: string; - members: TeamMemberViewModel[]; - + leagues: string[]; + createdAt?: string; + specialization?: string; + region?: string; + languages?: string[]; + membership: { role: string; joinedAt: string; isActive: boolean } | null; + canManage: boolean; private currentUserId: string; - constructor(dto: TeamDetailsDTO, currentUserId: string) { - this.id = dto.id; - this.name = dto.name; - this.description = dto.description; - this.logoUrl = dto.logoUrl; - this.memberCount = dto.memberCount; - this.ownerId = dto.ownerId; - this.members = dto.members.map(m => new TeamMemberViewModel(m, currentUserId, dto.ownerId)); + constructor(dto: GetTeamDetailsOutputDTO, currentUserId: string) { + this.id = dto.team.id; + this.name = dto.team.name; + this.tag = dto.team.tag; + this.description = dto.team.description; + this.ownerId = dto.team.ownerId; + this.leagues = dto.team.leagues; + this.createdAt = dto.team.createdAt; + this.specialization = dto.team.specialization; + this.region = dto.team.region; + this.languages = dto.team.languages; + this.membership = dto.membership; + this.canManage = dto.canManage; this.currentUserId = currentUserId; } /** UI-specific: Whether current user is owner */ get isOwner(): boolean { - return this.currentUserId === this.ownerId; + return this.membership?.role === 'owner'; } - /** UI-specific: Whether can add members */ - get canAddMembers(): boolean { - return this.isOwner && this.memberCount < 10; // Assuming max 10 + /** UI-specific: Whether can manage team */ + get canManage(): boolean { + return this.canManage; } - /** UI-specific: Member management actions available */ - get memberActionsAvailable(): boolean { - return this.isOwner; + /** UI-specific: Whether current user is member */ + get isMember(): boolean { + return this.membership !== null; } - /** UI-specific: Team status */ - get teamStatus(): string { - if (this.memberCount < 5) return 'Recruiting'; - if (this.memberCount < 10) return 'Active'; - return 'Full'; + /** UI-specific: Current user's role */ + get userRole(): string { + return this.membership?.role || 'none'; } } \ No newline at end of file diff --git a/apps/website/lib/view-models/TeamMemberViewModel.ts b/apps/website/lib/view-models/TeamMemberViewModel.ts index 2ae78a44d..4ec26e87e 100644 --- a/apps/website/lib/view-models/TeamMemberViewModel.ts +++ b/apps/website/lib/view-models/TeamMemberViewModel.ts @@ -1,30 +1,23 @@ -// Note: No generated DTO available for TeamMember yet -interface DriverDTO { - id: string; - name: string; - avatarUrl?: string; - iracingId?: string; - rating?: number; -} - -interface TeamMemberDTO { - driverId: string; - driver?: DriverDTO; - role: string; - joinedAt: string; -} +import type { TeamMemberDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO'; export class TeamMemberViewModel { driverId: string; - driver?: any; - role: string; + driverName: string; + role: 'owner' | 'manager' | 'member'; joinedAt: string; + isActive: boolean; + avatarUrl: string; private currentUserId: string; private teamOwnerId: string; constructor(dto: TeamMemberDTO, currentUserId: string, teamOwnerId: string) { - Object.assign(this, dto); + this.driverId = dto.driverId; + this.driverName = dto.driverName; + this.role = dto.role; + this.joinedAt = dto.joinedAt; + this.isActive = dto.isActive; + this.avatarUrl = dto.avatarUrl; this.currentUserId = currentUserId; this.teamOwnerId = teamOwnerId; } @@ -33,7 +26,7 @@ export class TeamMemberViewModel { get roleBadgeVariant(): string { switch (this.role) { case 'owner': return 'primary'; - case 'captain': return 'secondary'; + case 'manager': return 'secondary'; case 'member': return 'default'; default: return 'default'; } diff --git a/apps/website/lib/view-models/TeamSummaryViewModel.ts b/apps/website/lib/view-models/TeamSummaryViewModel.ts index 9789afdc4..bc6d90b05 100644 --- a/apps/website/lib/view-models/TeamSummaryViewModel.ts +++ b/apps/website/lib/view-models/TeamSummaryViewModel.ts @@ -1,18 +1,10 @@ -// Note: No generated DTO available for TeamSummary yet -interface TeamSummaryDTO { - id: string; - name: string; - logoUrl?: string; - memberCount: number; - rating: number; -} +import type { TeamListItemDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO'; export class TeamSummaryViewModel { id: string; name: string; - logoUrl?: string; + tag: string; memberCount: number; - rating: number; description?: string; totalWins: number = 0; totalRaces: number = 0; @@ -21,23 +13,20 @@ export class TeamSummaryViewModel { specialization?: string; region?: string; languages: string[] = []; + leagues: string[] = []; private maxMembers = 10; // Assuming max members - constructor(dto: TeamSummaryDTO & { description?: string; totalWins?: number; totalRaces?: number; performanceLevel?: string; isRecruiting?: boolean; specialization?: string; region?: string; languages?: string[] }) { + constructor(dto: TeamListItemDTO) { this.id = dto.id; this.name = dto.name; - if (dto.logoUrl !== undefined) this.logoUrl = dto.logoUrl; + this.tag = dto.tag; this.memberCount = dto.memberCount; - this.rating = dto.rating; this.description = dto.description; - this.totalWins = dto.totalWins ?? 0; - this.totalRaces = dto.totalRaces ?? 0; - this.performanceLevel = dto.performanceLevel ?? ''; - this.isRecruiting = dto.isRecruiting ?? false; this.specialization = dto.specialization; this.region = dto.region; this.languages = dto.languages ?? []; + this.leagues = dto.leagues; } /** UI-specific: Whether team is full */ @@ -45,9 +34,9 @@ export class TeamSummaryViewModel { return this.memberCount >= this.maxMembers; } - /** UI-specific: Rating display */ - get ratingDisplay(): string { - return this.rating.toFixed(0); + /** UI-specific: Tag display */ + get tagDisplay(): string { + return `[${this.tag}]`; } /** UI-specific: Member count display */ diff --git a/core/racing/application/use-cases/GetSponsorUseCase.ts b/core/racing/application/use-cases/GetSponsorUseCase.ts new file mode 100644 index 000000000..5f2015aa9 --- /dev/null +++ b/core/racing/application/use-cases/GetSponsorUseCase.ts @@ -0,0 +1,40 @@ +/** + * Application Use Case: GetSponsorUseCase + * + * Retrieves a single sponsor by ID. + */ + +import type { ISponsorRepository } from '../../domain/repositories/ISponsorRepository'; +import { Result } from '@core/shared/application/Result'; +import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; + +export interface GetSponsorQueryParams { + sponsorId: string; +} + +export class GetSponsorUseCase { + constructor( + private readonly sponsorRepository: ISponsorRepository, + ) {} + + async execute(params: GetSponsorQueryParams): Promise>> { + try { + const sponsor = await this.sponsorRepository.findById(params.sponsorId); + + if (!sponsor) { + return Result.ok(null); + } + + const sponsorData = { + id: sponsor.id, + name: sponsor.name, + logoUrl: sponsor.logoUrl, + websiteUrl: sponsor.websiteUrl, + }; + + return Result.ok({ sponsor: sponsorData }); + } catch { + return Result.err({ code: 'REPOSITORY_ERROR', message: 'Failed to fetch sponsor' }); + } + } +} \ No newline at end of file