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

@@ -1,8 +1,16 @@
import { Controller, Post, Body, Res, HttpStatus } from '@nestjs/common';
import type { Response } from 'express';
import type { RecordPageViewInput, RecordPageViewOutput, RecordEngagementInput, RecordEngagementOutput } from './dto/AnalyticsDto';
import type { RecordPageViewInputDTO } from './dtos/RecordPageViewInputDTO';
import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO';
import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO';
import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO';
import { AnalyticsService } from './AnalyticsService';
type RecordPageViewInput = RecordPageViewInputDTO;
type RecordPageViewOutput = RecordPageViewOutputDTO;
type RecordEngagementInput = RecordEngagementInputDTO;
type RecordEngagementOutput = RecordEngagementOutputDTO;
@Controller('analytics')
export class AnalyticsController {
constructor(

View File

@@ -1,9 +1,17 @@
import { Injectable, Inject } from '@nestjs/common';
import { RecordEngagementInput, RecordEngagementOutput, RecordPageViewInput, RecordPageViewOutput } from './dto/AnalyticsDto';
import type { RecordPageViewInputDTO } from './dtos/RecordPageViewInputDTO';
import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO';
import type { RecordEngagementInputDTO } from './dtos/RecordEngagementInputDTO';
import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO';
import type { Logger } from '@core/shared/application';
import { RecordPageViewUseCase } from './use-cases/RecordPageViewUseCase';
import { RecordEngagementUseCase } from './use-cases/RecordEngagementUseCase';
type RecordPageViewInput = RecordPageViewInputDTO;
type RecordPageViewOutput = RecordPageViewOutputDTO;
type RecordEngagementInput = RecordEngagementInputDTO;
type RecordEngagementOutput = RecordEngagementOutputDTO;
const Logger_TOKEN = 'Logger_TOKEN';
const RECORD_PAGE_VIEW_USE_CASE_TOKEN = 'RecordPageViewUseCase_TOKEN';
const RECORD_ENGAGEMENT_USE_CASE_TOKEN = 'RecordEngagementUseCase_TOKEN';

View File

@@ -1,127 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsOptional, IsEnum, IsBoolean, IsNumber, IsObject } from 'class-validator';
// From core/analytics/domain/types/PageView.ts
export enum EntityType {
LEAGUE = 'league',
DRIVER = 'driver',
TEAM = 'team',
RACE = 'race',
SPONSOR = 'sponsor',
}
// From core/analytics/domain/types/PageView.ts
export enum VisitorType {
ANONYMOUS = 'anonymous',
DRIVER = 'driver',
SPONSOR = 'sponsor',
}
export class RecordPageViewInput {
@ApiProperty({ enum: EntityType })
@IsEnum(EntityType)
entityType!: EntityType;
@ApiProperty()
@IsString()
entityId!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
visitorId?: string;
@ApiProperty({ enum: VisitorType })
@IsEnum(VisitorType)
visitorType!: VisitorType;
@ApiProperty()
@IsString()
sessionId!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
referrer?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
userAgent?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
country?: string;
}
export class RecordPageViewOutput {
@ApiProperty()
@IsString()
pageViewId!: string;
}
// From core/analytics/domain/types/EngagementEvent.ts
export enum EngagementAction {
CLICK_SPONSOR_LOGO = 'click_sponsor_logo',
CLICK_SPONSOR_URL = 'click_sponsor_url',
DOWNLOAD_LIVERY_PACK = 'download_livery_pack',
JOIN_LEAGUE = 'join_league',
REGISTER_RACE = 'register_race',
VIEW_STANDINGS = 'view_standings',
VIEW_SCHEDULE = 'view_schedule',
SHARE_SOCIAL = 'share_social',
CONTACT_SPONSOR = 'contact_sponsor',
}
// From core/analytics/domain/types/EngagementEvent.ts
export enum EngagementEntityType {
LEAGUE = 'league',
DRIVER = 'driver',
TEAM = 'team',
RACE = 'race',
SPONSOR = 'sponsor',
SPONSORSHIP = 'sponsorship',
}
export class RecordEngagementInput {
@ApiProperty({ enum: EngagementAction })
@IsEnum(EngagementAction)
action!: EngagementAction;
@ApiProperty({ enum: EngagementEntityType })
@IsEnum(EngagementEntityType)
entityType!: EngagementEntityType;
@ApiProperty()
@IsString()
entityId!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
actorId?: string;
@ApiProperty({ enum: ['anonymous', 'driver', 'sponsor'] })
@IsEnum(['anonymous', 'driver', 'sponsor'])
actorType!: 'anonymous' | 'driver' | 'sponsor';
@ApiProperty()
@IsString()
sessionId!: string;
@ApiProperty({ required: false, type: Object })
@IsOptional()
@IsObject()
metadata?: Record<string, string | number | boolean>;
}
export class RecordEngagementOutput {
@ApiProperty()
@IsString()
eventId!: string;
@ApiProperty()
@IsNumber()
engagementWeight!: number;
}

View File

@@ -0,0 +1,12 @@
// From core/analytics/domain/types/EngagementEvent.ts
export enum EngagementAction {
CLICK_SPONSOR_LOGO = 'click_sponsor_logo',
CLICK_SPONSOR_URL = 'click_sponsor_url',
DOWNLOAD_LIVERY_PACK = 'download_livery_pack',
JOIN_LEAGUE = 'join_league',
REGISTER_RACE = 'register_race',
VIEW_STANDINGS = 'view_standings',
VIEW_SCHEDULE = 'view_schedule',
SHARE_SOCIAL = 'share_social',
CONTACT_SPONSOR = 'contact_sponsor',
}

View File

@@ -0,0 +1,9 @@
// From core/analytics/domain/types/EngagementEvent.ts
export enum EngagementEntityType {
LEAGUE = 'league',
DRIVER = 'driver',
TEAM = 'team',
RACE = 'race',
SPONSOR = 'sponsor',
SPONSORSHIP = 'sponsorship',
}

View File

@@ -0,0 +1,8 @@
// From core/analytics/domain/types/PageView.ts
export enum EntityType {
LEAGUE = 'league',
DRIVER = 'driver',
TEAM = 'team',
RACE = 'race',
SPONSOR = 'sponsor',
}

View File

@@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsOptional, IsEnum, IsObject } from 'class-validator';
import { EngagementAction } from './EngagementAction';
import { EngagementEntityType } from './EngagementEntityType';
export class RecordEngagementInputDTO {
@ApiProperty({ enum: EngagementAction })
@IsEnum(EngagementAction)
action!: EngagementAction;
@ApiProperty({ enum: EngagementEntityType })
@IsEnum(EngagementEntityType)
entityType!: EngagementEntityType;
@ApiProperty()
@IsString()
entityId!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
actorId?: string;
@ApiProperty({ enum: ['anonymous', 'driver', 'sponsor'] })
@IsEnum(['anonymous', 'driver', 'sponsor'])
actorType!: 'anonymous' | 'driver' | 'sponsor';
@ApiProperty()
@IsString()
sessionId!: string;
@ApiProperty({ required: false, type: Object })
@IsOptional()
@IsObject()
metadata?: Record<string, string | number | boolean>;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber } from 'class-validator';
export class RecordEngagementOutputDTO {
@ApiProperty()
@IsString()
eventId!: string;
@ApiProperty()
@IsNumber()
engagementWeight!: number;
}

View File

@@ -0,0 +1,42 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsOptional, IsEnum } from 'class-validator';
import { EntityType } from './EntityType';
import { VisitorType } from './VisitorType';
export class RecordPageViewInputDTO {
@ApiProperty({ enum: EntityType })
@IsEnum(EntityType)
entityType!: EntityType;
@ApiProperty()
@IsString()
entityId!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
visitorId?: string;
@ApiProperty({ enum: VisitorType })
@IsEnum(VisitorType)
visitorType!: VisitorType;
@ApiProperty()
@IsString()
sessionId!: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
referrer?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
userAgent?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
country?: string;
}

View File

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

View File

@@ -0,0 +1,6 @@
// From core/analytics/domain/types/PageView.ts
export enum VisitorType {
ANONYMOUS = 'anonymous',
DRIVER = 'driver',
SPONSOR = 'sponsor',
}

View File

@@ -1,9 +1,13 @@
import { Injectable, Inject } from '@nestjs/common';
import { RecordEngagementInput, RecordEngagementOutput } from '../dto/AnalyticsDto';
import type { RecordEngagementInputDTO } from '../dtos/RecordEngagementInputDTO';
import type { RecordEngagementOutputDTO } from '../dtos/RecordEngagementOutputDTO';
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
import type { Logger } from '@core/shared/application';
import { EngagementEvent } from '@core/analytics/domain/entities/EngagementEvent';
type RecordEngagementInput = RecordEngagementInputDTO;
type RecordEngagementOutput = RecordEngagementOutputDTO;
const Logger_TOKEN = 'Logger_TOKEN';
const IENGAGEMENT_REPO_TOKEN = 'IEngagementRepository_TOKEN';

View File

@@ -1,9 +1,13 @@
import { Injectable, Inject } from '@nestjs/common';
import { RecordPageViewInput, RecordPageViewOutput } from '../dto/AnalyticsDto';
import type { RecordPageViewInputDTO } from '../dtos/RecordPageViewInputDTO';
import type { RecordPageViewOutputDTO } from '../dtos/RecordPageViewOutputDTO';
import type { IPageViewRepository } from '@core/analytics/application/repositories/IPageViewRepository';
import type { Logger } from '@core/shared/application';
import { PageView } from '@core/analytics/domain/entities/PageView';
type RecordPageViewInput = RecordPageViewInputDTO;
type RecordPageViewOutput = RecordPageViewOutputDTO;
const Logger_TOKEN = 'Logger_TOKEN';
const IPAGE_VIEW_REPO_TOKEN = 'IPageViewRepository_TOKEN';

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

