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

View File

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

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

View File

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

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

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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

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

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

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

View File

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

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

View File

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

View File

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

View File

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

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') .setTitle('GridPilot API')
.setDescription('GridPilot API documentation') .setDescription('GridPilot API documentation')
.setVersion('1.0') .setVersion('1.0')
.addTag('dashboard', 'Dashboard endpoints')
.addTag('races', 'Race management endpoints') .addTag('races', 'Race management endpoints')
.addTag('leagues', 'League management endpoints') .addTag('leagues', 'League management endpoints')
.addTag('teams', 'Team management endpoints') .addTag('teams', 'Team management endpoints')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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 { export interface LeagueRoleDisplayData {
text: string; text: string;

View File

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

View File

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

View File

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

View File

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

View File

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

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