resolve manual DTOs

This commit is contained in:
2025-12-18 22:19:40 +01:00
parent 4a3087ae35
commit d617654928
179 changed files with 3716 additions and 1257 deletions

View File

@@ -8,6 +8,85 @@
"paths": {},
"components": {
"schemas": {
"UpdateTeamOutputDTO": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
},
"required": [
"success"
]
},
"GetTeamMembershipOutputDTO": {
"type": "object",
"properties": {
"role": {
"type": "string"
},
"joinedAt": {
"type": "string"
},
"isActive": {
"type": "boolean"
}
},
"required": [
"role",
"joinedAt",
"isActive"
]
},
"CreateTeamOutputDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"id",
"success"
]
},
"CreateTeamInputDTO": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"required": [
"name",
"tag"
]
},
"SponsorshipRequestDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"sponsorId": {
"type": "string"
},
"sponsorName": {
"type": "string"
}
},
"required": [
"id",
"sponsorId",
"sponsorName"
]
},
"SponsorshipPricingItemDTO": {
"type": "object",
"properties": {
@@ -161,6 +240,32 @@
"sponsorName"
]
},
"SponsorDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
]
},
"RejectSponsorshipRequestInputDTO": {
"type": "object",
"properties": {
"respondedBy": {
"type": "string"
}
},
"required": [
"respondedBy"
]
},
"GetSponsorSponsorshipsQueryParamsDTO": {
"type": "object",
"properties": {
@@ -183,6 +288,21 @@
"sponsorId"
]
},
"GetPendingSponsorshipRequestsOutputDTO": {
"type": "object",
"properties": {
"entityType": {
"type": "string"
},
"entityId": {
"type": "string"
}
},
"required": [
"entityType",
"entityId"
]
},
"CreateSponsorInputDTO": {
"type": "object",
"properties": {
@@ -198,6 +318,17 @@
"contactEmail"
]
},
"AcceptSponsorshipRequestInputDTO": {
"type": "object",
"properties": {
"respondedBy": {
"type": "string"
}
},
"required": [
"respondedBy"
]
},
"WithdrawFromRaceParamsDTO": {
"type": "object",
"properties": {
@@ -213,6 +344,25 @@
"driverId"
]
},
"ReviewProtestCommandDTO": {
"type": "object",
"properties": {
"protestId": {
"type": "string"
},
"stewardId": {
"type": "string"
},
"enum": {
"type": "string"
}
},
"required": [
"protestId",
"stewardId",
"enum"
]
},
"RequestProtestDefenseCommandDTO": {
"type": "object",
"properties": {
@@ -874,25 +1024,6 @@
"enum"
]
},
"RequestAvatarGenerationInputDTO": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"facePhotoData": {
"type": "string"
},
"suitColor": {
"type": "string"
}
},
"required": [
"userId",
"facePhotoData",
"suitColor"
]
},
"UpdatePaymentStatusInputDTO": {
"type": "object",
"properties": {
@@ -1058,99 +1189,7 @@
"id"
]
},
"GetDriverRegistrationStatusQueryDTO": {
"type": "object",
"properties": {
"raceId": {
"type": "string"
},
"driverId": {
"type": "string"
}
},
"required": [
"raceId",
"driverId"
]
},
"DriverStatsDTO": {
"type": "object",
"properties": {
"totalDrivers": {
"type": "number"
}
},
"required": [
"totalDrivers"
]
},
"DriverRegistrationStatusDTO": {
"type": "object",
"properties": {
"isRegistered": {
"type": "boolean"
},
"raceId": {
"type": "string"
},
"driverId": {
"type": "string"
}
},
"required": [
"isRegistered",
"raceId",
"driverId"
]
},
"DriverLeaderboardItemDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"rating": {
"type": "number"
},
"skillLevel": {
"type": "string"
},
"nationality": {
"type": "string"
},
"racesCompleted": {
"type": "number"
},
"wins": {
"type": "number"
},
"podiums": {
"type": "number"
},
"isActive": {
"type": "boolean"
},
"rank": {
"type": "number"
}
},
"required": [
"id",
"name",
"rating",
"skillLevel",
"nationality",
"racesCompleted",
"wins",
"podiums",
"isActive",
"rank"
]
},
"CompleteOnboardingOutputDTO": {
"UploadMediaOutputDTO": {
"type": "object",
"properties": {
"success": {
@@ -1161,27 +1200,101 @@
"success"
]
},
"CompleteOnboardingInputDTO": {
"UpdateAvatarOutputDTO": {
"type": "object",
"properties": {
"firstName": {
"success": {
"type": "boolean"
}
},
"required": [
"success"
]
},
"UpdateAvatarInputDTO": {
"type": "object",
"properties": {
"driverId": {
"type": "string"
},
"lastName": {
"type": "string"
},
"displayName": {
"type": "string"
},
"country": {
"avatarUrl": {
"type": "string"
}
},
"required": [
"firstName",
"lastName",
"displayName",
"country"
"driverId",
"avatarUrl"
]
},
"RequestAvatarGenerationInputDTO": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"facePhotoData": {
"type": "string"
},
"suitColor": {
"type": "string"
}
},
"required": [
"userId",
"facePhotoData",
"suitColor"
]
},
"GetMediaOutputDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"url": {
"type": "string"
},
"type": {
"type": "string"
},
"category": {
"type": "string"
},
"uploadedAt": {
"type": "string",
"format": "date-time"
},
"size": {
"type": "number"
}
},
"required": [
"id",
"url",
"type",
"uploadedAt"
]
},
"GetAvatarOutputDTO": {
"type": "object",
"properties": {
"avatarUrl": {
"type": "string"
}
},
"required": [
"avatarUrl"
]
},
"DeleteMediaOutputDTO": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
},
"required": [
"success"
]
},
"UpdateLeagueMemberRoleOutputDTO": {
@@ -1651,6 +1764,330 @@
"leagueId"
]
},
"GetDriverRegistrationStatusQueryDTO": {
"type": "object",
"properties": {
"raceId": {
"type": "string"
},
"driverId": {
"type": "string"
}
},
"required": [
"raceId",
"driverId"
]
},
"DriverProfileDriverSummaryDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"country": {
"type": "string"
},
"avatarUrl": {
"type": "string"
}
},
"required": [
"id",
"name",
"country",
"avatarUrl"
]
},
"DriverProfileStatsDTO": {
"type": "object",
"properties": {
"totalRaces": {
"type": "number"
},
"wins": {
"type": "number"
},
"podiums": {
"type": "number"
},
"dnfs": {
"type": "number"
}
},
"required": [
"totalRaces",
"wins",
"podiums",
"dnfs"
]
},
"DriverProfileFinishDistributionDTO": {
"type": "object",
"properties": {
"totalRaces": {
"type": "number"
},
"wins": {
"type": "number"
},
"podiums": {
"type": "number"
},
"topTen": {
"type": "number"
},
"dnfs": {
"type": "number"
},
"other": {
"type": "number"
}
},
"required": [
"totalRaces",
"wins",
"podiums",
"topTen",
"dnfs",
"other"
]
},
"DriverProfileTeamMembershipDTO": {
"type": "object",
"properties": {
"teamId": {
"type": "string"
},
"teamName": {
"type": "string"
}
},
"required": [
"teamId",
"teamName"
]
},
"DriverProfileSocialFriendSummaryDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"country": {
"type": "string"
},
"avatarUrl": {
"type": "string"
}
},
"required": [
"id",
"name",
"country",
"avatarUrl"
]
},
"DriverProfileSocialSummaryDTO": {
"type": "object",
"properties": {
"friendsCount": {
"type": "number"
}
},
"required": [
"friendsCount"
]
},
"DriverProfileAchievementDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": [
"id",
"title",
"description"
]
},
"GetDriverOutputDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"iracingId": {
"type": "string"
},
"name": {
"type": "string"
},
"country": {
"type": "string"
},
"bio": {
"type": "string"
},
"joinedAt": {
"type": "string"
}
},
"required": [
"id",
"iracingId",
"name",
"country",
"joinedAt"
]
},
"DriverStatsDTO": {
"type": "object",
"properties": {
"totalDrivers": {
"type": "number"
}
},
"required": [
"totalDrivers"
]
},
"DriverRegistrationStatusDTO": {
"type": "object",
"properties": {
"isRegistered": {
"type": "boolean"
},
"raceId": {
"type": "string"
},
"driverId": {
"type": "string"
}
},
"required": [
"isRegistered",
"raceId",
"driverId"
]
},
"DriverLeaderboardItemDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"rating": {
"type": "number"
},
"skillLevel": {
"type": "string"
},
"nationality": {
"type": "string"
},
"racesCompleted": {
"type": "number"
},
"wins": {
"type": "number"
},
"podiums": {
"type": "number"
},
"isActive": {
"type": "boolean"
},
"rank": {
"type": "number"
}
},
"required": [
"id",
"name",
"rating",
"skillLevel",
"nationality",
"racesCompleted",
"wins",
"podiums",
"isActive",
"rank"
]
},
"DriverDTO": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"iracingId": {
"type": "string"
},
"name": {
"type": "string"
},
"country": {
"type": "string"
}
},
"required": [
"id",
"iracingId",
"name",
"country"
]
},
"CompleteOnboardingOutputDTO": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
},
"required": [
"success"
]
},
"CompleteOnboardingInputDTO": {
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"displayName": {
"type": "string"
},
"country": {
"type": "string"
}
},
"required": [
"firstName",
"lastName",
"displayName",
"country"
]
},
"AuthenticatedUserDTO": {
"type": "object",
"properties": {
@@ -1710,6 +2147,52 @@
"eventId",
"engagementWeight"
]
},
"GetDashboardDataOutputDTO": {
"type": "object",
"properties": {
"totalUsers": {
"type": "number"
},
"activeUsers": {
"type": "number"
},
"totalRaces": {
"type": "number"
},
"totalLeagues": {
"type": "number"
}
},
"required": [
"totalUsers",
"activeUsers",
"totalRaces",
"totalLeagues"
]
},
"GetAnalyticsMetricsOutputDTO": {
"type": "object",
"properties": {
"pageViews": {
"type": "number"
},
"uniqueVisitors": {
"type": "number"
},
"averageSessionDuration": {
"type": "number"
},
"bounceRate": {
"type": "number"
}
},
"required": [
"pageViews",
"uniqueVisitors",
"averageSessionDuration",
"bounceRate"
]
}
}
}

View File

@@ -7,8 +7,10 @@ import { DatabaseModule } from './infrastructure/database/database.module';
import { LoggingModule } from './infrastructure/logging/LoggingModule';
import { BootstrapModule } from './infrastructure/bootstrap/BootstrapModule';
import { AuthModule } from './domain/auth/AuthModule';
import { DashboardModule } from './domain/dashboard/DashboardModule';
import { LeagueModule } from './domain/league/LeagueModule';
import { RaceModule } from './domain/race/RaceModule';
import { ProtestsModule } from './domain/protests/ProtestsModule';
import { TeamModule } from './domain/team/TeamModule';
import { SponsorModule } from './domain/sponsor/SponsorModule';
import { DriverModule } from './domain/driver/DriverModule';
@@ -22,8 +24,10 @@ import { PaymentsModule } from './domain/payments/PaymentsModule';
BootstrapModule,
AnalyticsModule,
AuthModule,
DashboardModule,
LeagueModule,
RaceModule,
ProtestsModule,
TeamModule,
SponsorModule,
DriverModule,

View File