View File

@@ -1,8 +1,35 @@
import { Controller, Get, Post, Patch, Body, Param } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation, ApiBody } from '@nestjs/swagger';
import { LeagueService } from './LeagueService';
import { AllLeaguesWithCapacityViewModel, LeagueStatsDto, LeagueJoinRequestViewModel, ApproveJoinRequestInput, ApproveJoinRequestOutput, RejectJoinRequestInput, RejectJoinRequestOutput, LeagueAdminPermissionsViewModel, RemoveLeagueMemberInput, RemoveLeagueMemberOutput, UpdateLeagueMemberRoleInput, UpdateLeagueMemberRoleOutput, LeagueOwnerSummaryViewModel, LeagueConfigFormModelDto, LeagueAdminProtestsViewModel, LeagueSeasonSummaryViewModel, LeagueMembershipsViewModel, LeagueStandingsViewModel, LeagueScheduleViewModel, LeagueStatsViewModel, LeagueAdminViewModel, CreateLeagueInput, CreateLeagueOutput } from './dto/LeagueDto';
import { GetLeagueAdminPermissionsInput, GetLeagueJoinRequestsQuery, GetLeagueProtestsQuery, GetLeagueSeasonsQuery, GetLeagueAdminConfigQuery, GetLeagueOwnerSummaryQuery } from './dto/LeagueDto'; // Explicitly import queries
import { AllLeaguesWithCapacityDTO } from './dtos/AllLeaguesWithCapacityDTO';
import { LeagueStatsDTO } from './dtos/LeagueStatsDTO';
import { LeagueJoinRequestDTO } from './dtos/LeagueJoinRequestDTO';
import { ApproveJoinRequestInputDTO } from './dtos/ApproveJoinRequestInputDTO';
import { ApproveJoinRequestOutputDTO } from './dtos/ApproveJoinRequestOutputDTO';
import { RejectJoinRequestInputDTO } from './dtos/RejectJoinRequestInputDTO';
import { RejectJoinRequestOutputDTO } from './dtos/RejectJoinRequestOutputDTO';
import { LeagueAdminPermissionsDTO } from './dtos/LeagueAdminPermissionsDTO';
import { RemoveLeagueMemberInputDTO } from './dtos/RemoveLeagueMemberInputDTO';
import { RemoveLeagueMemberOutputDTO } from './dtos/RemoveLeagueMemberOutputDTO';
import { UpdateLeagueMemberRoleInputDTO } from './dtos/UpdateLeagueMemberRoleInputDTO';
import { UpdateLeagueMemberRoleOutputDTO } from './dtos/UpdateLeagueMemberRoleOutputDTO';
import { LeagueOwnerSummaryDTO } from './dtos/LeagueOwnerSummaryDTO';
import { LeagueConfigFormModelDTO } from './dtos/LeagueConfigFormModelDTO';
import { LeagueAdminProtestsDTO } from './dtos/LeagueAdminProtestsDTO';
import { LeagueSeasonSummaryDTO } from './dtos/LeagueSeasonSummaryDTO';
import { LeagueMembershipsDTO } from './dtos/LeagueMembershipsDTO';
import { LeagueStandingsDTO } from './dtos/LeagueStandingsDTO';
import { LeagueScheduleDTO } from './dtos/LeagueScheduleDTO';
import { LeagueStatsDTO } from './dtos/LeagueStatsDTO';
import { LeagueAdminDTO } from './dtos/LeagueAdminDTO';
import { CreateLeagueInputDTO } from './dtos/CreateLeagueInputDTO';
import { CreateLeagueOutputDTO } from './dtos/CreateLeagueOutputDTO';
import { GetLeagueAdminPermissionsInputDTO } from './dtos/GetLeagueAdminPermissionsInputDTO';
import { GetLeagueJoinRequestsQueryDTO } from './dtos/GetLeagueJoinRequestsQueryDTO';
import { GetLeagueProtestsQueryDTO } from './dtos/GetLeagueProtestsQueryDTO';
import { GetLeagueSeasonsQueryDTO } from './dtos/GetLeagueSeasonsQueryDTO';
import { GetLeagueAdminConfigQueryDTO } from './dtos/GetLeagueAdminConfigQueryDTO';
import { GetLeagueOwnerSummaryQueryDTO } from './dtos/GetLeagueOwnerSummaryQueryDTO';
@ApiTags('leagues')
@Controller('leagues')
@@ -11,169 +38,197 @@ export class LeagueController {
@Get('all-with-capacity')
@ApiOperation({ summary: 'Get all leagues with their capacity information' })
@ApiResponse({ status: 200, description: 'List of leagues with capacity', type: AllLeaguesWithCapacityViewModel })
async getAllLeaguesWithCapacity(): Promise<AllLeaguesWithCapacityViewModel> {
@ApiResponse({ status: 200, description: 'List of leagues with capacity', type: AllLeaguesWithCapacityDTO })
async getAllLeaguesWithCapacity(): Promise<AllLeaguesWithCapacityDTO> {
return this.leagueService.getAllLeaguesWithCapacity();
}
@Get('total-leagues')
@ApiOperation({ summary: 'Get the total number of leagues' })
@ApiResponse({ status: 200, description: 'Total number of leagues', type: LeagueStatsDto })
async getTotalLeagues(): Promise<LeagueStatsDto> {
@ApiResponse({ status: 200, description: 'Total number of leagues', type: LeagueStatsDTO })
async getTotalLeagues(): Promise<LeagueStatsDTO> {
return this.leagueService.getTotalLeagues();
}
@Get(':leagueId/join-requests')
@ApiOperation({ summary: 'Get all outstanding join requests for a league' })
@ApiResponse({ status: 200, description: 'List of join requests', type: [LeagueJoinRequestViewModel] })
async getJoinRequests(@Param('leagueId') leagueId: string): Promise<LeagueJoinRequestViewModel[]> {
@ApiResponse({ status: 200, description: 'List of join requests', type: [LeagueJoinRequestDTO] })
async getJoinRequests(@Param('leagueId') leagueId: string): Promise<LeagueJoinRequestDTO[]> {
// No specific query DTO needed for GET, leagueId from param
return this.leagueService.getLeagueJoinRequests(leagueId);
}
@Post(':leagueId/join-requests/approve')
@ApiOperation({ summary: 'Approve a league join request' })
@ApiBody({ type: ApproveJoinRequestInput }) // Explicitly define body type for Swagger
@ApiResponse({ status: 200, description: 'Join request approved', type: ApproveJoinRequestOutput })
@ApiBody({ type: ApproveJoinRequestInputDTO }) // Explicitly define body type for Swagger
@ApiResponse({ status: 200, description: 'Join request approved', type: ApproveJoinRequestOutputDTO })
@ApiResponse({ status: 404, description: 'Join request not found' })
async approveJoinRequest(
@Param('leagueId') leagueId: string,
@Body() input: ApproveJoinRequestInput,
): Promise<ApproveJoinRequestOutput> {
@Body() input: ApproveJoinRequestInputDTO,
): Promise<ApproveJoinRequestOutputDTO> {
return this.leagueService.approveLeagueJoinRequest({ ...input, leagueId });
}
@Post(':leagueId/join-requests/reject')
@ApiOperation({ summary: 'Reject a league join request' })
@ApiBody({ type: RejectJoinRequestInput })
@ApiResponse({ status: 200, description: 'Join request rejected', type: RejectJoinRequestOutput })
@ApiBody({ type: RejectJoinRequestInputDTO })
@ApiResponse({ status: 200, description: 'Join request rejected', type: RejectJoinRequestOutputDTO })
@ApiResponse({ status: 404, description: 'Join request not found' })
async rejectJoinRequest(
@Param('leagueId') leagueId: string,
@Body() input: RejectJoinRequestInput,
): Promise<RejectJoinRequestOutput> {
@Body() input: RejectJoinRequestInputDTO,
): Promise<RejectJoinRequestOutputDTO> {
return this.leagueService.rejectLeagueJoinRequest({ ...input, leagueId });
}
@Get(':leagueId/permissions/:performerDriverId')
@ApiOperation({ summary: 'Get league admin permissions for a performer' })
@ApiResponse({ status: 200, description: 'League admin permissions', type: LeagueAdminPermissionsViewModel })
@ApiResponse({ status: 200, description: 'League admin permissions', type: LeagueAdminPermissionsDTO })
async getLeagueAdminPermissions(
@Param('leagueId') leagueId: string,
@Param('performerDriverId') performerDriverId: string,
): Promise<LeagueAdminPermissionsViewModel> {
): Promise<LeagueAdminPermissionsDTO> {
// No specific input DTO needed for Get, parameters from path
return this.leagueService.getLeagueAdminPermissions({ leagueId, performerDriverId });
}
@Patch(':leagueId/members/:targetDriverId/remove')
@ApiOperation({ summary: 'Remove a member from the league' })
@ApiBody({ type: RemoveLeagueMemberInput }) // Explicitly define body type for Swagger
@ApiResponse({ status: 200, description: 'Member removed successfully', type: RemoveLeagueMemberOutput })
@ApiBody({ type: RemoveLeagueMemberInputDTO }) // Explicitly define body type for Swagger
@ApiResponse({ status: 200, description: 'Member removed successfully', type: RemoveLeagueMemberOutputDTO })
@ApiResponse({ status: 400, description: 'Cannot remove member' })
@ApiResponse({ status: 404, description: 'Member not found' })
async removeLeagueMember(
@Param('leagueId') leagueId: string,
@Param('performerDriverId') performerDriverId: string,
@Param('targetDriverId') targetDriverId: string,
@Body() input: RemoveLeagueMemberInput, // Body content for a patch often includes IDs
): Promise<RemoveLeagueMemberOutput> {
@Body() input: RemoveLeagueMemberInputDTO, // Body content for a patch often includes IDs
): Promise<RemoveLeagueMemberOutputDTO> {
return this.leagueService.removeLeagueMember({ leagueId, performerDriverId, targetDriverId });
}
@Patch(':leagueId/members/:targetDriverId/role')
@ApiOperation({ summary: "Update a member's role in the league" })
@ApiBody({ type: UpdateLeagueMemberRoleInput }) // Explicitly define body type for Swagger
@ApiResponse({ status: 200, description: 'Member role updated successfully', type: UpdateLeagueMemberRoleOutput })
@ApiBody({ type: UpdateLeagueMemberRoleInputDTO }) // Explicitly define body type for Swagger
@ApiResponse({ status: 200, description: 'Member role updated successfully', type: UpdateLeagueMemberRoleOutputDTO })
@ApiResponse({ status: 400, description: 'Cannot update role' })
@ApiResponse({ status: 404, description: 'Member not found' })
async updateLeagueMemberRole(
@Param('leagueId') leagueId: string,
@Param('performerDriverId') performerDriverId: string,
@Param('targetDriverId') targetDriverId: string,
@Body() input: UpdateLeagueMemberRoleInput, // Body includes newRole, other for swagger
): Promise<UpdateLeagueMemberRoleOutput> {
@Body() input: UpdateLeagueMemberRoleInputDTO, // Body includes newRole, other for swagger
): Promise<UpdateLeagueMemberRoleOutputDTO> {
return this.leagueService.updateLeagueMemberRole({ leagueId, performerDriverId, targetDriverId, newRole: input.newRole });
}
@Get(':leagueId/owner-summary/:ownerId')
@ApiOperation({ summary: 'Get owner summary for a league' })
@ApiResponse({ status: 200, description: 'League owner summary', type: LeagueOwnerSummaryViewModel })
@ApiResponse({ status: 200, description: 'League owner summary', type: LeagueOwnerSummaryDTO })
@ApiResponse({ status: 404, description: 'Owner or league not found' })
async getLeagueOwnerSummary(
@Param('leagueId') leagueId: string,
@Param('ownerId') ownerId: string,
): Promise<LeagueOwnerSummaryViewModel | null> {
): Promise<LeagueOwnerSummaryDTO | null> {
const query: GetLeagueOwnerSummaryQuery = { ownerId, leagueId };
return this.leagueService.getLeagueOwnerSummary(query);
}
@Get(':leagueId/config')
@ApiOperation({ summary: 'Get league full configuration' })
@ApiResponse({ status: 200, description: 'League configuration form model', type: LeagueConfigFormModelDto })
@ApiResponse({ status: 200, description: 'League configuration form model', type: LeagueConfigFormModelDTO })
async getLeagueFullConfig(
@Param('leagueId') leagueId: string,
): Promise<LeagueConfigFormModelDto | null> {
): Promise<LeagueConfigFormModelDTO | null> {
const query: GetLeagueAdminConfigQuery = { leagueId };
return this.leagueService.getLeagueFullConfig(query);
}
@Get(':leagueId/protests')
@ApiOperation({ summary: 'Get protests for a league' })
@ApiResponse({ status: 200, description: 'List of protests for the league', type: LeagueAdminProtestsViewModel })
async getLeagueProtests(@Param('leagueId') leagueId: string): Promise<LeagueAdminProtestsViewModel> {
@ApiResponse({ status: 200, description: 'List of protests for the league', type: LeagueAdminProtestsDTO })
async getLeagueProtests(@Param('leagueId') leagueId: string): Promise<LeagueAdminProtestsDTO> {
const query: GetLeagueProtestsQuery = { leagueId };
return this.leagueService.getLeagueProtests(query);
}
@Get(':leagueId/seasons')
@ApiOperation({ summary: 'Get seasons for a league' })
@ApiResponse({ status: 200, description: 'List of seasons for the league', type: [LeagueSeasonSummaryViewModel] })
async getLeagueSeasons(@Param('leagueId') leagueId: string): Promise<LeagueSeasonSummaryViewModel[]> {
@ApiResponse({ status: 200, description: 'List of seasons for the league', type: [LeagueSeasonSummaryDTO] })
async getLeagueSeasons(@Param('leagueId') leagueId: string): Promise<LeagueSeasonSummaryDTO[]> {
const query: GetLeagueSeasonsQuery = { leagueId };
return this.leagueService.getLeagueSeasons(query);
}
@Get(':leagueId/memberships')
@ApiOperation({ summary: 'Get league memberships' })
@ApiResponse({ status: 200, description: 'List of league members', type: LeagueMembershipsViewModel })
async getLeagueMemberships(@Param('leagueId') leagueId: string): Promise<LeagueMembershipsViewModel> {
@ApiResponse({ status: 200, description: 'List of league members', type: LeagueMembershipsDTO })
async getLeagueMemberships(@Param('leagueId') leagueId: string): Promise<LeagueMembershipsDTO> {
return this.leagueService.getLeagueMemberships(leagueId);
}
@Get(':leagueId/standings')
@ApiOperation({ summary: 'Get league standings' })
@ApiResponse({ status: 200, description: 'League standings', type: LeagueStandingsViewModel })
async getLeagueStandings(@Param('leagueId') leagueId: string): Promise<LeagueStandingsViewModel> {
@ApiResponse({ status: 200, description: 'League standings', type: LeagueStandingsDTO })
async getLeagueStandings(@Param('leagueId') leagueId: string): Promise<LeagueStandingsDTO> {
return this.leagueService.getLeagueStandings(leagueId);
}
@Get(':leagueId/schedule')
@ApiOperation({ summary: 'Get league schedule' })
@ApiResponse({ status: 200, description: 'League schedule', type: LeagueScheduleViewModel })
async getLeagueSchedule(@Param('leagueId') leagueId: string): Promise<LeagueScheduleViewModel> {
@ApiResponse({ status: 200, description: 'League schedule', type: LeagueScheduleDTO })
async getLeagueSchedule(@Param('leagueId') leagueId: string): Promise<LeagueScheduleDTO> {
return this.leagueService.getLeagueSchedule(leagueId);
}
@Get(':leagueId/stats')
@ApiOperation({ summary: 'Get league stats' })
@ApiResponse({ status: 200, description: 'League stats', type: LeagueStatsViewModel })
async getLeagueStats(@Param('leagueId') leagueId: string): Promise<LeagueStatsViewModel> {
@ApiResponse({ status: 200, description: 'League stats', type: LeagueStatsDTO })
async getLeagueStats(@Param('leagueId') leagueId: string): Promise<LeagueStatsDTO> {
return this.leagueService.getLeagueStats(leagueId);
}
@Get(':leagueId/admin')
@ApiOperation({ summary: 'Get league admin data' })
@ApiResponse({ status: 200, description: 'League admin data', type: LeagueAdminViewModel })
async getLeagueAdmin(@Param('leagueId') leagueId: string): Promise<LeagueAdminViewModel> {
@ApiResponse({ status: 200, description: 'League admin data', type: LeagueAdminDTO })
async getLeagueAdmin(@Param('leagueId') leagueId: string): Promise<LeagueAdminDTO> {
return this.leagueService.getLeagueAdmin(leagueId);
}
@Post()
@ApiOperation({ summary: 'Create a new league' })
@ApiBody({ type: CreateLeagueInput })
@ApiResponse({ status: 201, description: 'League created successfully', type: CreateLeagueOutput })
async createLeague(@Body() input: CreateLeagueInput): Promise<CreateLeagueOutput> {
@ApiBody({ type: CreateLeagueInputDTO })
@ApiResponse({ status: 201, description: 'League created successfully', type: CreateLeagueOutputDTO })
async createLeague(@Body() input: CreateLeagueInputDTO): Promise<CreateLeagueOutputDTO> {
return this.leagueService.createLeague(input);
}
@Get('scoring-presets')
@ApiOperation({ summary: 'Get league scoring presets' })
@ApiResponse({ status: 200, description: 'List of scoring presets' })
async getLeagueScoringPresets() {
return this.leagueService.listLeagueScoringPresets();
}
@Get(':leagueId/scoring-config')
@ApiOperation({ summary: 'Get league scoring config' })
@ApiResponse({ status: 200, description: 'League scoring config' })
async getLeagueScoringConfig(@Param('leagueId') leagueId: string) {
return this.leagueService.getLeagueScoringConfig(leagueId);
}
@Post(':leagueId/join')
@ApiOperation({ summary: 'Join a league' })
@ApiResponse({ status: 200, description: 'Joined league successfully' })
async joinLeague(@Param('leagueId') leagueId: string, @Body() body: { driverId: string }) {
return this.leagueService.joinLeague(leagueId, body.driverId);
}
@Post(':leagueId/transfer-ownership')
@ApiOperation({ summary: 'Transfer league ownership' })
@ApiResponse({ status: 200, description: 'Ownership transferred successfully' })
async transferLeagueOwnership(@Param('leagueId') leagueId: string, @Body() body: { currentOwnerId: string, newOwnerId: string }) {
return this.leagueService.transferLeagueOwnership(leagueId, body.currentOwnerId, body.newOwnerId);
}
}

View File

@@ -1,5 +1,32 @@
import { Injectable, Inject } from '@nestjs/common';
import { AllLeaguesWithCapacityViewModel, LeagueStatsDto, LeagueJoinRequestViewModel, ApproveJoinRequestInput, ApproveJoinRequestOutput, RejectJoinRequestInput, RejectJoinRequestOutput, LeagueAdminPermissionsViewModel, RemoveLeagueMemberInput, RemoveLeagueMemberOutput, UpdateLeagueMemberRoleInput, UpdateLeagueMemberRoleOutput, LeagueOwnerSummaryViewModel, LeagueConfigFormModelDto, LeagueAdminProtestsViewModel, LeagueSeasonSummaryViewModel, GetLeagueAdminPermissionsInput, GetLeagueProtestsQuery, GetLeagueSeasonsQuery, GetLeagueAdminConfigQuery, GetLeagueOwnerSummaryQuery, LeagueMembershipsViewModel, LeagueStandingsViewModel, LeagueScheduleViewModel, LeagueStatsViewModel, LeagueAdminViewModel, CreateLeagueInput, CreateLeagueOutput } from './dto/LeagueDto';
import { AllLeaguesWithCapacityDTO } from './dtos/AllLeaguesWithCapacityDTO';
import { LeagueStatsDTO } from './dtos/LeagueStatsDTO';
import { LeagueJoinRequestDTO } from './dtos/LeagueJoinRequestDTO';
import { ApproveJoinRequestInputDTO } from './dtos/ApproveJoinRequestInputDTO';
import { ApproveJoinRequestOutputDTO } from './dtos/ApproveJoinRequestOutputDTO';
import { RejectJoinRequestInputDTO } from './dtos/RejectJoinRequestInputDTO';
import { RejectJoinRequestOutputDTO } from './dtos/RejectJoinRequestOutputDTO';
import { LeagueAdminPermissionsDTO } from './dtos/LeagueAdminPermissionsDTO';
import { RemoveLeagueMemberInputDTO } from './dtos/RemoveLeagueMemberInputDTO';
import { RemoveLeagueMemberOutputDTO } from './dtos/RemoveLeagueMemberOutputDTO';
import { UpdateLeagueMemberRoleInputDTO } from './dtos/UpdateLeagueMemberRoleInputDTO';
import { UpdateLeagueMemberRoleOutputDTO } from './dtos/UpdateLeagueMemberRoleOutputDTO';
import { LeagueOwnerSummaryDTO } from './dtos/LeagueOwnerSummaryDTO';
import { LeagueConfigFormModelDTO } from './dtos/LeagueConfigFormModelDTO';
import { LeagueAdminProtestsDTO } from './dtos/LeagueAdminProtestsDTO';
import { LeagueSeasonSummaryDTO } from './dtos/LeagueSeasonSummaryDTO';
import { GetLeagueAdminPermissionsInputDTO } from './dtos/GetLeagueAdminPermissionsInputDTO';
import { GetLeagueProtestsQueryDTO } from './dtos/GetLeagueProtestsQueryDTO';
import { GetLeagueSeasonsQueryDTO } from './dtos/GetLeagueSeasonsQueryDTO';
import { GetLeagueAdminConfigQueryDTO } from './dtos/GetLeagueAdminConfigQueryDTO';
import { GetLeagueOwnerSummaryQueryDTO } from './dtos/GetLeagueOwnerSummaryQueryDTO';
import { LeagueMembershipsDTO } from './dtos/LeagueMembershipsDTO';
import { LeagueStandingsDTO } from './dtos/LeagueStandingsDTO';
import { LeagueScheduleDTO } from './dtos/LeagueScheduleDTO';
import { LeagueStatsDTO } from './dtos/LeagueStatsDTO';
import { LeagueAdminDTO } from './dtos/LeagueAdminDTO';
import { CreateLeagueInputDTO } from './dtos/CreateLeagueInputDTO';
import { CreateLeagueOutputDTO } from './dtos/CreateLeagueOutputDTO';
// Core imports
import type { Logger } from '@core/shared/application/Logger';
@@ -9,6 +36,10 @@ import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-c
import { GetLeagueStandingsUseCase } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase';
import { GetLeagueStatsUseCase } from '@core/racing/application/use-cases/GetLeagueStatsUseCase';
import { GetLeagueFullConfigUseCase } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase';
import { GetLeagueScoringConfigUseCase } from '@core/racing/application/use-cases/GetLeagueScoringConfigUseCase';
import { ListLeagueScoringPresetsUseCase } from '@core/racing/application/use-cases/ListLeagueScoringPresetsUseCase';
import { JoinLeagueUseCase } from '@core/racing/application/use-cases/JoinLeagueUseCase';
import { TransferLeagueOwnershipUseCase } from '@core/racing/application/use-cases/TransferLeagueOwnershipUseCase';
import { CreateLeagueWithSeasonAndScoringUseCase } from '@core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase';
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
import { GetTotalLeaguesUseCase } from '@core/racing/application/use-cases/GetTotalLeaguesUseCase';
@@ -27,6 +58,8 @@ import { GetLeagueAdminPermissionsUseCase } from '@core/racing/application/use-c
// API Presenters
import { LeagueStandingsPresenter } from './presenters/LeagueStandingsPresenter';
import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter';
import { LeagueScoringConfigPresenter } from './presenters/LeagueScoringConfigPresenter';
import { LeagueScoringPresetsPresenter } from './presenters/LeagueScoringPresetsPresenter';
import { LeagueJoinRequestsPresenter } from './presenters/LeagueJoinRequestsPresenter';
import { ApproveLeagueJoinRequestPresenter } from './presenters/ApproveLeagueJoinRequestPresenter';
import { RejectLeagueJoinRequestPresenter } from './presenters/RejectLeagueJoinRequestPresenter';
@@ -52,6 +85,10 @@ export class LeagueService {
private readonly getLeagueStandingsUseCase: GetLeagueStandingsUseCase,
private readonly getLeagueStatsUseCase: GetLeagueStatsUseCase,
private readonly getLeagueFullConfigUseCase: GetLeagueFullConfigUseCase,
private readonly getLeagueScoringConfigUseCase: GetLeagueScoringConfigUseCase,
private readonly listLeagueScoringPresetsUseCase: ListLeagueScoringPresetsUseCase,
private readonly joinLeagueUseCase: JoinLeagueUseCase,
private readonly transferLeagueOwnershipUseCase: TransferLeagueOwnershipUseCase,
private readonly createLeagueWithSeasonAndScoringUseCase: CreateLeagueWithSeasonAndScoringUseCase,
private readonly getRaceProtestsUseCase: GetRaceProtestsUseCase,
private readonly getTotalLeaguesUseCase: GetTotalLeaguesUseCase,
@@ -234,4 +271,61 @@ export class LeagueService {
success: true,
};
}
async getLeagueScoringConfig(leagueId: string): Promise<LeagueScoringConfigViewModel | null> {
this.logger.debug('Getting league scoring config', { leagueId });
const presenter = new LeagueScoringConfigPresenter();
try {
const result = await this.getLeagueScoringConfigUseCase.execute({ leagueId });
if (result.isErr()) {
this.logger.error('Error getting league scoring config', result.error);
return null;
}
await presenter.present(result.value);
return presenter.getViewModel();
} catch (error) {
this.logger.error('Error getting league scoring config', error instanceof Error ? error : new Error(String(error)));
return null;
}
}
async listLeagueScoringPresets(): Promise<LeagueScoringPresetsViewModel> {
this.logger.debug('Listing league scoring presets');
const presenter = new LeagueScoringPresetsPresenter();
await this.listLeagueScoringPresetsUseCase.execute(undefined, presenter);
return presenter.getViewModel()!;
}
async joinLeague(leagueId: string, driverId: string): Promise<JoinLeagueOutput> {
this.logger.debug('Joining league', { leagueId, driverId });
const result = await this.joinLeagueUseCase.execute({ leagueId, driverId });
if (result.isErr()) {
return {
success: false,
error: result.error.code,
};
}
return {
success: true,
membershipId: result.value.id,
};
}
async transferLeagueOwnership(leagueId: string, currentOwnerId: string, newOwnerId: string): Promise<TransferLeagueOwnershipOutput> {
this.logger.debug('Transferring league ownership', { leagueId, currentOwnerId, newOwnerId });
const result = await this.transferLeagueOwnershipUseCase.execute({ leagueId, currentOwnerId, newOwnerId });
if (result.isErr()) {
return {
success: false,
error: result.error.code,
};
}
return {
success: true,
};
}
}

View File

@@ -1,666 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, IsBoolean, IsDate, IsOptional, IsEnum, IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { DriverDto } from '../../driver/dto/DriverDto';
import { RaceDto } from '../../race/dto/RaceDto';
export class LeagueSettingsDto {
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
maxDrivers?: number;
// Add other league settings properties as needed
}
export class LeagueWithCapacityViewModel {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
name: string;
// ... other properties of LeagueWithCapacityViewModel
@ApiProperty({ nullable: true })
@IsOptional()
@IsString()
description?: string;
@ApiProperty()
@IsString()
ownerId: string;
@ApiProperty({ type: () => LeagueSettingsDto })
@ValidateNested()
@Type(() => LeagueSettingsDto)
settings: LeagueSettingsDto;
@ApiProperty()
@IsString()
createdAt: string;
@ApiProperty()
@IsNumber()
usedSlots: number;
@ApiProperty({ type: () => Object, nullable: true }) // Using Object for generic social links
@IsOptional()
socialLinks?: {
discordUrl?: string;
youtubeUrl?: string;
websiteUrl?: string;
};
}
export class AllLeaguesWithCapacityViewModel {
@ApiProperty({ type: [LeagueWithCapacityViewModel] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueWithCapacityViewModel)
leagues: LeagueWithCapacityViewModel[];
@ApiProperty()
@IsNumber()
totalCount: number;
}
export class LeagueStatsDto {
@ApiProperty()
@IsNumber()
totalLeagues: number;
}
export class ProtestDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
raceId: string;
@ApiProperty()
@IsString()
protestingDriverId: string;
@ApiProperty()
@IsString()
accusedDriverId: string;
@ApiProperty()
@IsDate()
@Type(() => Date)
submittedAt: Date;
@ApiProperty()
@IsString()
description: string;
@ApiProperty({ enum: ['pending', 'accepted', 'rejected'] })
@IsEnum(['pending', 'accepted', 'rejected'])
status: 'pending' | 'accepted' | 'rejected';
}
export class SeasonDto {
@ApiProperty()
@IsString()
seasonId: string;
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
@Type(() => Date)
startDate?: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
@Type(() => Date)
endDate?: Date;
@ApiProperty({ enum: ['planned', 'active', 'completed'] })
@IsEnum(['planned', 'active', 'completed'])
status: 'planned' | 'active' | 'completed';
@ApiProperty()
@IsBoolean()
isPrimary: boolean;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonGroupId?: string;
}
export class LeagueJoinRequestViewModel {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
driverId: string;
@ApiProperty()
@IsDate()
@Type(() => Date)
requestedAt: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
message?: string;
@ApiProperty({ type: () => DriverDto, required: false })
@IsOptional()
@ValidateNested()
@Type(() => DriverDto)
driver?: DriverDto;
}
export class GetLeagueJoinRequestsQuery {
@ApiProperty()
@IsString()
leagueId: string;
}
export class ApproveJoinRequestInput {
@ApiProperty()
@IsString()
requestId: string;
@ApiProperty()
@IsString()
leagueId: string;
}
export class ApproveJoinRequestOutput {
@ApiProperty()
@IsBoolean()
success: boolean;
@ApiProperty({ required: false })
@IsString()
message?: string;
}
export class RejectJoinRequestInput {
@ApiProperty()
@IsString()
requestId: string;
@ApiProperty()
@IsString()
leagueId: string;
}
export class RejectJoinRequestOutput {
@ApiProperty()
@IsBoolean()
success: boolean;
@ApiProperty({ required: false })
@IsString()
message?: string;
}
export class GetLeagueAdminPermissionsInput {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
performerDriverId: string;
}
export class LeagueAdminPermissionsViewModel {
@ApiProperty()
@IsBoolean()
canRemoveMember: boolean;
@ApiProperty()
@IsBoolean()
canUpdateRoles: boolean;
}
export class RemoveLeagueMemberInput {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
performerDriverId: string;
@ApiProperty()
@IsString()
targetDriverId: string;
}
export class RemoveLeagueMemberOutput {
@ApiProperty()
@IsBoolean()
success: boolean;
}
export class UpdateLeagueMemberRoleInput {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
performerDriverId: string;
@ApiProperty()
@IsString()
targetDriverId: string;
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
@IsEnum(['owner', 'manager', 'member'])
newRole: 'owner' | 'manager' | 'member';
}
export class UpdateLeagueMemberRoleOutput {
@ApiProperty()
@IsBoolean()
success: boolean;
}
export class GetLeagueOwnerSummaryQuery {
@ApiProperty()
@IsString()
ownerId: string;
@ApiProperty()
@IsString()
leagueId: string;
}
export class LeagueOwnerSummaryViewModel {
@ApiProperty({ type: () => DriverDto })
@ValidateNested()
@Type(() => DriverDto)
driver: DriverDto;
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
rating: number | null;
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
rank: number | null;
}
export class LeagueConfigFormModelBasicsDto {
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
description: string;
@ApiProperty({ enum: ['public', 'private'] })
@IsEnum(['public', 'private'])
visibility: 'public' | 'private';
}
export class LeagueConfigFormModelStructureDto {
@ApiProperty()
@IsString()
@IsEnum(['solo', 'team'])
mode: 'solo' | 'team';
}
export class LeagueConfigFormModelScoringDto {
@ApiProperty()
@IsString()
type: string;
@ApiProperty()
@IsNumber()
points: number;
}
export class LeagueConfigFormModelDropPolicyDto {
@ApiProperty({ enum: ['none', 'worst_n'] })
@IsEnum(['none', 'worst_n'])
strategy: 'none' | 'worst_n';
@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
n?: number;
}
export class LeagueConfigFormModelStewardingDto {
@ApiProperty({ enum: ['single_steward', 'committee_vote'] })
@IsEnum(['single_steward', 'committee_vote'])
decisionMode: 'single_steward' | 'committee_vote';
@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
requiredVotes?: number;
@ApiProperty()
@IsBoolean()
requireDefense: boolean;
@ApiProperty()
@IsNumber()
defenseTimeLimit: number;
@ApiProperty()
@IsNumber()
voteTimeLimit: number;
@ApiProperty()
@IsNumber()
protestDeadlineHours: number;
@ApiProperty()
@IsNumber()
stewardingClosesHours: number;
@ApiProperty()
@IsBoolean()
notifyAccusedOnProtest: boolean;
@ApiProperty()
@IsBoolean()
notifyOnVoteRequired: boolean;
}
export class LeagueConfigFormModelTimingsDto {
@ApiProperty()
@IsString()
raceDayOfWeek: string;
@ApiProperty()
@IsNumber()
raceTimeHour: number;
@ApiProperty()
@IsNumber()
raceTimeMinute: number;
}
export class LeagueConfigFormModelDto {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ type: LeagueConfigFormModelBasicsDto })
@ValidateNested()
@Type(() => LeagueConfigFormModelBasicsDto)
basics: LeagueConfigFormModelBasicsDto;
@ApiProperty({ type: LeagueConfigFormModelStructureDto })
@ValidateNested()
@Type(() => LeagueConfigFormModelStructureDto)
structure: LeagueConfigFormModelStructureDto;
@ApiProperty({ type: [Object] })
@IsArray()
championships: any[];
@ApiProperty({ type: LeagueConfigFormModelScoringDto })
@ValidateNested()
@Type(() => LeagueConfigFormModelScoringDto)
scoring: LeagueConfigFormModelScoringDto;
@ApiProperty({ type: LeagueConfigFormModelDropPolicyDto })
@ValidateNested()
@Type(() => LeagueConfigFormModelDropPolicyDto)
dropPolicy: LeagueConfigFormModelDropPolicyDto;
@ApiProperty({ type: LeagueConfigFormModelTimingsDto })
@ValidateNested()
@Type(() => LeagueConfigFormModelTimingsDto)
timings: LeagueConfigFormModelTimingsDto;
@ApiProperty({ type: LeagueConfigFormModelStewardingDto })
@ValidateNested()
@Type(() => LeagueConfigFormModelStewardingDto)
stewarding: LeagueConfigFormModelStewardingDto;
}
export class GetLeagueAdminConfigQuery {
@ApiProperty()
@IsString()
leagueId: string;
}
export class GetLeagueAdminConfigOutput {
@ApiProperty({ type: () => LeagueConfigFormModelDto, nullable: true })
@IsOptional()
@ValidateNested()
@Type(() => LeagueConfigFormModelDto)
form: LeagueConfigFormModelDto | null;
}
export class LeagueAdminConfigViewModel {
@ApiProperty({ type: () => LeagueConfigFormModelDto, nullable: true })
@IsOptional()
@ValidateNested()
@Type(() => LeagueConfigFormModelDto)
form: LeagueConfigFormModelDto | null;
}
export class GetLeagueProtestsQuery {
@ApiProperty()
@IsString()
leagueId: string;
}
export class LeagueAdminProtestsViewModel {
@ApiProperty({ type: [ProtestDto] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => ProtestDto)
protests: ProtestDto[];
@ApiProperty({ type: () => RaceDto })
@ValidateNested()
@Type(() => RaceDto)
racesById: { [raceId: string]: RaceDto };
@ApiProperty({ type: () => DriverDto })
@ValidateNested()
@Type(() => DriverDto)
driversById: { [driverId: string]: DriverDto };
}
export class GetLeagueSeasonsQuery {
@ApiProperty()
@IsString()
leagueId: string;
}
export class LeagueSeasonSummaryViewModel {
@ApiProperty()
@IsString()
seasonId: string;
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
status: string;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
@Type(() => Date)
startDate?: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
@Type(() => Date)
endDate?: Date;
@ApiProperty()
@IsBoolean()
isPrimary: boolean;
@ApiProperty()
@IsBoolean()
isParallelActive: boolean;
}
export class LeagueAdminViewModel {
@ApiProperty({ type: [LeagueJoinRequestViewModel] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueJoinRequestViewModel)
joinRequests: LeagueJoinRequestViewModel[];
@ApiProperty({ type: () => LeagueOwnerSummaryViewModel, nullable: true })
@IsOptional()
@ValidateNested()
@Type(() => LeagueOwnerSummaryViewModel)
ownerSummary: LeagueOwnerSummaryViewModel | null;
@ApiProperty({ type: () => LeagueAdminConfigViewModel })
@ValidateNested()
@Type(() => LeagueAdminConfigViewModel)
config: LeagueAdminConfigViewModel;
@ApiProperty({ type: () => LeagueAdminProtestsViewModel })
@ValidateNested()
@Type(() => LeagueAdminProtestsViewModel)
protests: LeagueAdminProtestsViewModel;
@ApiProperty({ type: [LeagueSeasonSummaryViewModel] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueSeasonSummaryViewModel)
seasons: LeagueSeasonSummaryViewModel[];
}
export class LeagueMemberDto {
@ApiProperty()
@IsString()
driverId: string;
@ApiProperty({ type: () => DriverDto })
@ValidateNested()
@Type(() => DriverDto)
driver: DriverDto;
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
@IsEnum(['owner', 'manager', 'member'])
role: 'owner' | 'manager' | 'member';
@ApiProperty()
@IsDate()
@Type(() => Date)
joinedAt: Date;
}
export class LeagueMembershipsViewModel {
@ApiProperty({ type: [LeagueMemberDto] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueMemberDto)
members: LeagueMemberDto[];
}
export class LeagueStandingDto {
@ApiProperty()
@IsString()
driverId: string;
@ApiProperty({ type: () => DriverDto })
@ValidateNested()
@Type(() => DriverDto)
driver: DriverDto;
@ApiProperty()
@IsNumber()
points: number;
@ApiProperty()
@IsNumber()
rank: number;
}
export class LeagueStandingsViewModel {
@ApiProperty({ type: [LeagueStandingDto] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueStandingDto)
standings: LeagueStandingDto[];
}
export class LeagueScheduleViewModel {
@ApiProperty({ type: [RaceDto] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => RaceDto)
races: RaceDto[];
}
export class LeagueStatsViewModel {
@ApiProperty()
@IsNumber()
totalMembers: number;
@ApiProperty()
@IsNumber()
totalRaces: number;
@ApiProperty()
@IsNumber()
averageRating: number;
}
export class CreateLeagueInput {
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
description: string;
@ApiProperty({ enum: ['public', 'private'] })
@IsEnum(['public', 'private'])
visibility: 'public' | 'private';
@ApiProperty()
@IsString()
ownerId: string;
}
export class CreateLeagueOutput {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsBoolean()
success: boolean;
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueSummaryDTO } from './LeagueSummaryDTO';
export class AllLeaguesWithCapacityAndScoringDTO {
@ApiProperty({ type: [LeagueSummaryDTO] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueSummaryDTO)
leagues: LeagueSummaryDTO[];
@ApiProperty()
@IsNumber()
totalCount: number;
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueWithCapacityDTO } from './LeagueWithCapacityDTO';
export class AllLeaguesWithCapacityDTO {
@ApiProperty({ type: [LeagueWithCapacityDTO] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueWithCapacityDTO)
leagues: LeagueWithCapacityDTO[];
@ApiProperty()
@IsNumber()
totalCount: number;
}

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsEnum } from 'class-validator';
export class CreateLeagueInputDTO {
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
description: string;
@ApiProperty({ enum: ['public', 'private'] })
@IsEnum(['public', 'private'])
visibility: 'public' | 'private';
@ApiProperty()
@IsString()
ownerId: string;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsBoolean } from 'class-validator';
export class CreateLeagueOutputDTO {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsBoolean()
success: boolean;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueConfigFormModelDTO } from './LeagueConfigFormModelDTO';
export class GetLeagueAdminConfigOutputDTO {
@ApiProperty({ type: () => LeagueConfigFormModelDTO, nullable: true })
@IsOptional()
@ValidateNested()
@Type(() => LeagueConfigFormModelDTO)
form: LeagueConfigFormModelDTO | null;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueConfigFormModelDTO } from './LeagueConfigFormModelDTO';
export class LeagueAdminConfigDTO {
@ApiProperty({ type: () => LeagueConfigFormModelDTO, nullable: true })
@IsOptional()
@ValidateNested()
@Type(() => LeagueConfigFormModelDTO)
form: LeagueConfigFormModelDTO | null;
}

View File

@@ -0,0 +1,38 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueJoinRequestDTO } from './LeagueJoinRequestDTO';
import { LeagueOwnerSummaryDTO } from './LeagueOwnerSummaryDTO';
import { LeagueAdminConfigDTO } from './LeagueAdminConfigDTO';
import { LeagueAdminProtestsDTO } from './LeagueAdminProtestsDTO';
import { LeagueSeasonSummaryDTO } from './LeagueSeasonSummaryDTO';
export class LeagueAdminDTO {
@ApiProperty({ type: [LeagueJoinRequestDTO] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueJoinRequestDTO)
joinRequests: LeagueJoinRequestDTO[];
@ApiProperty({ type: () => LeagueOwnerSummaryDTO, nullable: true })
@IsOptional()
@ValidateNested()
@Type(() => LeagueOwnerSummaryDTO)
ownerSummary: LeagueOwnerSummaryDTO | null;
@ApiProperty({ type: () => LeagueAdminConfigDTO })
@ValidateNested()
@Type(() => LeagueAdminConfigDTO)
config: LeagueAdminConfigDTO;
@ApiProperty({ type: () => LeagueAdminProtestsDTO })
@ValidateNested()
@Type(() => LeagueAdminProtestsDTO)
protests: LeagueAdminProtestsDTO;
@ApiProperty({ type: [LeagueSeasonSummaryDTO] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueSeasonSummaryDTO)
seasons: LeagueSeasonSummaryDTO[];
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean } from 'class-validator';
export class LeagueAdminPermissionsDTO {
@ApiProperty()
@IsBoolean()
canRemoveMember: boolean;
@ApiProperty()
@IsBoolean()
canUpdateRoles: boolean;
}

View File

@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { DriverDto } from '../../driver/dto/DriverDto';
import { RaceDto } from '../../race/dto/RaceDto';
import { ProtestDTO } from './ProtestDTO';
export class LeagueAdminProtestsDTO {
@ApiProperty({ type: [ProtestDTO] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => ProtestDTO)
protests: ProtestDTO[];
@ApiProperty({ type: () => RaceDto })
@ValidateNested()
@Type(() => RaceDto)
racesById: { [raceId: string]: RaceDto };
@ApiProperty({ type: () => DriverDto })
@ValidateNested()
@Type(() => DriverDto)
driversById: { [driverId: string]: DriverDto };
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsEnum } from 'class-validator';
export class LeagueConfigFormModelBasicsDTO {
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
description: string;
@ApiProperty({ enum: ['public', 'private'] })
@IsEnum(['public', 'private'])
visibility: 'public' | 'private';
}

View File

@@ -0,0 +1,49 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueConfigFormModelBasicsDTO } from './LeagueConfigFormModelBasicsDTO';
import { LeagueConfigFormModelStructureDTO } from './LeagueConfigFormModelStructureDTO';
import { LeagueConfigFormModelScoringDTO } from './LeagueConfigFormModelScoringDTO';
import { LeagueConfigFormModelDropPolicyDTO } from './LeagueConfigFormModelDropPolicyDTO';
import { LeagueConfigFormModelStewardingDTO } from './LeagueConfigFormModelStewardingDTO';
import { LeagueConfigFormModelTimingsDTO } from './LeagueConfigFormModelTimingsDTO';
export class LeagueConfigFormModelDTO {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ type: LeagueConfigFormModelBasicsDTO })
@ValidateNested()
@Type(() => LeagueConfigFormModelBasicsDTO)
basics: LeagueConfigFormModelBasicsDTO;
@ApiProperty({ type: LeagueConfigFormModelStructureDTO })
@ValidateNested()
@Type(() => LeagueConfigFormModelStructureDTO)
structure: LeagueConfigFormModelStructureDTO;
@ApiProperty({ type: [Object] })
@IsArray()
championships: any[];
@ApiProperty({ type: LeagueConfigFormModelScoringDTO })
@ValidateNested()
@Type(() => LeagueConfigFormModelScoringDTO)
scoring: LeagueConfigFormModelScoringDTO;
@ApiProperty({ type: LeagueConfigFormModelDropPolicyDTO })
@ValidateNested()
@Type(() => LeagueConfigFormModelDropPolicyDTO)
dropPolicy: LeagueConfigFormModelDropPolicyDTO;
@ApiProperty({ type: LeagueConfigFormModelTimingsDTO })
@ValidateNested()
@Type(() => LeagueConfigFormModelTimingsDTO)
timings: LeagueConfigFormModelTimingsDTO;
@ApiProperty({ type: LeagueConfigFormModelStewardingDTO })
@ValidateNested()
@Type(() => LeagueConfigFormModelStewardingDTO)
stewarding: LeagueConfigFormModelStewardingDTO;
}

View File

@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsOptional, IsEnum } from 'class-validator';
export class LeagueConfigFormModelDropPolicyDTO {
@ApiProperty({ enum: ['none', 'worst_n'] })
@IsEnum(['none', 'worst_n'])
strategy: 'none' | 'worst_n';
@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
n?: number;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber } from 'class-validator';
export class LeagueConfigFormModelScoringDTO {
@ApiProperty()
@IsString()
type: string;
@ApiProperty()
@IsNumber()
points: number;
}

View File

@@ -0,0 +1,41 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsBoolean, IsOptional, IsEnum } from 'class-validator';
export class LeagueConfigFormModelStewardingDTO {
@ApiProperty({ enum: ['single_steward', 'committee_vote'] })
@IsEnum(['single_steward', 'committee_vote'])
decisionMode: 'single_steward' | 'committee_vote';
@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
requiredVotes?: number;
@ApiProperty()
@IsBoolean()
requireDefense: boolean;
@ApiProperty()
@IsNumber()
defenseTimeLimit: number;
@ApiProperty()
@IsNumber()
voteTimeLimit: number;
@ApiProperty()
@IsNumber()
protestDeadlineHours: number;
@ApiProperty()
@IsNumber()
stewardingClosesHours: number;
@ApiProperty()
@IsBoolean()
notifyAccusedOnProtest: boolean;
@ApiProperty()
@IsBoolean()
notifyOnVoteRequired: boolean;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsEnum } from 'class-validator';
export class LeagueConfigFormModelStructureDTO {
@ApiProperty()
@IsString()
@IsEnum(['solo', 'team'])
mode: 'solo' | 'team';
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber } from 'class-validator';
export class LeagueConfigFormModelTimingsDTO {
@ApiProperty()
@IsString()
raceDayOfWeek: string;
@ApiProperty()
@IsNumber()
raceTimeHour: number;
@ApiProperty()
@IsNumber()
raceTimeMinute: number;
}

View File

@@ -0,0 +1,34 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsDate, IsOptional, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { DriverDto } from '../../driver/dto/DriverDto';
export class LeagueJoinRequestDTO {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
driverId: string;
@ApiProperty()
@IsDate()
@Type(() => Date)
requestedAt: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
message?: string;
@ApiProperty({ type: () => DriverDto, required: false })
@IsOptional()
@ValidateNested()
@Type(() => DriverDto)
driver?: DriverDto;
}

View File

@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsDate, IsEnum, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { DriverDto } from '../../driver/dto/DriverDto';
export class LeagueMemberDTO {
@ApiProperty()
@IsString()
driverId: string;
@ApiProperty({ type: () => DriverDto })
@ValidateNested()
@Type(() => DriverDto)
driver: DriverDto;
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
@IsEnum(['owner', 'manager', 'member'])
role: 'owner' | 'manager' | 'member';
@ApiProperty()
@IsDate()
@Type(() => Date)
joinedAt: Date;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueMemberDTO } from './LeagueMemberDTO';
export class LeagueMembershipsDTO {
@ApiProperty({ type: [LeagueMemberDTO] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueMemberDTO)
members: LeagueMemberDTO[];
}

View File

@@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsOptional, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { DriverDto } from '../../driver/dto/DriverDto';
export class LeagueOwnerSummaryDTO {
@ApiProperty({ type: () => DriverDto })
@ValidateNested()
@Type(() => DriverDto)
driver: DriverDto;
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
rating: number | null;
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
rank: number | null;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { RaceDto } from '../../race/dto/RaceDto';
export class LeagueScheduleDTO {
@ApiProperty({ type: [RaceDto] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => RaceDto)
races: RaceDto[];
}

View File

@@ -0,0 +1,37 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsBoolean, IsDate, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';
export class LeagueSeasonSummaryDTO {
@ApiProperty()
@IsString()
seasonId: string;
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
status: string;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
@Type(() => Date)
startDate?: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
@Type(() => Date)
endDate?: Date;
@ApiProperty()
@IsBoolean()
isPrimary: boolean;
@ApiProperty()
@IsBoolean()
isParallelActive: boolean;
}

View File

@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsOptional } from 'class-validator';
export class LeagueSettingsDTO {
@ApiProperty({ nullable: true })
@IsOptional()
@IsNumber()
maxDrivers?: number;
// Add other league settings properties as needed
}

View File

@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { DriverDto } from '../../driver/dto/DriverDto';
export class LeagueStandingDTO {
@ApiProperty()
@IsString()
driverId: string;
@ApiProperty({ type: () => DriverDto })
@ValidateNested()
@Type(() => DriverDto)
driver: DriverDto;
@ApiProperty()
@IsNumber()
points: number;
@ApiProperty()
@IsNumber()
rank: number;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueStandingDTO } from './LeagueStandingDTO';
export class LeagueStandingsDTO {
@ApiProperty({ type: [LeagueStandingDTO] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => LeagueStandingDTO)
standings: LeagueStandingDTO[];
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
export class LeagueStatsDTO {
@ApiProperty()
@IsNumber()
totalMembers: number;
@ApiProperty()
@IsNumber()
totalRaces: number;
@ApiProperty()
@IsNumber()
averageRating: number;
}

View File

@@ -0,0 +1,58 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, IsBoolean, IsOptional } from 'class-validator';
export class LeagueSummaryDTO {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
name: string;
@ApiProperty({ nullable: true })
@IsOptional()
@IsString()
description?: string;
@ApiProperty({ nullable: true })
@IsOptional()
@IsString()
logoUrl?: string;
@ApiProperty({ nullable: true })
@IsOptional()
@IsString()
coverImage?: string;
@ApiProperty()
@IsNumber()
memberCount: number;
@ApiProperty()
@IsNumber()
maxMembers: number;
@ApiProperty()
@IsBoolean()
isPublic: boolean;
@ApiProperty()
@IsString()
ownerId: string;
@ApiProperty({ nullable: true })
@IsOptional()
@IsString()
ownerName?: string;
@ApiProperty({ nullable: true })
@IsOptional()
@IsString()
scoringType?: string;
@ApiProperty({ nullable: true })
@IsOptional()
@IsString()
status?: string;
}

View File

@@ -0,0 +1,45 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, IsOptional, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { LeagueSettingsDTO } from './LeagueSettingsDTO';
export class LeagueWithCapacityDTO {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
name: string;
// ... other properties of LeagueWithCapacityDTO
@ApiProperty({ nullable: true })
@IsOptional()
@IsString()
description?: string;
@ApiProperty()
@IsString()
ownerId: string;
@ApiProperty({ type: () => LeagueSettingsDTO })
@ValidateNested()
@Type(() => LeagueSettingsDTO)
settings: LeagueSettingsDTO;
@ApiProperty()
@IsString()
createdAt: string;
@ApiProperty()
@IsNumber()
usedSlots: number;
@ApiProperty({ type: () => Object, nullable: true }) // Using Object for generic social links
@IsOptional()
socialLinks?: {
discordUrl?: string;
youtubeUrl?: string;
websiteUrl?: string;
};
}

View File

@@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsDate, IsEnum } from 'class-validator';
import { Type } from 'class-transformer';
// TODO: protests are filed at race level but also managed on league level
export class ProtestDTO {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
raceId: string;
@ApiProperty()
@IsString()
protestingDriverId: string;
@ApiProperty()
@IsString()
accusedDriverId: string;
@ApiProperty()
@IsDate()
@Type(() => Date)
submittedAt: Date;
@ApiProperty()
@IsString()
description: string;
@ApiProperty({ enum: ['pending', 'accepted', 'rejected'] })
@IsEnum(['pending', 'accepted', 'rejected'])
status: 'pending' | 'accepted' | 'rejected';
}

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
export class RemoveLeagueMemberInputDTO {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
performerDriverId: string;
@ApiProperty()
@IsString()
targetDriverId: string;
}

View File

@@ -0,0 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean } from 'class-validator';
export class RemoveLeagueMemberOutputDTO {
@ApiProperty()
@IsBoolean()
success: boolean;
}

View File

@@ -0,0 +1,42 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsBoolean, IsDate, IsOptional, IsEnum } from 'class-validator';
import { Type } from 'class-transformer';
export class SeasonDTO {
@ApiProperty()
@IsString()
seasonId: string;
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
@Type(() => Date)
startDate?: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
@Type(() => Date)
endDate?: Date;
@ApiProperty({ enum: ['planned', 'active', 'completed'] })
@IsEnum(['planned', 'active', 'completed'])
status: 'planned' | 'active' | 'completed';
@ApiProperty()
@IsBoolean()
isPrimary: boolean;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonGroupId?: string;
}

View File

@@ -0,0 +1,20 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsEnum } from 'class-validator';
export class UpdateLeagueMemberRoleInputDTO {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
performerDriverId: string;
@ApiProperty()
@IsString()
targetDriverId: string;
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
@IsEnum(['owner', 'manager', 'member'])
newRole: 'owner' | 'manager' | 'member';
}

View File

@@ -0,0 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean } from 'class-validator';
export class UpdateLeagueMemberRoleOutputDTO {
@ApiProperty()
@IsBoolean()
success: boolean;
}

View File

@@ -1,5 +1,5 @@
import { IGetLeagueMembershipsPresenter, GetLeagueMembershipsResultDTO, GetLeagueMembershipsViewModel } from '@core/racing/application/presenters/IGetLeagueMembershipsPresenter';
import { LeagueMembershipsViewModel } from '../dto/LeagueDto';
import { LeagueMembershipsDTO } from '../dtos/LeagueMembershipsDTO';
export class GetLeagueMembershipsPresenter implements IGetLeagueMembershipsPresenter {
private result: GetLeagueMembershipsViewModel | null = null;
@@ -40,7 +40,7 @@ export class GetLeagueMembershipsPresenter implements IGetLeagueMembershipsPrese
}
// API-specific method
get apiViewModel(): LeagueMembershipsViewModel | null {
get apiViewModel(): LeagueMembershipsDTO | null {
if (!this.result?.memberships) return null;
return this.result.memberships as LeagueMembershipsViewModel;
}

View File

@@ -1,7 +1,7 @@
import { LeagueAdminViewModel } from '../dto/LeagueDto';
import { LeagueAdminDTO } from '../dtos/LeagueAdminDTO';
export class LeagueAdminPresenter {
private result: LeagueAdminViewModel | null = null;
private result: LeagueAdminDTO | null = null;
reset() {
this.result = null;
@@ -23,7 +23,7 @@ export class LeagueAdminPresenter {
};
}
getViewModel(): LeagueAdminViewModel {
getViewModel(): LeagueAdminDTO {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}

View File

@@ -1,5 +1,5 @@
import { ILeagueFullConfigPresenter, LeagueFullConfigData, LeagueConfigFormViewModel } from '@core/racing/application/presenters/ILeagueFullConfigPresenter';
import { LeagueConfigFormModelDto } from '../dto/LeagueDto';
import { LeagueConfigFormModelDTO } from '../dtos/LeagueConfigFormModelDTO';
export class LeagueConfigPresenter implements ILeagueFullConfigPresenter {
private result: LeagueConfigFormViewModel | null = null;
@@ -65,7 +65,7 @@ export class LeagueConfigPresenter implements ILeagueFullConfigPresenter {
}
// API-specific method to get the DTO
get viewModel(): LeagueConfigFormModelDto | null {
get viewModel(): LeagueConfigFormModelDTO | null {
if (!this.result) return null;
// Map from LeagueConfigFormViewModel to LeagueConfigFormModelDto

View File

@@ -0,0 +1,150 @@
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
import type {
ILeagueScoringConfigPresenter,
LeagueScoringConfigData,
LeagueScoringConfigViewModel,
LeagueScoringChampionshipViewModel,
} from '@core/racing/application/presenters/ILeagueScoringConfigPresenter';
export class LeagueScoringConfigPresenter implements ILeagueScoringConfigPresenter {
private viewModel: LeagueScoringConfigViewModel | null = null;
reset(): void {
this.viewModel = null;
}
present(data: LeagueScoringConfigData): LeagueScoringConfigViewModel {
const championships: LeagueScoringChampionshipViewModel[] =
data.championships.map((champ) => this.mapChampionship(champ));
const dropPolicySummary =
data.preset?.dropPolicySummary ??
this.deriveDropPolicyDescriptionFromChampionships(data.championships);
this.viewModel = {
leagueId: data.leagueId,
seasonId: data.seasonId,
gameId: data.gameId,
gameName: data.gameName,
scoringPresetId: data.scoringPresetId ?? 'custom',
scoringPresetName: data.preset?.name ?? 'Custom',
dropPolicySummary,
championships,
};
return this.viewModel;
}
getViewModel(): LeagueScoringConfigViewModel | null {
return this.viewModel;
}
private mapChampionship(championship: ChampionshipConfig): LeagueScoringChampionshipViewModel {
const sessionTypes = championship.sessionTypes.map((s) => s.toString());
const pointsPreview = this.buildPointsPreview(championship.pointsTableBySessionType);
const bonusSummary = this.buildBonusSummary(
championship.bonusRulesBySessionType ?? {},
);
const dropPolicyDescription = this.deriveDropPolicyDescription(
championship.dropScorePolicy,
);
return {
id: championship.id,
name: championship.name,
type: championship.type,
sessionTypes,
pointsPreview,
bonusSummary,
dropPolicyDescription,
};
}
private buildPointsPreview(
tables: Record<string, { getPointsForPosition: (position: number) => number }>,
): Array<{ sessionType: string; position: number; points: number }> {
const preview: Array<{
sessionType: string;
position: number;
points: number;
}> = [];
const maxPositions = 10;
for (const [sessionType, table] of Object.entries(tables)) {
for (let pos = 1; pos <= maxPositions; pos++) {
const points = table.getPointsForPosition(pos);
if (points && points !== 0) {
preview.push({
sessionType,
position: pos,
points,
});
}
}
}
return preview;
}
private buildBonusSummary(
bonusRulesBySessionType: Record<string, BonusRule[]>,
): string[] {
const summaries: string[] = [];
for (const [sessionType, rules] of Object.entries(bonusRulesBySessionType)) {
for (const rule of rules) {
if (rule.type === 'fastestLap') {
const base = `Fastest lap in ${sessionType}`;
if (rule.requiresFinishInTopN) {
summaries.push(
`${base} +${rule.points} points if finishing P${rule.requiresFinishInTopN} or better`,
);
} else {
summaries.push(`${base} +${rule.points} points`);
}
} else {
summaries.push(
`${rule.type} bonus in ${sessionType} worth ${rule.points} points`,
);
}
}
}
return summaries;
}
private deriveDropPolicyDescriptionFromChampionships(
championships: ChampionshipConfig[],
): string {
const first = championships[0];
if (!first) {
return 'All results count';
}
return this.deriveDropPolicyDescription(first.dropScorePolicy);
}
private deriveDropPolicyDescription(policy: {
strategy: string;
count?: number;
dropCount?: number;
}): string {
if (!policy || policy.strategy === 'none') {
return 'All results count';
}
if (policy.strategy === 'bestNResults' && typeof policy.count === 'number') {
return `Best ${policy.count} results count towards the championship`;
}
if (
policy.strategy === 'dropWorstN' &&
typeof policy.dropCount === 'number'
) {
return `Worst ${policy.dropCount} results are dropped from the championship total`;
}
return 'Custom drop score rules apply';
}
}

View File

@@ -0,0 +1,23 @@
import type {
ILeagueScoringPresetsPresenter,
LeagueScoringPresetsResultDTO,
LeagueScoringPresetsViewModel,
} from '@core/racing/application/presenters/ILeagueScoringPresetsPresenter';
export class LeagueScoringPresetsPresenter implements ILeagueScoringPresetsPresenter {
private viewModel: LeagueScoringPresetsViewModel | null = null;
reset(): void {
this.viewModel = null;
}
present(dto: LeagueScoringPresetsResultDTO): void {
this.viewModel = {
presets: dto.presets,
};
}
getViewModel(): LeagueScoringPresetsViewModel | null {
return this.viewModel;
}
}

View File

@@ -1,5 +1,5 @@
import { IGetTotalLeaguesPresenter, GetTotalLeaguesResultDTO, GetTotalLeaguesViewModel } from '@core/racing/application/presenters/IGetTotalLeaguesPresenter';
import { LeagueStatsDto } from '../dto/LeagueDto';
import { LeagueStatsDTO } from '../dtos/LeagueStatsDTO';
export class TotalLeaguesPresenter implements IGetTotalLeaguesPresenter {
private result: LeagueStatsDto | null = null;

View File

@@ -2,7 +2,11 @@ import { Controller, Post, Body, HttpStatus, Res } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { Response } from 'express';
import { MediaService } from './MediaService';
import { RequestAvatarGenerationInput, RequestAvatarGenerationOutput } from './dto/MediaDto'; // Assuming these DTOs are defined
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
@ApiTags('media')
@Controller('media')

View File

@@ -1,5 +1,9 @@
import { Injectable, Inject } from '@nestjs/common';
import { RequestAvatarGenerationInput, RequestAvatarGenerationOutput } from './dto/MediaDto';
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
// Use cases
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';

View File

@@ -1,40 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsBoolean } from 'class-validator';
export class RequestAvatarGenerationInput {
@ApiProperty()
@IsString()
@IsNotEmpty()
userId: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
facePhotoData: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
suitColor: string;
}
export class RequestAvatarGenerationOutput {
@ApiProperty({ type: Boolean })
@IsBoolean()
success: boolean;
@ApiProperty({ required: false })
@IsString()
requestId?: string;
@ApiProperty({ type: [String], required: false })
avatarUrls?: string[];
@ApiProperty({ required: false })
@IsString()
errorMessage?: string;
}
// Assuming FacePhotoData and SuitColor are simple string types for DTO purposes
export type FacePhotoData = string;
export type SuitColor = string;

View File

@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class RequestAvatarGenerationInputDTO {
@ApiProperty()
@IsString()
@IsNotEmpty()
userId: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
facePhotoData: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
suitColor: string;
}

View File

@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsString } from 'class-validator';
export class RequestAvatarGenerationOutputDTO {
@ApiProperty({ type: Boolean })
@IsBoolean()
success: boolean;
@ApiProperty({ required: false })
@IsString()
requestId?: string;
@ApiProperty({ type: [String], required: false })
avatarUrls?: string[];
@ApiProperty({ required: false })
@IsString()
errorMessage?: string;
}

View File

@@ -0,0 +1,2 @@
// Assuming FacePhotoData is a simple string type for DTO purposes
export type FacePhotoData = string;

View File

@@ -0,0 +1,2 @@
// Assuming SuitColor is a simple string type for DTO purposes
export type SuitColor = string;

View File

@@ -0,0 +1,31 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNumber, IsString, IsOptional } from 'class-validator';
import { PaymentType } from './PaymentType';
import { PayerType } from './PayerType';
export class CreatePaymentInputDTO {
@ApiProperty({ enum: PaymentType })
@IsEnum(PaymentType)
type: PaymentType;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty()
@IsString()
payerId: string;
@ApiProperty({ enum: PayerType })
@IsEnum(PayerType)
payerType: PayerType;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonId?: string;
}

View File

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

View File

@@ -0,0 +1,5 @@
export enum MemberPaymentStatus {
PENDING = 'pending',
PAID = 'paid',
OVERDUE = 'overdue',
}

View File

@@ -0,0 +1,5 @@
export enum MembershipFeeType {
SEASON = 'season',
MONTHLY = 'monthly',
PER_RACE = 'per_race',
}

View File

@@ -0,0 +1,4 @@
export enum PayerType {
SPONSOR = 'sponsor',
DRIVER = 'driver',
}

View File

@@ -0,0 +1,57 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, IsEnum, IsOptional, IsDate } from 'class-validator';
import { PaymentType } from './PaymentType';
import { PayerType } from './PayerType';
import { PaymentStatus } from './PaymentStatus';
export class PaymentDTO {
@ApiProperty()
@IsString()
id: string;
@ApiProperty({ enum: PaymentType })
@IsEnum(PaymentType)
type: PaymentType;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty()
@IsNumber()
platformFee: number;
@ApiProperty()
@IsNumber()
netAmount: number;
@ApiProperty()
@IsString()
payerId: string;
@ApiProperty({ enum: PayerType })
@IsEnum(PayerType)
payerType: PayerType;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonId?: string;
@ApiProperty({ enum: PaymentStatus })
@IsEnum(PaymentStatus)
status: PaymentStatus;
@ApiProperty()
@IsDate()
createdAt: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
completedAt?: Date;
}

View File

@@ -0,0 +1,6 @@
export enum PaymentStatus {
PENDING = 'pending',
COMPLETED = 'completed',
FAILED = 'failed',
REFUNDED = 'refunded',
}

View File

@@ -0,0 +1,4 @@
export enum PaymentType {
SPONSORSHIP = 'sponsorship',
MEMBERSHIP_FEE = 'membership_fee',
}

View File

@@ -0,0 +1,5 @@
export enum PrizeType {
CASH = 'cash',
MERCHANDISE = 'merchandise',
OTHER = 'other',
}

View File

@@ -0,0 +1,5 @@
export enum ReferenceType {
SPONSORSHIP = 'sponsorship',
MEMBERSHIP_FEE = 'membership_fee',
PRIZE = 'prize',
}

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