This commit is contained in:
2025-12-17 14:04:11 +01:00
parent 1ea9c9649f
commit daa4bb6576
238 changed files with 4263 additions and 1752 deletions

View File

@@ -2,7 +2,13 @@ import { Controller, Get, Post, Body, Req, Param } from '@nestjs/common';
import { Request } from 'express';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { DriverService } from './DriverService';
import { DriversLeaderboardViewModel, DriverStatsDto, CompleteOnboardingInput, CompleteOnboardingOutput, GetDriverRegistrationStatusQuery, DriverRegistrationStatusViewModel } from './dto/DriverDto';
import { DriversLeaderboardDTO } from './dtos/DriversLeaderboardDTO';
import { DriverStatsDTO } from './dtos/DriverStatsDTO';
import { CompleteOnboardingInputDTO } from './dtos/CompleteOnboardingInputDTO';
import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO';
import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
import { DriverDTO } from './dtos/DriverDTO';
@ApiTags('drivers')
@Controller('drivers')
@@ -11,15 +17,15 @@ export class DriverController {
@Get('leaderboard')
@ApiOperation({ summary: 'Get drivers leaderboard' })
@ApiResponse({ status: 200, description: 'List of drivers for the leaderboard', type: DriversLeaderboardViewModel })
async getDriversLeaderboard(): Promise<DriversLeaderboardViewModel> {
@ApiResponse({ status: 200, description: 'List of drivers for the leaderboard', type: DriversLeaderboardDTO })
async getDriversLeaderboard(): Promise<DriversLeaderboardDTO> {
return this.driverService.getDriversLeaderboard();
}
@Get('total-drivers')
@ApiOperation({ summary: 'Get the total number of drivers' })
@ApiResponse({ status: 200, description: 'Total number of drivers', type: DriverStatsDto })
async getTotalDrivers(): Promise<DriverStatsDto> {
@ApiResponse({ status: 200, description: 'Total number of drivers', type: DriverStatsDTO })
async getTotalDrivers(): Promise<DriverStatsDTO> {
return this.driverService.getTotalDrivers();
}
@@ -38,11 +44,11 @@ export class DriverController {
@Post('complete-onboarding')
@ApiOperation({ summary: 'Complete driver onboarding for a user' })
@ApiResponse({ status: 200, description: 'Onboarding complete', type: CompleteOnboardingOutput })
@ApiResponse({ status: 200, description: 'Onboarding complete', type: CompleteOnboardingOutputDTO })
async completeOnboarding(
@Body() input: CompleteOnboardingInput,
@Body() input: CompleteOnboardingInputDTO,
@Req() req: Request,
): Promise<CompleteOnboardingOutput> {
): Promise<CompleteOnboardingOutputDTO> {
// Assuming userId is available from the request (e.g., via auth middleware)
const userId = req['user'].userId; // Placeholder for actual user extraction
return this.driverService.completeOnboarding(userId, input);
@@ -50,13 +56,23 @@ export class DriverController {
@Get(':driverId/races/:raceId/registration-status')
@ApiOperation({ summary: 'Get driver registration status for a specific race' })
@ApiResponse({ status: 200, description: 'Driver registration status', type: DriverRegistrationStatusViewModel })
@ApiResponse({ status: 200, description: 'Driver registration status', type: DriverRegistrationStatusDTO })
async getDriverRegistrationStatus(
@Param('driverId') driverId: string,
@Param('raceId') raceId: string,
): Promise<DriverRegistrationStatusViewModel> {
): Promise<DriverRegistrationStatusDTO> {
return this.driverService.getDriverRegistrationStatus({ driverId, raceId });
}
@Put(':driverId/profile')
@ApiOperation({ summary: 'Update driver profile' })
@ApiResponse({ status: 200, description: 'Driver profile updated', type: DriverDTO })
async updateDriverProfile(
@Param('driverId') driverId: string,
@Body() body: { bio?: string; country?: string },
): Promise<DriverDTO | null> {
return this.driverService.updateDriverProfile(driverId, body.bio, body.country);
}
// Add other Driver endpoints here based on other presenters
}

View File

@@ -15,6 +15,9 @@ import type { Logger } from "@gridpilot/core/shared/application";
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
// Import concrete in-memory implementations
import { InMemoryDriverRepository } from '../../..//racing/persistence/inmemory/InMemoryDriverRepository';
@@ -41,6 +44,9 @@ export const GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN = 'GetDriversLeaderboardUseC
export const GET_TOTAL_DRIVERS_USE_CASE_TOKEN = 'GetTotalDriversUseCase';
export const COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN = 'CompleteDriverOnboardingUseCase';
export const IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN = 'IsDriverRegisteredForRaceUseCase';
export const GET_PROFILE_OVERVIEW_USE_CASE_TOKEN = 'GetProfileOverviewUseCase';
export const GET_DRIVER_TEAM_USE_CASE_TOKEN = 'GetDriverTeamUseCase';
export const UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN = 'UpdateDriverProfileUseCase';
export const DriverProviders: Provider[] = [
DriverService, // Provide the service itself
@@ -105,4 +111,9 @@ export const DriverProviders: Provider[] = [
useFactory: (registrationRepo: IRaceRegistrationRepository) => new IsDriverRegisteredForRaceUseCase(registrationRepo),
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN],
},
{
provide: UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
useFactory: (driverRepo: IDriverRepository) => new UpdateDriverProfileUseCase(driverRepo),
inject: [DRIVER_REPOSITORY_TOKEN],
},
];

View File

@@ -1,5 +1,10 @@
import { Injectable, Inject } from '@nestjs/common';
import { DriversLeaderboardViewModel, DriverStatsDto, CompleteOnboardingInput, CompleteOnboardingOutput, GetDriverRegistrationStatusQuery, DriverRegistrationStatusViewModel } from './dto/DriverDto';
import { DriversLeaderboardDTO } from './dtos/DriversLeaderboardDTO';
import { DriverStatsDTO } from './dtos/DriverStatsDTO';
import { CompleteOnboardingInputDTO } from './dtos/CompleteOnboardingInputDTO';
import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO';
import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
// Use cases
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
@@ -14,8 +19,10 @@ import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPres
import { DriverRegistrationStatusPresenter } from './presenters/DriverRegistrationStatusPresenter';
// Tokens
import { GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN, GET_TOTAL_DRIVERS_USE_CASE_TOKEN, COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN, IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN, LOGGER_TOKEN } from './DriverProviders';
import { GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN, GET_TOTAL_DRIVERS_USE_CASE_TOKEN, COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN, IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN, UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN, LOGGER_TOKEN, DRIVER_REPOSITORY_TOKEN } from './DriverProviders';
import type { Logger } from '@core/shared/application';
import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
@Injectable()
export class DriverService {
@@ -24,10 +31,12 @@ export class DriverService {
@Inject(GET_TOTAL_DRIVERS_USE_CASE_TOKEN) private readonly getTotalDriversUseCase: GetTotalDriversUseCase,
@Inject(COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN) private readonly completeDriverOnboardingUseCase: CompleteDriverOnboardingUseCase,
@Inject(IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN) private readonly isDriverRegisteredForRaceUseCase: IsDriverRegisteredForRaceUseCase,
@Inject(UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN) private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase,
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
async getDriversLeaderboard(): Promise<DriversLeaderboardViewModel> {
async getDriversLeaderboard(): Promise<DriversLeaderboardDTO> {
this.logger.debug('[DriverService] Fetching drivers leaderboard.');
const presenter = new DriversLeaderboardPresenter();
@@ -35,7 +44,7 @@ export class DriverService {
return presenter.viewModel;
}
async getTotalDrivers(): Promise<DriverStatsDto> {
async getTotalDrivers(): Promise<DriverStatsDTO> {
this.logger.debug('[DriverService] Fetching total drivers count.');
const presenter = new DriverStatsPresenter();
@@ -43,7 +52,7 @@ export class DriverService {
return presenter.viewModel;
}
async completeOnboarding(userId: string, input: CompleteOnboardingInput): Promise<CompleteOnboardingOutput> {
async completeOnboarding(userId: string, input: CompleteOnboardingInputDTO): Promise<CompleteOnboardingOutputDTO> {
this.logger.debug('Completing onboarding for user:', userId);
const presenter = new CompleteOnboardingPresenter();
@@ -59,11 +68,37 @@ export class DriverService {
return presenter.viewModel;
}
async getDriverRegistrationStatus(query: GetDriverRegistrationStatusQuery): Promise<DriverRegistrationStatusViewModel> {
async getDriverRegistrationStatus(query: GetDriverRegistrationStatusQueryDTO): Promise<DriverRegistrationStatusDTO> {
this.logger.debug('Checking driver registration status:', query);
const presenter = new DriverRegistrationStatusPresenter();
await this.isDriverRegisteredForRaceUseCase.execute({ raceId: query.raceId, driverId: query.driverId }, presenter);
return presenter.viewModel;
}
async getCurrentDriver(userId: string): Promise<DriverDTO | null> {
this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`);
const driver = await this.driverRepository.findById(userId);
if (!driver) {
return null;
}
return {
id: driver.id,
name: driver.name.value,
};
}
async updateDriverProfile(driverId: string, bio?: string, country?: string): Promise<DriverDTO | null> {
this.logger.debug(`[DriverService] Updating driver profile for driverId: ${driverId}`);
const result = await this.updateDriverProfileUseCase.execute({ driverId, bio, country });
if (result.isErr()) {
this.logger.error(`Failed to update driver profile: ${result.error.code}`);
return null;
}
return result.value;
}
}

View File

@@ -1,138 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsOptional, IsBoolean } from 'class-validator';
export class DriverLeaderboardItemViewModel {
@ApiProperty()
id: string;
@ApiProperty()
name: string;
@ApiProperty()
rating: number;
@ApiProperty()
skillLevel: string; // Assuming skillLevel is a string like 'Rookie', 'Pro', etc.
@ApiProperty()
nationality: string;
@ApiProperty()
racesCompleted: number;
@ApiProperty()
wins: number;
@ApiProperty()
podiums: number;
@ApiProperty()
isActive: boolean;
@ApiProperty()
rank: number;
@ApiProperty({ nullable: true })
avatarUrl?: string;
}
export class DriversLeaderboardViewModel {
@ApiProperty({ type: [DriverLeaderboardItemViewModel] })
drivers: DriverLeaderboardItemViewModel[];
@ApiProperty()
totalRaces: number;
@ApiProperty()
totalWins: number;
@ApiProperty()
activeCount: number;
}
export class DriverStatsDto {
@ApiProperty()
totalDrivers: number;
}
export class CompleteOnboardingInput {
@ApiProperty()
@IsString()
@IsNotEmpty()
firstName: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
lastName: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
displayName: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
country: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
timezone?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
bio?: string;
}
export class CompleteOnboardingOutput {
@ApiProperty()
@IsBoolean()
success: boolean;
@ApiProperty({ required: false })
@IsString()
driverId?: string;
@ApiProperty({ required: false })
@IsString()
errorMessage?: string;
}
export class GetDriverRegistrationStatusQuery {
@ApiProperty()
@IsString()
raceId: string;
@ApiProperty()
@IsString()
driverId: string;
}
export class DriverRegistrationStatusViewModel {
@ApiProperty()
@IsBoolean()
isRegistered: boolean;
@ApiProperty()
@IsString()
raceId: string;
@ApiProperty()
@IsString()
driverId: string;
}
export class DriverDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
name: string; // Display name or full name
}
// Add other DTOs for driver-related logic as needed

View File

@@ -0,0 +1,34 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
export class CompleteOnboardingInputDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
firstName: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
lastName: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
displayName: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
country: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
timezone?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
bio?: string;
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsString } from 'class-validator';
export class CompleteOnboardingOutputDTO {
@ApiProperty()
@IsBoolean()
success: boolean;
@ApiProperty({ required: false })
@IsString()
driverId?: string;
@ApiProperty({ required: false })
@IsString()
errorMessage?: string;
}

View File

@@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger';
export class DriverLeaderboardItemDTO {
@ApiProperty()
id: string;
@ApiProperty()
name: string;
@ApiProperty()
rating: number;
@ApiProperty()
skillLevel: string; // Assuming skillLevel is a string like 'Rookie', 'Pro', etc.
@ApiProperty()
nationality: string;
@ApiProperty()
racesCompleted: number;
@ApiProperty()
wins: number;
@ApiProperty()
podiums: number;
@ApiProperty()
isActive: boolean;
@ApiProperty()
rank: number;
@ApiProperty({ nullable: true })
avatarUrl?: string;
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsString } from 'class-validator';
export class DriverRegistrationStatusDTO {
@ApiProperty()
@IsBoolean()
isRegistered!: boolean;
@ApiProperty()
@IsString()
raceId!: string;
@ApiProperty()
@IsString()
driverId!: string;
}

View File

@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class DriverStatsDTO {
@ApiProperty()
totalDrivers: number;
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { DriverLeaderboardItemDTO } from './DriverLeaderboardItemDTO';
export class DriversLeaderboardDTO {
@ApiProperty({ type: [DriverLeaderboardItemDTO] })
drivers: DriverLeaderboardItemDTO[];
@ApiProperty()
totalRaces: number;
@ApiProperty()
totalWins: number;
@ApiProperty()
activeCount: number;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
export class GetDriverRegistrationStatusQueryDTO {
@ApiProperty()
@IsString()
raceId: string;
@ApiProperty()
@IsString()
driverId: string;
}

View File

@@ -1,8 +1,8 @@
import { CompleteOnboardingOutput } from '../dto/DriverDto';
import { CompleteOnboardingOutputDTO } from '../dtos/CompleteOnboardingOutputDTO';
import type { ICompleteDriverOnboardingPresenter, CompleteDriverOnboardingResultDTO } from '../../../../../core/racing/application/presenters/ICompleteDriverOnboardingPresenter';
export class CompleteOnboardingPresenter implements ICompleteDriverOnboardingPresenter {
private result: CompleteOnboardingOutput | null = null;
private result: CompleteOnboardingOutputDTO | null = null;
reset() {
this.result = null;
@@ -16,7 +16,7 @@ export class CompleteOnboardingPresenter implements ICompleteDriverOnboardingPre
};
}
get viewModel(): CompleteOnboardingOutput {
get viewModel(): CompleteOnboardingOutputDTO {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}

View File

@@ -1,8 +1,8 @@
import { DriverRegistrationStatusViewModel } from '../dto/DriverDto';
import { DriverRegistrationStatusDTO } from '../dtos/DriverRegistrationStatusDTO';
import type { IDriverRegistrationStatusPresenter } from '../../../../../core/racing/application/presenters/IDriverRegistrationStatusPresenter';
export class DriverRegistrationStatusPresenter implements IDriverRegistrationStatusPresenter {
private result: DriverRegistrationStatusViewModel | null = null;
private result: DriverRegistrationStatusDTO | null = null;
present(isRegistered: boolean, raceId: string, driverId: string) {
this.result = {
@@ -12,7 +12,7 @@ export class DriverRegistrationStatusPresenter implements IDriverRegistrationSta
};
}
getViewModel(): DriverRegistrationStatusViewModel {
getViewModel(): DriverRegistrationStatusDTO {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
@@ -22,7 +22,7 @@ export class DriverRegistrationStatusPresenter implements IDriverRegistrationSta
this.result = null;
}
get viewModel(): DriverRegistrationStatusViewModel {
get viewModel(): DriverRegistrationStatusDTO {
return this.getViewModel();
}
}

View File

@@ -1,8 +1,8 @@
import { DriverStatsDto } from '../dto/DriverDto';
import { DriverStatsDTO } from '../dtos/DriverStatsDTO';
import type { ITotalDriversPresenter, TotalDriversResultDTO } from '../../../../../core/racing/application/presenters/ITotalDriversPresenter';
export class DriverStatsPresenter implements ITotalDriversPresenter {
private result: DriverStatsDto | null = null;
private result: DriverStatsDTO | null = null;
reset() {
this.result = null;
@@ -14,7 +14,7 @@ export class DriverStatsPresenter implements ITotalDriversPresenter {
};
}
get viewModel(): DriverStatsDto {
get viewModel(): DriverStatsDTO {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}

View File

@@ -1,15 +1,16 @@
import { DriversLeaderboardViewModel, DriverLeaderboardItemViewModel } from '../dto/DriverDto';
import { DriversLeaderboardDTO, DriverLeaderboardItemDTO } from '../dtos/DriversLeaderboardDTO';
import { DriverLeaderboardItemDTO } from '../dtos/DriverLeaderboardItemDTO';
import type { IDriversLeaderboardPresenter, DriversLeaderboardResultDTO } from '../../../../../core/racing/application/presenters/IDriversLeaderboardPresenter';
export class DriversLeaderboardPresenter implements IDriversLeaderboardPresenter {
private result: DriversLeaderboardViewModel | null = null;
private result: DriversLeaderboardDTO | null = null;
reset() {
this.result = null;
}
present(dto: DriversLeaderboardResultDTO) {
const drivers: DriverLeaderboardItemViewModel[] = dto.drivers.map(driver => {
const drivers: DriverLeaderboardItemDTO[] = dto.drivers.map(driver => {
const ranking = dto.rankings.find(r => r.driverId === driver.id);
const stats = dto.stats[driver.id];
const avatarUrl = dto.avatarUrls[driver.id];
@@ -42,7 +43,7 @@ export class DriversLeaderboardPresenter implements IDriversLeaderboardPresenter
};
}
get viewModel(): DriversLeaderboardViewModel {
get viewModel(): DriversLeaderboardDTO {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}