resolve manual DTOs
This commit is contained in:
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<GetDashboardDataOutput> {
|
||||
return await this.analyticsService.getDashboardData();
|
||||
}
|
||||
|
||||
@Get('metrics')
|
||||
@ApiOperation({ summary: 'Get analytics metrics' })
|
||||
@ApiResponse({ status: 200, description: 'Analytics metrics', type: GetAnalyticsMetricsOutputDTO })
|
||||
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutput> {
|
||||
return await this.analyticsService.getAnalyticsMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RecordEngagementOutput> {
|
||||
return await this.recordEngagementUseCase.execute(input);
|
||||
}
|
||||
|
||||
async getDashboardData(): Promise<GetDashboardDataOutput> {
|
||||
// TODO: Implement actual dashboard data retrieval
|
||||
return {
|
||||
totalUsers: 0,
|
||||
activeUsers: 0,
|
||||
totalRaces: 0,
|
||||
totalLeagues: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutput> {
|
||||
// TODO: Implement actual analytics metrics retrieval
|
||||
return {
|
||||
pageViews: 0,
|
||||
uniqueVisitors: 0,
|
||||
averageSessionDuration: 0,
|
||||
bounceRate: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<AuthSessionDTO> {
|
||||
return this.authService.signupWithEmail(params);
|
||||
}
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() params: LoginParams) {
|
||||
async login(@Body() params: LoginParams): Promise<AuthSessionDTO> {
|
||||
return this.authService.loginWithEmail(params);
|
||||
}
|
||||
|
||||
@Get('session')
|
||||
async getSession() {
|
||||
async getSession(): Promise<AuthSessionDTO | null> {
|
||||
return this.authService.getCurrentSession();
|
||||
}
|
||||
|
||||
@Post('logout')
|
||||
async logout() {
|
||||
async logout(): Promise<void> {
|
||||
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<void> {
|
||||
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<AuthSessionDTO> {
|
||||
return this.authService.loginWithIracingCallback({ code, state, returnTo });
|
||||
}
|
||||
}
|
||||
|
||||
18
apps/api/src/domain/dashboard/DashboardController.ts
Normal file
18
apps/api/src/domain/dashboard/DashboardController.ts
Normal file
@@ -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<DashboardOverviewDTO> {
|
||||
return this.dashboardService.getDashboardOverview(driverId);
|
||||
}
|
||||
}
|
||||
11
apps/api/src/domain/dashboard/DashboardModule.ts
Normal file
11
apps/api/src/domain/dashboard/DashboardModule.ts
Normal file
@@ -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 {}
|
||||
23
apps/api/src/domain/dashboard/DashboardProviders.ts
Normal file
23
apps/api/src/domain/dashboard/DashboardProviders.ts
Normal file
@@ -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,
|
||||
];
|
||||
28
apps/api/src/domain/dashboard/DashboardService.ts
Normal file
28
apps/api/src/domain/dashboard/DashboardService.ts
Normal file
@@ -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<any> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
41
apps/api/src/domain/dashboard/dtos/DashboardOverviewDTO.ts
Normal file
41
apps/api/src/domain/dashboard/dtos/DashboardOverviewDTO.ts
Normal file
@@ -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[];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<DriverDTO | null> {
|
||||
async getCurrentDriver(@Req() req: Request): Promise<GetDriverOutputDTO | null> {
|
||||
// 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<GetDriverOutputDTO | null> {
|
||||
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<GetDriverProfileOutputDTO> {
|
||||
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<DriverDTO | null> {
|
||||
): Promise<GetDriverOutputDTO | null> {
|
||||
return this.driverService.updateDriverProfile(driverId, body.bio, body.country);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<DriverDTO | null> {
|
||||
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
|
||||
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<DriverDTO | null> {
|
||||
async updateDriverProfile(driverId: string, bio?: string, country?: string): Promise<GetDriverOutputDTO | null> {
|
||||
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<GetDriverOutputDTO | null> {
|
||||
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<GetDriverProfileOutputDTO> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
21
apps/api/src/domain/driver/dtos/DriverDTO.ts
Normal file
21
apps/api/src/domain/driver/dtos/DriverDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
21
apps/api/src/domain/driver/dtos/GetDriverOutputDTO.ts
Normal file
21
apps/api/src/domain/driver/dtos/GetDriverOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
226
apps/api/src/domain/driver/dtos/GetDriverProfileOutputDTO.ts
Normal file
226
apps/api/src/domain/driver/dtos/GetDriverProfileOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<GetSeasonSponsorshipsOutputDTO> {
|
||||
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<GetLeagueRacesOutputDTO> {
|
||||
return this.leagueService.getRaces(leagueId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<GetSeasonSponsorshipsOutputDTO> {
|
||||
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<GetLeagueRacesOutputDTO> {
|
||||
this.logger.debug('Getting league races', { leagueId });
|
||||
|
||||
// TODO: Implement actual logic to fetch league races
|
||||
// For now, return empty array as placeholder
|
||||
return {
|
||||
races: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
||||
|
||||
export class GetLeagueRacesOutputDTO {
|
||||
@ApiProperty({ type: [RaceDTO] })
|
||||
races: RaceDTO[];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { SponsorshipDetailDTO } from '../../sponsor/dtos/SponsorshipDetailDTO';
|
||||
|
||||
export class GetSeasonSponsorshipsOutputDTO {
|
||||
@ApiProperty({ type: [SponsorshipDetailDTO] })
|
||||
sponsorships: SponsorshipDetailDTO[];
|
||||
}
|
||||
28
apps/api/src/domain/league/dtos/LeagueMembershipDTO.ts
Normal file
28
apps/api/src/domain/league/dtos/LeagueMembershipDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
8
apps/api/src/domain/league/dtos/LeagueRoleDTO.ts
Normal file
8
apps/api/src/domain/league/dtos/LeagueRoleDTO.ts
Normal file
@@ -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';
|
||||
}
|
||||
32
apps/api/src/domain/league/dtos/LeagueScoringPresetDTO.ts
Normal file
32
apps/api/src/domain/league/dtos/LeagueScoringPresetDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
8
apps/api/src/domain/league/dtos/MembershipRoleDTO.ts
Normal file
8
apps/api/src/domain/league/dtos/MembershipRoleDTO.ts
Normal file
@@ -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';
|
||||
}
|
||||
8
apps/api/src/domain/league/dtos/MembershipStatusDTO.ts
Normal file
8
apps/api/src/domain/league/dtos/MembershipStatusDTO.ts
Normal file
@@ -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';
|
||||
}
|
||||
92
apps/api/src/domain/league/dtos/WizardErrorsDTO.ts
Normal file
92
apps/api/src/domain/league/dtos/WizardErrorsDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
8
apps/api/src/domain/league/dtos/WizardStepDTO.ts
Normal file
8
apps/api/src/domain/league/dtos/WizardStepDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
const result = await this.mediaService.updateAvatar(driverId, input);
|
||||
res.status(HttpStatus.OK).json(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UploadMediaOutput> {
|
||||
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<GetMediaOutput | null> {
|
||||
this.logger.debug(`[MediaService] Getting media: ${mediaId}`);
|
||||
// TODO: Implement get media logic
|
||||
return null;
|
||||
}
|
||||
|
||||
async deleteMedia(mediaId: string): Promise<DeleteMediaOutput> {
|
||||
this.logger.debug(`[MediaService] Deleting media: ${mediaId}`);
|
||||
// TODO: Implement delete media logic
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async getAvatar(driverId: string): Promise<GetAvatarOutput | null> {
|
||||
this.logger.debug(`[MediaService] Getting avatar for driver: ${driverId}`);
|
||||
// TODO: Implement get avatar logic
|
||||
return null;
|
||||
}
|
||||
|
||||
async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise<UpdateAvatarOutput> {
|
||||
this.logger.debug(`[MediaService] Updating avatar for driver: ${driverId}`);
|
||||
// TODO: Implement update avatar logic
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
13
apps/api/src/domain/media/dtos/DeleteMediaOutputDTO.ts
Normal file
13
apps/api/src/domain/media/dtos/DeleteMediaOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
8
apps/api/src/domain/media/dtos/GetAvatarOutputDTO.ts
Normal file
8
apps/api/src/domain/media/dtos/GetAvatarOutputDTO.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
export class GetAvatarOutputDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
avatarUrl: string;
|
||||
}
|
||||
29
apps/api/src/domain/media/dtos/GetMediaOutputDTO.ts
Normal file
29
apps/api/src/domain/media/dtos/GetMediaOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
12
apps/api/src/domain/media/dtos/UpdateAvatarInputDTO.ts
Normal file
12
apps/api/src/domain/media/dtos/UpdateAvatarInputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
13
apps/api/src/domain/media/dtos/UpdateAvatarOutputDTO.ts
Normal file
13
apps/api/src/domain/media/dtos/UpdateAvatarOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
16
apps/api/src/domain/media/dtos/UploadMediaInputDTO.ts
Normal file
16
apps/api/src/domain/media/dtos/UploadMediaInputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
23
apps/api/src/domain/media/dtos/UploadMediaOutputDTO.ts
Normal file
23
apps/api/src/domain/media/dtos/UploadMediaOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -55,7 +55,7 @@ import type {
|
||||
GetWalletOutput,
|
||||
ProcessWalletTransactionInput,
|
||||
ProcessWalletTransactionOutput,
|
||||
} from './dto/PaymentsDto';
|
||||
} from './dtos/PaymentsDto';
|
||||
|
||||
// Injection tokens
|
||||
import {
|
||||
|
||||
22
apps/api/src/domain/protests/ProtestsController.ts
Normal file
22
apps/api/src/domain/protests/ProtestsController.ts
Normal file
@@ -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<ReviewProtestCommandDTO, 'protestId'>,
|
||||
): Promise<void> {
|
||||
return this.raceService.reviewProtest({ protestId, ...body });
|
||||
}
|
||||
}
|
||||
9
apps/api/src/domain/protests/ProtestsModule.ts
Normal file
9
apps/api/src/domain/protests/ProtestsModule.ts
Normal file
@@ -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 {}
|
||||
@@ -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<DashboardOverviewDTO> {
|
||||
return this.raceService.getDashboardOverview(driverId);
|
||||
}
|
||||
|
||||
@Post('protests/file')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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<any> {
|
||||
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<any> {
|
||||
this.logger.debug('[RaceService] Filing protest:', command);
|
||||
@@ -294,4 +283,16 @@ export class RaceService {
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
||||
async reviewProtest(command: any): Promise<any> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
25
apps/api/src/domain/race/dtos/ReviewProtestCommandDTO.ts
Normal file
25
apps/api/src/domain/race/dtos/ReviewProtestCommandDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<SponsorSponsorshipsDTO | null> {
|
||||
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<GetSponsorOutputDTO | null> {
|
||||
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<GetPendingSponsorshipRequestsOutputDTO> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
return this.sponsorService.rejectSponsorshipRequest(requestId, input.respondedBy, input.reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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<GetSponsorOutputDTO | null> {
|
||||
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<GetPendingSponsorshipRequestsOutputDTO> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class AcceptSponsorshipRequestInputDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
respondedBy: string;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
7
apps/api/src/domain/sponsor/dtos/GetSponsorOutputDTO.ts
Normal file
7
apps/api/src/domain/sponsor/dtos/GetSponsorOutputDTO.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { SponsorDTO } from './SponsorDTO';
|
||||
|
||||
export class GetSponsorOutputDTO {
|
||||
@ApiProperty({ type: SponsorDTO })
|
||||
sponsor: SponsorDTO;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
15
apps/api/src/domain/sponsor/dtos/SponsorDTO.ts
Normal file
15
apps/api/src/domain/sponsor/dtos/SponsorDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
39
apps/api/src/domain/sponsor/dtos/SponsorshipRequestDTO.ts
Normal file
39
apps/api/src/domain/sponsor/dtos/SponsorshipRequestDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<AllTeamsViewModel> {
|
||||
return this.teamService.getAllTeams();
|
||||
@ApiResponse({ status: 200, description: 'List of all teams', type: GetAllTeamsOutputDTO })
|
||||
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||
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<TeamDetailsViewModel | null> {
|
||||
return this.teamService.getTeamDetails(teamId);
|
||||
async getDetails(@Param('teamId') teamId: string, @Req() req: Request): Promise<GetTeamDetailsOutputDTO | null> {
|
||||
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<TeamMembersViewModel> {
|
||||
return this.teamService.getTeamMembers(teamId);
|
||||
@ApiResponse({ status: 200, description: 'Team members', type: GetTeamMembersOutputDTO })
|
||||
async getMembers(@Param('teamId') teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||
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<TeamJoinRequestsViewModel> {
|
||||
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<ApproveTeamJoinRequestOutput> {
|
||||
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<RejectTeamJoinRequestOutput> {
|
||||
return this.teamService.rejectTeamJoinRequest({ ...input, teamId });
|
||||
@ApiResponse({ status: 200, description: 'Team join requests', type: GetTeamJoinRequestsOutputDTO })
|
||||
async getJoinRequests(@Param('teamId') teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||
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<CreateTeamOutput> {
|
||||
return this.teamService.createTeam(input);
|
||||
@ApiResponse({ status: 201, description: 'Team created', type: CreateTeamOutputDTO })
|
||||
async create(@Body() input: CreateTeamInputDTO, @Req() req: Request): Promise<CreateTeamOutputDTO> {
|
||||
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<UpdateTeamOutput> {
|
||||
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<UpdateTeamOutputDTO> {
|
||||
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<DriverTeamViewModel | null> {
|
||||
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<GetDriverTeamOutputDTO | null> {
|
||||
return this.teamService.getDriverTeam(driverId);
|
||||
}
|
||||
|
||||
@Get('leaderboard')
|
||||
@ApiOperation({ summary: 'Get teams leaderboard' })
|
||||
@ApiResponse({ status: 200, description: 'Teams leaderboard' })
|
||||
async getTeamsLeaderboard(): Promise<any> {
|
||||
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<GetTeamMembershipOutputDTO | null> {
|
||||
return this.teamService.getMembership(teamId, driverId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,4 @@ import { TeamProviders } from './TeamProviders';
|
||||
providers: TeamProviders,
|
||||
exports: [TeamService],
|
||||
})
|
||||
export class TeamModule {}
|
||||
export class TeamModule {}
|
||||
@@ -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,
|
||||
];
|
||||
@@ -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<AllTeamsViewModel> {
|
||||
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<GetAllTeamsOutputDTO> {
|
||||
// TODO: Implement getAll teams logic
|
||||
return {
|
||||
teams: [],
|
||||
totalCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async getDriverTeam(query: GetDriverTeamQuery): Promise<DriverTeamViewModel | null> {
|
||||
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<GetTeamDetailsOutputDTO | null> {
|
||||
// TODO: Implement get team details logic
|
||||
return null;
|
||||
}
|
||||
|
||||
async getTeamDetails(teamId: string): Promise<TeamDetailsViewModel | null> {
|
||||
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<GetTeamMembersOutputDTO> {
|
||||
// TODO: Implement get team members logic
|
||||
return {
|
||||
members: [],
|
||||
totalCount: 0,
|
||||
ownerCount: 0,
|
||||
managerCount: 0,
|
||||
memberCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async getTeamMembers(teamId: string): Promise<TeamMembersViewModel> {
|
||||
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<GetTeamJoinRequestsOutputDTO> {
|
||||
// TODO: Implement get team join requests logic
|
||||
return {
|
||||
requests: [],
|
||||
pendingCount: 0,
|
||||
totalCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async getTeamJoinRequests(teamId: string): Promise<TeamJoinRequestsViewModel> {
|
||||
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<CreateTeamOutputDTO> {
|
||||
// TODO: Implement create team logic
|
||||
return {
|
||||
id: 'placeholder-id',
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async createTeam(input: CreateTeamInput): Promise<CreateTeamOutput> {
|
||||
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<UpdateTeamOutputDTO> {
|
||||
// TODO: Implement update team logic
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async updateTeam(input: UpdateTeamInput & { teamId: string }): Promise<UpdateTeamOutput> {
|
||||
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<GetDriverTeamOutputDTO | null> {
|
||||
// TODO: Implement get driver team logic
|
||||
return null;
|
||||
}
|
||||
|
||||
async approveTeamJoinRequest(input: ApproveTeamJoinRequestInput & { teamId: string }): Promise<ApproveTeamJoinRequestOutput> {
|
||||
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<GetTeamMembershipOutputDTO | null> {
|
||||
// TODO: Implement get team membership logic
|
||||
return null;
|
||||
}
|
||||
|
||||
async rejectTeamJoinRequest(input: RejectTeamJoinRequestInput & { teamId: string }): Promise<RejectTeamJoinRequestOutput> {
|
||||
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<any> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
apps/api/src/domain/team/dtos/CreateTeamInputDTO.ts
Normal file
19
apps/api/src/domain/team/dtos/CreateTeamInputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
9
apps/api/src/domain/team/dtos/CreateTeamOutputDTO.ts
Normal file
9
apps/api/src/domain/team/dtos/CreateTeamOutputDTO.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CreateTeamOutputDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
success: boolean;
|
||||
}
|
||||
38
apps/api/src/domain/team/dtos/GetAllTeamsOutputDTO.ts
Normal file
38
apps/api/src/domain/team/dtos/GetAllTeamsOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
58
apps/api/src/domain/team/dtos/GetDriverTeamOutputDTO.ts
Normal file
58
apps/api/src/domain/team/dtos/GetDriverTeamOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
55
apps/api/src/domain/team/dtos/GetTeamDetailsOutputDTO.ts
Normal file
55
apps/api/src/domain/team/dtos/GetTeamDetailsOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
38
apps/api/src/domain/team/dtos/GetTeamMembersOutputDTO.ts
Normal file
38
apps/api/src/domain/team/dtos/GetTeamMembersOutputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
12
apps/api/src/domain/team/dtos/GetTeamMembershipOutputDTO.ts
Normal file
12
apps/api/src/domain/team/dtos/GetTeamMembershipOutputDTO.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class GetTeamMembershipOutputDTO {
|
||||
@ApiProperty()
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt: string;
|
||||
|
||||
@ApiProperty()
|
||||
isActive: boolean;
|
||||
}
|
||||
19
apps/api/src/domain/team/dtos/UpdateTeamInputDTO.ts
Normal file
19
apps/api/src/domain/team/dtos/UpdateTeamInputDTO.ts
Normal file
@@ -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;
|
||||
}
|
||||
6
apps/api/src/domain/team/dtos/UpdateTeamOutputDTO.ts
Normal file
6
apps/api/src/domain/team/dtos/UpdateTeamOutputDTO.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class UpdateTeamOutputDTO {
|
||||
@ApiProperty()
|
||||
success: boolean;
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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 */}
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<div className="text-center">
|
||||
<p className="text-purple-400 font-mono font-semibold">{team.rating?.toLocaleString()}</p>
|
||||
<p className="text-[10px] text-gray-500">Rating</p>
|
||||
<p className="text-purple-400 font-mono font-semibold">{team.memberCount}</p>
|
||||
<p className="text-[10px] text-gray-500">Members</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-performance-green font-mono font-semibold">{team.totalWins}</p>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<string, unknown> };
|
||||
|
||||
// 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<RecordPageViewOutputDTO> {
|
||||
recordPageView(input: RecordPageViewInputDTO): Promise<RecordPageViewOutputDTO> {
|
||||
return this.post<RecordPageViewOutputDTO>('/analytics/page-view', input);
|
||||
}
|
||||
|
||||
/** Record an engagement event */
|
||||
recordEngagement(input: RecordEngagementInputDto): Promise<RecordEngagementOutputDTO> {
|
||||
recordEngagement(input: RecordEngagementInputDTO): Promise<RecordEngagementOutputDTO> {
|
||||
return this.post<RecordEngagementOutputDTO>('/analytics/engagement', input);
|
||||
}
|
||||
|
||||
/** Get analytics dashboard data */
|
||||
getDashboardData(): Promise<AnalyticsDashboardDto> {
|
||||
return this.get<AnalyticsDashboardDto>('/analytics/dashboard');
|
||||
getDashboardData(): Promise<GetDashboardDataOutputDTO> {
|
||||
return this.get<GetDashboardDataOutputDTO>('/analytics/dashboard');
|
||||
}
|
||||
|
||||
/** Get analytics metrics */
|
||||
getAnalyticsMetrics(): Promise<AnalyticsMetricsDto> {
|
||||
return this.get<AnalyticsMetricsDto>('/analytics/metrics');
|
||||
getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutputDTO> {
|
||||
return this.get<GetAnalyticsMetricsOutputDTO>('/analytics/metrics');
|
||||
}
|
||||
}
|
||||
@@ -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<AuthSessionDTO> {
|
||||
signup(params: SignupParams): Promise<AuthSessionDTO> {
|
||||
return this.post<AuthSessionDTO>('/auth/signup', params);
|
||||
}
|
||||
|
||||
/** Login with email */
|
||||
login(params: LoginParamsDto): Promise<AuthSessionDTO> {
|
||||
login(params: LoginParams): Promise<AuthSessionDTO> {
|
||||
return this.post<AuthSessionDTO>('/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<IracingAuthRedirectResult> {
|
||||
const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
|
||||
return this.get<IracingAuthRedirectResult>(`/auth/iracing/start${query}`);
|
||||
}
|
||||
|
||||
/** Login with iRacing callback */
|
||||
loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise<AuthSessionDTO> {
|
||||
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<AuthSessionDTO>(`/auth/iracing/callback?${query.toString()}`);
|
||||
}
|
||||
}
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<DriverDTO | null> {
|
||||
return this.get<DriverDTO | null>('/drivers/current');
|
||||
getCurrent(): Promise<GetDriverOutputDTO | null> {
|
||||
return this.get<GetDriverOutputDTO | null>('/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<DriverDTO | null> {
|
||||
return this.get<DriverDTO | null>(`/drivers/${driverId}`);
|
||||
getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
|
||||
return this.get<GetDriverOutputDTO | null>(`/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<DriverDTO> {
|
||||
return this.put<DriverDTO>('/drivers/profile', updates);
|
||||
updateProfile(updates: { bio?: string; country?: string }): Promise<GetDriverOutputDTO> {
|
||||
return this.put<GetDriverOutputDTO>('/drivers/profile', updates);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
@@ -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<UploadMediaOutputDto> {
|
||||
uploadMedia(input: { file: File; type: string; category?: string }): Promise<UploadMediaOutputDTO> {
|
||||
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<UploadMediaOutputDto>('/media/upload', formData);
|
||||
return this.post<UploadMediaOutputDTO>('/media/upload', formData);
|
||||
}
|
||||
|
||||
/** Get media by ID */
|
||||
getMedia(mediaId: string): Promise<GetMediaOutputDto> {
|
||||
return this.get<GetMediaOutputDto>(`/media/${mediaId}`);
|
||||
getMedia(mediaId: string): Promise<GetMediaOutputDTO> {
|
||||
return this.get<GetMediaOutputDTO>(`/media/${mediaId}`);
|
||||
}
|
||||
|
||||
/** Delete media by ID */
|
||||
deleteMedia(mediaId: string): Promise<DeleteMediaOutputDto> {
|
||||
return this.delete<DeleteMediaOutputDto>(`/media/${mediaId}`);
|
||||
deleteMedia(mediaId: string): Promise<DeleteMediaOutputDTO> {
|
||||
return this.delete<DeleteMediaOutputDTO>(`/media/${mediaId}`);
|
||||
}
|
||||
|
||||
/** Request avatar generation */
|
||||
requestAvatarGeneration(input: RequestAvatarGenerationInputDto): Promise<RequestAvatarGenerationOutputDto> {
|
||||
return this.post<RequestAvatarGenerationOutputDto>('/media/avatar/generate', input);
|
||||
requestAvatarGeneration(input: RequestAvatarGenerationInputDTO): Promise<RequestAvatarGenerationOutputDTO> {
|
||||
return this.post<RequestAvatarGenerationOutputDTO>('/media/avatar/generate', input);
|
||||
}
|
||||
|
||||
/** Get avatar for driver */
|
||||
getAvatar(driverId: string): Promise<GetAvatarOutputDto> {
|
||||
return this.get<GetAvatarOutputDto>(`/media/avatar/${driverId}`);
|
||||
getAvatar(driverId: string): Promise<GetAvatarOutputDTO> {
|
||||
return this.get<GetAvatarOutputDTO>(`/media/avatar/${driverId}`);
|
||||
}
|
||||
|
||||
/** Update avatar for driver */
|
||||
updateAvatar(input: UpdateAvatarInputDto): Promise<UpdateAvatarOutputDto> {
|
||||
return this.put<UpdateAvatarOutputDto>(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl });
|
||||
updateAvatar(input: UpdateAvatarInputDTO): Promise<UpdateAvatarOutputDTO> {
|
||||
return this.put<UpdateAvatarOutputDTO>(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl });
|
||||
}
|
||||
}
|
||||
@@ -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<GetPaymentsOutputDto> {
|
||||
getPayments(query?: { leagueId?: string; payerId?: string; type?: 'sponsorship' | 'membership_fee'; status?: 'pending' | 'completed' | 'failed' | 'refunded' }): Promise<GetPaymentsOutputDto> {
|
||||
const params = new URLSearchParams();
|
||||
if (leagueId) params.append('leagueId', leagueId);
|
||||
if (driverId) params.append('driverId', driverId);
|
||||
const query = params.toString();
|
||||
return this.get<GetPaymentsOutputDto>(`/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<GetPaymentsOutputDto>(`/payments${queryString ? `?${queryString}` : ''}`);
|
||||
}
|
||||
|
||||
/** Create a payment */
|
||||
@@ -62,21 +90,63 @@ export class PaymentsApiClient extends BaseApiClient {
|
||||
}
|
||||
|
||||
/** Get membership fees */
|
||||
getMembershipFees(leagueId: string): Promise<GetMembershipFeesOutputDto> {
|
||||
return this.get<GetMembershipFeesOutputDto>(`/payments/membership-fees?leagueId=${leagueId}`);
|
||||
getMembershipFees(query: { leagueId: string; driverId?: string }): Promise<GetMembershipFeesOutputDto> {
|
||||
const params = new URLSearchParams();
|
||||
params.append('leagueId', query.leagueId);
|
||||
if (query.driverId) params.append('driverId', query.driverId);
|
||||
const queryString = params.toString();
|
||||
return this.get<GetMembershipFeesOutputDto>(`/payments/membership-fees?${queryString}`);
|
||||
}
|
||||
|
||||
/** Get prizes */
|
||||
getPrizes(leagueId?: string, seasonId?: string): Promise<GetPrizesOutputDto> {
|
||||
getPrizes(query?: { leagueId?: string; seasonId?: string }): Promise<GetPrizesOutputDto> {
|
||||
const params = new URLSearchParams();
|
||||
if (leagueId) params.append('leagueId', leagueId);
|
||||
if (seasonId) params.append('seasonId', seasonId);
|
||||
const query = params.toString();
|
||||
return this.get<GetPrizesOutputDto>(`/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<GetPrizesOutputDto>(`/payments/prizes${queryString ? `?${queryString}` : ''}`);
|
||||
}
|
||||
|
||||
/** Get wallet */
|
||||
getWallet(driverId: string): Promise<GetWalletOutputDto> {
|
||||
return this.get<GetWalletOutputDto>(`/payments/wallets?driverId=${driverId}`);
|
||||
getWallet(query?: { leagueId?: string }): Promise<GetWalletOutputDto> {
|
||||
const params = new URLSearchParams();
|
||||
if (query?.leagueId) params.append('leagueId', query.leagueId);
|
||||
const queryString = params.toString();
|
||||
return this.get<GetWalletOutputDto>(`/payments/wallets${queryString ? `?${queryString}` : ''}`);
|
||||
}
|
||||
|
||||
/** Update payment status */
|
||||
updatePaymentStatus(input: UpdatePaymentStatusInputDTO): Promise<UpdatePaymentStatusOutputDto> {
|
||||
return this.patch<UpdatePaymentStatusOutputDto>('/payments/status', input);
|
||||
}
|
||||
|
||||
/** Upsert membership fee */
|
||||
upsertMembershipFee(input: UpsertMembershipFeeInputDto): Promise<UpsertMembershipFeeOutputDto> {
|
||||
return this.post<UpsertMembershipFeeOutputDto>('/payments/membership-fees', input);
|
||||
}
|
||||
|
||||
/** Update member payment */
|
||||
updateMemberPayment(input: UpdateMemberPaymentInputDto): Promise<UpdateMemberPaymentOutputDto> {
|
||||
return this.patch<UpdateMemberPaymentOutputDto>('/payments/membership-fees/member-payment', input);
|
||||
}
|
||||
|
||||
/** Create prize */
|
||||
createPrize(input: CreatePrizeInputDto): Promise<CreatePrizeOutputDto> {
|
||||
return this.post<CreatePrizeOutputDto>('/payments/prizes', input);
|
||||
}
|
||||
|
||||
/** Award prize */
|
||||
awardPrize(input: AwardPrizeInputDto): Promise<AwardPrizeOutputDto> {
|
||||
return this.patch<AwardPrizeOutputDto>('/payments/prizes/award', input);
|
||||
}
|
||||
|
||||
/** Delete prize */
|
||||
deletePrize(prizeId: string): Promise<DeletePrizeOutputDto> {
|
||||
return this.delete<DeletePrizeOutputDto>(`/payments/prizes?prizeId=${prizeId}`);
|
||||
}
|
||||
|
||||
/** Process wallet transaction */
|
||||
processWalletTransaction(input: ProcessWalletTransactionInputDto): Promise<ProcessWalletTransactionOutputDto> {
|
||||
return this.post<ProcessWalletTransactionOutputDto>('/payments/wallets/transactions', input);
|
||||
}
|
||||
}
|
||||
@@ -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<RacePenaltiesDTO> {
|
||||
return this.get<RacePenaltiesDTO>(`/races/${raceId}/penalties`);
|
||||
}
|
||||
|
||||
/** Apply a penalty */
|
||||
applyPenalty(input: any): Promise<void> {
|
||||
applyPenalty(input: ApplyPenaltyCommandDTO): Promise<void> {
|
||||
return this.post<void>('/races/penalties/apply', input);
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
reviewProtest(input: ReviewProtestCommandDTO): Promise<void> {
|
||||
return this.post<void>(`/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<RaceProtestsDTO> {
|
||||
return this.get<RaceProtestsDTO>(`/races/${raceId}/protests`);
|
||||
}
|
||||
}
|
||||
@@ -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<RaceStatsDto> {
|
||||
return this.get<RaceStatsDto>('/races/total-races');
|
||||
getTotal(): Promise<RaceStatsDTO> {
|
||||
return this.get<RaceStatsDTO>('/races/total-races');
|
||||
}
|
||||
|
||||
/** Get races page data */
|
||||
getPageData(): Promise<RacesPageDataDto> {
|
||||
return this.get<RacesPageDataDto>('/races/page-data');
|
||||
getPageData(): Promise<RacesPageDataDTO> {
|
||||
return this.get<RacesPageDataDTO>('/races/page-data');
|
||||
}
|
||||
|
||||
/** Get race detail */
|
||||
getDetail(raceId: string, driverId: string): Promise<RaceDetailDto> {
|
||||
return this.get<RaceDetailDto>(`/races/${raceId}?driverId=${driverId}`);
|
||||
getDetail(raceId: string, driverId: string): Promise<RaceDetailDTO> {
|
||||
return this.get<RaceDetailDTO>(`/races/${raceId}?driverId=${driverId}`);
|
||||
}
|
||||
|
||||
/** Get race results detail */
|
||||
getResultsDetail(raceId: string): Promise<RaceResultsDetailDto> {
|
||||
return this.get<RaceResultsDetailDto>(`/races/${raceId}/results`);
|
||||
getResultsDetail(raceId: string): Promise<RaceResultsDetailDTO> {
|
||||
return this.get<RaceResultsDetailDTO>(`/races/${raceId}/results`);
|
||||
}
|
||||
|
||||
/** Get race with strength of field */
|
||||
getWithSOF(raceId: string): Promise<RaceWithSOFDto> {
|
||||
return this.get<RaceWithSOFDto>(`/races/${raceId}/sof`);
|
||||
getWithSOF(raceId: string): Promise<RaceWithSOFDTO> {
|
||||
return this.get<RaceWithSOFDTO>(`/races/${raceId}/sof`);
|
||||
}
|
||||
|
||||
/** Register for race */
|
||||
register(raceId: string, input: RegisterForRaceInputDto): Promise<void> {
|
||||
register(raceId: string, input: RegisterForRaceParamsDTO): Promise<void> {
|
||||
return this.post<void>(`/races/${raceId}/register`, input);
|
||||
}
|
||||
|
||||
/** Import race results */
|
||||
importResults(raceId: string, input: ImportRaceResultsInputDto): Promise<ImportRaceResultsSummaryDto> {
|
||||
return this.post<ImportRaceResultsSummaryDto>(`/races/${raceId}/import-results`, input);
|
||||
importResults(raceId: string, input: ImportRaceResultsDTO): Promise<ImportRaceResultsSummaryDTO> {
|
||||
return this.post<ImportRaceResultsSummaryDTO>(`/races/${raceId}/import-results`, input);
|
||||
}
|
||||
|
||||
/** Withdraw from race */
|
||||
withdraw(raceId: string, input: WithdrawFromRaceInputDto): Promise<void> {
|
||||
withdraw(raceId: string, input: WithdrawFromRaceParamsDTO): Promise<void> {
|
||||
return this.post<void>(`/races/${raceId}/withdraw`, input);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SponsorDTO | null> {
|
||||
return this.get<SponsorDTO | null>(`/sponsors/${sponsorId}`);
|
||||
getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO | null> {
|
||||
return this.get<GetSponsorOutputDTO | null>(`/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<GetPendingSponsorshipRequestsOutputDTO> {
|
||||
return this.get<GetPendingSponsorshipRequestsOutputDTO>(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`);
|
||||
}
|
||||
|
||||
/** Accept a sponsorship request */
|
||||
acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<void> {
|
||||
return this.post(`/sponsors/requests/${requestId}/accept`, { respondedBy });
|
||||
acceptSponsorshipRequest(requestId: string, input: AcceptSponsorshipRequestInputDTO): Promise<void> {
|
||||
return this.post(`/sponsors/requests/${requestId}/accept`, input);
|
||||
}
|
||||
|
||||
/** Reject a sponsorship request */
|
||||
rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise<void> {
|
||||
return this.post(`/sponsors/requests/${requestId}/reject`, { respondedBy, reason });
|
||||
rejectSponsorshipRequest(requestId: string, input: RejectSponsorshipRequestInputDTO): Promise<void> {
|
||||
return this.post(`/sponsors/requests/${requestId}/reject`, input);
|
||||
}
|
||||
}
|
||||
@@ -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<AllTeamsDto> {
|
||||
return this.get<AllTeamsDto>('/teams/all');
|
||||
getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||
return this.get<GetAllTeamsOutputDTO>('/teams/all');
|
||||
}
|
||||
|
||||
/** Get team details */
|
||||
getDetails(teamId: string): Promise<TeamDetailsDto | null> {
|
||||
return this.get<TeamDetailsDto | null>(`/teams/${teamId}`);
|
||||
getDetails(teamId: string): Promise<GetTeamDetailsOutputDTO | null> {
|
||||
return this.get<GetTeamDetailsOutputDTO | null>(`/teams/${teamId}`);
|
||||
}
|
||||
|
||||
/** Get team members */
|
||||
getMembers(teamId: string): Promise<TeamMembersDto> {
|
||||
return this.get<TeamMembersDto>(`/teams/${teamId}/members`);
|
||||
getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||
return this.get<GetTeamMembersOutputDTO>(`/teams/${teamId}/members`);
|
||||
}
|
||||
|
||||
/** Get team join requests */
|
||||
getJoinRequests(teamId: string): Promise<TeamJoinRequestsDto> {
|
||||
return this.get<TeamJoinRequestsDto>(`/teams/${teamId}/join-requests`);
|
||||
getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||
return this.get<GetTeamJoinRequestsOutputDTO>(`/teams/${teamId}/join-requests`);
|
||||
}
|
||||
|
||||
/** Create a new team */
|
||||
create(input: CreateTeamInputDto): Promise<CreateTeamOutputDto> {
|
||||
return this.post<CreateTeamOutputDto>('/teams', input);
|
||||
create(input: CreateTeamInputDTO): Promise<CreateTeamOutputDTO> {
|
||||
return this.post<CreateTeamOutputDTO>('/teams', input);
|
||||
}
|
||||
|
||||
/** Update team */
|
||||
update(teamId: string, input: UpdateTeamInputDto): Promise<UpdateTeamOutputDto> {
|
||||
return this.patch<UpdateTeamOutputDto>(`/teams/${teamId}`, input);
|
||||
update(teamId: string, input: UpdateTeamInputDTO): Promise<UpdateTeamOutputDTO> {
|
||||
return this.patch<UpdateTeamOutputDTO>(`/teams/${teamId}`, input);
|
||||
}
|
||||
|
||||
/** Get driver's team */
|
||||
getDriverTeam(driverId: string): Promise<DriverTeamDto | null> {
|
||||
return this.get<DriverTeamDto | null>(`/teams/driver/${driverId}`);
|
||||
getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||
return this.get<GetDriverTeamOutputDTO | null>(`/teams/driver/${driverId}`);
|
||||
}
|
||||
|
||||
/** Get membership for a driver in a team */
|
||||
getMembership(teamId: string, driverId: string): Promise<LeagueMemberDTO | null> {
|
||||
return this.get<LeagueMemberDTO | null>(`/teams/${teamId}/members/${driverId}`);
|
||||
getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||
return this.get<GetTeamMembershipOutputDTO | null>(`/teams/${teamId}/members/${driverId}`);
|
||||
}
|
||||
}
|
||||
@@ -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<AuthSessionDTO | null> {
|
||||
try {
|
||||
return await api.get<AuthSessionDTO>('/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<AuthSessionDTO> {
|
||||
return api.post<AuthSessionDTO>('/auth/signup', params);
|
||||
}
|
||||
|
||||
async loginWithEmail(params: LoginParams): Promise<AuthSessionDTO> {
|
||||
return api.post<AuthSessionDTO>('/auth/login', params);
|
||||
}
|
||||
|
||||
async startIracingAuthRedirect(returnTo?: string): Promise<IracingAuthRedirectResult> {
|
||||
const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
|
||||
return api.get<IracingAuthRedirectResult>(`/auth/iracing/start${query}`);
|
||||
}
|
||||
|
||||
async loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise<AuthSessionDTO> {
|
||||
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<AuthSessionDTO>(`/auth/iracing/callback?${query.toString()}`);
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
return api.post<void>('/auth/logout', {});
|
||||
}
|
||||
}
|
||||
|
||||
export const authApiClient = new AuthApiClient();
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
}
|
||||
import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO';
|
||||
import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO';
|
||||
|
||||
/**
|
||||
* Analytics Service
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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<CreateLeagueResult> {
|
||||
): Promise<CreateLeagueOutputDTO> {
|
||||
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<CreateLeagueResult> {
|
||||
): Promise<CreateLeagueOutputDTO> {
|
||||
return this.createLeague(form, ownerId);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user