resolve manual DTOs
This commit is contained in:
@@ -8,6 +8,85 @@
|
|||||||
"paths": {},
|
"paths": {},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"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": {
|
"SponsorshipPricingItemDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -161,6 +240,32 @@
|
|||||||
"sponsorName"
|
"sponsorName"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"SponsorDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"RejectSponsorshipRequestInputDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"respondedBy": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"respondedBy"
|
||||||
|
]
|
||||||
|
},
|
||||||
"GetSponsorSponsorshipsQueryParamsDTO": {
|
"GetSponsorSponsorshipsQueryParamsDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -183,6 +288,21 @@
|
|||||||
"sponsorId"
|
"sponsorId"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"GetPendingSponsorshipRequestsOutputDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"entityType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"entityId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"entityType",
|
||||||
|
"entityId"
|
||||||
|
]
|
||||||
|
},
|
||||||
"CreateSponsorInputDTO": {
|
"CreateSponsorInputDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -198,6 +318,17 @@
|
|||||||
"contactEmail"
|
"contactEmail"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"AcceptSponsorshipRequestInputDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"respondedBy": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"respondedBy"
|
||||||
|
]
|
||||||
|
},
|
||||||
"WithdrawFromRaceParamsDTO": {
|
"WithdrawFromRaceParamsDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -213,6 +344,25 @@
|
|||||||
"driverId"
|
"driverId"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ReviewProtestCommandDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"protestId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"stewardId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enum": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"protestId",
|
||||||
|
"stewardId",
|
||||||
|
"enum"
|
||||||
|
]
|
||||||
|
},
|
||||||
"RequestProtestDefenseCommandDTO": {
|
"RequestProtestDefenseCommandDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -874,25 +1024,6 @@
|
|||||||
"enum"
|
"enum"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"RequestAvatarGenerationInputDTO": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"userId": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"facePhotoData": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"suitColor": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"userId",
|
|
||||||
"facePhotoData",
|
|
||||||
"suitColor"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"UpdatePaymentStatusInputDTO": {
|
"UpdatePaymentStatusInputDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1058,99 +1189,7 @@
|
|||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"GetDriverRegistrationStatusQueryDTO": {
|
"UploadMediaOutputDTO": {
|
||||||
"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": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"success": {
|
"success": {
|
||||||
@@ -1161,27 +1200,101 @@
|
|||||||
"success"
|
"success"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CompleteOnboardingInputDTO": {
|
"UpdateAvatarOutputDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"firstName": {
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"success"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"UpdateAvatarInputDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"driverId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"lastName": {
|
"avatarUrl": {
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"displayName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"country": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"firstName",
|
"driverId",
|
||||||
"lastName",
|
"avatarUrl"
|
||||||
"displayName",
|
]
|
||||||
"country"
|
},
|
||||||
|
"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": {
|
"UpdateLeagueMemberRoleOutputDTO": {
|
||||||
@@ -1651,6 +1764,330 @@
|
|||||||
"leagueId"
|
"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": {
|
"AuthenticatedUserDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1710,6 +2147,52 @@
|
|||||||
"eventId",
|
"eventId",
|
||||||
"engagementWeight"
|
"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 { LoggingModule } from './infrastructure/logging/LoggingModule';
|
||||||
import { BootstrapModule } from './infrastructure/bootstrap/BootstrapModule';
|
import { BootstrapModule } from './infrastructure/bootstrap/BootstrapModule';
|
||||||
import { AuthModule } from './domain/auth/AuthModule';
|
import { AuthModule } from './domain/auth/AuthModule';
|
||||||
|
import { DashboardModule } from './domain/dashboard/DashboardModule';
|
||||||
import { LeagueModule } from './domain/league/LeagueModule';
|
import { LeagueModule } from './domain/league/LeagueModule';
|
||||||
import { RaceModule } from './domain/race/RaceModule';
|
import { RaceModule } from './domain/race/RaceModule';
|
||||||
|
import { ProtestsModule } from './domain/protests/ProtestsModule';
|
||||||
import { TeamModule } from './domain/team/TeamModule';
|
import { TeamModule } from './domain/team/TeamModule';
|
||||||
import { SponsorModule } from './domain/sponsor/SponsorModule';
|
import { SponsorModule } from './domain/sponsor/SponsorModule';
|
||||||
import { DriverModule } from './domain/driver/DriverModule';
|
import { DriverModule } from './domain/driver/DriverModule';
|
||||||
@@ -22,8 +24,10 @@ import { PaymentsModule } from './domain/payments/PaymentsModule';
|
|||||||
BootstrapModule,
|
BootstrapModule,
|
||||||
AnalyticsModule,
|
AnalyticsModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
DashboardModule,
|
||||||
LeagueModule,
|
LeagueModule,
|
||||||
RaceModule,
|
RaceModule,
|
||||||
|
ProtestsModule,
|
||||||
TeamModule,
|
TeamModule,
|
||||||
SponsorModule,
|
SponsorModule,
|
||||||
DriverModule,
|
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 { Response } from 'express';
|
||||||
import type { RecordPageViewInputDTO } from './dtos/RecordPageViewInputDTO';
|
import type { RecordPageViewInputDTO } from './dtos/RecordPageViewInputDTO';
|
||||||
import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO';
|
import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO';
|
||||||
import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO';
|
import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO';
|
||||||
import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO';
|
import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO';
|
||||||
|
import type { GetDashboardDataOutputDTO } from './dtos/GetDashboardDataOutputDTO';
|
||||||
|
import type { GetAnalyticsMetricsOutputDTO } from './dtos/GetAnalyticsMetricsOutputDTO';
|
||||||
import { AnalyticsService } from './AnalyticsService';
|
import { AnalyticsService } from './AnalyticsService';
|
||||||
|
|
||||||
type RecordPageViewInput = RecordPageViewInputDTO;
|
type RecordPageViewInput = RecordPageViewInputDTO;
|
||||||
type RecordPageViewOutput = RecordPageViewOutputDTO;
|
type RecordPageViewOutput = RecordPageViewOutputDTO;
|
||||||
type RecordEngagementInput = RecordEngagementInputDTO;
|
type RecordEngagementInput = RecordEngagementInputDTO;
|
||||||
type RecordEngagementOutput = RecordEngagementOutputDTO;
|
type RecordEngagementOutput = RecordEngagementOutputDTO;
|
||||||
|
type GetDashboardDataOutput = GetDashboardDataOutputDTO;
|
||||||
|
type GetAnalyticsMetricsOutput = GetAnalyticsMetricsOutputDTO;
|
||||||
|
|
||||||
|
@ApiTags('analytics')
|
||||||
@Controller('analytics')
|
@Controller('analytics')
|
||||||
export class AnalyticsController {
|
export class AnalyticsController {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -18,6 +24,9 @@ export class AnalyticsController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('page-view')
|
@Post('page-view')
|
||||||
|
@ApiOperation({ summary: 'Record a page view' })
|
||||||
|
@ApiBody({ type: RecordPageViewInputDTO })
|
||||||
|
@ApiResponse({ status: 201, description: 'Page view recorded', type: RecordPageViewOutputDTO })
|
||||||
async recordPageView(
|
async recordPageView(
|
||||||
@Body() input: RecordPageViewInput,
|
@Body() input: RecordPageViewInput,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
@@ -27,6 +36,9 @@ export class AnalyticsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('engagement')
|
@Post('engagement')
|
||||||
|
@ApiOperation({ summary: 'Record an engagement event' })
|
||||||
|
@ApiBody({ type: RecordEngagementInputDTO })
|
||||||
|
@ApiResponse({ status: 201, description: 'Engagement recorded', type: RecordEngagementOutputDTO })
|
||||||
async recordEngagement(
|
async recordEngagement(
|
||||||
@Body() input: RecordEngagementInput,
|
@Body() input: RecordEngagementInput,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
@@ -34,4 +46,18 @@ export class AnalyticsController {
|
|||||||
const output: RecordEngagementOutput = await this.analyticsService.recordEngagement(input);
|
const output: RecordEngagementOutput = await this.analyticsService.recordEngagement(input);
|
||||||
res.status(HttpStatus.CREATED).json(output);
|
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 { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO';
|
||||||
import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO';
|
import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO';
|
||||||
import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO';
|
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 type { Logger } from '@core/shared/application';
|
||||||
import { RecordPageViewUseCase } from './use-cases/RecordPageViewUseCase';
|
import { RecordPageViewUseCase } from './use-cases/RecordPageViewUseCase';
|
||||||
import { RecordEngagementUseCase } from './use-cases/RecordEngagementUseCase';
|
import { RecordEngagementUseCase } from './use-cases/RecordEngagementUseCase';
|
||||||
@@ -11,6 +13,8 @@ type RecordPageViewInput = RecordPageViewInputDTO;
|
|||||||
type RecordPageViewOutput = RecordPageViewOutputDTO;
|
type RecordPageViewOutput = RecordPageViewOutputDTO;
|
||||||
type RecordEngagementInput = RecordEngagementInputDTO;
|
type RecordEngagementInput = RecordEngagementInputDTO;
|
||||||
type RecordEngagementOutput = RecordEngagementOutputDTO;
|
type RecordEngagementOutput = RecordEngagementOutputDTO;
|
||||||
|
type GetDashboardDataOutput = GetDashboardDataOutputDTO;
|
||||||
|
type GetAnalyticsMetricsOutput = GetAnalyticsMetricsOutputDTO;
|
||||||
|
|
||||||
const Logger_TOKEN = 'Logger_TOKEN';
|
const Logger_TOKEN = 'Logger_TOKEN';
|
||||||
const RECORD_PAGE_VIEW_USE_CASE_TOKEN = 'RecordPageViewUseCase_TOKEN';
|
const RECORD_PAGE_VIEW_USE_CASE_TOKEN = 'RecordPageViewUseCase_TOKEN';
|
||||||
@@ -31,4 +35,24 @@ export class AnalyticsService {
|
|||||||
async recordEngagement(input: RecordEngagementInput): Promise<RecordEngagementOutput> {
|
async recordEngagement(input: RecordEngagementInput): Promise<RecordEngagementOutput> {
|
||||||
return await this.recordEngagementUseCase.execute(input);
|
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 { Controller, Get, Post, Body, Query, Res, Redirect, HttpStatus } from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { AuthService } from './AuthService';
|
import { AuthService } from './AuthService';
|
||||||
import { LoginParams, SignupParams, LoginWithIracingCallbackParams } from './dto/AuthDto';
|
import { LoginParams, SignupParams, LoginWithIracingCallbackParams, AuthSessionDTO, IracingAuthRedirectResult } from './dto/AuthDto';
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private readonly authService: AuthService) {}
|
constructor(private readonly authService: AuthService) {}
|
||||||
|
|
||||||
@Post('signup')
|
@Post('signup')
|
||||||
async signup(@Body() params: SignupParams) {
|
async signup(@Body() params: SignupParams): Promise<AuthSessionDTO> {
|
||||||
return this.authService.signupWithEmail(params);
|
return this.authService.signupWithEmail(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
async login(@Body() params: LoginParams) {
|
async login(@Body() params: LoginParams): Promise<AuthSessionDTO> {
|
||||||
return this.authService.loginWithEmail(params);
|
return this.authService.loginWithEmail(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('session')
|
@Get('session')
|
||||||
async getSession() {
|
async getSession(): Promise<AuthSessionDTO | null> {
|
||||||
return this.authService.getCurrentSession();
|
return this.authService.getCurrentSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('logout')
|
@Post('logout')
|
||||||
async logout() {
|
async logout(): Promise<void> {
|
||||||
return this.authService.logout();
|
return this.authService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('iracing/start')
|
@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);
|
const { redirectUrl, state } = await this.authService.startIracingAuthRedirect(returnTo);
|
||||||
// In real application, you might want to store 'state' in a secure cookie or session.
|
// In real application, you might want to store 'state' in a secure cookie or session.
|
||||||
// For this example, we'll just redirect.
|
// For this example, we'll just redirect.
|
||||||
@@ -36,7 +36,7 @@ export class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('iracing/callback')
|
@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 });
|
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 { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
|
||||||
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
|
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
|
||||||
import { DriverDTO } from './dtos/DriverDTO';
|
import { DriverDTO } from './dtos/DriverDTO';
|
||||||
|
import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
|
||||||
|
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
|
||||||
|
|
||||||
@ApiTags('drivers')
|
@ApiTags('drivers')
|
||||||
@Controller('drivers')
|
@Controller('drivers')
|
||||||
@@ -31,9 +33,9 @@ export class DriverController {
|
|||||||
|
|
||||||
@Get('current')
|
@Get('current')
|
||||||
@ApiOperation({ summary: 'Get current authenticated driver' })
|
@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' })
|
@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)
|
// Assuming userId is available from the request (e.g., via auth middleware)
|
||||||
const userId = req['user']?.userId;
|
const userId = req['user']?.userId;
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
@@ -64,13 +66,29 @@ export class DriverController {
|
|||||||
return this.driverService.getDriverRegistrationStatus({ driverId, raceId });
|
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')
|
@Put(':driverId/profile')
|
||||||
@ApiOperation({ summary: 'Update driver 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(
|
async updateDriverProfile(
|
||||||
@Param('driverId') driverId: string,
|
@Param('driverId') driverId: string,
|
||||||
@Body() body: { bio?: string; country?: string },
|
@Body() body: { bio?: string; country?: string },
|
||||||
): Promise<DriverDTO | null> {
|
): Promise<GetDriverOutputDTO | null> {
|
||||||
return this.driverService.updateDriverProfile(driverId, body.bio, body.country);
|
return this.driverService.updateDriverProfile(driverId, body.bio, body.country);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { CompleteOnboardingInputDTO } from './dtos/CompleteOnboardingInputDTO';
|
|||||||
import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO';
|
import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO';
|
||||||
import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
|
import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
|
||||||
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
|
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
|
||||||
|
import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
|
||||||
|
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
|
||||||
|
|
||||||
// Use cases
|
// Use cases
|
||||||
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
|
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
|
||||||
@@ -76,7 +78,7 @@ export class DriverService {
|
|||||||
return presenter.viewModel;
|
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}`);
|
this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`);
|
||||||
|
|
||||||
const driver = await this.driverRepository.findById(userId);
|
const driver = await this.driverRepository.findById(userId);
|
||||||
@@ -86,11 +88,15 @@ export class DriverService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: driver.id,
|
id: driver.id,
|
||||||
|
iracingId: driver.iracingId.value,
|
||||||
name: driver.name.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}`);
|
this.logger.debug(`[DriverService] Updating driver profile for driverId: ${driverId}`);
|
||||||
|
|
||||||
const result = await this.updateDriverProfileUseCase.execute({ driverId, bio, country });
|
const result = await this.updateDriverProfileUseCase.execute({ driverId, bio, country });
|
||||||
@@ -101,4 +107,40 @@ export class DriverService {
|
|||||||
|
|
||||||
return result.value;
|
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 { GetLeagueSeasonsQueryDTO } from './dtos/GetLeagueSeasonsQueryDTO';
|
||||||
import { GetLeagueAdminConfigQueryDTO } from './dtos/GetLeagueAdminConfigQueryDTO';
|
import { GetLeagueAdminConfigQueryDTO } from './dtos/GetLeagueAdminConfigQueryDTO';
|
||||||
import { GetLeagueOwnerSummaryQueryDTO } from './dtos/GetLeagueOwnerSummaryQueryDTO';
|
import { GetLeagueOwnerSummaryQueryDTO } from './dtos/GetLeagueOwnerSummaryQueryDTO';
|
||||||
|
import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO';
|
||||||
|
import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO';
|
||||||
|
|
||||||
@ApiTags('leagues')
|
@ApiTags('leagues')
|
||||||
@Controller('leagues')
|
@Controller('leagues')
|
||||||
@@ -262,4 +264,18 @@ export class LeagueController {
|
|||||||
async transferLeagueOwnership(@Param('leagueId') leagueId: string, @Body() body: { currentOwnerId: string, newOwnerId: string }) {
|
async transferLeagueOwnership(@Param('leagueId') leagueId: string, @Body() body: { currentOwnerId: string, newOwnerId: string }) {
|
||||||
return this.leagueService.transferLeagueOwnership(leagueId, body.currentOwnerId, body.newOwnerId);
|
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 { LeagueAdminDTO } from './dtos/LeagueAdminDTO';
|
||||||
import { CreateLeagueInputDTO } from './dtos/CreateLeagueInputDTO';
|
import { CreateLeagueInputDTO } from './dtos/CreateLeagueInputDTO';
|
||||||
import { CreateLeagueOutputDTO } from './dtos/CreateLeagueOutputDTO';
|
import { CreateLeagueOutputDTO } from './dtos/CreateLeagueOutputDTO';
|
||||||
|
import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO';
|
||||||
|
import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO';
|
||||||
|
|
||||||
// Core imports
|
// Core imports
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
import type { Logger } from '@core/shared/application/Logger';
|
||||||
@@ -328,4 +330,24 @@ export class LeagueService {
|
|||||||
success: true,
|
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 { Controller, Post, Get, Delete, Put, Body, HttpStatus, Res, Param, UseInterceptors, UploadedFile } from '@nestjs/common';
|
||||||
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiResponse, ApiOperation, ApiParam, ApiConsumes } from '@nestjs/swagger';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { MediaService } from './MediaService';
|
import { MediaService } from './MediaService';
|
||||||
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
||||||
import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
|
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 RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
|
||||||
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
|
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')
|
@ApiTags('media')
|
||||||
@Controller('media')
|
@Controller('media')
|
||||||
@@ -27,4 +42,79 @@ export class MediaController {
|
|||||||
res.status(HttpStatus.BAD_REQUEST).json(result);
|
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 { Injectable, Inject } from '@nestjs/common';
|
||||||
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
||||||
import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
|
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 RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
|
||||||
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
|
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
|
// Use cases
|
||||||
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||||
@@ -33,4 +47,42 @@ export class MediaService {
|
|||||||
}, presenter);
|
}, presenter);
|
||||||
return presenter.viewModel;
|
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 { Controller, Get, Post, Patch, Delete, Body, Query, HttpCode, HttpStatus } from '@nestjs/common';
|
||||||
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
|
||||||
import { PaymentsService } from './PaymentsService';
|
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')
|
@ApiTags('payments')
|
||||||
@Controller('payments')
|
@Controller('payments')
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import type {
|
|||||||
GetWalletOutput,
|
GetWalletOutput,
|
||||||
ProcessWalletTransactionInput,
|
ProcessWalletTransactionInput,
|
||||||
ProcessWalletTransactionOutput,
|
ProcessWalletTransactionOutput,
|
||||||
} from './dto/PaymentsDto';
|
} from './dtos/PaymentsDto';
|
||||||
|
|
||||||
// Injection tokens
|
// Injection tokens
|
||||||
import {
|
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 { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO';
|
||||||
import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO';
|
import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO';
|
||||||
import { ImportRaceResultsSummaryDTO } from './dtos/ImportRaceResultsSummaryDTO';
|
import { ImportRaceResultsSummaryDTO } from './dtos/ImportRaceResultsSummaryDTO';
|
||||||
import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO';
|
|
||||||
import { FileProtestCommandDTO } from './dtos/FileProtestCommandDTO';
|
import { FileProtestCommandDTO } from './dtos/FileProtestCommandDTO';
|
||||||
import { QuickPenaltyCommandDTO } from './dtos/QuickPenaltyCommandDTO';
|
import { QuickPenaltyCommandDTO } from './dtos/QuickPenaltyCommandDTO';
|
||||||
import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO';
|
import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO';
|
||||||
@@ -152,13 +151,6 @@ export class RaceController {
|
|||||||
return this.raceService.importRaceResults({ raceId, ...body });
|
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')
|
@Post('protests/file')
|
||||||
@HttpCode(HttpStatus.OK)
|
@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 { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
|
||||||
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
|
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
|
||||||
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
|
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 { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
|
||||||
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
|
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
|
||||||
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
|
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
|
||||||
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
|
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
|
||||||
|
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||||
|
|
||||||
// Define injection tokens
|
// Define injection tokens
|
||||||
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
||||||
@@ -239,4 +239,10 @@ export const RaceProviders: Provider[] = [
|
|||||||
new RequestProtestDefenseUseCase(protestRepo, raceRepo, leagueMembershipRepo),
|
new RequestProtestDefenseUseCase(protestRepo, raceRepo, leagueMembershipRepo),
|
||||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
|
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 { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
|
||||||
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
|
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
|
||||||
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
|
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 { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
|
||||||
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
|
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
|
||||||
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
|
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
|
||||||
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
|
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
|
||||||
|
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||||
|
|
||||||
// Presenters
|
// Presenters
|
||||||
import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter';
|
import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter';
|
||||||
@@ -67,11 +67,11 @@ export class RaceService {
|
|||||||
private readonly cancelRaceUseCase: CancelRaceUseCase,
|
private readonly cancelRaceUseCase: CancelRaceUseCase,
|
||||||
private readonly completeRaceUseCase: CompleteRaceUseCase,
|
private readonly completeRaceUseCase: CompleteRaceUseCase,
|
||||||
private readonly importRaceResultsUseCase: ImportRaceResultsUseCase,
|
private readonly importRaceResultsUseCase: ImportRaceResultsUseCase,
|
||||||
private readonly dashboardOverviewUseCase: DashboardOverviewUseCase,
|
|
||||||
private readonly fileProtestUseCase: FileProtestUseCase,
|
private readonly fileProtestUseCase: FileProtestUseCase,
|
||||||
private readonly quickPenaltyUseCase: QuickPenaltyUseCase,
|
private readonly quickPenaltyUseCase: QuickPenaltyUseCase,
|
||||||
private readonly applyPenaltyUseCase: ApplyPenaltyUseCase,
|
private readonly applyPenaltyUseCase: ApplyPenaltyUseCase,
|
||||||
private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase,
|
private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase,
|
||||||
|
private readonly reviewProtestUseCase: ReviewProtestUseCase,
|
||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@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> {
|
async fileProtest(command: any): Promise<any> {
|
||||||
this.logger.debug('[RaceService] Filing protest:', command);
|
this.logger.debug('[RaceService] Filing protest:', command);
|
||||||
@@ -294,4 +283,16 @@ export class RaceService {
|
|||||||
|
|
||||||
return result.value;
|
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 { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
|
||||||
import { SponsorService } from './SponsorService';
|
import { SponsorService } from './SponsorService';
|
||||||
import { GetEntitySponsorshipPricingResultDTO } from './dtos/GetEntitySponsorshipPricingResultDTO';
|
import { GetEntitySponsorshipPricingResultDTO } from './dtos/GetEntitySponsorshipPricingResultDTO';
|
||||||
@@ -9,6 +9,10 @@ import { GetSponsorDashboardQueryParamsDTO } from './dtos/GetSponsorDashboardQue
|
|||||||
import { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO';
|
import { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO';
|
||||||
import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO';
|
import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO';
|
||||||
import { SponsorSponsorshipsDTO } from './dtos/SponsorSponsorshipsDTO';
|
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')
|
@ApiTags('sponsors')
|
||||||
@Controller('sponsors')
|
@Controller('sponsors')
|
||||||
@@ -52,4 +56,39 @@ export class SponsorController {
|
|||||||
async getSponsorSponsorships(@Param('sponsorId') sponsorId: string): Promise<SponsorSponsorshipsDTO | null> {
|
async getSponsorSponsorships(@Param('sponsorId') sponsorId: string): Promise<SponsorSponsorshipsDTO | null> {
|
||||||
return this.sponsorService.getSponsorSponsorships({ sponsorId } as GetSponsorSponsorshipsQueryParamsDTO);
|
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 { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||||
import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
||||||
import { GetEntitySponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
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 concrete in-memory implementations
|
||||||
import { InMemorySponsorRepository } from '@adapters/racing/persistence/inmemory/InMemorySponsorRepository';
|
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_DASHBOARD_USE_CASE_TOKEN = 'GetSponsorDashboardUseCase';
|
||||||
export const GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN = 'GetSponsorSponsorshipsUseCase';
|
export const GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN = 'GetSponsorSponsorshipsUseCase';
|
||||||
export const GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetEntitySponsorshipPricingUseCase';
|
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[] = [
|
export const SponsorProviders: Provider[] = [
|
||||||
SponsorService,
|
SponsorService,
|
||||||
@@ -131,4 +139,27 @@ export const SponsorProviders: Provider[] = [
|
|||||||
new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, sponsorshipRequestRepo, seasonSponsorshipRepo, logger),
|
new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, sponsorshipRequestRepo, seasonSponsorshipRepo, logger),
|
||||||
inject: [SPONSORSHIP_PRICING_REPOSITORY_TOKEN, SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
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 { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO';
|
||||||
import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO';
|
import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO';
|
||||||
import { SponsorSponsorshipsDTO } from './dtos/SponsorSponsorshipsDTO';
|
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 { SponsorDTO } from './dtos/SponsorDTO';
|
||||||
import { SponsorDashboardMetricsDTO } from './dtos/SponsorDashboardMetricsDTO';
|
import { SponsorDashboardMetricsDTO } from './dtos/SponsorDashboardMetricsDTO';
|
||||||
import { SponsoredLeagueDTO } from './dtos/SponsoredLeagueDTO';
|
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 { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
|
||||||
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||||
import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
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
|
// Presenters
|
||||||
import { GetSponsorshipPricingPresenter } from './presenters/GetSponsorshipPricingPresenter';
|
import { GetSponsorshipPricingPresenter } from './presenters/GetSponsorshipPricingPresenter';
|
||||||
@@ -28,7 +36,7 @@ import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPr
|
|||||||
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
|
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
|
||||||
|
|
||||||
// Tokens
|
// 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';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -39,6 +47,10 @@ export class SponsorService {
|
|||||||
@Inject(CREATE_SPONSOR_USE_CASE_TOKEN) private readonly createSponsorUseCase: CreateSponsorUseCase,
|
@Inject(CREATE_SPONSOR_USE_CASE_TOKEN) private readonly createSponsorUseCase: CreateSponsorUseCase,
|
||||||
@Inject(GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN) private readonly getSponsorDashboardUseCase: GetSponsorDashboardUseCase,
|
@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_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,
|
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -81,4 +93,48 @@ export class SponsorService {
|
|||||||
await this.getSponsorSponsorshipsUseCase.execute(params, presenter);
|
await this.getSponsorSponsorshipsUseCase.execute(params, presenter);
|
||||||
return presenter.viewModel as SponsorSponsorshipsDTO | null;
|
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 { Controller, Get, Post, Patch, Body, Req, Param } from '@nestjs/common';
|
||||||
import { ApiTags, ApiResponse, ApiOperation, ApiBody } from '@nestjs/swagger';
|
import { Request } from 'express';
|
||||||
|
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
|
||||||
import { TeamService } from './TeamService';
|
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')
|
@ApiTags('teams')
|
||||||
@Controller('teams')
|
@Controller('teams')
|
||||||
@@ -10,91 +20,63 @@ export class TeamController {
|
|||||||
|
|
||||||
@Get('all')
|
@Get('all')
|
||||||
@ApiOperation({ summary: 'Get all teams' })
|
@ApiOperation({ summary: 'Get all teams' })
|
||||||
@ApiResponse({ status: 200, description: 'List of all teams', type: AllTeamsViewModel })
|
@ApiResponse({ status: 200, description: 'List of all teams', type: GetAllTeamsOutputDTO })
|
||||||
async getAllTeams(): Promise<AllTeamsViewModel> {
|
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||||
return this.teamService.getAllTeams();
|
return this.teamService.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':teamId')
|
@Get(':teamId')
|
||||||
@ApiOperation({ summary: 'Get team details' })
|
@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' })
|
@ApiResponse({ status: 404, description: 'Team not found' })
|
||||||
async getTeamDetails(
|
async getDetails(@Param('teamId') teamId: string, @Req() req: Request): Promise<GetTeamDetailsOutputDTO | null> {
|
||||||
@Param('teamId') teamId: string,
|
const userId = req['user']?.userId;
|
||||||
): Promise<TeamDetailsViewModel | null> {
|
return this.teamService.getDetails(teamId, userId);
|
||||||
return this.teamService.getTeamDetails(teamId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':teamId/members')
|
@Get(':teamId/members')
|
||||||
@ApiOperation({ summary: 'Get team members' })
|
@ApiOperation({ summary: 'Get team members' })
|
||||||
@ApiResponse({ status: 200, description: 'Team members', type: TeamMembersViewModel })
|
@ApiResponse({ status: 200, description: 'Team members', type: GetTeamMembersOutputDTO })
|
||||||
async getTeamMembers(@Param('teamId') teamId: string): Promise<TeamMembersViewModel> {
|
async getMembers(@Param('teamId') teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||||
return this.teamService.getTeamMembers(teamId);
|
return this.teamService.getMembers(teamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':teamId/join-requests')
|
@Get(':teamId/join-requests')
|
||||||
@ApiOperation({ summary: 'Get team join requests' })
|
@ApiOperation({ summary: 'Get team join requests' })
|
||||||
@ApiResponse({ status: 200, description: 'Team join requests', type: TeamJoinRequestsViewModel })
|
@ApiResponse({ status: 200, description: 'Team join requests', type: GetTeamJoinRequestsOutputDTO })
|
||||||
async getTeamJoinRequests(@Param('teamId') teamId: string): Promise<TeamJoinRequestsViewModel> {
|
async getJoinRequests(@Param('teamId') teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||||
return this.teamService.getTeamJoinRequests(teamId);
|
return this.teamService.getJoinRequests(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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: 'Create a new team' })
|
@ApiOperation({ summary: 'Create a new team' })
|
||||||
@ApiBody({ type: CreateTeamInput })
|
@ApiResponse({ status: 201, description: 'Team created', type: CreateTeamOutputDTO })
|
||||||
@ApiResponse({ status: 201, description: 'Team created successfully', type: CreateTeamOutput })
|
async create(@Body() input: CreateTeamInputDTO, @Req() req: Request): Promise<CreateTeamOutputDTO> {
|
||||||
async createTeam(@Body() input: CreateTeamInput): Promise<CreateTeamOutput> {
|
const userId = req['user']?.userId;
|
||||||
return this.teamService.createTeam(input);
|
return this.teamService.create(input, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':teamId')
|
@Patch(':teamId')
|
||||||
@ApiOperation({ summary: 'Update team details' })
|
@ApiOperation({ summary: 'Update team' })
|
||||||
@ApiBody({ type: UpdateTeamInput })
|
@ApiResponse({ status: 200, description: 'Team updated', type: UpdateTeamOutputDTO })
|
||||||
@ApiResponse({ status: 200, description: 'Team updated successfully', type: UpdateTeamOutput })
|
async update(@Param('teamId') teamId: string, @Body() input: UpdateTeamInputDTO, @Req() req: Request): Promise<UpdateTeamOutputDTO> {
|
||||||
@ApiResponse({ status: 404, description: 'Team not found' })
|
const userId = req['user']?.userId;
|
||||||
async updateTeam(
|
return this.teamService.update(teamId, input, userId);
|
||||||
@Param('teamId') teamId: string,
|
|
||||||
@Body() input: UpdateTeamInput,
|
|
||||||
): Promise<UpdateTeamOutput> {
|
|
||||||
return this.teamService.updateTeam({ ...input, teamId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('driver/:driverId')
|
@Get('driver/:driverId')
|
||||||
@ApiOperation({ summary: 'Get team for a driver' })
|
@ApiOperation({ summary: 'Get driver\'s team' })
|
||||||
@ApiResponse({ status: 200, description: 'Driver team membership', type: DriverTeamViewModel })
|
@ApiResponse({ status: 200, description: 'Driver\'s team', type: GetDriverTeamOutputDTO })
|
||||||
@ApiResponse({ status: 404, description: 'Driver not in a team' })
|
@ApiResponse({ status: 404, description: 'Team not found' })
|
||||||
async getDriverTeam(@Param('driverId') driverId: string): Promise<DriverTeamViewModel | null> {
|
async getDriverTeam(@Param('driverId') driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||||
return this.teamService.getDriverTeam({ teamId: '', driverId });
|
return this.teamService.getDriverTeam(driverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('leaderboard')
|
@Get(':teamId/members/:driverId')
|
||||||
@ApiOperation({ summary: 'Get teams leaderboard' })
|
@ApiOperation({ summary: 'Get team membership for a driver' })
|
||||||
@ApiResponse({ status: 200, description: 'Teams leaderboard' })
|
@ApiResponse({ status: 200, description: 'Team membership', type: GetTeamMembershipOutputDTO })
|
||||||
async getTeamsLeaderboard(): Promise<any> {
|
@ApiResponse({ status: 404, description: 'Membership not found' })
|
||||||
return this.teamService.getTeamsLeaderboard();
|
async getMembership(@Param('teamId') teamId: string, @Param('driverId') driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||||
|
return this.teamService.getMembership(teamId, driverId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,166 +1,6 @@
|
|||||||
import { Provider } from '@nestjs/common';
|
import { Provider } from '@nestjs/common';
|
||||||
import { TeamService } from './TeamService';
|
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[] = [
|
export const TeamProviders: Provider[] = [
|
||||||
TeamService, // Provide the service itself
|
TeamService,
|
||||||
{
|
|
||||||
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],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
@@ -1,181 +1,72 @@
|
|||||||
import { Injectable, Inject } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { AllTeamsViewModel, GetDriverTeamQuery, 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';
|
||||||
// Use cases
|
import { GetTeamMembersOutputDTO } from './dtos/GetTeamMembersOutputDTO';
|
||||||
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
import { GetTeamJoinRequestsOutputDTO } from './dtos/GetTeamJoinRequestsOutputDTO';
|
||||||
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO';
|
||||||
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
|
import { CreateTeamOutputDTO } from './dtos/CreateTeamOutputDTO';
|
||||||
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
|
import { UpdateTeamInputDTO } from './dtos/UpdateTeamInputDTO';
|
||||||
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
|
import { UpdateTeamOutputDTO } from './dtos/UpdateTeamOutputDTO';
|
||||||
import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase';
|
import { GetDriverTeamOutputDTO } from './dtos/GetDriverTeamOutputDTO';
|
||||||
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
|
import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO';
|
||||||
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';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamService {
|
export class TeamService {
|
||||||
constructor(
|
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||||
@Inject(TEAM_GET_ALL_USE_CASE_TOKEN) private readonly getAllTeamsUseCase: GetAllTeamsUseCase,
|
// TODO: Implement getAll teams logic
|
||||||
@Inject(TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN) private readonly getDriverTeamUseCase: GetDriverTeamUseCase,
|
return {
|
||||||
@Inject(TEAM_GET_DETAILS_USE_CASE_TOKEN) private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase,
|
teams: [],
|
||||||
@Inject(TEAM_GET_MEMBERS_USE_CASE_TOKEN) private readonly getTeamMembersUseCase: GetTeamMembersUseCase,
|
totalCount: 0,
|
||||||
@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 getDriverTeam(query: GetDriverTeamQuery): Promise<DriverTeamViewModel | null> {
|
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
|
||||||
this.logger.debug(`[TeamService] Fetching driver team for driverId: ${query.driverId}`);
|
// TODO: Implement get team details logic
|
||||||
|
return null;
|
||||||
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 getTeamDetails(teamId: string): Promise<TeamDetailsViewModel | null> {
|
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}`);
|
// TODO: Implement get team members logic
|
||||||
|
return {
|
||||||
const presenter = new TeamDetailsPresenter();
|
members: [],
|
||||||
try {
|
totalCount: 0,
|
||||||
await this.getTeamDetailsUseCase.execute({ teamId, driverId: '' }, presenter);
|
ownerCount: 0,
|
||||||
return presenter.viewModel as unknown as TeamDetailsViewModel;
|
managerCount: 0,
|
||||||
} catch (error) {
|
memberCount: 0,
|
||||||
this.logger.error(`Error fetching team details: ${error}`);
|
};
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTeamMembers(teamId: string): Promise<TeamMembersViewModel> {
|
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
|
// TODO: Implement get team join requests logic
|
||||||
|
return {
|
||||||
const presenter = new TeamMembersPresenter();
|
requests: [],
|
||||||
await this.getTeamMembersUseCase.execute({ teamId }, presenter);
|
pendingCount: 0,
|
||||||
return presenter.viewModel as unknown as TeamMembersViewModel;
|
totalCount: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTeamJoinRequests(teamId: string): Promise<TeamJoinRequestsViewModel> {
|
async create(input: CreateTeamInputDTO, userId?: string): Promise<CreateTeamOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Fetching join requests for teamId: ${teamId}`);
|
// TODO: Implement create team logic
|
||||||
|
return {
|
||||||
const presenter = new TeamJoinRequestsPresenter();
|
id: 'placeholder-id',
|
||||||
await this.getTeamJoinRequestsUseCase.execute({ teamId }, presenter);
|
success: true,
|
||||||
return presenter.viewModel as unknown as TeamJoinRequestsViewModel;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTeam(input: CreateTeamInput): Promise<CreateTeamOutput> {
|
async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise<UpdateTeamOutputDTO> {
|
||||||
this.logger.debug('[TeamService] Creating team', input);
|
// TODO: Implement update team logic
|
||||||
|
return {
|
||||||
try {
|
success: true,
|
||||||
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 updateTeam(input: UpdateTeamInput & { teamId: string }): Promise<UpdateTeamOutput> {
|
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||||
this.logger.debug('[TeamService] Updating team', input);
|
// TODO: Implement get driver team logic
|
||||||
|
return null;
|
||||||
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 approveTeamJoinRequest(input: ApproveTeamJoinRequestInput & { teamId: string }): Promise<ApproveTeamJoinRequestOutput> {
|
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||||
this.logger.debug('[TeamService] Approving team join request', input);
|
// TODO: Implement get team membership logic
|
||||||
|
return null;
|
||||||
try {
|
|
||||||
await this.approveTeamJoinRequestUseCase.execute({ requestId: input.requestId });
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`Error approving join request: ${error}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
.setTitle('GridPilot API')
|
||||||
.setDescription('GridPilot API documentation')
|
.setDescription('GridPilot API documentation')
|
||||||
.setVersion('1.0')
|
.setVersion('1.0')
|
||||||
|
.addTag('dashboard', 'Dashboard endpoints')
|
||||||
.addTag('races', 'Race management endpoints')
|
.addTag('races', 'Race management endpoints')
|
||||||
.addTag('leagues', 'League management endpoints')
|
.addTag('leagues', 'League management endpoints')
|
||||||
.addTag('teams', 'Team management endpoints')
|
.addTag('teams', 'Team management endpoints')
|
||||||
|
|||||||
@@ -173,8 +173,7 @@ interface TeamLeaderboardPreviewProps {
|
|||||||
function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewProps) {
|
function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const top5 = [...teams]
|
const top5 = [...teams]
|
||||||
.filter((t) => t.rating !== null)
|
.sort((a, b) => b.memberCount - a.memberCount)
|
||||||
.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0))
|
|
||||||
.slice(0, 5);
|
.slice(0, 5);
|
||||||
|
|
||||||
const getMedalColor = (position: number) => {
|
const getMedalColor = (position: number) => {
|
||||||
@@ -257,8 +256,8 @@ function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewPr
|
|||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="flex items-center gap-4 text-sm">
|
<div className="flex items-center gap-4 text-sm">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-purple-400 font-mono font-semibold">{team.rating?.toLocaleString()}</p>
|
<p className="text-purple-400 font-mono font-semibold">{team.memberCount}</p>
|
||||||
<p className="text-[10px] text-gray-500">Rating</p>
|
<p className="text-[10px] text-gray-500">Members</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-performance-green font-mono font-semibold">{team.totalWins}</p>
|
<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 { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||||
import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles';
|
import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles';
|
||||||
import { useServices } from '@/lib/services/ServiceProvider';
|
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 { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel';
|
||||||
import { AlertTriangle, Settings, UserCog } from 'lucide-react';
|
import { AlertTriangle, Settings, UserCog } from 'lucide-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Button from '@/components/ui/Button';
|
|||||||
import Card from '@/components/ui/Card';
|
import Card from '@/components/ui/Card';
|
||||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||||
import { useServices } from '@/lib/services/ServiceProvider';
|
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 type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|||||||
@@ -1,24 +1,10 @@
|
|||||||
import { BaseApiClient } from '../base/BaseApiClient';
|
import { BaseApiClient } from '../base/BaseApiClient';
|
||||||
import { RecordPageViewOutputDTO } from '../../types/generated/RecordPageViewOutputDTO';
|
import { RecordPageViewOutputDTO } from '../../types/generated/RecordPageViewOutputDTO';
|
||||||
import { RecordEngagementOutputDTO } from '../../types/generated/RecordEngagementOutputDTO';
|
import { RecordEngagementOutputDTO } from '../../types/generated/RecordEngagementOutputDTO';
|
||||||
|
import { GetDashboardDataOutputDTO } from '../../types/generated/GetDashboardDataOutputDTO';
|
||||||
// TODO: Move these types to apps/website/lib/types/generated when available
|
import { GetAnalyticsMetricsOutputDTO } from '../../types/generated/GetAnalyticsMetricsOutputDTO';
|
||||||
type RecordPageViewInputDto = { path: string; userId?: string };
|
import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO';
|
||||||
type RecordEngagementInputDto = { eventType: string; userId?: string; metadata?: Record<string, unknown> };
|
import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO';
|
||||||
|
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analytics API Client
|
* Analytics API Client
|
||||||
@@ -27,22 +13,22 @@ type AnalyticsMetricsDto = {
|
|||||||
*/
|
*/
|
||||||
export class AnalyticsApiClient extends BaseApiClient {
|
export class AnalyticsApiClient extends BaseApiClient {
|
||||||
/** Record a page view */
|
/** Record a page view */
|
||||||
recordPageView(input: RecordPageViewInputDto): Promise<RecordPageViewOutputDTO> {
|
recordPageView(input: RecordPageViewInputDTO): Promise<RecordPageViewOutputDTO> {
|
||||||
return this.post<RecordPageViewOutputDTO>('/analytics/page-view', input);
|
return this.post<RecordPageViewOutputDTO>('/analytics/page-view', input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Record an engagement event */
|
/** Record an engagement event */
|
||||||
recordEngagement(input: RecordEngagementInputDto): Promise<RecordEngagementOutputDTO> {
|
recordEngagement(input: RecordEngagementInputDTO): Promise<RecordEngagementOutputDTO> {
|
||||||
return this.post<RecordEngagementOutputDTO>('/analytics/engagement', input);
|
return this.post<RecordEngagementOutputDTO>('/analytics/engagement', input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get analytics dashboard data */
|
/** Get analytics dashboard data */
|
||||||
getDashboardData(): Promise<AnalyticsDashboardDto> {
|
getDashboardData(): Promise<GetDashboardDataOutputDTO> {
|
||||||
return this.get<AnalyticsDashboardDto>('/analytics/dashboard');
|
return this.get<GetDashboardDataOutputDTO>('/analytics/dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get analytics metrics */
|
/** Get analytics metrics */
|
||||||
getAnalyticsMetrics(): Promise<AnalyticsMetricsDto> {
|
getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutputDTO> {
|
||||||
return this.get<AnalyticsMetricsDto>('/analytics/metrics');
|
return this.get<GetAnalyticsMetricsOutputDTO>('/analytics/metrics');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { BaseApiClient } from '../base/BaseApiClient';
|
import { BaseApiClient } from '../base/BaseApiClient';
|
||||||
import { AuthSessionDTO } from '../../types/generated/AuthSessionDTO';
|
import { AuthSessionDTO } from '../../types/generated/AuthSessionDTO';
|
||||||
|
import { LoginParams } from '../../types/generated/LoginParams';
|
||||||
// TODO: Create DTOs for login/signup params in apps/website/lib/types/generated
|
import { SignupParams } from '../../types/generated/SignupParams';
|
||||||
type LoginParamsDto = { email: string; password: string };
|
import { LoginWithIracingCallbackParams } from '../../types/generated/LoginWithIracingCallbackParams';
|
||||||
type SignupParamsDto = { email: string; password: string; displayName: string };
|
import { IracingAuthRedirectResult } from '../../types/generated/IracingAuthRedirectResult';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auth API Client
|
* Auth API Client
|
||||||
@@ -12,12 +12,12 @@ type SignupParamsDto = { email: string; password: string; displayName: string };
|
|||||||
*/
|
*/
|
||||||
export class AuthApiClient extends BaseApiClient {
|
export class AuthApiClient extends BaseApiClient {
|
||||||
/** Sign up with email */
|
/** Sign up with email */
|
||||||
signup(params: SignupParamsDto): Promise<AuthSessionDTO> {
|
signup(params: SignupParams): Promise<AuthSessionDTO> {
|
||||||
return this.post<AuthSessionDTO>('/auth/signup', params);
|
return this.post<AuthSessionDTO>('/auth/signup', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Login with email */
|
/** Login with email */
|
||||||
login(params: LoginParamsDto): Promise<AuthSessionDTO> {
|
login(params: LoginParams): Promise<AuthSessionDTO> {
|
||||||
return this.post<AuthSessionDTO>('/auth/login', params);
|
return this.post<AuthSessionDTO>('/auth/login', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,9 +32,19 @@ export class AuthApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Start iRacing auth redirect */
|
/** Start iRacing auth redirect */
|
||||||
getIracingAuthUrl(returnTo?: string): string {
|
startIracingAuthRedirect(returnTo?: string): Promise<IracingAuthRedirectResult> {
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
|
const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
|
||||||
const params = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
|
return this.get<IracingAuthRedirectResult>(`/auth/iracing/start${query}`);
|
||||||
return `${baseUrl}/auth/iracing/start${params}`;
|
}
|
||||||
|
|
||||||
|
/** 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 { BaseApiClient } from '../base/BaseApiClient';
|
||||||
|
import {
|
||||||
|
DashboardDriverSummaryDTO,
|
||||||
|
DashboardRaceSummaryDTO,
|
||||||
|
DashboardLeagueStandingSummaryDTO,
|
||||||
|
DashboardFeedItemSummaryDTO,
|
||||||
|
DashboardFriendSummaryDTO,
|
||||||
|
DashboardRecentResultDTO,
|
||||||
|
} from '../../types/generated';
|
||||||
|
|
||||||
// DTOs
|
// Define DashboardOverviewDTO using generated types
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DashboardOverviewDto = {
|
export type DashboardOverviewDto = {
|
||||||
currentDriver: DriverDto;
|
currentDriver: DashboardDriverSummaryDTO | null;
|
||||||
nextRace: RaceDto | null;
|
myUpcomingRaces: DashboardRaceSummaryDTO[];
|
||||||
upcomingRaces: RaceDto[];
|
otherUpcomingRaces: DashboardRaceSummaryDTO[];
|
||||||
leagueStandings: LeagueStandingDto[];
|
upcomingRaces: DashboardRaceSummaryDTO[];
|
||||||
feedItems: FeedItemDto[];
|
|
||||||
friends: FriendDto[];
|
|
||||||
activeLeaguesCount: number;
|
activeLeaguesCount: number;
|
||||||
|
nextRace: DashboardRaceSummaryDTO | null;
|
||||||
|
recentResults: DashboardRecentResultDTO[];
|
||||||
|
leagueStandingsSummaries: DashboardLeagueStandingSummaryDTO[];
|
||||||
|
feedSummary: {
|
||||||
|
feedItems: DashboardFeedItemSummaryDTO[];
|
||||||
|
};
|
||||||
|
friends: DashboardFriendSummaryDTO[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import { BaseApiClient } from '../base/BaseApiClient';
|
import { BaseApiClient } from '../base/BaseApiClient';
|
||||||
// Import generated types
|
// Import generated types
|
||||||
import type { CompleteOnboardingInputDTO, CompleteOnboardingOutputDTO, DriverRegistrationStatusDTO, DriverLeaderboardItemDTO, DriverProfileDTO } from '../../types/generated';
|
import type { CompleteOnboardingInputDTO, CompleteOnboardingOutputDTO, DriverRegistrationStatusDTO, DriverLeaderboardItemDTO, DriverProfileDTO, GetDriverOutputDTO } from '../../types/generated';
|
||||||
|
|
||||||
// TODO: Create proper DriverDTO in generated types
|
|
||||||
type DriverDTO = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
avatarUrl?: string;
|
|
||||||
iracingId?: string;
|
|
||||||
rating?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DriversLeaderboardDto = {
|
type DriversLeaderboardDto = {
|
||||||
drivers: DriverLeaderboardItemDTO[];
|
drivers: DriverLeaderboardItemDTO[];
|
||||||
@@ -32,8 +23,8 @@ export class DriversApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Get current driver (based on session) */
|
/** Get current driver (based on session) */
|
||||||
getCurrent(): Promise<DriverDTO | null> {
|
getCurrent(): Promise<GetDriverOutputDTO | null> {
|
||||||
return this.get<DriverDTO | null>('/drivers/current');
|
return this.get<GetDriverOutputDTO | null>('/drivers/current');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get driver registration status for a specific race */
|
/** Get driver registration status for a specific race */
|
||||||
@@ -42,8 +33,8 @@ export class DriversApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Get driver by ID */
|
/** Get driver by ID */
|
||||||
getDriver(driverId: string): Promise<DriverDTO | null> {
|
getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
|
||||||
return this.get<DriverDTO | null>(`/drivers/${driverId}`);
|
return this.get<GetDriverOutputDTO | null>(`/drivers/${driverId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get driver profile with full details */
|
/** Get driver profile with full details */
|
||||||
@@ -52,7 +43,7 @@ export class DriversApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Update current driver profile */
|
/** Update current driver profile */
|
||||||
updateProfile(updates: { bio?: string; country?: string }): Promise<DriverDTO> {
|
updateProfile(updates: { bio?: string; country?: string }): Promise<GetDriverOutputDTO> {
|
||||||
return this.put<DriverDTO>('/drivers/profile', updates);
|
return this.put<GetDriverOutputDTO>('/drivers/profile', updates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@ import { AnalyticsApiClient } from './analytics/AnalyticsApiClient';
|
|||||||
import { AuthApiClient } from './auth/AuthApiClient';
|
import { AuthApiClient } from './auth/AuthApiClient';
|
||||||
import { PaymentsApiClient } from './payments/PaymentsApiClient';
|
import { PaymentsApiClient } from './payments/PaymentsApiClient';
|
||||||
import { DashboardApiClient } from './dashboard/DashboardApiClient';
|
import { DashboardApiClient } from './dashboard/DashboardApiClient';
|
||||||
|
import { PenaltiesApiClient } from './penalties/PenaltiesApiClient';
|
||||||
|
import { ProtestsApiClient } from './protests/ProtestsApiClient';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main API Client
|
* Main API Client
|
||||||
@@ -25,6 +27,8 @@ export class ApiClient {
|
|||||||
public readonly auth: AuthApiClient;
|
public readonly auth: AuthApiClient;
|
||||||
public readonly payments: PaymentsApiClient;
|
public readonly payments: PaymentsApiClient;
|
||||||
public readonly dashboard: DashboardApiClient;
|
public readonly dashboard: DashboardApiClient;
|
||||||
|
public readonly penalties: PenaltiesApiClient;
|
||||||
|
public readonly protests: ProtestsApiClient;
|
||||||
|
|
||||||
constructor(baseUrl: string) {
|
constructor(baseUrl: string) {
|
||||||
this.leagues = new LeaguesApiClient(baseUrl);
|
this.leagues = new LeaguesApiClient(baseUrl);
|
||||||
@@ -37,6 +41,8 @@ export class ApiClient {
|
|||||||
this.auth = new AuthApiClient(baseUrl);
|
this.auth = new AuthApiClient(baseUrl);
|
||||||
this.payments = new PaymentsApiClient(baseUrl);
|
this.payments = new PaymentsApiClient(baseUrl);
|
||||||
this.dashboard = new DashboardApiClient(baseUrl);
|
this.dashboard = new DashboardApiClient(baseUrl);
|
||||||
|
this.penalties = new PenaltiesApiClient(baseUrl);
|
||||||
|
this.protests = new ProtestsApiClient(baseUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import type {
|
|||||||
LeagueMembershipsDto,
|
LeagueMembershipsDto,
|
||||||
CreateLeagueInputDto,
|
CreateLeagueInputDto,
|
||||||
CreateLeagueOutputDto,
|
CreateLeagueOutputDto,
|
||||||
|
SponsorshipDetailDTO,
|
||||||
|
RaceDTO,
|
||||||
} from '../../dtos';
|
} from '../../dtos';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,8 +63,8 @@ export class LeaguesApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Get season sponsorships */
|
/** Get season sponsorships */
|
||||||
getSeasonSponsorships(seasonId: string): Promise<{ sponsorships: Array<{ sponsorId: string; tier: string; status: string }> }> {
|
getSeasonSponsorships(seasonId: string): Promise<{ sponsorships: SponsorshipDetailDTO[] }> {
|
||||||
return this.get<{ sponsorships: Array<{ sponsorId: string; tier: string; status: string }> }>(`/seasons/${seasonId}/sponsorships`);
|
return this.get<{ sponsorships: SponsorshipDetailDTO[] }>(`/leagues/seasons/${seasonId}/sponsorships`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get league config */
|
/** Get league config */
|
||||||
@@ -84,7 +86,7 @@ export class LeaguesApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Get races for a league */
|
/** Get races for a league */
|
||||||
getRaces(leagueId: string): Promise<{ races: any[] }> {
|
getRaces(leagueId: string): Promise<{ races: RaceDTO[] }> {
|
||||||
return this.get<{ races: any[] }>(`/leagues/${leagueId}/races`);
|
return this.get<{ races: RaceDTO[] }>(`/leagues/${leagueId}/races`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import type {
|
import type {
|
||||||
DeleteMediaOutputDto,
|
DeleteMediaOutputDTO,
|
||||||
GetMediaOutputDto,
|
GetMediaOutputDTO,
|
||||||
RequestAvatarGenerationInputDto,
|
RequestAvatarGenerationInputDTO,
|
||||||
RequestAvatarGenerationOutputDto,
|
RequestAvatarGenerationOutputDTO,
|
||||||
UpdateAvatarInputDto,
|
UpdateAvatarInputDTO,
|
||||||
UpdateAvatarOutputDto,
|
UpdateAvatarOutputDTO,
|
||||||
UploadMediaInputDto,
|
UploadMediaOutputDTO,
|
||||||
UploadMediaOutputDto,
|
} from '../generated';
|
||||||
} from '../../dtos';
|
import type { GetAvatarOutputDTO } from '../generated';
|
||||||
import type { GetAvatarOutputDto } from '../../types/GetAvatarOutputDto';
|
|
||||||
import { BaseApiClient } from '../base/BaseApiClient';
|
import { BaseApiClient } from '../base/BaseApiClient';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,38 +17,38 @@ import { BaseApiClient } from '../base/BaseApiClient';
|
|||||||
*/
|
*/
|
||||||
export class MediaApiClient extends BaseApiClient {
|
export class MediaApiClient extends BaseApiClient {
|
||||||
/** Upload media file */
|
/** Upload media file */
|
||||||
uploadMedia(input: UploadMediaInputDto): Promise<UploadMediaOutputDto> {
|
uploadMedia(input: { file: File; type: string; category?: string }): Promise<UploadMediaOutputDTO> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', input.file);
|
formData.append('file', input.file);
|
||||||
formData.append('type', input.type);
|
formData.append('type', input.type);
|
||||||
if (input.category) {
|
if (input.category) {
|
||||||
formData.append('category', 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 */
|
/** Get media by ID */
|
||||||
getMedia(mediaId: string): Promise<GetMediaOutputDto> {
|
getMedia(mediaId: string): Promise<GetMediaOutputDTO> {
|
||||||
return this.get<GetMediaOutputDto>(`/media/${mediaId}`);
|
return this.get<GetMediaOutputDTO>(`/media/${mediaId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete media by ID */
|
/** Delete media by ID */
|
||||||
deleteMedia(mediaId: string): Promise<DeleteMediaOutputDto> {
|
deleteMedia(mediaId: string): Promise<DeleteMediaOutputDTO> {
|
||||||
return this.delete<DeleteMediaOutputDto>(`/media/${mediaId}`);
|
return this.delete<DeleteMediaOutputDTO>(`/media/${mediaId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Request avatar generation */
|
/** Request avatar generation */
|
||||||
requestAvatarGeneration(input: RequestAvatarGenerationInputDto): Promise<RequestAvatarGenerationOutputDto> {
|
requestAvatarGeneration(input: RequestAvatarGenerationInputDTO): Promise<RequestAvatarGenerationOutputDTO> {
|
||||||
return this.post<RequestAvatarGenerationOutputDto>('/media/avatar/generate', input);
|
return this.post<RequestAvatarGenerationOutputDTO>('/media/avatar/generate', input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get avatar for driver */
|
/** Get avatar for driver */
|
||||||
getAvatar(driverId: string): Promise<GetAvatarOutputDto> {
|
getAvatar(driverId: string): Promise<GetAvatarOutputDTO> {
|
||||||
return this.get<GetAvatarOutputDto>(`/media/avatar/${driverId}`);
|
return this.get<GetAvatarOutputDTO>(`/media/avatar/${driverId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update avatar for driver */
|
/** Update avatar for driver */
|
||||||
updateAvatar(input: UpdateAvatarInputDto): Promise<UpdateAvatarOutputDto> {
|
updateAvatar(input: UpdateAvatarInputDTO): Promise<UpdateAvatarOutputDTO> {
|
||||||
return this.put<UpdateAvatarOutputDto>(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl });
|
return this.put<UpdateAvatarOutputDTO>(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { BaseApiClient } from '../base/BaseApiClient';
|
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
|
// Define missing types that are not fully generated
|
||||||
type GetPaymentsOutputDto = { payments: import('../types/generated').PaymentDto[] };
|
type GetPaymentsOutputDto = { payments: PaymentDto[] };
|
||||||
type CreatePaymentInputDto = {
|
type CreatePaymentInputDto = {
|
||||||
type: 'sponsorship' | 'membership_fee';
|
type: 'sponsorship' | 'membership_fee';
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -10,15 +11,15 @@ type CreatePaymentInputDto = {
|
|||||||
leagueId: string;
|
leagueId: string;
|
||||||
seasonId?: string;
|
seasonId?: string;
|
||||||
};
|
};
|
||||||
type CreatePaymentOutputDto = { payment: import('../types/generated').PaymentDto };
|
type CreatePaymentOutputDto = { payment: PaymentDto };
|
||||||
type GetMembershipFeesOutputDto = {
|
type GetMembershipFeesOutputDto = {
|
||||||
fee: import('../types/generated').MembershipFeeDto | null;
|
fee: MembershipFeeDto | null;
|
||||||
payments: import('../types/generated').MemberPaymentDto[]
|
payments: MemberPaymentDto[]
|
||||||
};
|
};
|
||||||
type GetPrizesOutputDto = { prizes: import('../types/generated').PrizeDto[] };
|
type GetPrizesOutputDto = { prizes: PrizeDto[] };
|
||||||
type GetWalletOutputDto = {
|
type GetWalletOutputDto = {
|
||||||
wallet: import('../types/generated').WalletDto;
|
wallet: WalletDto;
|
||||||
transactions: import('../types/generated').TransactionDto[]
|
transactions: TransactionDto[]
|
||||||
};
|
};
|
||||||
type ProcessWalletTransactionInputDto = {
|
type ProcessWalletTransactionInputDto = {
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
@@ -29,8 +30,8 @@ type ProcessWalletTransactionInputDto = {
|
|||||||
referenceType?: 'sponsorship' | 'membership_fee' | 'prize';
|
referenceType?: 'sponsorship' | 'membership_fee' | 'prize';
|
||||||
};
|
};
|
||||||
type ProcessWalletTransactionOutputDto = {
|
type ProcessWalletTransactionOutputDto = {
|
||||||
wallet: import('../types/generated').WalletDto;
|
wallet: WalletDto;
|
||||||
transaction: import('../types/generated').TransactionDto
|
transaction: TransactionDto
|
||||||
};
|
};
|
||||||
type UpdateMemberPaymentInputDto = {
|
type UpdateMemberPaymentInputDto = {
|
||||||
feeId: string;
|
feeId: string;
|
||||||
@@ -38,8 +39,33 @@ type UpdateMemberPaymentInputDto = {
|
|||||||
status?: 'pending' | 'paid' | 'overdue';
|
status?: 'pending' | 'paid' | 'overdue';
|
||||||
paidAt?: Date | string;
|
paidAt?: Date | string;
|
||||||
};
|
};
|
||||||
type UpdateMemberPaymentOutputDto = { payment: import('../types/generated').MemberPaymentDto };
|
type UpdateMemberPaymentOutputDto = { payment: MemberPaymentDto };
|
||||||
type GetWalletTransactionsOutputDto = { transactions: import('../types/generated').TransactionDto[] };
|
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
|
* Payments API Client
|
||||||
@@ -48,12 +74,14 @@ type GetWalletTransactionsOutputDto = { transactions: import('../types/generated
|
|||||||
*/
|
*/
|
||||||
export class PaymentsApiClient extends BaseApiClient {
|
export class PaymentsApiClient extends BaseApiClient {
|
||||||
/** Get payments */
|
/** 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();
|
const params = new URLSearchParams();
|
||||||
if (leagueId) params.append('leagueId', leagueId);
|
if (query?.leagueId) params.append('leagueId', query.leagueId);
|
||||||
if (driverId) params.append('driverId', driverId);
|
if (query?.payerId) params.append('payerId', query.payerId);
|
||||||
const query = params.toString();
|
if (query?.type) params.append('type', query.type);
|
||||||
return this.get<GetPaymentsOutputDto>(`/payments${query ? `?${query}` : ''}`);
|
if (query?.status) params.append('status', query.status);
|
||||||
|
const queryString = params.toString();
|
||||||
|
return this.get<GetPaymentsOutputDto>(`/payments${queryString ? `?${queryString}` : ''}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a payment */
|
/** Create a payment */
|
||||||
@@ -62,21 +90,63 @@ export class PaymentsApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Get membership fees */
|
/** Get membership fees */
|
||||||
getMembershipFees(leagueId: string): Promise<GetMembershipFeesOutputDto> {
|
getMembershipFees(query: { leagueId: string; driverId?: string }): Promise<GetMembershipFeesOutputDto> {
|
||||||
return this.get<GetMembershipFeesOutputDto>(`/payments/membership-fees?leagueId=${leagueId}`);
|
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 */
|
/** Get prizes */
|
||||||
getPrizes(leagueId?: string, seasonId?: string): Promise<GetPrizesOutputDto> {
|
getPrizes(query?: { leagueId?: string; seasonId?: string }): Promise<GetPrizesOutputDto> {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (leagueId) params.append('leagueId', leagueId);
|
if (query?.leagueId) params.append('leagueId', query.leagueId);
|
||||||
if (seasonId) params.append('seasonId', seasonId);
|
if (query?.seasonId) params.append('seasonId', query.seasonId);
|
||||||
const query = params.toString();
|
const queryString = params.toString();
|
||||||
return this.get<GetPrizesOutputDto>(`/payments/prizes${query ? `?${query}` : ''}`);
|
return this.get<GetPrizesOutputDto>(`/payments/prizes${queryString ? `?${queryString}` : ''}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get wallet */
|
/** Get wallet */
|
||||||
getWallet(driverId: string): Promise<GetWalletOutputDto> {
|
getWallet(query?: { leagueId?: string }): Promise<GetWalletOutputDto> {
|
||||||
return this.get<GetWalletOutputDto>(`/payments/wallets?driverId=${driverId}`);
|
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 { BaseApiClient } from '../base/BaseApiClient';
|
||||||
|
import { RacePenaltiesDTO } from '../../types/generated/RacePenaltiesDTO';
|
||||||
|
import { ApplyPenaltyCommandDTO } from '../../types/generated/ApplyPenaltyCommandDTO';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Penalties API Client
|
* Penalties API Client
|
||||||
@@ -7,12 +9,12 @@ import { BaseApiClient } from '../base/BaseApiClient';
|
|||||||
*/
|
*/
|
||||||
export class PenaltiesApiClient extends BaseApiClient {
|
export class PenaltiesApiClient extends BaseApiClient {
|
||||||
/** Get penalties for a race */
|
/** Get penalties for a race */
|
||||||
getRacePenalties(raceId: string): Promise<{ penalties: any[] }> {
|
getRacePenalties(raceId: string): Promise<RacePenaltiesDTO> {
|
||||||
return this.get<{ penalties: any[] }>(`/races/${raceId}/penalties`);
|
return this.get<RacePenaltiesDTO>(`/races/${raceId}/penalties`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Apply a penalty */
|
/** Apply a penalty */
|
||||||
applyPenalty(input: any): Promise<void> {
|
applyPenalty(input: ApplyPenaltyCommandDTO): Promise<void> {
|
||||||
return this.post<void>('/races/penalties/apply', input);
|
return this.post<void>('/races/penalties/apply', input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,9 @@ import type {
|
|||||||
LeagueAdminProtestsDTO,
|
LeagueAdminProtestsDTO,
|
||||||
ApplyPenaltyCommandDTO,
|
ApplyPenaltyCommandDTO,
|
||||||
RequestProtestDefenseCommandDTO,
|
RequestProtestDefenseCommandDTO,
|
||||||
} from '../../types';
|
ReviewProtestCommandDTO,
|
||||||
|
} from '../../types/generated';
|
||||||
|
import type { RaceProtestsDTO } from '../../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protests API Client
|
* Protests API Client
|
||||||
@@ -32,12 +34,12 @@ export class ProtestsApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Review protest */
|
/** 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);
|
return this.post<void>(`/protests/${input.protestId}/review`, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get protests for a race */
|
/** Get protests for a race */
|
||||||
getRaceProtests(raceId: string): Promise<{ protests: any[] }> {
|
getRaceProtests(raceId: string): Promise<RaceProtestsDTO> {
|
||||||
return this.get<{ protests: any[] }>(`/races/${raceId}/protests`);
|
return this.get<RaceProtestsDTO>(`/races/${raceId}/protests`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,34 @@
|
|||||||
import { BaseApiClient } from '../base/BaseApiClient';
|
import { BaseApiClient } from '../base/BaseApiClient';
|
||||||
import type {
|
import type { RaceStatsDTO } from '../../types/generated/RaceStatsDTO';
|
||||||
RaceStatsDto,
|
import type { RacesPageDataRaceDTO } from '../../types/generated/RacesPageDataRaceDTO';
|
||||||
RacesPageDataDto,
|
import type { RaceResultsDetailDTO } from '../../types/generated/RaceResultsDetailDTO';
|
||||||
RaceDetailDto,
|
import type { RaceWithSOFDTO } from '../../types/generated/RaceWithSOFDTO';
|
||||||
RaceResultsDetailDto,
|
import type { RegisterForRaceParamsDTO } from '../../types/generated/RegisterForRaceParamsDTO';
|
||||||
RaceWithSOFDto,
|
import type { ImportRaceResultsDTO } from '../../types/generated/ImportRaceResultsDTO';
|
||||||
RegisterForRaceInputDto,
|
import type { WithdrawFromRaceParamsDTO } from '../../types/generated/WithdrawFromRaceParamsDTO';
|
||||||
ImportRaceResultsInputDto,
|
import type { RaceDetailRaceDTO } from '../../types/generated/RaceDetailRaceDTO';
|
||||||
ImportRaceResultsSummaryDto,
|
import type { RaceDetailLeagueDTO } from '../../types/generated/RaceDetailLeagueDTO';
|
||||||
WithdrawFromRaceInputDto,
|
import type { RaceDetailEntryDTO } from '../../types/generated/RaceDetailEntryDTO';
|
||||||
} from '../../dtos';
|
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
|
* Races API Client
|
||||||
@@ -18,42 +37,42 @@ import type {
|
|||||||
*/
|
*/
|
||||||
export class RacesApiClient extends BaseApiClient {
|
export class RacesApiClient extends BaseApiClient {
|
||||||
/** Get total number of races */
|
/** Get total number of races */
|
||||||
getTotal(): Promise<RaceStatsDto> {
|
getTotal(): Promise<RaceStatsDTO> {
|
||||||
return this.get<RaceStatsDto>('/races/total-races');
|
return this.get<RaceStatsDTO>('/races/total-races');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get races page data */
|
/** Get races page data */
|
||||||
getPageData(): Promise<RacesPageDataDto> {
|
getPageData(): Promise<RacesPageDataDTO> {
|
||||||
return this.get<RacesPageDataDto>('/races/page-data');
|
return this.get<RacesPageDataDTO>('/races/page-data');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get race detail */
|
/** Get race detail */
|
||||||
getDetail(raceId: string, driverId: string): Promise<RaceDetailDto> {
|
getDetail(raceId: string, driverId: string): Promise<RaceDetailDTO> {
|
||||||
return this.get<RaceDetailDto>(`/races/${raceId}?driverId=${driverId}`);
|
return this.get<RaceDetailDTO>(`/races/${raceId}?driverId=${driverId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get race results detail */
|
/** Get race results detail */
|
||||||
getResultsDetail(raceId: string): Promise<RaceResultsDetailDto> {
|
getResultsDetail(raceId: string): Promise<RaceResultsDetailDTO> {
|
||||||
return this.get<RaceResultsDetailDto>(`/races/${raceId}/results`);
|
return this.get<RaceResultsDetailDTO>(`/races/${raceId}/results`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get race with strength of field */
|
/** Get race with strength of field */
|
||||||
getWithSOF(raceId: string): Promise<RaceWithSOFDto> {
|
getWithSOF(raceId: string): Promise<RaceWithSOFDTO> {
|
||||||
return this.get<RaceWithSOFDto>(`/races/${raceId}/sof`);
|
return this.get<RaceWithSOFDTO>(`/races/${raceId}/sof`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Register for race */
|
/** 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);
|
return this.post<void>(`/races/${raceId}/register`, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Import race results */
|
/** Import race results */
|
||||||
importResults(raceId: string, input: ImportRaceResultsInputDto): Promise<ImportRaceResultsSummaryDto> {
|
importResults(raceId: string, input: ImportRaceResultsDTO): Promise<ImportRaceResultsSummaryDTO> {
|
||||||
return this.post<ImportRaceResultsSummaryDto>(`/races/${raceId}/import-results`, input);
|
return this.post<ImportRaceResultsSummaryDTO>(`/races/${raceId}/import-results`, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Withdraw from race */
|
/** 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);
|
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 { CreateSponsorInputDTO } from '../../types/generated/CreateSponsorInputDTO';
|
||||||
import type { SponsorDashboardDTO } from '../../types/generated/SponsorDashboardDTO';
|
import type { SponsorDashboardDTO } from '../../types/generated/SponsorDashboardDTO';
|
||||||
import type { SponsorSponsorshipsDTO } from '../../types/generated/SponsorSponsorshipsDTO';
|
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 CreateSponsorOutputDto = { id: string; name: string };
|
||||||
export type GetEntitySponsorshipPricingResultDto = { pricing: Array<{ entityType: string; price: number }> };
|
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[] };
|
export type GetSponsorsOutputDto = { sponsors: SponsorDTO[] };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,22 +45,22 @@ export class SponsorsApiClient extends BaseApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Get sponsor by ID */
|
/** Get sponsor by ID */
|
||||||
getSponsor(sponsorId: string): Promise<SponsorDTO | null> {
|
getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO | null> {
|
||||||
return this.get<SponsorDTO | null>(`/sponsors/${sponsorId}`);
|
return this.get<GetSponsorOutputDTO | null>(`/sponsors/${sponsorId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get pending sponsorship requests for an entity */
|
/** Get pending sponsorship requests for an entity */
|
||||||
getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<{ requests: any[] }> {
|
getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<GetPendingSponsorshipRequestsOutputDTO> {
|
||||||
return this.get<{ requests: any[] }>(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`);
|
return this.get<GetPendingSponsorshipRequestsOutputDTO>(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Accept a sponsorship request */
|
/** Accept a sponsorship request */
|
||||||
acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<void> {
|
acceptSponsorshipRequest(requestId: string, input: AcceptSponsorshipRequestInputDTO): Promise<void> {
|
||||||
return this.post(`/sponsors/requests/${requestId}/accept`, { respondedBy });
|
return this.post(`/sponsors/requests/${requestId}/accept`, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reject a sponsorship request */
|
/** Reject a sponsorship request */
|
||||||
rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise<void> {
|
rejectSponsorshipRequest(requestId: string, input: RejectSponsorshipRequestInputDTO): Promise<void> {
|
||||||
return this.post(`/sponsors/requests/${requestId}/reject`, { respondedBy, reason });
|
return this.post(`/sponsors/requests/${requestId}/reject`, input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
|
import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
|
||||||
import type {
|
import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
|
||||||
AllTeamsDto,
|
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
|
||||||
CreateTeamInputDto,
|
import type { GetTeamMembersOutputDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO';
|
||||||
CreateTeamOutputDto,
|
import type { GetTeamJoinRequestsOutputDTO } from '@/lib/types/generated/GetTeamJoinRequestsOutputDTO';
|
||||||
DriverTeamDto,
|
import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO';
|
||||||
TeamDetailsDto,
|
import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO';
|
||||||
TeamJoinRequestsDto,
|
import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO';
|
||||||
TeamMembersDto,
|
import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO';
|
||||||
UpdateTeamInputDto,
|
import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO';
|
||||||
UpdateTeamOutputDto,
|
import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO';
|
||||||
} from '../../dtos';
|
|
||||||
import { BaseApiClient } from '../base/BaseApiClient';
|
import { BaseApiClient } from '../base/BaseApiClient';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,42 +18,42 @@ import { BaseApiClient } from '../base/BaseApiClient';
|
|||||||
*/
|
*/
|
||||||
export class TeamsApiClient extends BaseApiClient {
|
export class TeamsApiClient extends BaseApiClient {
|
||||||
/** Get all teams */
|
/** Get all teams */
|
||||||
getAll(): Promise<AllTeamsDto> {
|
getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||||
return this.get<AllTeamsDto>('/teams/all');
|
return this.get<GetAllTeamsOutputDTO>('/teams/all');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get team details */
|
/** Get team details */
|
||||||
getDetails(teamId: string): Promise<TeamDetailsDto | null> {
|
getDetails(teamId: string): Promise<GetTeamDetailsOutputDTO | null> {
|
||||||
return this.get<TeamDetailsDto | null>(`/teams/${teamId}`);
|
return this.get<GetTeamDetailsOutputDTO | null>(`/teams/${teamId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get team members */
|
/** Get team members */
|
||||||
getMembers(teamId: string): Promise<TeamMembersDto> {
|
getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||||
return this.get<TeamMembersDto>(`/teams/${teamId}/members`);
|
return this.get<GetTeamMembersOutputDTO>(`/teams/${teamId}/members`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get team join requests */
|
/** Get team join requests */
|
||||||
getJoinRequests(teamId: string): Promise<TeamJoinRequestsDto> {
|
getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||||
return this.get<TeamJoinRequestsDto>(`/teams/${teamId}/join-requests`);
|
return this.get<GetTeamJoinRequestsOutputDTO>(`/teams/${teamId}/join-requests`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new team */
|
/** Create a new team */
|
||||||
create(input: CreateTeamInputDto): Promise<CreateTeamOutputDto> {
|
create(input: CreateTeamInputDTO): Promise<CreateTeamOutputDTO> {
|
||||||
return this.post<CreateTeamOutputDto>('/teams', input);
|
return this.post<CreateTeamOutputDTO>('/teams', input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update team */
|
/** Update team */
|
||||||
update(teamId: string, input: UpdateTeamInputDto): Promise<UpdateTeamOutputDto> {
|
update(teamId: string, input: UpdateTeamInputDTO): Promise<UpdateTeamOutputDTO> {
|
||||||
return this.patch<UpdateTeamOutputDto>(`/teams/${teamId}`, input);
|
return this.patch<UpdateTeamOutputDTO>(`/teams/${teamId}`, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get driver's team */
|
/** Get driver's team */
|
||||||
getDriverTeam(driverId: string): Promise<DriverTeamDto | null> {
|
getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||||
return this.get<DriverTeamDto | null>(`/teams/driver/${driverId}`);
|
return this.get<GetDriverTeamOutputDTO | null>(`/teams/driver/${driverId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get membership for a driver in a team */
|
/** Get membership for a driver in a team */
|
||||||
getMembership(teamId: string, driverId: string): Promise<LeagueMemberDTO | null> {
|
getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||||
return this.get<LeagueMemberDTO | null>(`/teams/${teamId}/members/${driverId}`);
|
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 { CreateLeagueInputDTO } from '@/lib/types/CreateLeagueInputDTO';
|
||||||
import { LeagueWizardValidationMessages } from '@/lib/display-objects/LeagueWizardValidationMessages';
|
import { LeagueWizardValidationMessages } from '@/lib/display-objects/LeagueWizardValidationMessages';
|
||||||
import { ScoringPresetApplier } from '@/lib/utilities/ScoringPresetApplier';
|
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 = {
|
type LeagueWizardFormData = {
|
||||||
leagueId: string | undefined;
|
leagueId: string | undefined;
|
||||||
basics: {
|
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 {
|
export interface LeagueRoleDisplayData {
|
||||||
text: string;
|
text: string;
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
import { AnalyticsApiClient } from '../../api/analytics/AnalyticsApiClient';
|
import { AnalyticsApiClient } from '../../api/analytics/AnalyticsApiClient';
|
||||||
import { RecordPageViewOutputViewModel } from '../../view-models/RecordPageViewOutputViewModel';
|
import { RecordPageViewOutputViewModel } from '../../view-models/RecordPageViewOutputViewModel';
|
||||||
import { RecordEngagementOutputViewModel } from '../../view-models/RecordEngagementOutputViewModel';
|
import { RecordEngagementOutputViewModel } from '../../view-models/RecordEngagementOutputViewModel';
|
||||||
|
import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO';
|
||||||
// TODO: Create proper DTOs in generated types
|
import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO';
|
||||||
interface RecordPageViewInputDTO {
|
|
||||||
path: string;
|
|
||||||
userId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RecordEngagementInputDTO {
|
|
||||||
eventType: string;
|
|
||||||
userId?: string;
|
|
||||||
metadata?: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analytics Service
|
* Analytics Service
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { apiClient } from '@/lib/apiClient';
|
import { apiClient } from '@/lib/apiClient';
|
||||||
import { LeagueMembership } from '@/lib/types/LeagueMembership';
|
import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
|
||||||
import { MembershipRole } from '@/lib/types/MembershipRole';
|
import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole';
|
||||||
import { MembershipStatus } from '@/lib/types/MembershipStatus';
|
import type { MembershipStatus } from '@core/racing/domain/entities/MembershipStatus';
|
||||||
|
|
||||||
export class LeagueMembershipService {
|
export class LeagueMembershipService {
|
||||||
// In-memory cache for memberships (populated via API calls)
|
// 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 { LeagueDetailPageViewModel, SponsorInfo } from "@/lib/view-models/LeagueDetailPageViewModel";
|
||||||
import { RaceViewModel } from "@/lib/view-models/RaceViewModel";
|
import { RaceViewModel } from "@/lib/view-models/RaceViewModel";
|
||||||
import { SubmitBlocker, ThrottleBlocker } from "@/lib/blockers";
|
import { SubmitBlocker, ThrottleBlocker } from "@/lib/blockers";
|
||||||
import { DriverDTO } from "@/lib/types/DriverDTO";
|
|
||||||
import { RaceDTO } from "@/lib/types/generated/RaceDTO";
|
import { RaceDTO } from "@/lib/types/generated/RaceDTO";
|
||||||
import { LeagueScoringConfigDTO } from "@/lib/types/LeagueScoringConfigDTO";
|
import { LeagueScoringConfigDTO } from "@/lib/types/LeagueScoringConfigDTO";
|
||||||
import { LeagueStatsDTO } from "@/lib/types/generated/LeagueStatsDTO";
|
import { LeagueStatsDTO } from "@/lib/types/generated/LeagueStatsDTO";
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
|
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
|
||||||
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
|
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
|
||||||
import type { LeagueConfigFormModel } from "@/lib/types/LeagueConfigFormModel";
|
import type { LeagueConfigFormModel } from "@core/racing/application";
|
||||||
import type { LeagueScoringPresetDTO } from "@/lib/types/LeagueScoringPresetDTO";
|
import type { LeagueScoringPresetDTO } from "@core/racing/application/ports/LeagueScoringPresetProvider";
|
||||||
import type { DriverDTO } from "@/lib/types/DriverDTO";
|
import type { GetDriverOutputDTO } from "@/lib/types/generated/GetDriverOutputDTO";
|
||||||
import { LeagueSettingsViewModel } from "@/lib/view-models/LeagueSettingsViewModel";
|
import { LeagueSettingsViewModel } from "@/lib/view-models/LeagueSettingsViewModel";
|
||||||
import { DriverSummaryViewModel } from "@/lib/view-models/DriverSummaryViewModel";
|
import { DriverSummaryViewModel } from "@/lib/view-models/DriverSummaryViewModel";
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ export class LeagueSettingsService {
|
|||||||
// TODO: get rating and rank from API
|
// TODO: get rating and rank from API
|
||||||
owner = new DriverSummaryViewModel({
|
owner = new DriverSummaryViewModel({
|
||||||
driver: ownerDriver,
|
driver: ownerDriver,
|
||||||
rating: ownerDriver.rating ?? null,
|
rating: null, // TODO: get from API
|
||||||
rank: null, // TODO: get from API
|
rank: null, // TODO: get from API
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ export class LeagueSettingsService {
|
|||||||
if (driver) {
|
if (driver) {
|
||||||
members.push(new DriverSummaryViewModel({
|
members.push(new DriverSummaryViewModel({
|
||||||
driver,
|
driver,
|
||||||
rating: driver.rating ?? null,
|
rating: null, // TODO: get from API
|
||||||
rank: null, // TODO: get from API
|
rank: null, // TODO: get from API
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,23 @@
|
|||||||
import { apiClient } from '@/lib/apiClient';
|
import { apiClient } from '@/lib/apiClient';
|
||||||
import { LeagueWizardCommandModel } from '@/lib/command-models/leagues/LeagueWizardCommandModel';
|
import { LeagueWizardCommandModel } from '@/lib/command-models/leagues/LeagueWizardCommandModel';
|
||||||
import { CreateLeagueResult } from '@/lib/types/CreateLeagueResult';
|
import { CreateLeagueOutputDTO } from '@/lib/types/generated/CreateLeagueOutputDTO';
|
||||||
|
|
||||||
export class LeagueWizardService {
|
export class LeagueWizardService {
|
||||||
static async createLeague(
|
static async createLeague(
|
||||||
form: LeagueWizardCommandModel,
|
form: LeagueWizardCommandModel,
|
||||||
ownerId: string,
|
ownerId: string,
|
||||||
): Promise<CreateLeagueResult> {
|
): Promise<CreateLeagueOutputDTO> {
|
||||||
const command = form.toCreateLeagueCommand(ownerId);
|
const command = form.toCreateLeagueCommand(ownerId);
|
||||||
const result = await apiClient.leagues.create(command);
|
const result = await apiClient.leagues.create(command);
|
||||||
|
|
||||||
return {
|
return result;
|
||||||
leagueId: result.leagueId,
|
|
||||||
success: result.success,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static method for backward compatibility
|
// Static method for backward compatibility
|
||||||
static async createLeagueFromConfig(
|
static async createLeagueFromConfig(
|
||||||
form: LeagueWizardCommandModel,
|
form: LeagueWizardCommandModel,
|
||||||
ownerId: string,
|
ownerId: string,
|
||||||
): Promise<CreateLeagueResult> {
|
): Promise<CreateLeagueOutputDTO> {
|
||||||
return this.createLeague(form, ownerId);
|
return this.createLeague(form, ownerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,9 +83,9 @@ describe('MediaService', () => {
|
|||||||
const expectedOutput = {
|
const expectedOutput = {
|
||||||
id: 'media-123',
|
id: 'media-123',
|
||||||
url: 'https://example.com/image.jpg',
|
url: 'https://example.com/image.jpg',
|
||||||
type: 'image' as const,
|
type: 'image',
|
||||||
category: 'avatar' as const,
|
category: 'avatar',
|
||||||
uploadedAt: new Date('2023-01-15'),
|
uploadedAt: '2023-01-15T00:00:00.000Z',
|
||||||
size: 2048000,
|
size: 2048000,
|
||||||
};
|
};
|
||||||
mockApiClient.getMedia.mockResolvedValue(expectedOutput);
|
mockApiClient.getMedia.mockResolvedValue(expectedOutput);
|
||||||
@@ -98,7 +98,7 @@ describe('MediaService', () => {
|
|||||||
expect(result.url).toBe('https://example.com/image.jpg');
|
expect(result.url).toBe('https://example.com/image.jpg');
|
||||||
expect(result.type).toBe('image');
|
expect(result.type).toBe('image');
|
||||||
expect(result.category).toBe('avatar');
|
expect(result.category).toBe('avatar');
|
||||||
expect(result.uploadedAt).toEqual(new Date('2023-01-15'));
|
expect(result.uploadedAt).toEqual(new Date('2023-01-15T00:00:00.000Z'));
|
||||||
expect(result.size).toBe(2048000);
|
expect(result.size).toBe(2048000);
|
||||||
expect(result.formattedSize).toBe('2000.00 KB');
|
expect(result.formattedSize).toBe('2000.00 KB');
|
||||||
});
|
});
|
||||||
@@ -109,8 +109,8 @@ describe('MediaService', () => {
|
|||||||
const expectedOutput = {
|
const expectedOutput = {
|
||||||
id: 'media-456',
|
id: 'media-456',
|
||||||
url: 'https://example.com/video.mp4',
|
url: 'https://example.com/video.mp4',
|
||||||
type: 'video' as const,
|
type: 'video',
|
||||||
uploadedAt: new Date('2023-02-20'),
|
uploadedAt: '2023-02-20T00:00:00.000Z',
|
||||||
};
|
};
|
||||||
mockApiClient.getMedia.mockResolvedValue(expectedOutput);
|
mockApiClient.getMedia.mockResolvedValue(expectedOutput);
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user