@@ -1,16 +1,22 @@
import { Controller, Post, Body, Res, HttpStatus } from '@nestjs/common';
import { Controller, Get, Post, Body, Res, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBody, ApiResponse } from '@nestjs/swagger';
import type { Response } from 'express';
import type { RecordPageViewInputDTO } from './dtos/RecordPageViewInputDTO';
import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO';
import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO';
import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO';
import type { GetDashboardDataOutputDTO } from './dtos/GetDashboardDataOutputDTO';
import type { GetAnalyticsMetricsOutputDTO } from './dtos/GetAnalyticsMetricsOutputDTO';
import { AnalyticsService } from './AnalyticsService';
type RecordPageViewInput = RecordPageViewInputDTO;
type RecordPageViewOutput = RecordPageViewOutputDTO;
type RecordEngagementInput = RecordEngagementInputDTO;
type RecordEngagementOutput = RecordEngagementOutputDTO;
type GetDashboardDataOutput = GetDashboardDataOutputDTO;
type GetAnalyticsMetricsOutput = GetAnalyticsMetricsOutputDTO;
@ApiTags('analytics')
@Controller('analytics')
export class AnalyticsController {
constructor(
@@ -18,6 +24,9 @@ export class AnalyticsController {
) {}
@Post('page-view')
@ApiOperation({ summary: 'Record a page view' })
@ApiBody({ type: RecordPageViewInputDTO })
@ApiResponse({ status: 201, description: 'Page view recorded', type: RecordPageViewOutputDTO })
async recordPageView(
@Body() input: RecordPageViewInput,
@Res() res: Response,
@@ -27,6 +36,9 @@ export class AnalyticsController {
}
@Post('engagement')
@ApiOperation({ summary: 'Record an engagement event' })
@ApiBody({ type: RecordEngagementInputDTO })
@ApiResponse({ status: 201, description: 'Engagement recorded', type: RecordEngagementOutputDTO })
async recordEngagement(
@Body() input: RecordEngagementInput,
@Res() res: Response,
@@ -34,4 +46,18 @@ export class AnalyticsController {
const output: RecordEngagementOutput = await this.analyticsService.recordEngagement(input);
res.status(HttpStatus.CREATED).json(output);
}
@Get('dashboard')
@ApiOperation({ summary: 'Get analytics dashboard data' })
@ApiResponse({ status: 200, description: 'Dashboard data', type: GetDashboardDataOutputDTO })
async getDashboardData(): Promise<GetDashboardDataOutput> {
return await this.analyticsService.getDashboardData();
}
@Get('metrics')
@ApiOperation({ summary: 'Get analytics metrics' })
@ApiResponse({ status: 200, description: 'Analytics metrics', type: GetAnalyticsMetricsOutputDTO })
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutput> {
return await this.analyticsService.getAnalyticsMetrics();
}
}

View File

@@ -3,6 +3,8 @@ import type { RecordPageViewInputDTO } from './dtos/RecordPageViewInputDTO';
import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO';
import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO';
import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO';
import type { GetDashboardDataOutputDTO } from './dtos/GetDashboardDataOutputDTO';
import type { GetAnalyticsMetricsOutputDTO } from './dtos/GetAnalyticsMetricsOutputDTO';
import type { Logger } from '@core/shared/application';
import { RecordPageViewUseCase } from './use-cases/RecordPageViewUseCase';
import { RecordEngagementUseCase } from './use-cases/RecordEngagementUseCase';
@@ -11,6 +13,8 @@ type RecordPageViewInput = RecordPageViewInputDTO;
type RecordPageViewOutput = RecordPageViewOutputDTO;
type RecordEngagementInput = RecordEngagementInputDTO;
type RecordEngagementOutput = RecordEngagementOutputDTO;
type GetDashboardDataOutput = GetDashboardDataOutputDTO;
type GetAnalyticsMetricsOutput = GetAnalyticsMetricsOutputDTO;
const Logger_TOKEN = 'Logger_TOKEN';
const RECORD_PAGE_VIEW_USE_CASE_TOKEN = 'RecordPageViewUseCase_TOKEN';
@@ -31,4 +35,24 @@ export class AnalyticsService {
async recordEngagement(input: RecordEngagementInput): Promise<RecordEngagementOutput> {
return await this.recordEngagementUseCase.execute(input);
}
async getDashboardData(): Promise<GetDashboardDataOutput> {
// TODO: Implement actual dashboard data retrieval
return {
totalUsers: 0,
activeUsers: 0,
totalRaces: 0,
totalLeagues: 0,
};
}
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutput> {
// TODO: Implement actual analytics metrics retrieval
return {
pageViews: 0,
uniqueVisitors: 0,
averageSessionDuration: 0,
bounceRate: 0,
};
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,34 +1,34 @@
import { Controller, Get, Post, Body, Query, Res, Redirect, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
import { AuthService } from './AuthService';
import { LoginParams, SignupParams, LoginWithIracingCallbackParams } from './dto/AuthDto';
import { LoginParams, SignupParams, LoginWithIracingCallbackParams, AuthSessionDTO, IracingAuthRedirectResult } from './dto/AuthDto';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('signup')
async signup(@Body() params: SignupParams) {
async signup(@Body() params: SignupParams): Promise<AuthSessionDTO> {
return this.authService.signupWithEmail(params);
}
@Post('login')
async login(@Body() params: LoginParams) {
async login(@Body() params: LoginParams): Promise<AuthSessionDTO> {
return this.authService.loginWithEmail(params);
}
@Get('session')
async getSession() {
async getSession(): Promise<AuthSessionDTO | null> {
return this.authService.getCurrentSession();
}
@Post('logout')
async logout() {
async logout(): Promise<void> {
return this.authService.logout();
}
@Get('iracing/start')
async startIracingAuthRedirect(@Query('returnTo') returnTo?: string, @Res() res?: Response) {
async startIracingAuthRedirect(@Query('returnTo') returnTo?: string, @Res() res?: Response): Promise<void> {
const { redirectUrl, state } = await this.authService.startIracingAuthRedirect(returnTo);
// In real application, you might want to store 'state' in a secure cookie or session.
// For this example, we'll just redirect.
@@ -36,7 +36,7 @@ export class AuthController {
}
@Get('iracing/callback')
async loginWithIracingCallback(@Query('code') code: string, @Query('state') state: string, @Query('returnTo') returnTo?: string) {
async loginWithIracingCallback(@Query('code') code: string, @Query('state') state: string, @Query('returnTo') returnTo?: string): Promise<AuthSessionDTO> {
return this.authService.loginWithIracingCallback({ code, state, returnTo });
}
}

View 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);
}
}

View 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 {}

View 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,
];

View 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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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[];
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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[];
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -9,6 +9,8 @@ import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO'
import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
import { DriverDTO } from './dtos/DriverDTO';
import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
@ApiTags('drivers')
@Controller('drivers')
@@ -31,9 +33,9 @@ export class DriverController {
@Get('current')
@ApiOperation({ summary: 'Get current authenticated driver' })
@ApiResponse({ status: 200, description: 'Current driver data', type: DriverDTO })
@ApiResponse({ status: 200, description: 'Current driver data', type: GetDriverOutputDTO })
@ApiResponse({ status: 404, description: 'Driver not found' })
async getCurrentDriver(@Req() req: Request): Promise<DriverDTO | null> {
async getCurrentDriver(@Req() req: Request): Promise<GetDriverOutputDTO | null> {
// Assuming userId is available from the request (e.g., via auth middleware)
const userId = req['user']?.userId;
if (!userId) {
@@ -64,13 +66,29 @@ export class DriverController {
return this.driverService.getDriverRegistrationStatus({ driverId, raceId });
}
@Get(':driverId')
@ApiOperation({ summary: 'Get driver by ID' })
@ApiResponse({ status: 200, description: 'Driver data', type: GetDriverOutputDTO })
@ApiResponse({ status: 404, description: 'Driver not found' })
async getDriver(@Param('driverId') driverId: string): Promise<GetDriverOutputDTO | null> {
return this.driverService.getDriver(driverId);
}
@Get(':driverId/profile')
@ApiOperation({ summary: 'Get driver profile with full details' })
@ApiResponse({ status: 200, description: 'Driver profile data', type: GetDriverProfileOutputDTO })
@ApiResponse({ status: 404, description: 'Driver not found' })
async getDriverProfile(@Param('driverId') driverId: string): Promise<GetDriverProfileOutputDTO> {
return this.driverService.getDriverProfile(driverId);
}
@Put(':driverId/profile')
@ApiOperation({ summary: 'Update driver profile' })
@ApiResponse({ status: 200, description: 'Driver profile updated', type: DriverDTO })
@ApiResponse({ status: 200, description: 'Driver profile updated', type: GetDriverOutputDTO })
async updateDriverProfile(
@Param('driverId') driverId: string,
@Body() body: { bio?: string; country?: string },
): Promise<DriverDTO | null> {
): Promise<GetDriverOutputDTO | null> {
return this.driverService.updateDriverProfile(driverId, body.bio, body.country);
}

View File

@@ -5,6 +5,8 @@ import { CompleteOnboardingInputDTO } from './dtos/CompleteOnboardingInputDTO';
import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO';
import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
// Use cases
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
@@ -76,7 +78,7 @@ export class DriverService {
return presenter.viewModel;
}
async getCurrentDriver(userId: string): Promise<DriverDTO | null> {
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`);
const driver = await this.driverRepository.findById(userId);
@@ -86,11 +88,15 @@ export class DriverService {
return {
id: driver.id,
iracingId: driver.iracingId.value,
name: driver.name.value,
country: driver.country.value,
bio: driver.bio?.value,
joinedAt: driver.joinedAt.toISOString(),
};
}
async updateDriverProfile(driverId: string, bio?: string, country?: string): Promise<DriverDTO | null> {
async updateDriverProfile(driverId: string, bio?: string, country?: string): Promise<GetDriverOutputDTO | null> {
this.logger.debug(`[DriverService] Updating driver profile for driverId: ${driverId}`);
const result = await this.updateDriverProfileUseCase.execute({ driverId, bio, country });
@@ -101,4 +107,40 @@ export class DriverService {
return result.value;
}
async getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
this.logger.debug(`[DriverService] Fetching driver for driverId: ${driverId}`);
const driver = await this.driverRepository.findById(driverId);
if (!driver) {
return null;
}
return {
id: driver.id,
iracingId: driver.iracingId.value,
name: driver.name.value,
country: driver.country.value,
bio: driver.bio?.value,
joinedAt: driver.joinedAt.toISOString(),
};
}
async getDriverProfile(driverId: string): Promise<GetDriverProfileOutputDTO> {
this.logger.debug(`[DriverService] Fetching driver profile for driverId: ${driverId}`);
// TODO: Implement proper driver profile fetching with all the detailed data
// For now, return a placeholder structure
return {
currentDriver: null,
stats: null,
finishDistribution: null,
teamMemberships: [],
socialSummary: {
friendsCount: 0,
friends: [],
},
extendedProfile: null,
};
}
}

View 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;
}

View 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;
}

View 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;
}

View File

@@ -30,6 +30,8 @@ import { GetLeagueProtestsQueryDTO } from './dtos/GetLeagueProtestsQueryDTO';
import { GetLeagueSeasonsQueryDTO } from './dtos/GetLeagueSeasonsQueryDTO';
import { GetLeagueAdminConfigQueryDTO } from './dtos/GetLeagueAdminConfigQueryDTO';
import { GetLeagueOwnerSummaryQueryDTO } from './dtos/GetLeagueOwnerSummaryQueryDTO';
import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO';
import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO';
@ApiTags('leagues')
@Controller('leagues')
@@ -262,4 +264,18 @@ export class LeagueController {
async transferLeagueOwnership(@Param('leagueId') leagueId: string, @Body() body: { currentOwnerId: string, newOwnerId: string }) {
return this.leagueService.transferLeagueOwnership(leagueId, body.currentOwnerId, body.newOwnerId);
}
@Get('seasons/:seasonId/sponsorships')
@ApiOperation({ summary: 'Get season sponsorships' })
@ApiResponse({ status: 200, description: 'Season sponsorships', type: GetSeasonSponsorshipsOutputDTO })
async getSeasonSponsorships(@Param('seasonId') seasonId: string): Promise<GetSeasonSponsorshipsOutputDTO> {
return this.leagueService.getSeasonSponsorships(seasonId);
}
@Get(':leagueId/races')
@ApiOperation({ summary: 'Get league races' })
@ApiResponse({ status: 200, description: 'League races', type: GetLeagueRacesOutputDTO })
async getRaces(@Param('leagueId') leagueId: string): Promise<GetLeagueRacesOutputDTO> {
return this.leagueService.getRaces(leagueId);
}
}

View File

@@ -27,6 +27,8 @@ import { LeagueStatsDTO } from './dtos/LeagueStatsDTO';
import { LeagueAdminDTO } from './dtos/LeagueAdminDTO';
import { CreateLeagueInputDTO } from './dtos/CreateLeagueInputDTO';
import { CreateLeagueOutputDTO } from './dtos/CreateLeagueOutputDTO';
import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO';
import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO';
// Core imports
import type { Logger } from '@core/shared/application/Logger';
@@ -328,4 +330,24 @@ export class LeagueService {
success: true,
};
}
async getSeasonSponsorships(seasonId: string): Promise<GetSeasonSponsorshipsOutputDTO> {
this.logger.debug('Getting season sponsorships', { seasonId });
// TODO: Implement actual logic to fetch season sponsorships
// For now, return empty array as placeholder
return {
sponsorships: [],
};
}
async getRaces(leagueId: string): Promise<GetLeagueRacesOutputDTO> {
this.logger.debug('Getting league races', { leagueId });
// TODO: Implement actual logic to fetch league races
// For now, return empty array as placeholder
return {
races: [],
};
}
}

View File

@@ -0,0 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { RaceDTO } from '../../race/dtos/RaceDTO';
export class GetLeagueRacesOutputDTO {
@ApiProperty({ type: [RaceDTO] })
races: RaceDTO[];
}

View File

@@ -0,0 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { SponsorshipDetailDTO } from '../../sponsor/dtos/SponsorshipDetailDTO';
export class GetSeasonSponsorshipsOutputDTO {
@ApiProperty({ type: [SponsorshipDetailDTO] })
sponsorships: SponsorshipDetailDTO[];
}

View 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;
}

View 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';
}

View 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;
}

View 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';
}

View 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';
}

View 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;
}

View 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;
}

View File

@@ -1,12 +1,27 @@
import { Controller, Post, Body, HttpStatus, Res } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { Controller, Post, Get, Delete, Put, Body, HttpStatus, Res, Param, UseInterceptors, UploadedFile } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation, ApiParam, ApiConsumes } from '@nestjs/swagger';
import { Response } from 'express';
import { FileInterceptor } from '@nestjs/platform-express';
import { MediaService } from './MediaService';
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
import type { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO';
import type { UploadMediaOutputDTO } from './dtos/UploadMediaOutputDTO';
import type { GetMediaOutputDTO } from './dtos/GetMediaOutputDTO';
import type { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO';
import type { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO';
import type { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO';
import type { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO';
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
type UploadMediaInput = UploadMediaInputDTO;
type UploadMediaOutput = UploadMediaOutputDTO;
type GetMediaOutput = GetMediaOutputDTO;
type DeleteMediaOutput = DeleteMediaOutputDTO;
type GetAvatarOutput = GetAvatarOutputDTO;
type UpdateAvatarInput = UpdateAvatarInputDTO;
type UpdateAvatarOutput = UpdateAvatarOutputDTO;
@ApiTags('media')
@Controller('media')
@@ -27,4 +42,79 @@ export class MediaController {
res.status(HttpStatus.BAD_REQUEST).json(result);
}
}
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
@ApiOperation({ summary: 'Upload media file' })
@ApiConsumes('multipart/form-data')
@ApiResponse({ status: 201, description: 'Media uploaded successfully', type: UploadMediaOutput })
async uploadMedia(
@UploadedFile() file: Express.Multer.File,
@Body() input: UploadMediaInput,
@Res() res: Response,
): Promise<void> {
const result = await this.mediaService.uploadMedia({ ...input, file });
if (result.success) {
res.status(HttpStatus.CREATED).json(result);
} else {
res.status(HttpStatus.BAD_REQUEST).json(result);
}
}
@Get(':mediaId')
@ApiOperation({ summary: 'Get media by ID' })
@ApiParam({ name: 'mediaId', description: 'Media ID' })
@ApiResponse({ status: 200, description: 'Media details', type: GetMediaOutput })
async getMedia(
@Param('mediaId') mediaId: string,
@Res() res: Response,
): Promise<void> {
const result = await this.mediaService.getMedia(mediaId);
if (result) {
res.status(HttpStatus.OK).json(result);
} else {
res.status(HttpStatus.NOT_FOUND).json({ error: 'Media not found' });
}
}
@Delete(':mediaId')
@ApiOperation({ summary: 'Delete media by ID' })
@ApiParam({ name: 'mediaId', description: 'Media ID' })
@ApiResponse({ status: 200, description: 'Media deleted', type: DeleteMediaOutput })
async deleteMedia(
@Param('mediaId') mediaId: string,
@Res() res: Response,
): Promise<void> {
const result = await this.mediaService.deleteMedia(mediaId);
res.status(HttpStatus.OK).json(result);
}
@Get('avatar/:driverId')
@ApiOperation({ summary: 'Get avatar for driver' })
@ApiParam({ name: 'driverId', description: 'Driver ID' })
@ApiResponse({ status: 200, description: 'Avatar details', type: GetAvatarOutput })
async getAvatar(
@Param('driverId') driverId: string,
@Res() res: Response,
): Promise<void> {
const result = await this.mediaService.getAvatar(driverId);
if (result) {
res.status(HttpStatus.OK).json(result);
} else {
res.status(HttpStatus.NOT_FOUND).json({ error: 'Avatar not found' });
}
}
@Put('avatar/:driverId')
@ApiOperation({ summary: 'Update avatar for driver' })
@ApiParam({ name: 'driverId', description: 'Driver ID' })
@ApiResponse({ status: 200, description: 'Avatar updated', type: UpdateAvatarOutput })
async updateAvatar(
@Param('driverId') driverId: string,
@Body() input: UpdateAvatarInput,
@Res() res: Response,
): Promise<void> {
const result = await this.mediaService.updateAvatar(driverId, input);
res.status(HttpStatus.OK).json(result);
}
}

View File

@@ -1,9 +1,23 @@
import { Injectable, Inject } from '@nestjs/common';
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
import type { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO';
import type { UploadMediaOutputDTO } from './dtos/UploadMediaOutputDTO';
import type { GetMediaOutputDTO } from './dtos/GetMediaOutputDTO';
import type { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO';
import type { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO';
import type { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO';
import type { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO';
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
type UploadMediaInput = UploadMediaInputDTO;
type UploadMediaOutput = UploadMediaOutputDTO;
type GetMediaOutput = GetMediaOutputDTO;
type DeleteMediaOutput = DeleteMediaOutputDTO;
type GetAvatarOutput = GetAvatarOutputDTO;
type UpdateAvatarInput = UpdateAvatarInputDTO;
type UpdateAvatarOutput = UpdateAvatarOutputDTO;
// Use cases
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
@@ -33,4 +47,42 @@ export class MediaService {
}, presenter);
return presenter.viewModel;
}
async uploadMedia(input: UploadMediaInput & { file: Express.Multer.File }): Promise<UploadMediaOutput> {
this.logger.debug('[MediaService] Uploading media.');
// TODO: Implement media upload logic
return {
success: true,
mediaId: 'placeholder-media-id',
url: 'placeholder-url',
};
}
async getMedia(mediaId: string): Promise<GetMediaOutput | null> {
this.logger.debug(`[MediaService] Getting media: ${mediaId}`);
// TODO: Implement get media logic
return null;
}
async deleteMedia(mediaId: string): Promise<DeleteMediaOutput> {
this.logger.debug(`[MediaService] Deleting media: ${mediaId}`);
// TODO: Implement delete media logic
return {
success: true,
};
}
async getAvatar(driverId: string): Promise<GetAvatarOutput | null> {
this.logger.debug(`[MediaService] Getting avatar for driver: ${driverId}`);
// TODO: Implement get avatar logic
return null;
}
async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise<UpdateAvatarOutput> {
this.logger.debug(`[MediaService] Updating avatar for driver: ${driverId}`);
// TODO: Implement update avatar logic
return {
success: true,
};
}
}

View 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;
}

View File

@@ -0,0 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
export class GetAvatarOutputDTO {
@ApiProperty()
@IsString()
avatarUrl: string;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@@ -1,7 +1,7 @@
import { Controller, Get, Post, Patch, Delete, Body, Query, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { PaymentsService } from './PaymentsService';
import { CreatePaymentInput, CreatePaymentOutput, UpdatePaymentStatusInput, UpdatePaymentStatusOutput, GetPaymentsQuery, GetPaymentsOutput, GetMembershipFeesQuery, GetMembershipFeesOutput, UpsertMembershipFeeInput, UpsertMembershipFeeOutput, UpdateMemberPaymentInput, UpdateMemberPaymentOutput, GetPrizesQuery, GetPrizesOutput, CreatePrizeInput, CreatePrizeOutput, AwardPrizeInput, AwardPrizeOutput, DeletePrizeInput, DeletePrizeOutput, GetWalletQuery, GetWalletOutput, ProcessWalletTransactionInput, ProcessWalletTransactionOutput } from './dto/PaymentsDto';
import { CreatePaymentInput, CreatePaymentOutput, UpdatePaymentStatusInput, UpdatePaymentStatusOutput, GetPaymentsQuery, GetPaymentsOutput, GetMembershipFeesQuery, GetMembershipFeesOutput, UpsertMembershipFeeInput, UpsertMembershipFeeOutput, UpdateMemberPaymentInput, UpdateMemberPaymentOutput, GetPrizesQuery, GetPrizesOutput, CreatePrizeInput, CreatePrizeOutput, AwardPrizeInput, AwardPrizeOutput, DeletePrizeInput, DeletePrizeOutput, GetWalletQuery, GetWalletOutput, ProcessWalletTransactionInput, ProcessWalletTransactionOutput } from './dtos/PaymentsDto';
@ApiTags('payments')
@Controller('payments')

View File

@@ -55,7 +55,7 @@ import type {
GetWalletOutput,
ProcessWalletTransactionInput,
ProcessWalletTransactionOutput,
} from './dto/PaymentsDto';
} from './dtos/PaymentsDto';
// Injection tokens
import {

View 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 });
}
}

View 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 {}

View File

@@ -15,7 +15,6 @@ import { WithdrawFromRaceParamsDTO } from './dtos/WithdrawFromRaceParamsDTO';
import { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO';
import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO';
import { ImportRaceResultsSummaryDTO } from './dtos/ImportRaceResultsSummaryDTO';
import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO';
import { FileProtestCommandDTO } from './dtos/FileProtestCommandDTO';
import { QuickPenaltyCommandDTO } from './dtos/QuickPenaltyCommandDTO';
import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO';
@@ -152,13 +151,6 @@ export class RaceController {
return this.raceService.importRaceResults({ raceId, ...body });
}
@Get('dashboard/overview')
@ApiOperation({ summary: 'Get dashboard overview' })
@ApiQuery({ name: 'driverId', description: 'Driver ID' })
@ApiResponse({ status: 200, description: 'Dashboard overview', type: DashboardOverviewDTO })
async getDashboardOverview(@Query('driverId') driverId: string): Promise<DashboardOverviewDTO> {
return this.raceService.getDashboardOverview(driverId);
}
@Post('protests/file')
@HttpCode(HttpStatus.OK)

View File

@@ -43,11 +43,11 @@ import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/With
import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
// Define injection tokens
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
@@ -239,4 +239,10 @@ export const RaceProviders: Provider[] = [
new RequestProtestDefenseUseCase(protestRepo, raceRepo, leagueMembershipRepo),
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
},
{
provide: ReviewProtestUseCase,
useFactory: (protestRepo: IProtestRepository, raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository) =>
new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo),
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
},
];

View File

@@ -35,11 +35,11 @@ import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/With
import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
// Presenters
import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter';
@@ -67,11 +67,11 @@ export class RaceService {
private readonly cancelRaceUseCase: CancelRaceUseCase,
private readonly completeRaceUseCase: CompleteRaceUseCase,
private readonly importRaceResultsUseCase: ImportRaceResultsUseCase,
private readonly dashboardOverviewUseCase: DashboardOverviewUseCase,
private readonly fileProtestUseCase: FileProtestUseCase,
private readonly quickPenaltyUseCase: QuickPenaltyUseCase,
private readonly applyPenaltyUseCase: ApplyPenaltyUseCase,
private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase,
private readonly reviewProtestUseCase: ReviewProtestUseCase,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
@@ -235,17 +235,6 @@ export class RaceService {
}
}
async getDashboardOverview(driverId: string): Promise<any> {
this.logger.debug('[RaceService] Getting dashboard overview:', { driverId });
const result = await this.dashboardOverviewUseCase.execute({ driverId });
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to get dashboard overview');
}
return result.value;
}
async fileProtest(command: any): Promise<any> {
this.logger.debug('[RaceService] Filing protest:', command);
@@ -294,4 +283,16 @@ export class RaceService {
return result.value;
}
async reviewProtest(command: any): Promise<any> {
this.logger.debug('[RaceService] Reviewing protest:', command);
const result = await this.reviewProtestUseCase.execute(command);
if (result.isErr()) {
throw new Error(result.error.details.message || 'Failed to review protest');
}
return result.value;
}
}

View 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;
}

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Post, Body, HttpCode, HttpStatus, Param } from '@nestjs/common';
import { Controller, Get, Post, Body, HttpCode, HttpStatus, Param, Query } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { SponsorService } from './SponsorService';
import { GetEntitySponsorshipPricingResultDTO } from './dtos/GetEntitySponsorshipPricingResultDTO';
@@ -9,6 +9,10 @@ import { GetSponsorDashboardQueryParamsDTO } from './dtos/GetSponsorDashboardQue
import { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO';
import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO';
import { SponsorSponsorshipsDTO } from './dtos/SponsorSponsorshipsDTO';
import { GetSponsorOutputDTO } from './dtos/GetSponsorOutputDTO';
import { GetPendingSponsorshipRequestsOutputDTO } from './dtos/GetPendingSponsorshipRequestsOutputDTO';
import { AcceptSponsorshipRequestInputDTO } from './dtos/AcceptSponsorshipRequestInputDTO';
import { RejectSponsorshipRequestInputDTO } from './dtos/RejectSponsorshipRequestInputDTO';
@ApiTags('sponsors')
@Controller('sponsors')
@@ -52,4 +56,39 @@ export class SponsorController {
async getSponsorSponsorships(@Param('sponsorId') sponsorId: string): Promise<SponsorSponsorshipsDTO | null> {
return this.sponsorService.getSponsorSponsorships({ sponsorId } as GetSponsorSponsorshipsQueryParamsDTO);
}
@Get(':sponsorId')
@ApiOperation({ summary: 'Get a sponsor by ID' })
@ApiResponse({ status: 200, description: 'Sponsor data', type: GetSponsorOutputDTO })
@ApiResponse({ status: 404, description: 'Sponsor not found' })
async getSponsor(@Param('sponsorId') sponsorId: string): Promise<GetSponsorOutputDTO | null> {
return this.sponsorService.getSponsor(sponsorId);
}
@Get('requests')
@ApiOperation({ summary: 'Get pending sponsorship requests' })
@ApiResponse({ status: 200, description: 'List of pending sponsorship requests', type: GetPendingSponsorshipRequestsOutputDTO })
async getPendingSponsorshipRequests(@Query() query: { entityType: string; entityId: string }): Promise<GetPendingSponsorshipRequestsOutputDTO> {
return this.sponsorService.getPendingSponsorshipRequests(query);
}
@Post('requests/:requestId/accept')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Accept a sponsorship request' })
@ApiResponse({ status: 200, description: 'Sponsorship request accepted' })
@ApiResponse({ status: 400, description: 'Invalid request' })
@ApiResponse({ status: 404, description: 'Request not found' })
async acceptSponsorshipRequest(@Param('requestId') requestId: string, @Body() input: AcceptSponsorshipRequestInputDTO): Promise<any> {
return this.sponsorService.acceptSponsorshipRequest(requestId, input.respondedBy);
}
@Post('requests/:requestId/reject')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Reject a sponsorship request' })
@ApiResponse({ status: 200, description: 'Sponsorship request rejected' })
@ApiResponse({ status: 400, description: 'Invalid request' })
@ApiResponse({ status: 404, description: 'Request not found' })
async rejectSponsorshipRequest(@Param('requestId') requestId: string, @Body() input: RejectSponsorshipRequestInputDTO): Promise<any> {
return this.sponsorService.rejectSponsorshipRequest(requestId, input.respondedBy, input.reason);
}
}

View File

@@ -19,6 +19,10 @@ import { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateS
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
import { GetEntitySponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
import { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
import { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
import { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
// Import concrete in-memory implementations
import { InMemorySponsorRepository } from '@adapters/racing/persistence/inmemory/InMemorySponsorRepository';
@@ -49,6 +53,10 @@ export const CREATE_SPONSOR_USE_CASE_TOKEN = 'CreateSponsorUseCase';
export const GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN = 'GetSponsorDashboardUseCase';
export const GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN = 'GetSponsorSponsorshipsUseCase';
export const GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetEntitySponsorshipPricingUseCase';
export const GET_SPONSOR_USE_CASE_TOKEN = 'GetSponsorUseCase';
export const GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN = 'GetPendingSponsorshipRequestsUseCase';
export const ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'AcceptSponsorshipRequestUseCase';
export const REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'RejectSponsorshipRequestUseCase';
export const SponsorProviders: Provider[] = [
SponsorService,
@@ -131,4 +139,27 @@ export const SponsorProviders: Provider[] = [
new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, sponsorshipRequestRepo, seasonSponsorshipRepo, logger),
inject: [SPONSORSHIP_PRICING_REPOSITORY_TOKEN, SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GET_SPONSOR_USE_CASE_TOKEN,
useFactory: (sponsorRepo: ISponsorRepository) => new GetSponsorUseCase(sponsorRepo),
inject: [SPONSOR_REPOSITORY_TOKEN],
},
{
provide: GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN,
useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, sponsorRepo: ISponsorRepository) =>
new GetPendingSponsorshipRequestsUseCase(sponsorshipRequestRepo, sponsorRepo),
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SPONSOR_REPOSITORY_TOKEN],
},
{
provide: ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository, seasonRepo: ISeasonRepository, notificationService: any, paymentGateway: any, walletRepository: any, leagueWalletRepository: any, logger: Logger) =>
new AcceptSponsorshipRequestUseCase(sponsorshipRequestRepo, seasonSponsorshipRepo, seasonRepo, notificationService, paymentGateway, walletRepository, leagueWalletRepository, logger),
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, SEASON_REPOSITORY_TOKEN, 'INotificationService', 'IPaymentGateway', 'IWalletRepository', 'ILeagueWalletRepository', LOGGER_TOKEN],
},
{
provide: REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, logger: Logger) =>
new RejectSponsorshipRequestUseCase(sponsorshipRequestRepo, logger),
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
];

View File

@@ -7,6 +7,10 @@ import { GetSponsorDashboardQueryParamsDTO } from './dtos/GetSponsorDashboardQue
import { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO';
import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO';
import { SponsorSponsorshipsDTO } from './dtos/SponsorSponsorshipsDTO';
import { GetSponsorOutputDTO } from './dtos/GetSponsorOutputDTO';
import { GetPendingSponsorshipRequestsOutputDTO } from './dtos/GetPendingSponsorshipRequestsOutputDTO';
import { AcceptSponsorshipRequestInputDTO } from './dtos/AcceptSponsorshipRequestInputDTO';
import { RejectSponsorshipRequestInputDTO } from './dtos/RejectSponsorshipRequestInputDTO';
import { SponsorDTO } from './dtos/SponsorDTO';
import { SponsorDashboardMetricsDTO } from './dtos/SponsorDashboardMetricsDTO';
import { SponsoredLeagueDTO } from './dtos/SponsoredLeagueDTO';
@@ -19,6 +23,10 @@ import { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponso
import { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
import { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
import { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
import { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
// Presenters
import { GetSponsorshipPricingPresenter } from './presenters/GetSponsorshipPricingPresenter';
@@ -28,7 +36,7 @@ import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPr
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
// Tokens
import { GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN, GET_SPONSORS_USE_CASE_TOKEN, CREATE_SPONSOR_USE_CASE_TOKEN, GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN, GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN, LOGGER_TOKEN } from './SponsorProviders';
import { GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN, GET_SPONSORS_USE_CASE_TOKEN, CREATE_SPONSOR_USE_CASE_TOKEN, GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN, GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN, GET_SPONSOR_USE_CASE_TOKEN, GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN, ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN, REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN, LOGGER_TOKEN } from './SponsorProviders';
import type { Logger } from '@core/shared/application';
@Injectable()
@@ -39,6 +47,10 @@ export class SponsorService {
@Inject(CREATE_SPONSOR_USE_CASE_TOKEN) private readonly createSponsorUseCase: CreateSponsorUseCase,
@Inject(GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN) private readonly getSponsorDashboardUseCase: GetSponsorDashboardUseCase,
@Inject(GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN) private readonly getSponsorSponsorshipsUseCase: GetSponsorSponsorshipsUseCase,
@Inject(GET_SPONSOR_USE_CASE_TOKEN) private readonly getSponsorUseCase: GetSponsorUseCase,
@Inject(GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN) private readonly getPendingSponsorshipRequestsUseCase: GetPendingSponsorshipRequestsUseCase,
@Inject(ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN) private readonly acceptSponsorshipRequestUseCase: AcceptSponsorshipRequestUseCase,
@Inject(REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN) private readonly rejectSponsorshipRequestUseCase: RejectSponsorshipRequestUseCase,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
@@ -81,4 +93,48 @@ export class SponsorService {
await this.getSponsorSponsorshipsUseCase.execute(params, presenter);
return presenter.viewModel as SponsorSponsorshipsDTO | null;
}
async getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO | null> {
this.logger.debug('[SponsorService] Fetching sponsor.', { sponsorId });
const result = await this.getSponsorUseCase.execute({ sponsorId });
if (result.isErr()) {
this.logger.error('[SponsorService] Failed to fetch sponsor.', result.error);
return null;
}
return result.value as GetSponsorOutputDTO | null;
}
async getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<GetPendingSponsorshipRequestsOutputDTO> {
this.logger.debug('[SponsorService] Fetching pending sponsorship requests.', { params });
const result = await this.getPendingSponsorshipRequestsUseCase.execute(params as any);
if (result.isErr()) {
this.logger.error('[SponsorService] Failed to fetch pending sponsorship requests.', result.error);
return { entityType: params.entityType as any, entityId: params.entityId, requests: [], totalCount: 0 };
}
return result.value as GetPendingSponsorshipRequestsOutputDTO;
}
async acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<{ requestId: string; sponsorshipId: string; status: string; acceptedAt: Date; platformFee: number; netAmount: number } | null> {
this.logger.debug('[SponsorService] Accepting sponsorship request.', { requestId, respondedBy });
const result = await this.acceptSponsorshipRequestUseCase.execute({ requestId, respondedBy });
if (result.isErr()) {
this.logger.error('[SponsorService] Failed to accept sponsorship request.', result.error);
return null;
}
return result.value;
}
async rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise<{ requestId: string; status: string; rejectedAt: Date } | null> {
this.logger.debug('[SponsorService] Rejecting sponsorship request.', { requestId, respondedBy, reason });
const result = await this.rejectSponsorshipRequestUseCase.execute({ requestId, respondedBy, reason });
if (result.isErr()) {
this.logger.error('[SponsorService] Failed to reject sponsorship request.', result.error);
return null;
}
return result.value;
}
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class AcceptSponsorshipRequestInputDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
respondedBy: string;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { SponsorDTO } from './SponsorDTO';
export class GetSponsorOutputDTO {
@ApiProperty({ type: SponsorDTO })
sponsor: SponsorDTO;
}

View File

@@ -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;
}

View 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;
}

View 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;
}

View File

@@ -1,7 +1,17 @@
import { Controller, Get, Post, Patch, Body, Param } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation, ApiBody } from '@nestjs/swagger';
import { Controller, Get, Post, Patch, Body, Req, Param } from '@nestjs/common';
import { Request } from 'express';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { TeamService } from './TeamService';
import { AllTeamsViewModel, DriverTeamViewModel, TeamDetailsViewModel, TeamMembersViewModel, TeamJoinRequestsViewModel, CreateTeamInput, CreateTeamOutput, UpdateTeamInput, UpdateTeamOutput, ApproveTeamJoinRequestInput, ApproveTeamJoinRequestOutput, RejectTeamJoinRequestInput, RejectTeamJoinRequestOutput } from './dto/TeamDto';
import { GetAllTeamsOutputDTO } from './dtos/GetAllTeamsOutputDTO';
import { GetTeamDetailsOutputDTO } from './dtos/GetTeamDetailsOutputDTO';
import { GetTeamMembersOutputDTO } from './dtos/GetTeamMembersOutputDTO';
import { GetTeamJoinRequestsOutputDTO } from './dtos/GetTeamJoinRequestsOutputDTO';
import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO';
import { CreateTeamOutputDTO } from './dtos/CreateTeamOutputDTO';
import { UpdateTeamInputDTO } from './dtos/UpdateTeamInputDTO';
import { UpdateTeamOutputDTO } from './dtos/UpdateTeamOutputDTO';
import { GetDriverTeamOutputDTO } from './dtos/GetDriverTeamOutputDTO';
import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO';
@ApiTags('teams')
@Controller('teams')
@@ -10,91 +20,63 @@ export class TeamController {
@Get('all')
@ApiOperation({ summary: 'Get all teams' })
@ApiResponse({ status: 200, description: 'List of all teams', type: AllTeamsViewModel })
async getAllTeams(): Promise<AllTeamsViewModel> {
return this.teamService.getAllTeams();
@ApiResponse({ status: 200, description: 'List of all teams', type: GetAllTeamsOutputDTO })
async getAll(): Promise<GetAllTeamsOutputDTO> {
return this.teamService.getAll();
}
@Get(':teamId')
@ApiOperation({ summary: 'Get team details' })
@ApiResponse({ status: 200, description: 'Team details', type: TeamDetailsViewModel })
@ApiResponse({ status: 200, description: 'Team details', type: GetTeamDetailsOutputDTO })
@ApiResponse({ status: 404, description: 'Team not found' })
async getTeamDetails(
@Param('teamId') teamId: string,
): Promise<TeamDetailsViewModel | null> {
return this.teamService.getTeamDetails(teamId);
async getDetails(@Param('teamId') teamId: string, @Req() req: Request): Promise<GetTeamDetailsOutputDTO | null> {
const userId = req['user']?.userId;
return this.teamService.getDetails(teamId, userId);
}
@Get(':teamId/members')
@ApiOperation({ summary: 'Get team members' })
@ApiResponse({ status: 200, description: 'Team members', type: TeamMembersViewModel })
async getTeamMembers(@Param('teamId') teamId: string): Promise<TeamMembersViewModel> {
return this.teamService.getTeamMembers(teamId);
@ApiResponse({ status: 200, description: 'Team members', type: GetTeamMembersOutputDTO })
async getMembers(@Param('teamId') teamId: string): Promise<GetTeamMembersOutputDTO> {
return this.teamService.getMembers(teamId);
}
@Get(':teamId/join-requests')
@ApiOperation({ summary: 'Get team join requests' })
@ApiResponse({ status: 200, description: 'Team join requests', type: TeamJoinRequestsViewModel })
async getTeamJoinRequests(@Param('teamId') teamId: string): Promise<TeamJoinRequestsViewModel> {
return this.teamService.getTeamJoinRequests(teamId);
}
@Post(':teamId/join-requests/approve')
@ApiOperation({ summary: 'Approve a team join request' })
@ApiBody({ type: ApproveTeamJoinRequestInput })
@ApiResponse({ status: 200, description: 'Join request approved', type: ApproveTeamJoinRequestOutput })
@ApiResponse({ status: 404, description: 'Join request not found' })
async approveJoinRequest(
@Param('teamId') teamId: string,
@Body() input: ApproveTeamJoinRequestInput,
): Promise<ApproveTeamJoinRequestOutput> {
return this.teamService.approveTeamJoinRequest({ ...input, teamId });
}
@Post(':teamId/join-requests/reject')
@ApiOperation({ summary: 'Reject a team join request' })
@ApiBody({ type: RejectTeamJoinRequestInput })
@ApiResponse({ status: 200, description: 'Join request rejected', type: RejectTeamJoinRequestOutput })
@ApiResponse({ status: 404, description: 'Join request not found' })
async rejectJoinRequest(
@Param('teamId') teamId: string,
@Body() input: RejectTeamJoinRequestInput,
): Promise<RejectTeamJoinRequestOutput> {
return this.teamService.rejectTeamJoinRequest({ ...input, teamId });
@ApiResponse({ status: 200, description: 'Team join requests', type: GetTeamJoinRequestsOutputDTO })
async getJoinRequests(@Param('teamId') teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
return this.teamService.getJoinRequests(teamId);
}
@Post()
@ApiOperation({ summary: 'Create a new team' })
@ApiBody({ type: CreateTeamInput })
@ApiResponse({ status: 201, description: 'Team created successfully', type: CreateTeamOutput })
async createTeam(@Body() input: CreateTeamInput): Promise<CreateTeamOutput> {
return this.teamService.createTeam(input);
@ApiResponse({ status: 201, description: 'Team created', type: CreateTeamOutputDTO })
async create(@Body() input: CreateTeamInputDTO, @Req() req: Request): Promise<CreateTeamOutputDTO> {
const userId = req['user']?.userId;
return this.teamService.create(input, userId);
}
@Patch(':teamId')
@ApiOperation({ summary: 'Update team details' })
@ApiBody({ type: UpdateTeamInput })
@ApiResponse({ status: 200, description: 'Team updated successfully', type: UpdateTeamOutput })
@ApiResponse({ status: 404, description: 'Team not found' })
async updateTeam(
@Param('teamId') teamId: string,
@Body() input: UpdateTeamInput,
): Promise<UpdateTeamOutput> {
return this.teamService.updateTeam({ ...input, teamId });
@ApiOperation({ summary: 'Update team' })
@ApiResponse({ status: 200, description: 'Team updated', type: UpdateTeamOutputDTO })
async update(@Param('teamId') teamId: string, @Body() input: UpdateTeamInputDTO, @Req() req: Request): Promise<UpdateTeamOutputDTO> {
const userId = req['user']?.userId;
return this.teamService.update(teamId, input, userId);
}
@Get('driver/:driverId')
@ApiOperation({ summary: 'Get team for a driver' })
@ApiResponse({ status: 200, description: 'Driver team membership', type: DriverTeamViewModel })
@ApiResponse({ status: 404, description: 'Driver not in a team' })
async getDriverTeam(@Param('driverId') driverId: string): Promise<DriverTeamViewModel | null> {
return this.teamService.getDriverTeam({ teamId: '', driverId });
@ApiOperation({ summary: 'Get driver\'s team' })
@ApiResponse({ status: 200, description: 'Driver\'s team', type: GetDriverTeamOutputDTO })
@ApiResponse({ status: 404, description: 'Team not found' })
async getDriverTeam(@Param('driverId') driverId: string): Promise<GetDriverTeamOutputDTO | null> {
return this.teamService.getDriverTeam(driverId);
}
@Get('leaderboard')
@ApiOperation({ summary: 'Get teams leaderboard' })
@ApiResponse({ status: 200, description: 'Teams leaderboard' })
async getTeamsLeaderboard(): Promise<any> {
return this.teamService.getTeamsLeaderboard();
@Get(':teamId/members/:driverId')
@ApiOperation({ summary: 'Get team membership for a driver' })
@ApiResponse({ status: 200, description: 'Team membership', type: GetTeamMembershipOutputDTO })
@ApiResponse({ status: 404, description: 'Membership not found' })
async getMembership(@Param('teamId') teamId: string, @Param('driverId') driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
return this.teamService.getMembership(teamId, driverId);
}
}
}

View File

@@ -8,4 +8,4 @@ import { TeamProviders } from './TeamProviders';
providers: TeamProviders,
exports: [TeamService],
})
export class TeamModule {}
export class TeamModule {}

View File

@@ -1,166 +1,6 @@
import { Provider } from '@nestjs/common';
import { TeamService } from './TeamService';
// Import core interfaces
import { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
import { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
import { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
import type { Logger } from '@core/shared/application/Logger';
// Import concrete in-memory implementations
import { InMemoryTeamRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
// Import use cases
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase';
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
import { ApproveTeamJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveTeamJoinRequestUseCase';
import { RejectTeamJoinRequestUseCase } from '@core/racing/application/use-cases/RejectTeamJoinRequestUseCase';
import { GetTeamsLeaderboardUseCase } from '@core/racing/application/use-cases/GetTeamsLeaderboardUseCase';
// Import presenters for use case initialization
import { DriverTeamPresenter } from './presenters/DriverTeamPresenter';
import { TeamMembersPresenter } from './presenters/TeamMembersPresenter';
import { TeamJoinRequestsPresenter } from './presenters/TeamJoinRequestsPresenter';
import { TeamsLeaderboardPresenter } from './presenters/TeamsLeaderboardPresenter';
// Tokens
export const TEAM_REPOSITORY_TOKEN = 'ITeamRepository';
export const TEAM_MEMBERSHIP_REPOSITORY_TOKEN = 'ITeamMembershipRepository';
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
export const TEAM_GET_ALL_USE_CASE_TOKEN = 'GetAllTeamsUseCase';
export const TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN = 'GetDriverTeamUseCase';
export const TEAM_GET_DETAILS_USE_CASE_TOKEN = 'GetTeamDetailsUseCase';
export const TEAM_GET_MEMBERS_USE_CASE_TOKEN = 'GetTeamMembersUseCase';
export const TEAM_GET_JOIN_REQUESTS_USE_CASE_TOKEN = 'GetTeamJoinRequestsUseCase';
export const TEAM_CREATE_USE_CASE_TOKEN = 'CreateTeamUseCase';
export const TEAM_UPDATE_USE_CASE_TOKEN = 'UpdateTeamUseCase';
export const TEAM_APPROVE_JOIN_REQUEST_USE_CASE_TOKEN = 'ApproveTeamJoinRequestUseCase';
export const TEAM_REJECT_JOIN_REQUEST_USE_CASE_TOKEN = 'RejectTeamJoinRequestUseCase';
export const TEAM_GET_LEADERBOARD_USE_CASE_TOKEN = 'GetTeamsLeaderboardUseCase';
export const TEAM_LOGGER_TOKEN = 'Logger';
// Simple image service implementation for team module
class SimpleImageService implements IImageServicePort {
getDriverAvatar(driverId: string): string {
return `/api/media/avatars/${driverId}`;
}
getTeamLogo(teamId: string): string {
return `/api/media/teams/${teamId}/logo`;
}
getLeagueCover(leagueId: string): string {
return `/api/media/leagues/${leagueId}/cover`;
}
getLeagueLogo(leagueId: string): string {
return `/api/media/leagues/${leagueId}/logo`;
}
}
export const TeamProviders: Provider[] = [
TeamService, // Provide the service itself
{
provide: TEAM_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryTeamRepository(logger),
inject: [TEAM_LOGGER_TOKEN],
},
{
provide: TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryTeamMembershipRepository(logger),
inject: [TEAM_LOGGER_TOKEN],
},
{
provide: DRIVER_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryDriverRepository(logger),
inject: [TEAM_LOGGER_TOKEN],
},
{
provide: IMAGE_SERVICE_TOKEN,
useClass: SimpleImageService,
},
{
provide: TEAM_LOGGER_TOKEN,
useClass: ConsoleLogger,
},
// Use cases
{
provide: TEAM_GET_ALL_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
new GetAllTeamsUseCase(teamRepo, membershipRepo, logger),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN],
},
{
provide: TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
new GetDriverTeamUseCase(teamRepo, membershipRepo, logger, new DriverTeamPresenter()),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN],
},
{
provide: TEAM_GET_DETAILS_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
new GetTeamDetailsUseCase(teamRepo, membershipRepo),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
},
{
provide: TEAM_GET_MEMBERS_USE_CASE_TOKEN,
useFactory: (
membershipRepo: ITeamMembershipRepository,
driverRepo: IDriverRepository,
imageService: IImageServicePort,
logger: Logger,
) => new GetTeamMembersUseCase(membershipRepo, driverRepo, imageService, logger, new TeamMembersPresenter()),
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, TEAM_LOGGER_TOKEN],
},
{
provide: TEAM_GET_JOIN_REQUESTS_USE_CASE_TOKEN,
useFactory: (
membershipRepo: ITeamMembershipRepository,
driverRepo: IDriverRepository,
imageService: IImageServicePort,
logger: Logger,
) => new GetTeamJoinRequestsUseCase(membershipRepo, driverRepo, imageService, logger, new TeamJoinRequestsPresenter()),
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, TEAM_LOGGER_TOKEN],
},
{
provide: TEAM_CREATE_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
new CreateTeamUseCase(teamRepo, membershipRepo),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
},
{
provide: TEAM_UPDATE_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
new UpdateTeamUseCase(teamRepo, membershipRepo),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
},
{
provide: TEAM_APPROVE_JOIN_REQUEST_USE_CASE_TOKEN,
useFactory: (membershipRepo: ITeamMembershipRepository, logger: Logger) =>
new ApproveTeamJoinRequestUseCase(membershipRepo, logger),
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN],
},
{
provide: TEAM_REJECT_JOIN_REQUEST_USE_CASE_TOKEN,
useFactory: (membershipRepo: ITeamMembershipRepository) =>
new RejectTeamJoinRequestUseCase(membershipRepo),
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
},
{
provide: TEAM_GET_LEADERBOARD_USE_CASE_TOKEN,
useFactory: (
teamRepo: ITeamRepository,
membershipRepo: ITeamMembershipRepository,
driverRepo: IDriverRepository,
logger: Logger,
) => new GetTeamsLeaderboardUseCase(teamRepo, membershipRepo, driverRepo, () => null, logger),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN],
},
];
TeamService,
];

View File

@@ -1,181 +1,72 @@
import { Injectable, Inject } from '@nestjs/common';
import { AllTeamsViewModel, GetDriverTeamQuery, DriverTeamViewModel, TeamDetailsViewModel, TeamMembersViewModel, TeamJoinRequestsViewModel, CreateTeamInput, CreateTeamOutput, UpdateTeamInput, UpdateTeamOutput, ApproveTeamJoinRequestInput, ApproveTeamJoinRequestOutput, RejectTeamJoinRequestInput, RejectTeamJoinRequestOutput } from './dto/TeamDto';
// Use cases
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase';
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
import { ApproveTeamJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveTeamJoinRequestUseCase';
import { RejectTeamJoinRequestUseCase } from '@core/racing/application/use-cases/RejectTeamJoinRequestUseCase';
// Presenters
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
import { DriverTeamPresenter } from './presenters/DriverTeamPresenter';
import { TeamDetailsPresenter } from './presenters/TeamDetailsPresenter';
import { TeamMembersPresenter } from './presenters/TeamMembersPresenter';
import { TeamJoinRequestsPresenter } from './presenters/TeamJoinRequestsPresenter';
// Logger
import type { Logger } from '@core/shared/application/Logger';
// Tokens
import {
TEAM_GET_ALL_USE_CASE_TOKEN,
TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN,
TEAM_GET_DETAILS_USE_CASE_TOKEN,
TEAM_GET_MEMBERS_USE_CASE_TOKEN,
TEAM_GET_JOIN_REQUESTS_USE_CASE_TOKEN,
TEAM_CREATE_USE_CASE_TOKEN,
TEAM_UPDATE_USE_CASE_TOKEN,
TEAM_APPROVE_JOIN_REQUEST_USE_CASE_TOKEN,
TEAM_REJECT_JOIN_REQUEST_USE_CASE_TOKEN,
TEAM_GET_LEADERBOARD_USE_CASE_TOKEN,
TEAM_LOGGER_TOKEN
} from './TeamProviders';
import { Injectable } from '@nestjs/common';
import { GetAllTeamsOutputDTO } from './dtos/GetAllTeamsOutputDTO';
import { GetTeamDetailsOutputDTO } from './dtos/GetTeamDetailsOutputDTO';
import { GetTeamMembersOutputDTO } from './dtos/GetTeamMembersOutputDTO';
import { GetTeamJoinRequestsOutputDTO } from './dtos/GetTeamJoinRequestsOutputDTO';
import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO';
import { CreateTeamOutputDTO } from './dtos/CreateTeamOutputDTO';
import { UpdateTeamInputDTO } from './dtos/UpdateTeamInputDTO';
import { UpdateTeamOutputDTO } from './dtos/UpdateTeamOutputDTO';
import { GetDriverTeamOutputDTO } from './dtos/GetDriverTeamOutputDTO';
import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO';
@Injectable()
export class TeamService {
constructor(
@Inject(TEAM_GET_ALL_USE_CASE_TOKEN) private readonly getAllTeamsUseCase: GetAllTeamsUseCase,
@Inject(TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN) private readonly getDriverTeamUseCase: GetDriverTeamUseCase,
@Inject(TEAM_GET_DETAILS_USE_CASE_TOKEN) private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase,
@Inject(TEAM_GET_MEMBERS_USE_CASE_TOKEN) private readonly getTeamMembersUseCase: GetTeamMembersUseCase,
@Inject(TEAM_GET_JOIN_REQUESTS_USE_CASE_TOKEN) private readonly getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase,
@Inject(TEAM_CREATE_USE_CASE_TOKEN) private readonly createTeamUseCase: CreateTeamUseCase,
@Inject(TEAM_UPDATE_USE_CASE_TOKEN) private readonly updateTeamUseCase: UpdateTeamUseCase,
@Inject(TEAM_APPROVE_JOIN_REQUEST_USE_CASE_TOKEN) private readonly approveTeamJoinRequestUseCase: ApproveTeamJoinRequestUseCase,
@Inject(TEAM_REJECT_JOIN_REQUEST_USE_CASE_TOKEN) private readonly rejectTeamJoinRequestUseCase: RejectTeamJoinRequestUseCase,
@Inject(TEAM_GET_LEADERBOARD_USE_CASE_TOKEN) private readonly getTeamsLeaderboardUseCase: GetTeamsLeaderboardUseCase,
@Inject(TEAM_LOGGER_TOKEN) private readonly logger: Logger,
) {}
async getAllTeams(): Promise<AllTeamsViewModel> {
this.logger.debug('[TeamService] Fetching all teams.');
const presenter = new AllTeamsPresenter();
await this.getAllTeamsUseCase.execute(undefined, presenter);
return presenter.viewModel as unknown as AllTeamsViewModel;
async getAll(): Promise<GetAllTeamsOutputDTO> {
// TODO: Implement getAll teams logic
return {
teams: [],
totalCount: 0,
};
}
async getDriverTeam(query: GetDriverTeamQuery): Promise<DriverTeamViewModel | null> {
this.logger.debug(`[TeamService] Fetching driver team for driverId: ${query.driverId}`);
const presenter = new DriverTeamPresenter();
try {
await this.getDriverTeamUseCase.execute({ driverId: query.driverId }, presenter);
return presenter.viewModel as unknown as DriverTeamViewModel;
} catch (error) {
this.logger.error(`Error fetching driver team: ${error}`);
return null;
}
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
// TODO: Implement get team details logic
return null;
}
async getTeamDetails(teamId: string): Promise<TeamDetailsViewModel | null> {
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}`);
const presenter = new TeamDetailsPresenter();
try {
await this.getTeamDetailsUseCase.execute({ teamId, driverId: '' }, presenter);
return presenter.viewModel as unknown as TeamDetailsViewModel;
} catch (error) {
this.logger.error(`Error fetching team details: ${error}`);
return null;
}
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
// TODO: Implement get team members logic
return {
members: [],
totalCount: 0,
ownerCount: 0,
managerCount: 0,
memberCount: 0,
};
}
async getTeamMembers(teamId: string): Promise<TeamMembersViewModel> {
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
const presenter = new TeamMembersPresenter();
await this.getTeamMembersUseCase.execute({ teamId }, presenter);
return presenter.viewModel as unknown as TeamMembersViewModel;
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
// TODO: Implement get team join requests logic
return {
requests: [],
pendingCount: 0,
totalCount: 0,
};
}
async getTeamJoinRequests(teamId: string): Promise<TeamJoinRequestsViewModel> {
this.logger.debug(`[TeamService] Fetching join requests for teamId: ${teamId}`);
const presenter = new TeamJoinRequestsPresenter();
await this.getTeamJoinRequestsUseCase.execute({ teamId }, presenter);
return presenter.viewModel as unknown as TeamJoinRequestsViewModel;
async create(input: CreateTeamInputDTO, userId?: string): Promise<CreateTeamOutputDTO> {
// TODO: Implement create team logic
return {
id: 'placeholder-id',
success: true,
};
}
async createTeam(input: CreateTeamInput): Promise<CreateTeamOutput> {
this.logger.debug('[TeamService] Creating team', input);
try {
const result = await this.createTeamUseCase.execute({
name: input.name,
tag: input.tag,
description: input.description,
ownerId: input.ownerId,
leagues: [],
});
return {
teamId: result.team.id,
success: true,
};
} catch (error) {
this.logger.error(`Error creating team: ${error}`);
throw error;
}
async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise<UpdateTeamOutputDTO> {
// TODO: Implement update team logic
return {
success: true,
};
}
async updateTeam(input: UpdateTeamInput & { teamId: string }): Promise<UpdateTeamOutput> {
this.logger.debug('[TeamService] Updating team', input);
try {
await this.updateTeamUseCase.execute({
teamId: input.teamId,
updates: {
name: input.name,
tag: input.tag,
description: input.description,
},
updatedBy: input.updatedBy,
});
return { success: true };
} catch (error) {
this.logger.error(`Error updating team: ${error}`);
throw error;
}
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
// TODO: Implement get driver team logic
return null;
}
async approveTeamJoinRequest(input: ApproveTeamJoinRequestInput & { teamId: string }): Promise<ApproveTeamJoinRequestOutput> {
this.logger.debug('[TeamService] Approving team join request', input);
try {
await this.approveTeamJoinRequestUseCase.execute({ requestId: input.requestId });
return { success: true };
} catch (error) {
this.logger.error(`Error approving join request: ${error}`);
throw error;
}
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
// TODO: Implement get team membership logic
return null;
}
async rejectTeamJoinRequest(input: RejectTeamJoinRequestInput & { teamId: string }): Promise<RejectTeamJoinRequestOutput> {
this.logger.debug('[TeamService] Rejecting team join request', input);
try {
await this.rejectTeamJoinRequestUseCase.execute({ requestId: input.requestId });
return { success: true };
} catch (error) {
this.logger.error(`Error rejecting join request: ${error}`);
throw error;
}
}
async getTeamsLeaderboard(): Promise<any> {
this.logger.debug('[TeamService] Fetching teams leaderboard');
const result = await this.getTeamsLeaderboardUseCase.execute();
if (result.isErr()) {
this.logger.error(`Error fetching teams leaderboard: ${result.error}`);
throw new Error('Failed to fetch teams leaderboard');
}
return result.value;
}
}
}

View 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;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class CreateTeamOutputDTO {
@ApiProperty()
id: string;
@ApiProperty()
success: boolean;
}

View 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;
}

View 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;
}

View 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;
}

View File

@@ -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;
}

View 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;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
export class GetTeamMembershipOutputDTO {
@ApiProperty()
role: 'owner' | 'manager' | 'member';
@ApiProperty()
joinedAt: string;
@ApiProperty()
isActive: boolean;
}

View 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;
}

View File

@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class UpdateTeamOutputDTO {
@ApiProperty()
success: boolean;
}

View File

@@ -15,6 +15,7 @@ async function bootstrap() {
.setTitle('GridPilot API')
.setDescription('GridPilot API documentation')
.setVersion('1.0')
.addTag('dashboard', 'Dashboard endpoints')
.addTag('races', 'Race management endpoints')
.addTag('leagues', 'League management endpoints')
.addTag('teams', 'Team management endpoints')

View File

@@ -173,8 +173,7 @@ interface TeamLeaderboardPreviewProps {
function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewProps) {
const router = useRouter();
const top5 = [...teams]
.filter((t) => t.rating !== null)
.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0))
.sort((a, b) => b.memberCount - a.memberCount)
.slice(0, 5);
const getMedalColor = (position: number) => {
@@ -257,8 +256,8 @@ function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewPr
{/* Stats */}
<div className="flex items-center gap-4 text-sm">
<div className="text-center">
<p className="text-purple-400 font-mono font-semibold">{team.rating?.toLocaleString()}</p>
<p className="text-[10px] text-gray-500">Rating</p>
<p className="text-purple-400 font-mono font-semibold">{team.memberCount}</p>
<p className="text-[10px] text-gray-500">Members</p>
</div>
<div className="text-center">
<p className="text-performance-green font-mono font-semibold">{team.totalWins}</p>

View File

@@ -7,7 +7,7 @@ import Card from '@/components/ui/Card';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles';
import { useServices } from '@/lib/services/ServiceProvider';
import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
import type { LeagueConfigFormModel } from '@core/racing/application';
import { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel';
import { AlertTriangle, Settings, UserCog } from 'lucide-react';
import { useParams, useRouter } from 'next/navigation';

View File

@@ -4,7 +4,7 @@ import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useServices } from '@/lib/services/ServiceProvider';
import type { LeagueMembership } from '@/lib/types/LeagueMembership';
import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
import Link from 'next/link';
import { useEffect, useState } from 'react';

View File

@@ -1,24 +1,10 @@
import { BaseApiClient } from '../base/BaseApiClient';
import { RecordPageViewOutputDTO } from '../../types/generated/RecordPageViewOutputDTO';
import { RecordEngagementOutputDTO } from '../../types/generated/RecordEngagementOutputDTO';
// TODO: Move these types to apps/website/lib/types/generated when available
type RecordPageViewInputDto = { path: string; userId?: string };
type RecordEngagementInputDto = { eventType: string; userId?: string; metadata?: Record<string, unknown> };
// TODO: Move these types to apps/website/lib/types/generated when available
type AnalyticsDashboardDto = {
totalUsers: number;
activeUsers: number;
totalRaces: number;
totalLeagues: number;
};
type AnalyticsMetricsDto = {
pageViews: number;
uniqueVisitors: number;
averageSessionDuration: number;
bounceRate: number;
};
import { GetDashboardDataOutputDTO } from '../../types/generated/GetDashboardDataOutputDTO';
import { GetAnalyticsMetricsOutputDTO } from '../../types/generated/GetAnalyticsMetricsOutputDTO';
import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO';
import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO';
/**
* Analytics API Client
@@ -27,22 +13,22 @@ type AnalyticsMetricsDto = {
*/
export class AnalyticsApiClient extends BaseApiClient {
/** Record a page view */
recordPageView(input: RecordPageViewInputDto): Promise<RecordPageViewOutputDTO> {
recordPageView(input: RecordPageViewInputDTO): Promise<RecordPageViewOutputDTO> {
return this.post<RecordPageViewOutputDTO>('/analytics/page-view', input);
}
/** Record an engagement event */
recordEngagement(input: RecordEngagementInputDto): Promise<RecordEngagementOutputDTO> {
recordEngagement(input: RecordEngagementInputDTO): Promise<RecordEngagementOutputDTO> {
return this.post<RecordEngagementOutputDTO>('/analytics/engagement', input);
}
/** Get analytics dashboard data */
getDashboardData(): Promise<AnalyticsDashboardDto> {
return this.get<AnalyticsDashboardDto>('/analytics/dashboard');
getDashboardData(): Promise<GetDashboardDataOutputDTO> {
return this.get<GetDashboardDataOutputDTO>('/analytics/dashboard');
}
/** Get analytics metrics */
getAnalyticsMetrics(): Promise<AnalyticsMetricsDto> {
return this.get<AnalyticsMetricsDto>('/analytics/metrics');
getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutputDTO> {
return this.get<GetAnalyticsMetricsOutputDTO>('/analytics/metrics');
}
}

View File

@@ -1,9 +1,9 @@
import { BaseApiClient } from '../base/BaseApiClient';
import { AuthSessionDTO } from '../../types/generated/AuthSessionDTO';
// TODO: Create DTOs for login/signup params in apps/website/lib/types/generated
type LoginParamsDto = { email: string; password: string };
type SignupParamsDto = { email: string; password: string; displayName: string };
import { LoginParams } from '../../types/generated/LoginParams';
import { SignupParams } from '../../types/generated/SignupParams';
import { LoginWithIracingCallbackParams } from '../../types/generated/LoginWithIracingCallbackParams';
import { IracingAuthRedirectResult } from '../../types/generated/IracingAuthRedirectResult';
/**
* Auth API Client
@@ -12,12 +12,12 @@ type SignupParamsDto = { email: string; password: string; displayName: string };
*/
export class AuthApiClient extends BaseApiClient {
/** Sign up with email */
signup(params: SignupParamsDto): Promise<AuthSessionDTO> {
signup(params: SignupParams): Promise<AuthSessionDTO> {
return this.post<AuthSessionDTO>('/auth/signup', params);
}
/** Login with email */
login(params: LoginParamsDto): Promise<AuthSessionDTO> {
login(params: LoginParams): Promise<AuthSessionDTO> {
return this.post<AuthSessionDTO>('/auth/login', params);
}
@@ -32,9 +32,19 @@ export class AuthApiClient extends BaseApiClient {
}
/** Start iRacing auth redirect */
getIracingAuthUrl(returnTo?: string): string {
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const params = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
return `${baseUrl}/auth/iracing/start${params}`;
startIracingAuthRedirect(returnTo?: string): Promise<IracingAuthRedirectResult> {
const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
return this.get<IracingAuthRedirectResult>(`/auth/iracing/start${query}`);
}
/** Login with iRacing callback */
loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise<AuthSessionDTO> {
const query = new URLSearchParams();
query.append('code', params.code);
query.append('state', params.state);
if (params.returnTo) {
query.append('returnTo', params.returnTo);
}
return this.get<AuthSessionDTO>(`/auth/iracing/callback?${query.toString()}`);
}
}

View File

@@ -1,61 +1,27 @@
import { BaseApiClient } from '../base/BaseApiClient';
import {
DashboardDriverSummaryDTO,
DashboardRaceSummaryDTO,
DashboardLeagueStandingSummaryDTO,
DashboardFeedItemSummaryDTO,
DashboardFriendSummaryDTO,
DashboardRecentResultDTO,
} from '../../types/generated';
// DTOs
export type DriverDto = {
id: string;
name: string;
avatarUrl: string;
country: string;
totalRaces: number;
wins: number;
podiums: number;
rating: number;
globalRank: number;
consistency: number;
};
export type RaceDto = {
id: string;
track: string;
car: string;
scheduledAt: string; // ISO date string
isMyLeague: boolean;
leagueName?: string;
};
export type LeagueStandingDto = {
leagueId: string;
leagueName: string;
position: number;
points: number;
totalDrivers: number;
};
export type FeedItemDto = {
id: string;
type: string;
headline: string;
body: string | null;
timestamp: string; // ISO date string
ctaHref?: string;
ctaLabel?: string;
};
export type FriendDto = {
id: string;
name: string;
avatarUrl: string;
country: string;
};
// Define DashboardOverviewDTO using generated types
export type DashboardOverviewDto = {
currentDriver: DriverDto;
nextRace: RaceDto | null;
upcomingRaces: RaceDto[];
leagueStandings: LeagueStandingDto[];
feedItems: FeedItemDto[];
friends: FriendDto[];
currentDriver: DashboardDriverSummaryDTO | null;
myUpcomingRaces: DashboardRaceSummaryDTO[];
otherUpcomingRaces: DashboardRaceSummaryDTO[];
upcomingRaces: DashboardRaceSummaryDTO[];
activeLeaguesCount: number;
nextRace: DashboardRaceSummaryDTO | null;
recentResults: DashboardRecentResultDTO[];
leagueStandingsSummaries: DashboardLeagueStandingSummaryDTO[];
feedSummary: {
feedItems: DashboardFeedItemSummaryDTO[];
};
friends: DashboardFriendSummaryDTO[];
};
/**

View File

@@ -1,15 +1,6 @@
import { BaseApiClient } from '../base/BaseApiClient';
// Import generated types
import type { CompleteOnboardingInputDTO, CompleteOnboardingOutputDTO, DriverRegistrationStatusDTO, DriverLeaderboardItemDTO, DriverProfileDTO } from '../../types/generated';
// TODO: Create proper DriverDTO in generated types
type DriverDTO = {
id: string;
name: string;
avatarUrl?: string;
iracingId?: string;
rating?: number;
};
import type { CompleteOnboardingInputDTO, CompleteOnboardingOutputDTO, DriverRegistrationStatusDTO, DriverLeaderboardItemDTO, DriverProfileDTO, GetDriverOutputDTO } from '../../types/generated';
type DriversLeaderboardDto = {
drivers: DriverLeaderboardItemDTO[];
@@ -32,8 +23,8 @@ export class DriversApiClient extends BaseApiClient {
}
/** Get current driver (based on session) */
getCurrent(): Promise<DriverDTO | null> {
return this.get<DriverDTO | null>('/drivers/current');
getCurrent(): Promise<GetDriverOutputDTO | null> {
return this.get<GetDriverOutputDTO | null>('/drivers/current');
}
/** Get driver registration status for a specific race */
@@ -42,8 +33,8 @@ export class DriversApiClient extends BaseApiClient {
}
/** Get driver by ID */
getDriver(driverId: string): Promise<DriverDTO | null> {
return this.get<DriverDTO | null>(`/drivers/${driverId}`);
getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
return this.get<GetDriverOutputDTO | null>(`/drivers/${driverId}`);
}
/** Get driver profile with full details */
@@ -52,7 +43,7 @@ export class DriversApiClient extends BaseApiClient {
}
/** Update current driver profile */
updateProfile(updates: { bio?: string; country?: string }): Promise<DriverDTO> {
return this.put<DriverDTO>('/drivers/profile', updates);
updateProfile(updates: { bio?: string; country?: string }): Promise<GetDriverOutputDTO> {
return this.put<GetDriverOutputDTO>('/drivers/profile', updates);
}
}

View File

@@ -8,6 +8,8 @@ import { AnalyticsApiClient } from './analytics/AnalyticsApiClient';
import { AuthApiClient } from './auth/AuthApiClient';
import { PaymentsApiClient } from './payments/PaymentsApiClient';
import { DashboardApiClient } from './dashboard/DashboardApiClient';
import { PenaltiesApiClient } from './penalties/PenaltiesApiClient';
import { ProtestsApiClient } from './protests/ProtestsApiClient';
/**
* Main API Client
@@ -25,6 +27,8 @@ export class ApiClient {
public readonly auth: AuthApiClient;
public readonly payments: PaymentsApiClient;
public readonly dashboard: DashboardApiClient;
public readonly penalties: PenaltiesApiClient;
public readonly protests: ProtestsApiClient;
constructor(baseUrl: string) {
this.leagues = new LeaguesApiClient(baseUrl);
@@ -37,6 +41,8 @@ export class ApiClient {
this.auth = new AuthApiClient(baseUrl);
this.payments = new PaymentsApiClient(baseUrl);
this.dashboard = new DashboardApiClient(baseUrl);
this.penalties = new PenaltiesApiClient(baseUrl);
this.protests = new ProtestsApiClient(baseUrl);
}
}

View File

@@ -7,6 +7,8 @@ import type {
LeagueMembershipsDto,
CreateLeagueInputDto,
CreateLeagueOutputDto,
SponsorshipDetailDTO,
RaceDTO,
} from '../../dtos';
/**
@@ -61,8 +63,8 @@ export class LeaguesApiClient extends BaseApiClient {
}
/** Get season sponsorships */
getSeasonSponsorships(seasonId: string): Promise<{ sponsorships: Array<{ sponsorId: string; tier: string; status: string }> }> {
return this.get<{ sponsorships: Array<{ sponsorId: string; tier: string; status: string }> }>(`/seasons/${seasonId}/sponsorships`);
getSeasonSponsorships(seasonId: string): Promise<{ sponsorships: SponsorshipDetailDTO[] }> {
return this.get<{ sponsorships: SponsorshipDetailDTO[] }>(`/leagues/seasons/${seasonId}/sponsorships`);
}
/** Get league config */
@@ -84,7 +86,7 @@ export class LeaguesApiClient extends BaseApiClient {
}
/** Get races for a league */
getRaces(leagueId: string): Promise<{ races: any[] }> {
return this.get<{ races: any[] }>(`/leagues/${leagueId}/races`);
getRaces(leagueId: string): Promise<{ races: RaceDTO[] }> {
return this.get<{ races: RaceDTO[] }>(`/leagues/${leagueId}/races`);
}
}

View File

@@ -1,14 +1,13 @@
import type {
DeleteMediaOutputDto,
GetMediaOutputDto,
RequestAvatarGenerationInputDto,
RequestAvatarGenerationOutputDto,
UpdateAvatarInputDto,
UpdateAvatarOutputDto,
UploadMediaInputDto,
UploadMediaOutputDto,
} from '../../dtos';
import type { GetAvatarOutputDto } from '../../types/GetAvatarOutputDto';
DeleteMediaOutputDTO,
GetMediaOutputDTO,
RequestAvatarGenerationInputDTO,
RequestAvatarGenerationOutputDTO,
UpdateAvatarInputDTO,
UpdateAvatarOutputDTO,
UploadMediaOutputDTO,
} from '../generated';
import type { GetAvatarOutputDTO } from '../generated';
import { BaseApiClient } from '../base/BaseApiClient';
/**
@@ -18,38 +17,38 @@ import { BaseApiClient } from '../base/BaseApiClient';
*/
export class MediaApiClient extends BaseApiClient {
/** Upload media file */
uploadMedia(input: UploadMediaInputDto): Promise<UploadMediaOutputDto> {
uploadMedia(input: { file: File; type: string; category?: string }): Promise<UploadMediaOutputDTO> {
const formData = new FormData();
formData.append('file', input.file);
formData.append('type', input.type);
if (input.category) {
formData.append('category', input.category);
}
return this.post<UploadMediaOutputDto>('/media/upload', formData);
return this.post<UploadMediaOutputDTO>('/media/upload', formData);
}
/** Get media by ID */
getMedia(mediaId: string): Promise<GetMediaOutputDto> {
return this.get<GetMediaOutputDto>(`/media/${mediaId}`);
getMedia(mediaId: string): Promise<GetMediaOutputDTO> {
return this.get<GetMediaOutputDTO>(`/media/${mediaId}`);
}
/** Delete media by ID */
deleteMedia(mediaId: string): Promise<DeleteMediaOutputDto> {
return this.delete<DeleteMediaOutputDto>(`/media/${mediaId}`);
deleteMedia(mediaId: string): Promise<DeleteMediaOutputDTO> {
return this.delete<DeleteMediaOutputDTO>(`/media/${mediaId}`);
}
/** Request avatar generation */
requestAvatarGeneration(input: RequestAvatarGenerationInputDto): Promise<RequestAvatarGenerationOutputDto> {
return this.post<RequestAvatarGenerationOutputDto>('/media/avatar/generate', input);
requestAvatarGeneration(input: RequestAvatarGenerationInputDTO): Promise<RequestAvatarGenerationOutputDTO> {
return this.post<RequestAvatarGenerationOutputDTO>('/media/avatar/generate', input);
}
/** Get avatar for driver */
getAvatar(driverId: string): Promise<GetAvatarOutputDto> {
return this.get<GetAvatarOutputDto>(`/media/avatar/${driverId}`);
getAvatar(driverId: string): Promise<GetAvatarOutputDTO> {
return this.get<GetAvatarOutputDTO>(`/media/avatar/${driverId}`);
}
/** Update avatar for driver */
updateAvatar(input: UpdateAvatarInputDto): Promise<UpdateAvatarOutputDto> {
return this.put<UpdateAvatarOutputDto>(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl });
updateAvatar(input: UpdateAvatarInputDTO): Promise<UpdateAvatarOutputDTO> {
return this.put<UpdateAvatarOutputDTO>(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl });
}
}

View File

@@ -1,7 +1,8 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type { PaymentDto, MembershipFeeDto, MemberPaymentDto, PrizeDto, WalletDto, TransactionDto, UpdatePaymentStatusInputDTO } from '../types/generated';
// TODO: Import these types from apps/website/lib/types/generated when available
type GetPaymentsOutputDto = { payments: import('../types/generated').PaymentDto[] };
// Define missing types that are not fully generated
type GetPaymentsOutputDto = { payments: PaymentDto[] };
type CreatePaymentInputDto = {
type: 'sponsorship' | 'membership_fee';
amount: number;
@@ -10,15 +11,15 @@ type CreatePaymentInputDto = {
leagueId: string;
seasonId?: string;
};
type CreatePaymentOutputDto = { payment: import('../types/generated').PaymentDto };
type CreatePaymentOutputDto = { payment: PaymentDto };
type GetMembershipFeesOutputDto = {
fee: import('../types/generated').MembershipFeeDto | null;
payments: import('../types/generated').MemberPaymentDto[]
fee: MembershipFeeDto | null;
payments: MemberPaymentDto[]
};
type GetPrizesOutputDto = { prizes: import('../types/generated').PrizeDto[] };
type GetPrizesOutputDto = { prizes: PrizeDto[] };
type GetWalletOutputDto = {
wallet: import('../types/generated').WalletDto;
transactions: import('../types/generated').TransactionDto[]
wallet: WalletDto;
transactions: TransactionDto[]
};
type ProcessWalletTransactionInputDto = {
leagueId: string;
@@ -29,8 +30,8 @@ type ProcessWalletTransactionInputDto = {
referenceType?: 'sponsorship' | 'membership_fee' | 'prize';
};
type ProcessWalletTransactionOutputDto = {
wallet: import('../types/generated').WalletDto;
transaction: import('../types/generated').TransactionDto
wallet: WalletDto;
transaction: TransactionDto
};
type UpdateMemberPaymentInputDto = {
feeId: string;
@@ -38,8 +39,33 @@ type UpdateMemberPaymentInputDto = {
status?: 'pending' | 'paid' | 'overdue';
paidAt?: Date | string;
};
type UpdateMemberPaymentOutputDto = { payment: import('../types/generated').MemberPaymentDto };
type GetWalletTransactionsOutputDto = { transactions: import('../types/generated').TransactionDto[] };
type UpdateMemberPaymentOutputDto = { payment: MemberPaymentDto };
type GetWalletTransactionsOutputDto = { transactions: TransactionDto[] };
type UpdatePaymentStatusOutputDto = { payment: PaymentDto };
type UpsertMembershipFeeInputDto = {
leagueId: string;
seasonId?: string;
type: 'season' | 'monthly' | 'per_race';
amount: number;
};
type UpsertMembershipFeeOutputDto = { fee: MembershipFeeDto };
type CreatePrizeInputDto = {
leagueId: string;
seasonId: string;
position: number;
name: string;
amount: number;
type: 'cash' | 'merchandise' | 'other';
description?: string;
};
type CreatePrizeOutputDto = { prize: PrizeDto };
type AwardPrizeInputDto = {
prizeId: string;
driverId: string;
};
type AwardPrizeOutputDto = { prize: PrizeDto };
type DeletePrizeInputDto = { prizeId: string };
type DeletePrizeOutputDto = { success: boolean };
/**
* Payments API Client
@@ -48,12 +74,14 @@ type GetWalletTransactionsOutputDto = { transactions: import('../types/generated
*/
export class PaymentsApiClient extends BaseApiClient {
/** Get payments */
getPayments(leagueId?: string, driverId?: string): Promise<GetPaymentsOutputDto> {
getPayments(query?: { leagueId?: string; payerId?: string; type?: 'sponsorship' | 'membership_fee'; status?: 'pending' | 'completed' | 'failed' | 'refunded' }): Promise<GetPaymentsOutputDto> {
const params = new URLSearchParams();
if (leagueId) params.append('leagueId', leagueId);
if (driverId) params.append('driverId', driverId);
const query = params.toString();
return this.get<GetPaymentsOutputDto>(`/payments${query ? `?${query}` : ''}`);
if (query?.leagueId) params.append('leagueId', query.leagueId);
if (query?.payerId) params.append('payerId', query.payerId);
if (query?.type) params.append('type', query.type);
if (query?.status) params.append('status', query.status);
const queryString = params.toString();
return this.get<GetPaymentsOutputDto>(`/payments${queryString ? `?${queryString}` : ''}`);
}
/** Create a payment */
@@ -62,21 +90,63 @@ export class PaymentsApiClient extends BaseApiClient {
}
/** Get membership fees */
getMembershipFees(leagueId: string): Promise<GetMembershipFeesOutputDto> {
return this.get<GetMembershipFeesOutputDto>(`/payments/membership-fees?leagueId=${leagueId}`);
getMembershipFees(query: { leagueId: string; driverId?: string }): Promise<GetMembershipFeesOutputDto> {
const params = new URLSearchParams();
params.append('leagueId', query.leagueId);
if (query.driverId) params.append('driverId', query.driverId);
const queryString = params.toString();
return this.get<GetMembershipFeesOutputDto>(`/payments/membership-fees?${queryString}`);
}
/** Get prizes */
getPrizes(leagueId?: string, seasonId?: string): Promise<GetPrizesOutputDto> {
getPrizes(query?: { leagueId?: string; seasonId?: string }): Promise<GetPrizesOutputDto> {
const params = new URLSearchParams();
if (leagueId) params.append('leagueId', leagueId);
if (seasonId) params.append('seasonId', seasonId);
const query = params.toString();
return this.get<GetPrizesOutputDto>(`/payments/prizes${query ? `?${query}` : ''}`);
if (query?.leagueId) params.append('leagueId', query.leagueId);
if (query?.seasonId) params.append('seasonId', query.seasonId);
const queryString = params.toString();
return this.get<GetPrizesOutputDto>(`/payments/prizes${queryString ? `?${queryString}` : ''}`);
}
/** Get wallet */
getWallet(driverId: string): Promise<GetWalletOutputDto> {
return this.get<GetWalletOutputDto>(`/payments/wallets?driverId=${driverId}`);
getWallet(query?: { leagueId?: string }): Promise<GetWalletOutputDto> {
const params = new URLSearchParams();
if (query?.leagueId) params.append('leagueId', query.leagueId);
const queryString = params.toString();
return this.get<GetWalletOutputDto>(`/payments/wallets${queryString ? `?${queryString}` : ''}`);
}
/** Update payment status */
updatePaymentStatus(input: UpdatePaymentStatusInputDTO): Promise<UpdatePaymentStatusOutputDto> {
return this.patch<UpdatePaymentStatusOutputDto>('/payments/status', input);
}
/** Upsert membership fee */
upsertMembershipFee(input: UpsertMembershipFeeInputDto): Promise<UpsertMembershipFeeOutputDto> {
return this.post<UpsertMembershipFeeOutputDto>('/payments/membership-fees', input);
}
/** Update member payment */
updateMemberPayment(input: UpdateMemberPaymentInputDto): Promise<UpdateMemberPaymentOutputDto> {
return this.patch<UpdateMemberPaymentOutputDto>('/payments/membership-fees/member-payment', input);
}
/** Create prize */
createPrize(input: CreatePrizeInputDto): Promise<CreatePrizeOutputDto> {
return this.post<CreatePrizeOutputDto>('/payments/prizes', input);
}
/** Award prize */
awardPrize(input: AwardPrizeInputDto): Promise<AwardPrizeOutputDto> {
return this.patch<AwardPrizeOutputDto>('/payments/prizes/award', input);
}
/** Delete prize */
deletePrize(prizeId: string): Promise<DeletePrizeOutputDto> {
return this.delete<DeletePrizeOutputDto>(`/payments/prizes?prizeId=${prizeId}`);
}
/** Process wallet transaction */
processWalletTransaction(input: ProcessWalletTransactionInputDto): Promise<ProcessWalletTransactionOutputDto> {
return this.post<ProcessWalletTransactionOutputDto>('/payments/wallets/transactions', input);
}
}

View File

@@ -1,4 +1,6 @@
import { BaseApiClient } from '../base/BaseApiClient';
import { RacePenaltiesDTO } from '../../types/generated/RacePenaltiesDTO';
import { ApplyPenaltyCommandDTO } from '../../types/generated/ApplyPenaltyCommandDTO';
/**
* Penalties API Client
@@ -7,12 +9,12 @@ import { BaseApiClient } from '../base/BaseApiClient';
*/
export class PenaltiesApiClient extends BaseApiClient {
/** Get penalties for a race */
getRacePenalties(raceId: string): Promise<{ penalties: any[] }> {
return this.get<{ penalties: any[] }>(`/races/${raceId}/penalties`);
getRacePenalties(raceId: string): Promise<RacePenaltiesDTO> {
return this.get<RacePenaltiesDTO>(`/races/${raceId}/penalties`);
}
/** Apply a penalty */
applyPenalty(input: any): Promise<void> {
applyPenalty(input: ApplyPenaltyCommandDTO): Promise<void> {
return this.post<void>('/races/penalties/apply', input);
}
}

View File

@@ -3,7 +3,9 @@ import type {
LeagueAdminProtestsDTO,
ApplyPenaltyCommandDTO,
RequestProtestDefenseCommandDTO,
} from '../../types';
ReviewProtestCommandDTO,
} from '../../types/generated';
import type { RaceProtestsDTO } from '../../types';
/**
* Protests API Client
@@ -32,12 +34,12 @@ export class ProtestsApiClient extends BaseApiClient {
}
/** Review protest */
reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): Promise<void> {
reviewProtest(input: ReviewProtestCommandDTO): Promise<void> {
return this.post<void>(`/protests/${input.protestId}/review`, input);
}
/** Get protests for a race */
getRaceProtests(raceId: string): Promise<{ protests: any[] }> {
return this.get<{ protests: any[] }>(`/races/${raceId}/protests`);
getRaceProtests(raceId: string): Promise<RaceProtestsDTO> {
return this.get<RaceProtestsDTO>(`/races/${raceId}/protests`);
}
}

View File

@@ -1,15 +1,34 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
RaceStatsDto,
RacesPageDataDto,
RaceDetailDto,
RaceResultsDetailDto,
RaceWithSOFDto,
RegisterForRaceInputDto,
ImportRaceResultsInputDto,
ImportRaceResultsSummaryDto,
WithdrawFromRaceInputDto,
} from '../../dtos';
import type { RaceStatsDTO } from '../../types/generated/RaceStatsDTO';
import type { RacesPageDataRaceDTO } from '../../types/generated/RacesPageDataRaceDTO';
import type { RaceResultsDetailDTO } from '../../types/generated/RaceResultsDetailDTO';
import type { RaceWithSOFDTO } from '../../types/generated/RaceWithSOFDTO';
import type { RegisterForRaceParamsDTO } from '../../types/generated/RegisterForRaceParamsDTO';
import type { ImportRaceResultsDTO } from '../../types/generated/ImportRaceResultsDTO';
import type { WithdrawFromRaceParamsDTO } from '../../types/generated/WithdrawFromRaceParamsDTO';
import type { RaceDetailRaceDTO } from '../../types/generated/RaceDetailRaceDTO';
import type { RaceDetailLeagueDTO } from '../../types/generated/RaceDetailLeagueDTO';
import type { RaceDetailEntryDTO } from '../../types/generated/RaceDetailEntryDTO';
import type { RaceDetailRegistrationDTO } from '../../types/generated/RaceDetailRegistrationDTO';
import type { RaceDetailUserResultDTO } from '../../types/generated/RaceDetailUserResultDTO';
// Define missing types
type RacesPageDataDTO = { races: RacesPageDataRaceDTO[] };
type RaceDetailDTO = {
race: RaceDetailRaceDTO | null;
league: RaceDetailLeagueDTO | null;
entryList: RaceDetailEntryDTO[];
registration: RaceDetailRegistrationDTO;
userResult: RaceDetailUserResultDTO | null;
error?: string;
};
type ImportRaceResultsSummaryDTO = {
success: boolean;
raceId: string;
driversProcessed: number;
resultsRecorded: number;
errors?: string[];
};
/**
* Races API Client
@@ -18,42 +37,42 @@ import type {
*/
export class RacesApiClient extends BaseApiClient {
/** Get total number of races */
getTotal(): Promise<RaceStatsDto> {
return this.get<RaceStatsDto>('/races/total-races');
getTotal(): Promise<RaceStatsDTO> {
return this.get<RaceStatsDTO>('/races/total-races');
}
/** Get races page data */
getPageData(): Promise<RacesPageDataDto> {
return this.get<RacesPageDataDto>('/races/page-data');
getPageData(): Promise<RacesPageDataDTO> {
return this.get<RacesPageDataDTO>('/races/page-data');
}
/** Get race detail */
getDetail(raceId: string, driverId: string): Promise<RaceDetailDto> {
return this.get<RaceDetailDto>(`/races/${raceId}?driverId=${driverId}`);
getDetail(raceId: string, driverId: string): Promise<RaceDetailDTO> {
return this.get<RaceDetailDTO>(`/races/${raceId}?driverId=${driverId}`);
}
/** Get race results detail */
getResultsDetail(raceId: string): Promise<RaceResultsDetailDto> {
return this.get<RaceResultsDetailDto>(`/races/${raceId}/results`);
getResultsDetail(raceId: string): Promise<RaceResultsDetailDTO> {
return this.get<RaceResultsDetailDTO>(`/races/${raceId}/results`);
}
/** Get race with strength of field */
getWithSOF(raceId: string): Promise<RaceWithSOFDto> {
return this.get<RaceWithSOFDto>(`/races/${raceId}/sof`);
getWithSOF(raceId: string): Promise<RaceWithSOFDTO> {
return this.get<RaceWithSOFDTO>(`/races/${raceId}/sof`);
}
/** Register for race */
register(raceId: string, input: RegisterForRaceInputDto): Promise<void> {
register(raceId: string, input: RegisterForRaceParamsDTO): Promise<void> {
return this.post<void>(`/races/${raceId}/register`, input);
}
/** Import race results */
importResults(raceId: string, input: ImportRaceResultsInputDto): Promise<ImportRaceResultsSummaryDto> {
return this.post<ImportRaceResultsSummaryDto>(`/races/${raceId}/import-results`, input);
importResults(raceId: string, input: ImportRaceResultsDTO): Promise<ImportRaceResultsSummaryDTO> {
return this.post<ImportRaceResultsSummaryDTO>(`/races/${raceId}/import-results`, input);
}
/** Withdraw from race */
withdraw(raceId: string, input: WithdrawFromRaceInputDto): Promise<void> {
withdraw(raceId: string, input: WithdrawFromRaceParamsDTO): Promise<void> {
return this.post<void>(`/races/${raceId}/withdraw`, input);
}

View File

@@ -2,11 +2,15 @@ import { BaseApiClient } from '../base/BaseApiClient';
import type { CreateSponsorInputDTO } from '../../types/generated/CreateSponsorInputDTO';
import type { SponsorDashboardDTO } from '../../types/generated/SponsorDashboardDTO';
import type { SponsorSponsorshipsDTO } from '../../types/generated/SponsorSponsorshipsDTO';
import type { GetPendingSponsorshipRequestsOutputDTO } from '../../types/generated/GetPendingSponsorshipRequestsOutputDTO';
import type { AcceptSponsorshipRequestInputDTO } from '../../types/generated/AcceptSponsorshipRequestInputDTO';
import type { RejectSponsorshipRequestInputDTO } from '../../types/generated/RejectSponsorshipRequestInputDTO';
import type { GetSponsorOutputDTO } from '../../types/generated/GetSponsorOutputDTO';
import type { SponsorDTO } from '../../types/generated/SponsorDTO';
// TODO: Move these types to apps/website/lib/types/generated when available
// Types that are not yet generated
export type CreateSponsorOutputDto = { id: string; name: string };
export type GetEntitySponsorshipPricingResultDto = { pricing: Array<{ entityType: string; price: number }> };
export type SponsorDTO = { id: string; name: string; logoUrl?: string; websiteUrl?: string };
export type GetSponsorsOutputDto = { sponsors: SponsorDTO[] };
/**
@@ -41,22 +45,22 @@ export class SponsorsApiClient extends BaseApiClient {
}
/** Get sponsor by ID */
getSponsor(sponsorId: string): Promise<SponsorDTO | null> {
return this.get<SponsorDTO | null>(`/sponsors/${sponsorId}`);
getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO | null> {
return this.get<GetSponsorOutputDTO | null>(`/sponsors/${sponsorId}`);
}
/** Get pending sponsorship requests for an entity */
getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<{ requests: any[] }> {
return this.get<{ requests: any[] }>(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`);
getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<GetPendingSponsorshipRequestsOutputDTO> {
return this.get<GetPendingSponsorshipRequestsOutputDTO>(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`);
}
/** Accept a sponsorship request */
acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<void> {
return this.post(`/sponsors/requests/${requestId}/accept`, { respondedBy });
acceptSponsorshipRequest(requestId: string, input: AcceptSponsorshipRequestInputDTO): Promise<void> {
return this.post(`/sponsors/requests/${requestId}/accept`, input);
}
/** Reject a sponsorship request */
rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise<void> {
return this.post(`/sponsors/requests/${requestId}/reject`, { respondedBy, reason });
rejectSponsorshipRequest(requestId: string, input: RejectSponsorshipRequestInputDTO): Promise<void> {
return this.post(`/sponsors/requests/${requestId}/reject`, input);
}
}

View File

@@ -1,15 +1,14 @@
import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
import type {
AllTeamsDto,
CreateTeamInputDto,
CreateTeamOutputDto,
DriverTeamDto,
TeamDetailsDto,
TeamJoinRequestsDto,
TeamMembersDto,
UpdateTeamInputDto,
UpdateTeamOutputDto,
} from '../../dtos';
import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
import type { GetTeamMembersOutputDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO';
import type { GetTeamJoinRequestsOutputDTO } from '@/lib/types/generated/GetTeamJoinRequestsOutputDTO';
import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO';
import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO';
import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO';
import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO';
import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO';
import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO';
import { BaseApiClient } from '../base/BaseApiClient';
/**
@@ -19,42 +18,42 @@ import { BaseApiClient } from '../base/BaseApiClient';
*/
export class TeamsApiClient extends BaseApiClient {
/** Get all teams */
getAll(): Promise<AllTeamsDto> {
return this.get<AllTeamsDto>('/teams/all');
getAll(): Promise<GetAllTeamsOutputDTO> {
return this.get<GetAllTeamsOutputDTO>('/teams/all');
}
/** Get team details */
getDetails(teamId: string): Promise<TeamDetailsDto | null> {
return this.get<TeamDetailsDto | null>(`/teams/${teamId}`);
getDetails(teamId: string): Promise<GetTeamDetailsOutputDTO | null> {
return this.get<GetTeamDetailsOutputDTO | null>(`/teams/${teamId}`);
}
/** Get team members */
getMembers(teamId: string): Promise<TeamMembersDto> {
return this.get<TeamMembersDto>(`/teams/${teamId}/members`);
getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
return this.get<GetTeamMembersOutputDTO>(`/teams/${teamId}/members`);
}
/** Get team join requests */
getJoinRequests(teamId: string): Promise<TeamJoinRequestsDto> {
return this.get<TeamJoinRequestsDto>(`/teams/${teamId}/join-requests`);
getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
return this.get<GetTeamJoinRequestsOutputDTO>(`/teams/${teamId}/join-requests`);
}
/** Create a new team */
create(input: CreateTeamInputDto): Promise<CreateTeamOutputDto> {
return this.post<CreateTeamOutputDto>('/teams', input);
create(input: CreateTeamInputDTO): Promise<CreateTeamOutputDTO> {
return this.post<CreateTeamOutputDTO>('/teams', input);
}
/** Update team */
update(teamId: string, input: UpdateTeamInputDto): Promise<UpdateTeamOutputDto> {
return this.patch<UpdateTeamOutputDto>(`/teams/${teamId}`, input);
update(teamId: string, input: UpdateTeamInputDTO): Promise<UpdateTeamOutputDTO> {
return this.patch<UpdateTeamOutputDTO>(`/teams/${teamId}`, input);
}
/** Get driver's team */
getDriverTeam(driverId: string): Promise<DriverTeamDto | null> {
return this.get<DriverTeamDto | null>(`/teams/driver/${driverId}`);
getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
return this.get<GetDriverTeamOutputDTO | null>(`/teams/driver/${driverId}`);
}
/** Get membership for a driver in a team */
getMembership(teamId: string, driverId: string): Promise<LeagueMemberDTO | null> {
return this.get<LeagueMemberDTO | null>(`/teams/${teamId}/members/${driverId}`);
getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
return this.get<GetTeamMembershipOutputDTO | null>(`/teams/${teamId}/members/${driverId}`);
}
}

View File

@@ -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();

View File

@@ -1,9 +1,31 @@
import { WizardStep } from '@/lib/types/WizardStep';
import { WizardErrors } from '@/lib/types/WizardErrors';
import { CreateLeagueInputDTO } from '@/lib/types/CreateLeagueInputDTO';
import { LeagueWizardValidationMessages } from '@/lib/display-objects/LeagueWizardValidationMessages';
import { ScoringPresetApplier } from '@/lib/utilities/ScoringPresetApplier';
export type WizardStep = 1 | 2 | 3 | 4 | 5 | 6 | 7;
export interface WizardErrors {
basics?: {
name?: string;
description?: string;
visibility?: string;
};
structure?: {
maxDrivers?: string;
maxTeams?: string;
driversPerTeam?: string;
};
timings?: {
qualifyingMinutes?: string;
mainRaceMinutes?: string;
roundsPlanned?: string;
};
scoring?: {
patternId?: string;
};
submit?: string;
}
type LeagueWizardFormData = {
leagueId: string | undefined;
basics: {

View File

@@ -1,4 +1,5 @@
import { LeagueRole } from '@/lib/types/LeagueRole';
import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole';
type LeagueRole = MembershipRole;
export interface LeagueRoleDisplayData {
text: string;

View File

@@ -1,18 +1,8 @@
import { AnalyticsApiClient } from '../../api/analytics/AnalyticsApiClient';
import { RecordPageViewOutputViewModel } from '../../view-models/RecordPageViewOutputViewModel';
import { RecordEngagementOutputViewModel } from '../../view-models/RecordEngagementOutputViewModel';
// TODO: Create proper DTOs in generated types
interface RecordPageViewInputDTO {
path: string;
userId?: string;
}
interface RecordEngagementInputDTO {
eventType: string;
userId?: string;
metadata?: Record<string, unknown>;
}
import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO';
import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO';
/**
* Analytics Service

View File

@@ -1,7 +1,7 @@
import { apiClient } from '@/lib/apiClient';
import { LeagueMembership } from '@/lib/types/LeagueMembership';
import { MembershipRole } from '@/lib/types/MembershipRole';
import { MembershipStatus } from '@/lib/types/MembershipStatus';
import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole';
import type { MembershipStatus } from '@core/racing/domain/entities/MembershipStatus';
export class LeagueMembershipService {
// In-memory cache for memberships (populated via API calls)

View File

@@ -15,7 +15,6 @@ import { LeagueDetailViewModel } from "@/lib/view-models/LeagueDetailViewModel";
import { LeagueDetailPageViewModel, SponsorInfo } from "@/lib/view-models/LeagueDetailPageViewModel";
import { RaceViewModel } from "@/lib/view-models/RaceViewModel";
import { SubmitBlocker, ThrottleBlocker } from "@/lib/blockers";
import { DriverDTO } from "@/lib/types/DriverDTO";
import { RaceDTO } from "@/lib/types/generated/RaceDTO";
import { LeagueScoringConfigDTO } from "@/lib/types/LeagueScoringConfigDTO";
import { LeagueStatsDTO } from "@/lib/types/generated/LeagueStatsDTO";

View File

@@ -1,8 +1,8 @@
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
import type { LeagueConfigFormModel } from "@/lib/types/LeagueConfigFormModel";
import type { LeagueScoringPresetDTO } from "@/lib/types/LeagueScoringPresetDTO";
import type { DriverDTO } from "@/lib/types/DriverDTO";
import type { LeagueConfigFormModel } from "@core/racing/application";
import type { LeagueScoringPresetDTO } from "@core/racing/application/ports/LeagueScoringPresetProvider";
import type { GetDriverOutputDTO } from "@/lib/types/generated/GetDriverOutputDTO";
import { LeagueSettingsViewModel } from "@/lib/view-models/LeagueSettingsViewModel";
import { DriverSummaryViewModel } from "@/lib/view-models/DriverSummaryViewModel";
@@ -50,7 +50,7 @@ export class LeagueSettingsService {
// TODO: get rating and rank from API
owner = new DriverSummaryViewModel({
driver: ownerDriver,
rating: ownerDriver.rating ?? null,
rating: null, // TODO: get from API
rank: null, // TODO: get from API
});
}
@@ -64,7 +64,7 @@ export class LeagueSettingsService {
if (driver) {
members.push(new DriverSummaryViewModel({
driver,
rating: driver.rating ?? null,
rating: null, // TODO: get from API
rank: null, // TODO: get from API
}));
}

View File

@@ -1,26 +1,23 @@
import { apiClient } from '@/lib/apiClient';
import { LeagueWizardCommandModel } from '@/lib/command-models/leagues/LeagueWizardCommandModel';
import { CreateLeagueResult } from '@/lib/types/CreateLeagueResult';
import { CreateLeagueOutputDTO } from '@/lib/types/generated/CreateLeagueOutputDTO';
export class LeagueWizardService {
static async createLeague(
form: LeagueWizardCommandModel,
ownerId: string,
): Promise<CreateLeagueResult> {
): Promise<CreateLeagueOutputDTO> {
const command = form.toCreateLeagueCommand(ownerId);
const result = await apiClient.leagues.create(command);
return {
leagueId: result.leagueId,
success: result.success,
};
return result;
}
// Static method for backward compatibility
static async createLeagueFromConfig(
form: LeagueWizardCommandModel,
ownerId: string,
): Promise<CreateLeagueResult> {
): Promise<CreateLeagueOutputDTO> {
return this.createLeague(form, ownerId);
}
}

Some files were not shown because too many files have changed in this diff Show More