refactor core presenters
This commit is contained in:
112
.eslintrc.json
112
.eslintrc.json
@@ -43,6 +43,118 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"files": ["core/*/application/ports/*/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "TSInterfaceDeclaration[id.name=/^Get.*Port$/]",
|
||||||
|
"message": "Port interface names should not start with 'Get'. Use descriptive names without the 'Get' prefix."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["core/**/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/Blocker$/], TSInterfaceDeclaration[id.name=/Blocker$/]",
|
||||||
|
"message": "Blocker classes/interfaces are not allowed in core. Use Guards in backend."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/Presenter$/], TSInterfaceDeclaration[id.name=/Presenter$/]",
|
||||||
|
"message": "Presenter classes/interfaces are not allowed in core. Presenters belong in API or frontend layers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/Dto$/], TSInterfaceDeclaration[id.name=/Dto$/]",
|
||||||
|
"message": "DTO classes/interfaces are not allowed in core. DTOs belong in API or frontend layers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/ViewModel$/], TSInterfaceDeclaration[id.name=/ViewModel$/]",
|
||||||
|
"message": "ViewModel classes/interfaces are not allowed in core. View Models belong in frontend."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/CommandModel$/], TSInterfaceDeclaration[id.name=/CommandModel$/]",
|
||||||
|
"message": "CommandModel classes/interfaces are not allowed in core. Command Models belong in frontend."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["apps/website/**/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/Guard$/], TSInterfaceDeclaration[id.name=/Guard$/]",
|
||||||
|
"message": "Guard classes/interfaces are not allowed in frontend. Use Blockers in frontend."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["apps/api/**/*.ts", "apps/website/lib/dtos/**/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "TSEnumDeclaration[id.name=/^(?!.*Enum$).+/]",
|
||||||
|
"message": "Transport enums must end with 'Enum'."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["core/*/application/use-cases/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/^(?!.*UseCase$).+/]",
|
||||||
|
"message": "Use Case classes must end with 'UseCase'."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["core/*/application/services/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/^(?!.*Service$).+/]",
|
||||||
|
"message": "Application Service classes must end with 'Service'."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["apps/website/lib/view-models/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/^(?!.*ViewModel$).+/]",
|
||||||
|
"message": "View Model classes must end with 'ViewModel'."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["apps/website/lib/commands/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "TSClassDeclaration[id.name=/^(?!.*CommandModel$).+/]",
|
||||||
|
"message": "Command Model classes must end with 'CommandModel'."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"files": ["**/*.ts", "**/*.tsx"],
|
"files": ["**/*.ts", "**/*.tsx"],
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Injectable, Inject } from '@nestjs/common';
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
||||||
import type { DashboardOverviewViewModel } from '@core/racing/application/presenters/IDashboardOverviewPresenter';
|
import type { DashboardOverviewOutputPort } from '@core/racing/application/ports/output/DashboardOverviewOutputPort';
|
||||||
|
import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO';
|
||||||
|
|
||||||
// Core imports
|
// Core imports
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
import type { Logger } from '@core/shared/application/Logger';
|
||||||
@@ -62,7 +64,7 @@ export class DashboardService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDashboardOverview(driverId: string): Promise<DashboardOverviewViewModel> {
|
async getDashboardOverview(driverId: string): Promise<DashboardOverviewDTO> {
|
||||||
this.logger.debug('[DashboardService] Getting dashboard overview:', { driverId });
|
this.logger.debug('[DashboardService] Getting dashboard overview:', { driverId });
|
||||||
|
|
||||||
const result = await this.dashboardOverviewUseCase.execute({ driverId });
|
const result = await this.dashboardOverviewUseCase.execute({ driverId });
|
||||||
@@ -71,6 +73,6 @@ export class DashboardService {
|
|||||||
throw new Error(result.error?.message || 'Failed to get dashboard overview');
|
throw new Error(result.error?.message || 'Failed to get dashboard overview');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value!;
|
return plainToClass(DashboardOverviewDTO, result.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,224 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsNumber } from 'class-validator';
|
import { IsString, IsNumber, IsOptional, IsBoolean, IsArray, ValidateNested } from 'class-validator';
|
||||||
import { DashboardDriverSummaryDTO } from './DashboardDriverSummaryDTO';
|
import { Type } from 'class-transformer';
|
||||||
import { DashboardRaceSummaryDTO } from './DashboardRaceSummaryDTO';
|
|
||||||
import { DashboardRecentResultDTO } from './DashboardRecentResultDTO';
|
export class DashboardDriverSummaryDTO {
|
||||||
import { DashboardLeagueStandingSummaryDTO } from './DashboardLeagueStandingSummaryDTO';
|
@ApiProperty()
|
||||||
import { DashboardFeedSummaryDTO } from './DashboardFeedSummaryDTO';
|
@IsString()
|
||||||
import { DashboardFriendSummaryDTO } from './DashboardFriendSummaryDTO';
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
country!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
avatarUrl!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
rating?: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
globalRank?: number | null;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
totalRaces!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
wins!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
podiums!: number;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
consistency?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardRaceSummaryDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
leagueId!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
leagueName!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
track!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
car!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
scheduledAt!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
status!: 'scheduled' | 'running' | 'completed' | 'cancelled';
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsBoolean()
|
||||||
|
isMyLeague!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardRecentResultDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
raceId!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
raceName!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
leagueId!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
leagueName!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
finishedAt!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
position!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
incidents!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardLeagueStandingSummaryDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
leagueId!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
leagueName!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
position!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
totalDrivers!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
points!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardFeedItemSummaryDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
type!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
headline!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
body?: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
timestamp!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
ctaLabel?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
ctaHref?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardFeedSummaryDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
notificationCount!: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: [DashboardFeedItemSummaryDTO] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => DashboardFeedItemSummaryDTO)
|
||||||
|
items!: DashboardFeedItemSummaryDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardFriendSummaryDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
country!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
avatarUrl!: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class DashboardOverviewDTO {
|
export class DashboardOverviewDTO {
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
currentDriver!: DashboardDriverSummaryDTO | null;
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DashboardDriverSummaryDTO)
|
||||||
|
currentDriver?: DashboardDriverSummaryDTO | null;
|
||||||
|
|
||||||
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
|
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => DashboardRaceSummaryDTO)
|
||||||
myUpcomingRaces!: DashboardRaceSummaryDTO[];
|
myUpcomingRaces!: DashboardRaceSummaryDTO[];
|
||||||
|
|
||||||
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
|
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => DashboardRaceSummaryDTO)
|
||||||
otherUpcomingRaces!: DashboardRaceSummaryDTO[];
|
otherUpcomingRaces!: DashboardRaceSummaryDTO[];
|
||||||
|
|
||||||
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
|
@ApiProperty({ type: [DashboardRaceSummaryDTO] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => DashboardRaceSummaryDTO)
|
||||||
upcomingRaces!: DashboardRaceSummaryDTO[];
|
upcomingRaces!: DashboardRaceSummaryDTO[];
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@@ -25,17 +226,31 @@ export class DashboardOverviewDTO {
|
|||||||
activeLeaguesCount!: number;
|
activeLeaguesCount!: number;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
nextRace!: DashboardRaceSummaryDTO | null;
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DashboardRaceSummaryDTO)
|
||||||
|
nextRace?: DashboardRaceSummaryDTO | null;
|
||||||
|
|
||||||
@ApiProperty({ type: [DashboardRecentResultDTO] })
|
@ApiProperty({ type: [DashboardRecentResultDTO] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => DashboardRecentResultDTO)
|
||||||
recentResults!: DashboardRecentResultDTO[];
|
recentResults!: DashboardRecentResultDTO[];
|
||||||
|
|
||||||
@ApiProperty({ type: [DashboardLeagueStandingSummaryDTO] })
|
@ApiProperty({ type: [DashboardLeagueStandingSummaryDTO] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => DashboardLeagueStandingSummaryDTO)
|
||||||
leagueStandingsSummaries!: DashboardLeagueStandingSummaryDTO[];
|
leagueStandingsSummaries!: DashboardLeagueStandingSummaryDTO[];
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DashboardFeedSummaryDTO)
|
||||||
feedSummary!: DashboardFeedSummaryDTO;
|
feedSummary!: DashboardFeedSummaryDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: [DashboardFriendSummaryDTO] })
|
@ApiProperty({ type: [DashboardFriendSummaryDTO] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => DashboardFriendSummaryDTO)
|
||||||
friends!: DashboardFriendSummaryDTO[];
|
friends!: DashboardFriendSummaryDTO[];
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTo
|
|||||||
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
|
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
|
||||||
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
||||||
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
|
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
|
||||||
|
import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
|
||||||
|
|
||||||
// Import concrete in-memory implementations
|
// Import concrete in-memory implementations
|
||||||
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
|
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
|
||||||
@@ -44,6 +45,7 @@ export const GET_TOTAL_DRIVERS_USE_CASE_TOKEN = 'GetTotalDriversUseCase';
|
|||||||
export const COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN = 'CompleteDriverOnboardingUseCase';
|
export const COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN = 'CompleteDriverOnboardingUseCase';
|
||||||
export const IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN = 'IsDriverRegisteredForRaceUseCase';
|
export const IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN = 'IsDriverRegisteredForRaceUseCase';
|
||||||
export const UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN = 'UpdateDriverProfileUseCase';
|
export const UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN = 'UpdateDriverProfileUseCase';
|
||||||
|
export const GET_PROFILE_OVERVIEW_USE_CASE_TOKEN = 'GetProfileOverviewUseCase';
|
||||||
|
|
||||||
export const DriverProviders: Provider[] = [
|
export const DriverProviders: Provider[] = [
|
||||||
DriverService, // Provide the service itself
|
DriverService, // Provide the service itself
|
||||||
@@ -113,4 +115,19 @@ export const DriverProviders: Provider[] = [
|
|||||||
useFactory: (driverRepo: IDriverRepository) => new UpdateDriverProfileUseCase(driverRepo),
|
useFactory: (driverRepo: IDriverRepository) => new UpdateDriverProfileUseCase(driverRepo),
|
||||||
inject: [DRIVER_REPOSITORY_TOKEN],
|
inject: [DRIVER_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
|
||||||
|
useFactory: (driverRepo: IDriverRepository, imageService: IImageServicePort, logger: Logger) =>
|
||||||
|
new GetProfileOverviewUseCase(
|
||||||
|
driverRepo,
|
||||||
|
// TODO: Add teamRepository, teamMembershipRepository, socialRepository, etc.
|
||||||
|
null as any, // teamRepository
|
||||||
|
null as any, // teamMembershipRepository
|
||||||
|
null as any, // socialRepository
|
||||||
|
imageService,
|
||||||
|
() => null, // getDriverStats
|
||||||
|
() => [], // getAllDriverRankings
|
||||||
|
),
|
||||||
|
inject: [DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_PORT_TOKEN, LOGGER_TOKEN],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-c
|
|||||||
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||||
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
import type { CompleteDriverOnboardingOutputPort } from '@core/racing/application/ports/output/CompleteDriverOnboardingOutputPort';
|
||||||
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
|
|
||||||
describe('DriverService', () => {
|
describe('DriverService', () => {
|
||||||
let service: DriverService;
|
let service: DriverService;
|
||||||
@@ -15,7 +18,9 @@ describe('DriverService', () => {
|
|||||||
let getTotalDriversUseCase: ReturnType<typeof vi.mocked<GetTotalDriversUseCase>>;
|
let getTotalDriversUseCase: ReturnType<typeof vi.mocked<GetTotalDriversUseCase>>;
|
||||||
let completeDriverOnboardingUseCase: ReturnType<typeof vi.mocked<CompleteDriverOnboardingUseCase>>;
|
let completeDriverOnboardingUseCase: ReturnType<typeof vi.mocked<CompleteDriverOnboardingUseCase>>;
|
||||||
let isDriverRegisteredForRaceUseCase: ReturnType<typeof vi.mocked<IsDriverRegisteredForRaceUseCase>>;
|
let isDriverRegisteredForRaceUseCase: ReturnType<typeof vi.mocked<IsDriverRegisteredForRaceUseCase>>;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
let updateDriverProfileUseCase: ReturnType<typeof vi.mocked<UpdateDriverProfileUseCase>>;
|
let updateDriverProfileUseCase: ReturnType<typeof vi.mocked<UpdateDriverProfileUseCase>>;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
let driverRepository: ReturnType<typeof vi.mocked<IDriverRepository>>;
|
let driverRepository: ReturnType<typeof vi.mocked<IDriverRepository>>;
|
||||||
let logger: ReturnType<typeof vi.mocked<Logger>>;
|
let logger: ReturnType<typeof vi.mocked<Logger>>;
|
||||||
|
|
||||||
@@ -102,17 +107,11 @@ describe('DriverService', () => {
|
|||||||
activeCount: 1,
|
activeCount: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockPresenter = {
|
getDriversLeaderboardUseCase.execute.mockResolvedValue(Result.ok(mockViewModel));
|
||||||
viewModel: mockViewModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
getDriversLeaderboardUseCase.execute.mockImplementation(async (input, presenter) => {
|
|
||||||
Object.assign(presenter, mockPresenter);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getDriversLeaderboard();
|
const result = await service.getDriversLeaderboard();
|
||||||
|
|
||||||
expect(getDriversLeaderboardUseCase.execute).toHaveBeenCalledWith(undefined, expect.any(Object));
|
expect(getDriversLeaderboardUseCase.execute).toHaveBeenCalledWith();
|
||||||
expect(logger.debug).toHaveBeenCalledWith('[DriverService] Fetching drivers leaderboard.');
|
expect(logger.debug).toHaveBeenCalledWith('[DriverService] Fetching drivers leaderboard.');
|
||||||
expect(result).toEqual(mockViewModel);
|
expect(result).toEqual(mockViewModel);
|
||||||
});
|
});
|
||||||
@@ -120,26 +119,20 @@ describe('DriverService', () => {
|
|||||||
|
|
||||||
describe('getTotalDrivers', () => {
|
describe('getTotalDrivers', () => {
|
||||||
it('should call GetTotalDriversUseCase and return the view model', async () => {
|
it('should call GetTotalDriversUseCase and return the view model', async () => {
|
||||||
const mockViewModel = { totalDrivers: 5 };
|
const mockOutput = { totalDrivers: 5 };
|
||||||
|
|
||||||
const mockPresenter = {
|
getTotalDriversUseCase.execute.mockResolvedValue(Result.ok(mockOutput));
|
||||||
viewModel: mockViewModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
getTotalDriversUseCase.execute.mockImplementation(async (input, presenter) => {
|
|
||||||
Object.assign(presenter, mockPresenter);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getTotalDrivers();
|
const result = await service.getTotalDrivers();
|
||||||
|
|
||||||
expect(getTotalDriversUseCase.execute).toHaveBeenCalledWith(undefined, expect.any(Object));
|
expect(getTotalDriversUseCase.execute).toHaveBeenCalledWith();
|
||||||
expect(logger.debug).toHaveBeenCalledWith('[DriverService] Fetching total drivers count.');
|
expect(logger.debug).toHaveBeenCalledWith('[DriverService] Fetching total drivers count.');
|
||||||
expect(result).toEqual(mockViewModel);
|
expect(result).toEqual(mockOutput);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('completeOnboarding', () => {
|
describe('completeOnboarding', () => {
|
||||||
it('should call CompleteDriverOnboardingUseCase and return the view model', async () => {
|
it('should call CompleteDriverOnboardingUseCase and return success', async () => {
|
||||||
const input = {
|
const input = {
|
||||||
firstName: 'John',
|
firstName: 'John',
|
||||||
lastName: 'Doe',
|
lastName: 'Doe',
|
||||||
@@ -149,30 +142,43 @@ describe('DriverService', () => {
|
|||||||
bio: 'Racing enthusiast',
|
bio: 'Racing enthusiast',
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockViewModel = {
|
completeDriverOnboardingUseCase.execute.mockResolvedValue(
|
||||||
success: true,
|
Result.ok<CompleteDriverOnboardingOutputPort, ApplicationErrorCode<string>>({ driverId: 'user-123' })
|
||||||
driverId: 'user-123',
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const mockPresenter = {
|
|
||||||
viewModel: mockViewModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
completeDriverOnboardingUseCase.execute.mockImplementation(async (input, presenter) => {
|
|
||||||
Object.assign(presenter, mockPresenter);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.completeOnboarding('user-123', input);
|
const result = await service.completeOnboarding('user-123', input);
|
||||||
|
|
||||||
expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith(
|
expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({
|
||||||
{
|
userId: 'user-123',
|
||||||
userId: 'user-123',
|
...input,
|
||||||
...input,
|
});
|
||||||
},
|
|
||||||
expect.any(Object)
|
|
||||||
);
|
|
||||||
expect(logger.debug).toHaveBeenCalledWith('Completing onboarding for user:', 'user-123');
|
expect(logger.debug).toHaveBeenCalledWith('Completing onboarding for user:', 'user-123');
|
||||||
expect(result).toEqual(mockViewModel);
|
expect(result).toEqual({
|
||||||
|
success: true,
|
||||||
|
driverId: 'user-123',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle error from use case', async () => {
|
||||||
|
const input = {
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
displayName: 'John Doe',
|
||||||
|
country: 'US',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
bio: 'Racing enthusiast',
|
||||||
|
};
|
||||||
|
|
||||||
|
completeDriverOnboardingUseCase.execute.mockResolvedValue(
|
||||||
|
Result.err<CompleteDriverOnboardingOutputPort, ApplicationErrorCode<string>>({ code: 'DRIVER_ALREADY_EXISTS' })
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await service.completeOnboarding('user-123', input);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
success: false,
|
||||||
|
errorMessage: 'DRIVER_ALREADY_EXISTS',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -183,25 +189,19 @@ describe('DriverService', () => {
|
|||||||
raceId: 'race-1',
|
raceId: 'race-1',
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockViewModel = {
|
const mockOutput = {
|
||||||
isRegistered: true,
|
isRegistered: true,
|
||||||
raceId: 'race-1',
|
raceId: 'race-1',
|
||||||
driverId: 'driver-1',
|
driverId: 'driver-1',
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockPresenter = {
|
isDriverRegisteredForRaceUseCase.execute.mockResolvedValue(Result.ok(mockOutput));
|
||||||
viewModel: mockViewModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
isDriverRegisteredForRaceUseCase.execute.mockImplementation(async (params, presenter) => {
|
|
||||||
Object.assign(presenter, mockPresenter);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getDriverRegistrationStatus(query);
|
const result = await service.getDriverRegistrationStatus(query);
|
||||||
|
|
||||||
expect(isDriverRegisteredForRaceUseCase.execute).toHaveBeenCalledWith(query, expect.any(Object));
|
expect(isDriverRegisteredForRaceUseCase.execute).toHaveBeenCalledWith(query);
|
||||||
expect(logger.debug).toHaveBeenCalledWith('Checking driver registration status:', query);
|
expect(logger.debug).toHaveBeenCalledWith('Checking driver registration status:', query);
|
||||||
expect(result).toEqual(mockViewModel);
|
expect(result).toEqual(mockOutput);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -13,15 +13,14 @@ import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases
|
|||||||
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
|
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
|
||||||
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
|
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
|
||||||
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
|
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
|
||||||
|
import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
|
||||||
|
|
||||||
// Presenters
|
// Presenters
|
||||||
import { DriversLeaderboardPresenter } from './presenters/DriversLeaderboardPresenter';
|
|
||||||
import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
|
import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
|
||||||
import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter';
|
|
||||||
import { DriverRegistrationStatusPresenter } from './presenters/DriverRegistrationStatusPresenter';
|
|
||||||
|
|
||||||
// Tokens
|
// 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, UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN, LOGGER_TOKEN, DRIVER_REPOSITORY_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, GET_PROFILE_OVERVIEW_USE_CASE_TOKEN, LOGGER_TOKEN, DRIVER_REPOSITORY_TOKEN } from './DriverProviders';
|
||||||
|
import type { ProfileOverviewOutputPort } from '@core/racing/application/ports/output/ProfileOverviewOutputPort';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||||
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
||||||
@@ -34,6 +33,7 @@ export class DriverService {
|
|||||||
@Inject(COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN) private readonly completeDriverOnboardingUseCase: CompleteDriverOnboardingUseCase,
|
@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(IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN) private readonly isDriverRegisteredForRaceUseCase: IsDriverRegisteredForRaceUseCase,
|
||||||
@Inject(UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN) private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase,
|
@Inject(UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN) private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase,
|
||||||
|
@Inject(GET_PROFILE_OVERVIEW_USE_CASE_TOKEN) private readonly getProfileOverviewUseCase: GetProfileOverviewUseCase,
|
||||||
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
|
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
|
||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
@@ -41,24 +41,31 @@ export class DriverService {
|
|||||||
async getDriversLeaderboard(): Promise<DriversLeaderboardDTO> {
|
async getDriversLeaderboard(): Promise<DriversLeaderboardDTO> {
|
||||||
this.logger.debug('[DriverService] Fetching drivers leaderboard.');
|
this.logger.debug('[DriverService] Fetching drivers leaderboard.');
|
||||||
|
|
||||||
const presenter = new DriversLeaderboardPresenter();
|
const result = await this.getDriversLeaderboardUseCase.execute();
|
||||||
await this.getDriversLeaderboardUseCase.execute(undefined, presenter);
|
if (result.isOk()) {
|
||||||
return presenter.viewModel;
|
return result.value as DriversLeaderboardDTO;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to fetch drivers leaderboard: ${result.error.details.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTotalDrivers(): Promise<DriverStatsDTO> {
|
async getTotalDrivers(): Promise<DriverStatsDTO> {
|
||||||
this.logger.debug('[DriverService] Fetching total drivers count.');
|
this.logger.debug('[DriverService] Fetching total drivers count.');
|
||||||
|
|
||||||
|
const result = await this.getTotalDriversUseCase.execute();
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
|
||||||
const presenter = new DriverStatsPresenter();
|
const presenter = new DriverStatsPresenter();
|
||||||
await this.getTotalDriversUseCase.execute(undefined, presenter);
|
presenter.present(result.unwrap());
|
||||||
return presenter.viewModel;
|
return presenter.viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async completeOnboarding(userId: string, input: CompleteOnboardingInputDTO): Promise<CompleteOnboardingOutputDTO> {
|
async completeOnboarding(userId: string, input: CompleteOnboardingInputDTO): Promise<CompleteOnboardingOutputDTO> {
|
||||||
this.logger.debug('Completing onboarding for user:', userId);
|
this.logger.debug('Completing onboarding for user:', userId);
|
||||||
|
|
||||||
const presenter = new CompleteOnboardingPresenter();
|
const result = await this.completeDriverOnboardingUseCase.execute({
|
||||||
await this.completeDriverOnboardingUseCase.execute({
|
|
||||||
userId,
|
userId,
|
||||||
firstName: input.firstName,
|
firstName: input.firstName,
|
||||||
lastName: input.lastName,
|
lastName: input.lastName,
|
||||||
@@ -66,16 +73,31 @@ export class DriverService {
|
|||||||
country: input.country,
|
country: input.country,
|
||||||
timezone: input.timezone,
|
timezone: input.timezone,
|
||||||
bio: input.bio,
|
bio: input.bio,
|
||||||
}, presenter);
|
});
|
||||||
return presenter.viewModel;
|
|
||||||
|
if (result.isOk()) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
driverId: result.value.driverId,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
errorMessage: result.error.code,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDriverRegistrationStatus(query: GetDriverRegistrationStatusQueryDTO): Promise<DriverRegistrationStatusDTO> {
|
async getDriverRegistrationStatus(query: GetDriverRegistrationStatusQueryDTO): Promise<DriverRegistrationStatusDTO> {
|
||||||
this.logger.debug('Checking driver registration status:', query);
|
this.logger.debug('Checking driver registration status:', query);
|
||||||
|
|
||||||
const presenter = new DriverRegistrationStatusPresenter();
|
const result = await this.isDriverRegisteredForRaceUseCase.execute({ raceId: query.raceId, driverId: query.driverId });
|
||||||
await this.isDriverRegisteredForRaceUseCase.execute({ raceId: query.raceId, driverId: query.driverId }, presenter);
|
if (result.isOk()) {
|
||||||
return presenter.viewModel;
|
return result.value;
|
||||||
|
} else {
|
||||||
|
// For now, throw error or handle appropriately. Since it's a query, perhaps return default or throw.
|
||||||
|
throw new Error(`Failed to check registration status: ${result.error.code}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
|
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
|
||||||
@@ -129,18 +151,42 @@ export class DriverService {
|
|||||||
async getDriverProfile(driverId: string): Promise<GetDriverProfileOutputDTO> {
|
async getDriverProfile(driverId: string): Promise<GetDriverProfileOutputDTO> {
|
||||||
this.logger.debug(`[DriverService] Fetching driver profile for driverId: ${driverId}`);
|
this.logger.debug(`[DriverService] Fetching driver profile for driverId: ${driverId}`);
|
||||||
|
|
||||||
// TODO: Implement proper driver profile fetching with all the detailed data
|
const result = await this.getProfileOverviewUseCase.execute({ driverId });
|
||||||
// For now, return a placeholder structure
|
if (result.isErr()) {
|
||||||
|
throw new Error(`Failed to fetch driver profile: ${result.error.code}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputPort = result.value;
|
||||||
|
return this.mapProfileOverviewToDTO(outputPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapProfileOverviewToDTO(outputPort: ProfileOverviewOutputPort): GetDriverProfileOutputDTO {
|
||||||
return {
|
return {
|
||||||
currentDriver: null,
|
currentDriver: outputPort.driver ? {
|
||||||
stats: null,
|
id: outputPort.driver.id,
|
||||||
finishDistribution: null,
|
name: outputPort.driver.name,
|
||||||
teamMemberships: [],
|
country: outputPort.driver.country,
|
||||||
socialSummary: {
|
avatarUrl: outputPort.driver.avatarUrl,
|
||||||
friendsCount: 0,
|
iracingId: outputPort.driver.iracingId,
|
||||||
friends: [],
|
joinedAt: outputPort.driver.joinedAt.toISOString(),
|
||||||
},
|
rating: outputPort.driver.rating,
|
||||||
extendedProfile: null,
|
globalRank: outputPort.driver.globalRank,
|
||||||
|
consistency: outputPort.driver.consistency,
|
||||||
|
bio: outputPort.driver.bio,
|
||||||
|
totalDrivers: outputPort.driver.totalDrivers,
|
||||||
|
} : null,
|
||||||
|
stats: outputPort.stats,
|
||||||
|
finishDistribution: outputPort.finishDistribution,
|
||||||
|
teamMemberships: outputPort.teamMemberships.map(membership => ({
|
||||||
|
teamId: membership.teamId,
|
||||||
|
teamName: membership.teamName,
|
||||||
|
teamTag: membership.teamTag,
|
||||||
|
role: membership.role,
|
||||||
|
joinedAt: membership.joinedAt.toISOString(),
|
||||||
|
isCurrent: membership.isCurrent,
|
||||||
|
})),
|
||||||
|
socialSummary: outputPort.socialSummary,
|
||||||
|
extendedProfile: outputPort.extendedProfile,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import { CompleteOnboardingPresenter } from './CompleteOnboardingPresenter';
|
|
||||||
import type { CompleteDriverOnboardingResultDTO } from '../../../../../core/racing/application/presenters/ICompleteDriverOnboardingPresenter';
|
|
||||||
|
|
||||||
describe('CompleteOnboardingPresenter', () => {
|
|
||||||
let presenter: CompleteOnboardingPresenter;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
presenter = new CompleteOnboardingPresenter();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('present', () => {
|
|
||||||
it('should map successful core DTO to API view model', () => {
|
|
||||||
const dto: CompleteDriverOnboardingResultDTO = {
|
|
||||||
success: true,
|
|
||||||
driverId: 'driver-123',
|
|
||||||
};
|
|
||||||
|
|
||||||
presenter.present(dto);
|
|
||||||
|
|
||||||
const result = presenter.viewModel;
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
success: true,
|
|
||||||
driverId: 'driver-123',
|
|
||||||
errorMessage: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should map failed core DTO to API view model', () => {
|
|
||||||
const dto: CompleteDriverOnboardingResultDTO = {
|
|
||||||
success: false,
|
|
||||||
errorMessage: 'Driver already exists',
|
|
||||||
};
|
|
||||||
|
|
||||||
presenter.present(dto);
|
|
||||||
|
|
||||||
const result = presenter.viewModel;
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
success: false,
|
|
||||||
driverId: undefined,
|
|
||||||
errorMessage: 'Driver already exists',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reset', () => {
|
|
||||||
it('should reset the result', () => {
|
|
||||||
const dto: CompleteDriverOnboardingResultDTO = {
|
|
||||||
success: true,
|
|
||||||
driverId: 'driver-123',
|
|
||||||
};
|
|
||||||
|
|
||||||
presenter.present(dto);
|
|
||||||
expect(presenter.viewModel).toBeDefined();
|
|
||||||
|
|
||||||
presenter.reset();
|
|
||||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { CompleteOnboardingOutputDTO } from '../dtos/CompleteOnboardingOutputDTO';
|
|
||||||
import type { ICompleteDriverOnboardingPresenter, CompleteDriverOnboardingResultDTO } from '../../../../../core/racing/application/presenters/ICompleteDriverOnboardingPresenter';
|
|
||||||
|
|
||||||
export class CompleteOnboardingPresenter implements ICompleteDriverOnboardingPresenter {
|
|
||||||
private result: CompleteOnboardingOutputDTO | null = null;
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: CompleteDriverOnboardingResultDTO) {
|
|
||||||
this.result = {
|
|
||||||
success: dto.success,
|
|
||||||
driverId: dto.driverId,
|
|
||||||
errorMessage: dto.errorMessage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): CompleteOnboardingOutputDTO {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { DriverRegistrationStatusDTO } from '../dtos/DriverRegistrationStatusDTO';
|
|
||||||
import type { IDriverRegistrationStatusPresenter } from '../../../../../core/racing/application/presenters/IDriverRegistrationStatusPresenter';
|
|
||||||
|
|
||||||
export class DriverRegistrationStatusPresenter implements IDriverRegistrationStatusPresenter {
|
|
||||||
private result: DriverRegistrationStatusDTO | null = null;
|
|
||||||
|
|
||||||
present(isRegistered: boolean, raceId: string, driverId: string) {
|
|
||||||
this.result = {
|
|
||||||
isRegistered,
|
|
||||||
raceId,
|
|
||||||
driverId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): DriverRegistrationStatusDTO {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For consistency with other presenters
|
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): DriverRegistrationStatusDTO {
|
|
||||||
return this.getViewModel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { DriverStatsDTO } from '../dtos/DriverStatsDTO';
|
import { DriverStatsDTO } from '../dtos/DriverStatsDTO';
|
||||||
import type { ITotalDriversPresenter, TotalDriversResultDTO } from '../../../../../core/racing/application/presenters/ITotalDriversPresenter';
|
import type { TotalDriversOutputPort } from '../../../../../core/racing/application/ports/output/TotalDriversOutputPort';
|
||||||
|
|
||||||
export class DriverStatsPresenter implements ITotalDriversPresenter {
|
export class DriverStatsPresenter {
|
||||||
private result: DriverStatsDTO | null = null;
|
private result: DriverStatsDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: TotalDriversResultDTO) {
|
present(output: TotalDriversOutputPort) {
|
||||||
this.result = {
|
this.result = {
|
||||||
totalDrivers: dto.totalDrivers,
|
totalDrivers: output.totalDrivers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/In
|
|||||||
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
|
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
|
||||||
import { InMemoryStandingRepository } from '@adapters/racing/persistence/inmemory/InMemoryStandingRepository';
|
import { InMemoryStandingRepository } from '@adapters/racing/persistence/inmemory/InMemoryStandingRepository';
|
||||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||||
|
import { listLeagueScoringPresets } from '@adapters/bootstrap/LeagueScoringPresets';
|
||||||
|
|
||||||
// Import use cases
|
// Import use cases
|
||||||
import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase';
|
import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase';
|
||||||
@@ -131,4 +132,8 @@ export const LeagueProviders: Provider[] = [
|
|||||||
GetLeagueScheduleUseCase,
|
GetLeagueScheduleUseCase,
|
||||||
GetLeagueStatsUseCase,
|
GetLeagueStatsUseCase,
|
||||||
GetLeagueAdminPermissionsUseCase,
|
GetLeagueAdminPermissionsUseCase,
|
||||||
|
{
|
||||||
|
provide: ListLeagueScoringPresetsUseCase,
|
||||||
|
useFactory: () => new ListLeagueScoringPresetsUseCase(listLeagueScoringPresets()),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-case
|
|||||||
import { GetLeagueOwnerSummaryUseCase } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase';
|
import { GetLeagueOwnerSummaryUseCase } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase';
|
||||||
import { GetLeagueProtestsUseCase } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase';
|
import { GetLeagueProtestsUseCase } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase';
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
import type { Logger } from '@core/shared/application/Logger';
|
||||||
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
|
||||||
describe('LeagueService', () => {
|
describe('LeagueService', () => {
|
||||||
let service: LeagueService;
|
let service: LeagueService;
|
||||||
@@ -60,9 +61,7 @@ describe('LeagueService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should get total leagues', async () => {
|
it('should get total leagues', async () => {
|
||||||
mockGetTotalLeaguesUseCase.execute.mockImplementation(async (params, presenter) => {
|
mockGetTotalLeaguesUseCase.execute.mockResolvedValue(Result.ok({ totalLeagues: 5 }));
|
||||||
presenter.present({ totalLeagues: 5 });
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getTotalLeagues();
|
const result = await service.getTotalLeagues();
|
||||||
|
|
||||||
|
|||||||
@@ -10,30 +10,27 @@ import { RejectJoinRequestInputDTO } from './dtos/RejectJoinRequestInputDTO';
|
|||||||
import { RemoveLeagueMemberInputDTO } from './dtos/RemoveLeagueMemberInputDTO';
|
import { RemoveLeagueMemberInputDTO } from './dtos/RemoveLeagueMemberInputDTO';
|
||||||
import { UpdateLeagueMemberRoleInputDTO } from './dtos/UpdateLeagueMemberRoleInputDTO';
|
import { UpdateLeagueMemberRoleInputDTO } from './dtos/UpdateLeagueMemberRoleInputDTO';
|
||||||
import { LeagueAdminDTO } from './dtos/LeagueAdminDTO';
|
import { LeagueAdminDTO } from './dtos/LeagueAdminDTO';
|
||||||
|
import { LeagueAdminPermissionsDTO } from './dtos/LeagueAdminPermissionsDTO';
|
||||||
import { LeagueAdminProtestsDTO } from './dtos/LeagueAdminProtestsDTO';
|
import { LeagueAdminProtestsDTO } from './dtos/LeagueAdminProtestsDTO';
|
||||||
import { LeagueMembershipsDTO } from './dtos/LeagueMembershipsDTO';
|
import { LeagueMembershipsDTO } from './dtos/LeagueMembershipsDTO';
|
||||||
import { LeagueSeasonSummaryDTO } from './dtos/LeagueSeasonSummaryDTO';
|
import { LeagueSeasonSummaryDTO } from './dtos/LeagueSeasonSummaryDTO';
|
||||||
import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO';
|
import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO';
|
||||||
import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO';
|
import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO';
|
||||||
|
import { RejectJoinRequestOutputDTO } from './dtos/RejectJoinRequestOutputDTO';
|
||||||
|
import { RemoveLeagueMemberOutputDTO } from './dtos/RemoveLeagueMemberOutputDTO';
|
||||||
|
import { TransferLeagueOwnershipOutputDTO } from './dtos/TransferLeagueOwnershipOutputDTO';
|
||||||
|
import { UpdateLeagueMemberRoleOutputDTO } from './dtos/UpdateLeagueMemberRoleOutputDTO';
|
||||||
|
|
||||||
// Core imports for view models
|
// Core imports for view models
|
||||||
import type { LeagueScoringConfigViewModel } from '@core/racing/application/presenters/ILeagueScoringConfigPresenter';
|
import type { LeagueScoringConfigViewModel } from './presenters/LeagueScoringConfigPresenter';
|
||||||
import type { LeagueScoringPresetsViewModel } from '@core/racing/application/presenters/ILeagueScoringPresetsPresenter';
|
import type { LeagueScoringPresetsViewModel } from './presenters/LeagueScoringPresetsPresenter';
|
||||||
import type { AllLeaguesWithCapacityViewModel } from '@core/racing/application/presenters/IAllLeaguesWithCapacityPresenter';
|
import type { AllLeaguesWithCapacityDTO as AllLeaguesWithCapacityViewModel } from '../dtos/AllLeaguesWithCapacityDTO';
|
||||||
import type { GetTotalLeaguesViewModel } from '@core/racing/application/presenters/IGetTotalLeaguesPresenter';
|
|
||||||
import type { GetLeagueJoinRequestsViewModel } from '@core/racing/application/presenters/IGetLeagueJoinRequestsPresenter';
|
import type { GetLeagueJoinRequestsViewModel } from '@core/racing/application/presenters/IGetLeagueJoinRequestsPresenter';
|
||||||
import type { ApproveLeagueJoinRequestViewModel } from '@core/racing/application/presenters/IApproveLeagueJoinRequestPresenter';
|
import { TotalLeaguesDTO } from './dtos/TotalLeaguesDTO';
|
||||||
import type { RejectLeagueJoinRequestViewModel } from '@core/racing/application/presenters/IRejectLeagueJoinRequestPresenter';
|
import type { ApproveLeagueJoinRequestDTO } from './dtos/ApproveLeagueJoinRequestDTO';
|
||||||
|
import type { JoinLeagueOutputDTO } from './dtos/JoinLeagueOutputDTO';
|
||||||
import type { GetLeagueAdminPermissionsViewModel } from '@core/racing/application/presenters/IGetLeagueAdminPermissionsPresenter';
|
import type { GetLeagueAdminPermissionsViewModel } from '@core/racing/application/presenters/IGetLeagueAdminPermissionsPresenter';
|
||||||
import type { RemoveLeagueMemberViewModel } from '@core/racing/application/presenters/IRemoveLeagueMemberPresenter';
|
import type { CreateLeagueViewModel } from './dtos/CreateLeagueDTO';
|
||||||
import type { UpdateLeagueMemberRoleViewModel } from '@core/racing/application/presenters/IUpdateLeagueMemberRolePresenter';
|
|
||||||
import type { GetLeagueOwnerSummaryViewModel } from '@core/racing/application/presenters/IGetLeagueOwnerSummaryPresenter';
|
|
||||||
import type { LeagueStandingsViewModel } from '@core/racing/application/presenters/ILeagueStandingsPresenter';
|
|
||||||
import type { LeagueStatsViewModel } from '@core/racing/application/presenters/ILeagueStatsPresenter';
|
|
||||||
import type { LeagueConfigFormViewModel } from '@core/racing/application/presenters/ILeagueFullConfigPresenter';
|
|
||||||
import type { CreateLeagueViewModel } from '@core/racing/application/presenters/ICreateLeaguePresenter';
|
|
||||||
import type { JoinLeagueViewModel } from '@core/racing/application/presenters/IJoinLeaguePresenter';
|
|
||||||
import type { TransferLeagueOwnershipViewModel } from '@core/racing/application/presenters/ITransferLeagueOwnershipPresenter';
|
|
||||||
|
|
||||||
// Core imports
|
// Core imports
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
import type { Logger } from '@core/shared/application/Logger';
|
||||||
@@ -67,22 +64,22 @@ import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapa
|
|||||||
import { TotalLeaguesPresenter } from './presenters/TotalLeaguesPresenter';
|
import { TotalLeaguesPresenter } from './presenters/TotalLeaguesPresenter';
|
||||||
import { LeagueScoringConfigPresenter } from './presenters/LeagueScoringConfigPresenter';
|
import { LeagueScoringConfigPresenter } from './presenters/LeagueScoringConfigPresenter';
|
||||||
import { LeagueScoringPresetsPresenter } from './presenters/LeagueScoringPresetsPresenter';
|
import { LeagueScoringPresetsPresenter } from './presenters/LeagueScoringPresetsPresenter';
|
||||||
import { ApproveLeagueJoinRequestPresenter } from './presenters/ApproveLeagueJoinRequestPresenter';
|
import { mapApproveLeagueJoinRequestPortToDTO } from './presenters/ApproveLeagueJoinRequestPresenter';
|
||||||
import { GetLeagueAdminPermissionsPresenter } from './presenters/GetLeagueAdminPermissionsPresenter';
|
import { GetLeagueAdminPermissionsPresenter } from './presenters/GetLeagueAdminPermissionsPresenter';
|
||||||
import { GetLeagueOwnerSummaryPresenter } from './presenters/GetLeagueOwnerSummaryPresenter';
|
import { mapGetLeagueOwnerSummaryOutputPortToDTO } from './presenters/GetLeagueOwnerSummaryPresenter';
|
||||||
import { LeagueJoinRequestsPresenter } from './presenters/LeagueJoinRequestsPresenter';
|
import { LeagueJoinRequestsPresenter } from './presenters/LeagueJoinRequestsPresenter';
|
||||||
import { LeagueSchedulePresenter } from './presenters/LeagueSchedulePresenter';
|
import { mapGetLeagueScheduleOutputPortToDTO } from './presenters/LeagueSchedulePresenter';
|
||||||
import { LeagueStatsPresenter } from './presenters/LeagueStatsPresenter';
|
import { LeagueStatsPresenter } from './presenters/LeagueStatsPresenter';
|
||||||
import { RejectLeagueJoinRequestPresenter } from './presenters/RejectLeagueJoinRequestPresenter';
|
import { mapRejectLeagueJoinRequestOutputPortToDTO } from './presenters/RejectLeagueJoinRequestPresenter';
|
||||||
import { RemoveLeagueMemberPresenter } from './presenters/RemoveLeagueMemberPresenter';
|
import { mapRemoveLeagueMemberOutputPortToDTO } from './presenters/RemoveLeagueMemberPresenter';
|
||||||
import { UpdateLeagueMemberRolePresenter } from './presenters/UpdateLeagueMemberRolePresenter';
|
import { mapUpdateLeagueMemberRoleOutputPortToDTO } from './presenters/UpdateLeagueMemberRolePresenter';
|
||||||
import { CreateLeaguePresenter } from './presenters/CreateLeaguePresenter';
|
import { CreateLeaguePresenter } from './presenters/CreateLeaguePresenter';
|
||||||
import { JoinLeaguePresenter } from './presenters/JoinLeaguePresenter';
|
import { mapJoinLeagueOutputPortToDTO } from './presenters/JoinLeaguePresenter';
|
||||||
import { TransferLeagueOwnershipPresenter } from './presenters/TransferLeagueOwnershipPresenter';
|
import { mapTransferLeagueOwnershipOutputPortToDTO } from './presenters/TransferLeagueOwnershipPresenter';
|
||||||
import { GetLeagueProtestsPresenter } from './presenters/GetLeagueProtestsPresenter';
|
import { mapGetLeagueProtestsOutputPortToDTO } from './presenters/GetLeagueProtestsPresenter';
|
||||||
import { GetLeagueSeasonsPresenter } from './presenters/GetLeagueSeasonsPresenter';
|
import { mapGetLeagueSeasonsOutputPortToDTO } from './presenters/GetLeagueSeasonsPresenter';
|
||||||
import { GetLeagueMembershipsPresenter } from './presenters/GetLeagueMembershipsPresenter';
|
import { LeagueConfigPresenter } from './presenters/LeagueConfigPresenter';
|
||||||
|
import { LeagueStandingsPresenter } from './presenters/LeagueStandingsPresenter';
|
||||||
// Tokens
|
// Tokens
|
||||||
import { LOGGER_TOKEN } from './LeagueProviders';
|
import { LOGGER_TOKEN } from './LeagueProviders';
|
||||||
|
|
||||||
@@ -126,7 +123,7 @@ export class LeagueService {
|
|||||||
return presenter.getViewModel()!;
|
return presenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTotalLeagues(): Promise<GetTotalLeaguesViewModel> {
|
async getTotalLeagues(): Promise<TotalLeaguesDTO> {
|
||||||
this.logger.debug('[LeagueService] Fetching total leagues count.');
|
this.logger.debug('[LeagueService] Fetching total leagues count.');
|
||||||
const result = await this.getTotalLeaguesUseCase.execute();
|
const result = await this.getTotalLeaguesUseCase.execute();
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
@@ -148,26 +145,22 @@ export class LeagueService {
|
|||||||
return presenter.getViewModel();
|
return presenter.getViewModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
async approveLeagueJoinRequest(input: ApproveJoinRequestInputDTO): Promise<ApproveLeagueJoinRequestViewModel> {
|
async approveLeagueJoinRequest(input: ApproveJoinRequestInputDTO): Promise<ApproveLeagueJoinRequestDTO> {
|
||||||
this.logger.debug('Approving join request:', input);
|
this.logger.debug('Approving join request:', input);
|
||||||
const result = await this.approveLeagueJoinRequestUseCase.execute({ leagueId: input.leagueId, requestId: input.requestId });
|
const result = await this.approveLeagueJoinRequestUseCase.execute({ leagueId: input.leagueId, requestId: input.requestId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
const presenter = new ApproveLeagueJoinRequestPresenter();
|
return mapApproveLeagueJoinRequestPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rejectLeagueJoinRequest(input: RejectJoinRequestInputDTO): Promise<RejectLeagueJoinRequestViewModel> {
|
async rejectLeagueJoinRequest(input: RejectJoinRequestInputDTO): Promise<RejectJoinRequestOutputDTO> {
|
||||||
this.logger.debug('Rejecting join request:', input);
|
this.logger.debug('Rejecting join request:', input);
|
||||||
const result = await this.rejectLeagueJoinRequestUseCase.execute({ requestId: input.requestId });
|
const result = await this.rejectLeagueJoinRequestUseCase.execute({ requestId: input.requestId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
const presenter = new RejectLeagueJoinRequestPresenter();
|
return mapRejectLeagueJoinRequestOutputPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueAdminPermissions(query: GetLeagueAdminPermissionsInputDTO): Promise<GetLeagueAdminPermissionsViewModel> {
|
async getLeagueAdminPermissions(query: GetLeagueAdminPermissionsInputDTO): Promise<GetLeagueAdminPermissionsViewModel> {
|
||||||
@@ -179,40 +172,34 @@ export class LeagueService {
|
|||||||
return presenter.getViewModel()!;
|
return presenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeLeagueMember(input: RemoveLeagueMemberInputDTO): Promise<RemoveLeagueMemberViewModel> {
|
async removeLeagueMember(input: RemoveLeagueMemberInputDTO): Promise<RemoveLeagueMemberOutputDTO> {
|
||||||
this.logger.debug('Removing league member', { leagueId: input.leagueId, targetDriverId: input.targetDriverId });
|
this.logger.debug('Removing league member', { leagueId: input.leagueId, targetDriverId: input.targetDriverId });
|
||||||
const result = await this.removeLeagueMemberUseCase.execute({ leagueId: input.leagueId, targetDriverId: input.targetDriverId });
|
const result = await this.removeLeagueMemberUseCase.execute({ leagueId: input.leagueId, targetDriverId: input.targetDriverId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
const presenter = new RemoveLeagueMemberPresenter();
|
return mapRemoveLeagueMemberOutputPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateLeagueMemberRole(input: UpdateLeagueMemberRoleInputDTO): Promise<UpdateLeagueMemberRoleViewModel> {
|
async updateLeagueMemberRole(input: UpdateLeagueMemberRoleInputDTO): Promise<UpdateLeagueMemberRoleOutputDTO> {
|
||||||
this.logger.debug('Updating league member role', { leagueId: input.leagueId, targetDriverId: input.targetDriverId, newRole: input.newRole });
|
this.logger.debug('Updating league member role', { leagueId: input.leagueId, targetDriverId: input.targetDriverId, newRole: input.newRole });
|
||||||
const result = await this.updateLeagueMemberRoleUseCase.execute({ leagueId: input.leagueId, targetDriverId: input.targetDriverId, newRole: input.newRole });
|
const result = await this.updateLeagueMemberRoleUseCase.execute({ leagueId: input.leagueId, targetDriverId: input.targetDriverId, newRole: input.newRole });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
const presenter = new UpdateLeagueMemberRolePresenter();
|
return mapUpdateLeagueMemberRoleOutputPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueOwnerSummary(query: GetLeagueOwnerSummaryQueryDTO): Promise<GetLeagueOwnerSummaryViewModel> {
|
async getLeagueOwnerSummary(query: GetLeagueOwnerSummaryQueryDTO): Promise<LeagueOwnerSummaryDTO | null> {
|
||||||
this.logger.debug('Getting league owner summary:', query);
|
this.logger.debug('Getting league owner summary:', query);
|
||||||
const result = await this.getLeagueOwnerSummaryUseCase.execute({ ownerId: query.ownerId });
|
const result = await this.getLeagueOwnerSummaryUseCase.execute({ ownerId: query.ownerId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
const presenter = new GetLeagueOwnerSummaryPresenter();
|
return mapGetLeagueOwnerSummaryOutputPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueFullConfig(query: GetLeagueAdminConfigQueryDTO): Promise<LeagueConfigFormViewModel | null> {
|
async getLeagueFullConfig(query: GetLeagueAdminConfigQueryDTO): Promise<LeagueConfigFormModelDTO | null> {
|
||||||
this.logger.debug('Getting league full config', { query });
|
this.logger.debug('Getting league full config', { query });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -221,7 +208,9 @@ export class LeagueService {
|
|||||||
this.logger.error('Error getting league full config', new Error(result.unwrapErr().code));
|
this.logger.error('Error getting league full config', new Error(result.unwrapErr().code));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return result.unwrap();
|
const presenter = new LeagueConfigPresenter();
|
||||||
|
presenter.present(result.unwrap());
|
||||||
|
return presenter.getViewModel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Error getting league full config', error instanceof Error ? error : new Error(String(error)));
|
this.logger.error('Error getting league full config', error instanceof Error ? error : new Error(String(error)));
|
||||||
return null;
|
return null;
|
||||||
@@ -234,9 +223,7 @@ export class LeagueService {
|
|||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
const presenter = new GetLeagueProtestsPresenter();
|
return mapGetLeagueProtestsOutputPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel() as LeagueAdminProtestsDTO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueSeasons(query: GetLeagueSeasonsQueryDTO): Promise<LeagueSeasonSummaryDTO[]> {
|
async getLeagueSeasons(query: GetLeagueSeasonsQueryDTO): Promise<LeagueSeasonSummaryDTO[]> {
|
||||||
@@ -245,9 +232,7 @@ export class LeagueService {
|
|||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
const presenter = new GetLeagueSeasonsPresenter();
|
return mapGetLeagueSeasonsOutputPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel().seasons;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
|
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
|
||||||
@@ -261,25 +246,27 @@ export class LeagueService {
|
|||||||
return presenter.getViewModel().memberships as LeagueMembershipsDTO;
|
return presenter.getViewModel().memberships as LeagueMembershipsDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueStandings(leagueId: string): Promise<LeagueStandingsViewModel> {
|
async getLeagueStandings(leagueId: string): Promise<LeagueStandingsDTO> {
|
||||||
this.logger.debug('Getting league standings', { leagueId });
|
this.logger.debug('Getting league standings', { leagueId });
|
||||||
const result = await this.getLeagueStandingsUseCase.execute(leagueId);
|
const result = await this.getLeagueStandingsUseCase.execute({ leagueId });
|
||||||
// The use case returns a view model directly, so we return it as-is
|
if (result.isErr()) {
|
||||||
return result as unknown as LeagueStandingsViewModel;
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
const presenter = new LeagueStandingsPresenter();
|
||||||
|
presenter.present(result.unwrap());
|
||||||
|
return presenter.getViewModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueSchedule(leagueId: string): Promise<ReturnType<LeagueSchedulePresenter['getViewModel']>> {
|
async getLeagueSchedule(leagueId: string): Promise<LeagueScheduleDTO> {
|
||||||
this.logger.debug('Getting league schedule', { leagueId });
|
this.logger.debug('Getting league schedule', { leagueId });
|
||||||
const result = await this.getLeagueScheduleUseCase.execute({ leagueId });
|
const result = await this.getLeagueScheduleUseCase.execute({ leagueId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
const presenter = new LeagueSchedulePresenter();
|
return mapGetLeagueScheduleOutputPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel()!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueStats(leagueId: string): Promise<LeagueStatsViewModel> {
|
async getLeagueStats(leagueId: string): Promise<LeagueStatsDTO> {
|
||||||
this.logger.debug('Getting league stats', { leagueId });
|
this.logger.debug('Getting league stats', { leagueId });
|
||||||
const result = await this.getLeagueStatsUseCase.execute({ leagueId });
|
const result = await this.getLeagueStatsUseCase.execute({ leagueId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
@@ -404,7 +391,7 @@ export class LeagueService {
|
|||||||
return presenter.getViewModel()!;
|
return presenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinLeague(leagueId: string, driverId: string): Promise<JoinLeagueViewModel> {
|
async joinLeague(leagueId: string, driverId: string): Promise<JoinLeagueOutputDTO> {
|
||||||
this.logger.debug('Joining league', { leagueId, driverId });
|
this.logger.debug('Joining league', { leagueId, driverId });
|
||||||
|
|
||||||
const result = await this.joinLeagueUseCase.execute({ leagueId, driverId });
|
const result = await this.joinLeagueUseCase.execute({ leagueId, driverId });
|
||||||
@@ -415,12 +402,10 @@ export class LeagueService {
|
|||||||
error: error.code,
|
error: error.code,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const presenter = new JoinLeaguePresenter();
|
return mapJoinLeagueOutputPortToDTO(result.unwrap());
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async transferLeagueOwnership(leagueId: string, currentOwnerId: string, newOwnerId: string): Promise<TransferLeagueOwnershipViewModel> {
|
async transferLeagueOwnership(leagueId: string, currentOwnerId: string, newOwnerId: string): Promise<TransferLeagueOwnershipOutputDTO> {
|
||||||
this.logger.debug('Transferring league ownership', { leagueId, currentOwnerId, newOwnerId });
|
this.logger.debug('Transferring league ownership', { leagueId, currentOwnerId, newOwnerId });
|
||||||
|
|
||||||
const result = await this.transferLeagueOwnershipUseCase.execute({ leagueId, currentOwnerId, newOwnerId });
|
const result = await this.transferLeagueOwnershipUseCase.execute({ leagueId, currentOwnerId, newOwnerId });
|
||||||
@@ -431,9 +416,7 @@ export class LeagueService {
|
|||||||
error: error.code,
|
error: error.code,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const presenter = new TransferLeagueOwnershipPresenter();
|
return mapTransferLeagueOwnershipOutputPortToDTO(result.unwrap());
|
||||||
presenter.present({ success: true });
|
|
||||||
return presenter.getViewModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSeasonSponsorships(seasonId: string): Promise<GetSeasonSponsorshipsOutputDTO> {
|
async getSeasonSponsorships(seasonId: string): Promise<GetSeasonSponsorshipsOutputDTO> {
|
||||||
|
|||||||
@@ -1,16 +1,43 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsNumber, IsArray, ValidateNested } from 'class-validator';
|
|
||||||
import { Type } from 'class-transformer';
|
export class LeagueWithCapacityDTO {
|
||||||
import { LeagueWithCapacityDTO } from './LeagueWithCapacityDTO';
|
@ApiProperty()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
description!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
ownerId!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
settings!: {
|
||||||
|
maxDrivers: number;
|
||||||
|
sessionDuration?: number;
|
||||||
|
visibility?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
createdAt!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
socialLinks?: {
|
||||||
|
discordUrl?: string;
|
||||||
|
youtubeUrl?: string;
|
||||||
|
websiteUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
usedSlots!: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class AllLeaguesWithCapacityDTO {
|
export class AllLeaguesWithCapacityDTO {
|
||||||
@ApiProperty({ type: [LeagueWithCapacityDTO] })
|
@ApiProperty({ type: [LeagueWithCapacityDTO] })
|
||||||
@IsArray()
|
leagues!: LeagueWithCapacityDTO[];
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => LeagueWithCapacityDTO)
|
|
||||||
leagues: LeagueWithCapacityDTO[];
|
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
totalCount!: number;
|
||||||
totalCount: number;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface ApproveLeagueJoinRequestDTO {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
4
apps/api/src/domain/league/dtos/CreateLeagueDTO.ts
Normal file
4
apps/api/src/domain/league/dtos/CreateLeagueDTO.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface CreateLeagueViewModel {
|
||||||
|
leagueId: string;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
8
apps/api/src/domain/league/dtos/TotalLeaguesDTO.ts
Normal file
8
apps/api/src/domain/league/dtos/TotalLeaguesDTO.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNumber } from 'class-validator';
|
||||||
|
|
||||||
|
export class TotalLeaguesDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
totalLeagues: number;
|
||||||
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
import { IAllLeaguesWithCapacityPresenter, AllLeaguesWithCapacityResultDTO, AllLeaguesWithCapacityViewModel } from '@core/racing/application/presenters/IAllLeaguesWithCapacityPresenter';
|
import type { AllLeaguesWithCapacityOutputPort } from '@core/racing/application/ports/output/AllLeaguesWithCapacityOutputPort';
|
||||||
|
import { AllLeaguesWithCapacityDTO, LeagueWithCapacityDTO } from '../dtos/AllLeaguesWithCapacityDTO';
|
||||||
|
|
||||||
export class AllLeaguesWithCapacityPresenter implements IAllLeaguesWithCapacityPresenter {
|
export class AllLeaguesWithCapacityPresenter {
|
||||||
private result: AllLeaguesWithCapacityViewModel | null = null;
|
private result: AllLeaguesWithCapacityDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: AllLeaguesWithCapacityResultDTO) {
|
present(output: AllLeaguesWithCapacityOutputPort) {
|
||||||
const leagues = dto.leagues.map(league => ({
|
const leagues: LeagueWithCapacityDTO[] = output.leagues.map(league => ({
|
||||||
id: league.id,
|
id: league.id,
|
||||||
name: league.name,
|
name: league.name,
|
||||||
description: league.description,
|
description: league.description,
|
||||||
ownerId: league.ownerId,
|
ownerId: league.ownerId,
|
||||||
settings: { maxDrivers: league.settings.maxDrivers || 0 },
|
settings: { maxDrivers: league.settings.maxDrivers || 0 },
|
||||||
createdAt: league.createdAt.toISOString(),
|
createdAt: league.createdAt.toISOString(),
|
||||||
usedSlots: dto.memberCounts.get(league.id) || 0,
|
usedSlots: output.memberCounts[league.id] || 0,
|
||||||
socialLinks: league.socialLinks,
|
socialLinks: league.socialLinks,
|
||||||
}));
|
}));
|
||||||
this.result = {
|
this.result = {
|
||||||
@@ -24,7 +25,7 @@ export class AllLeaguesWithCapacityPresenter implements IAllLeaguesWithCapacityP
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): AllLeaguesWithCapacityViewModel | null {
|
getViewModel(): AllLeaguesWithCapacityDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,6 @@
|
|||||||
import { IApproveLeagueJoinRequestPresenter, ApproveLeagueJoinRequestResultPort, ApproveLeagueJoinRequestViewModel } from '@core/racing/application/presenters/IApproveLeagueJoinRequestPresenter';
|
import type { ApproveLeagueJoinRequestResultPort } from '@core/racing/application/ports/output/ApproveLeagueJoinRequestResultPort';
|
||||||
|
import type { ApproveLeagueJoinRequestDTO } from '../dtos/ApproveLeagueJoinRequestDTO';
|
||||||
|
|
||||||
export class ApproveLeagueJoinRequestPresenter implements IApproveLeagueJoinRequestPresenter {
|
export function mapApproveLeagueJoinRequestPortToDTO(port: ApproveLeagueJoinRequestResultPort): ApproveLeagueJoinRequestDTO {
|
||||||
private result: ApproveLeagueJoinRequestViewModel | null = null;
|
return port;
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: ApproveLeagueJoinRequestResultPort) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): ApproveLeagueJoinRequestViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { ICreateLeaguePresenter, CreateLeagueResultDTO, CreateLeagueViewModel } from '@core/racing/application/presenters/ICreateLeaguePresenter';
|
import type { CreateLeagueWithSeasonAndScoringOutputPort } from '@core/racing/application/ports/output/CreateLeagueWithSeasonAndScoringOutputPort';
|
||||||
|
import type { CreateLeagueViewModel } from '../dtos/CreateLeagueDTO';
|
||||||
|
|
||||||
export class CreateLeaguePresenter implements ICreateLeaguePresenter {
|
export class CreateLeaguePresenter {
|
||||||
private result: CreateLeagueViewModel | null = null;
|
private result: CreateLeagueViewModel | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: CreateLeagueResultDTO): void {
|
present(dto: CreateLeagueWithSeasonAndScoringOutputPort): void {
|
||||||
this.result = {
|
this.result = {
|
||||||
leagueId: dto.leagueId,
|
leagueId: dto.leagueId,
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { IGetLeagueAdminPermissionsPresenter, GetLeagueAdminPermissionsResultDTO, GetLeagueAdminPermissionsViewModel } from '@core/racing/application/presenters/IGetLeagueAdminPermissionsPresenter';
|
|
||||||
|
|
||||||
export class GetLeagueAdminPermissionsPresenter implements IGetLeagueAdminPermissionsPresenter {
|
|
||||||
private result: GetLeagueAdminPermissionsViewModel | null = null;
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: GetLeagueAdminPermissionsResultDTO) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): GetLeagueAdminPermissionsViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { IGetLeagueMembershipsPresenter, GetLeagueMembershipsResultDTO, GetLeagueMembershipsViewModel } from '@core/racing/application/presenters/IGetLeagueMembershipsPresenter';
|
|
||||||
import { LeagueMembershipsDTO } from '../dtos/LeagueMembershipsDTO';
|
|
||||||
|
|
||||||
export class GetLeagueMembershipsPresenter implements IGetLeagueMembershipsPresenter {
|
|
||||||
private result: GetLeagueMembershipsViewModel | null = null;
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: GetLeagueMembershipsResultDTO) {
|
|
||||||
const driverMap = new Map(dto.drivers.map(d => [d.id, d]));
|
|
||||||
const members = dto.memberships.map(membership => ({
|
|
||||||
driverId: membership.driverId,
|
|
||||||
driver: driverMap.get(membership.driverId)!,
|
|
||||||
role: this.mapRole(membership.role) as 'owner' | 'manager' | 'member',
|
|
||||||
joinedAt: membership.joinedAt,
|
|
||||||
}));
|
|
||||||
this.result = { memberships: { members } };
|
|
||||||
}
|
|
||||||
|
|
||||||
private mapRole(role: string): 'owner' | 'manager' | 'member' {
|
|
||||||
switch (role) {
|
|
||||||
case 'owner':
|
|
||||||
return 'owner';
|
|
||||||
case 'admin':
|
|
||||||
return 'manager'; // Map admin to manager for API
|
|
||||||
case 'steward':
|
|
||||||
return 'member'; // Map steward to member for API
|
|
||||||
case 'member':
|
|
||||||
return 'member';
|
|
||||||
default:
|
|
||||||
return 'member';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): GetLeagueMembershipsViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// API-specific method
|
|
||||||
get apiViewModel(): LeagueMembershipsDTO | null {
|
|
||||||
if (!this.result?.memberships) return null;
|
|
||||||
return this.result.memberships as LeagueMembershipsViewModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
import { IGetLeagueOwnerSummaryPresenter, GetLeagueOwnerSummaryResultDTO, GetLeagueOwnerSummaryViewModel } from '@core/racing/application/presenters/IGetLeagueOwnerSummaryPresenter';
|
import { GetLeagueOwnerSummaryOutputPort } from '@core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort';
|
||||||
|
import { LeagueOwnerSummaryDTO } from '../dtos/LeagueOwnerSummaryDTO';
|
||||||
|
|
||||||
export class GetLeagueOwnerSummaryPresenter implements IGetLeagueOwnerSummaryPresenter {
|
export function mapGetLeagueOwnerSummaryOutputPortToDTO(output: GetLeagueOwnerSummaryOutputPort): LeagueOwnerSummaryDTO | null {
|
||||||
private result: GetLeagueOwnerSummaryViewModel | null = null;
|
if (!output.summary) return null;
|
||||||
|
|
||||||
reset() {
|
return {
|
||||||
this.result = null;
|
driver: {
|
||||||
}
|
id: output.summary.driver.id,
|
||||||
|
iracingId: output.summary.driver.iracingId,
|
||||||
present(dto: GetLeagueOwnerSummaryResultDTO) {
|
name: output.summary.driver.name,
|
||||||
this.result = { summary: dto.summary };
|
country: output.summary.driver.country,
|
||||||
}
|
bio: output.summary.driver.bio,
|
||||||
|
joinedAt: output.summary.driver.joinedAt,
|
||||||
getViewModel(): GetLeagueOwnerSummaryViewModel {
|
},
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
rating: output.summary.rating,
|
||||||
return this.result;
|
rank: output.summary.rank,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,47 @@
|
|||||||
import { IGetLeagueProtestsPresenter, GetLeagueProtestsResultDTO, GetLeagueProtestsViewModel } from '@core/racing/application/presenters/IGetLeagueProtestsPresenter';
|
import { GetLeagueProtestsOutputPort } from '@core/racing/application/ports/output/GetLeagueProtestsOutputPort';
|
||||||
|
import { LeagueAdminProtestsDTO } from '../dtos/LeagueAdminProtestsDTO';
|
||||||
|
import { ProtestDTO } from '../dtos/ProtestDTO';
|
||||||
|
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
||||||
|
import { DriverDTO } from '../../driver/dtos/DriverDTO';
|
||||||
|
|
||||||
export class GetLeagueProtestsPresenter implements IGetLeagueProtestsPresenter {
|
export function mapGetLeagueProtestsOutputPortToDTO(output: GetLeagueProtestsOutputPort): LeagueAdminProtestsDTO {
|
||||||
private result: GetLeagueProtestsViewModel | null = null;
|
const protests: ProtestDTO[] = output.protests.map(protest => ({
|
||||||
|
id: protest.id,
|
||||||
|
raceId: protest.raceId,
|
||||||
|
protestingDriverId: protest.protestingDriverId,
|
||||||
|
accusedDriverId: protest.accusedDriverId,
|
||||||
|
submittedAt: new Date(protest.filedAt),
|
||||||
|
description: protest.incident.description,
|
||||||
|
status: protest.status as 'pending' | 'accepted' | 'rejected', // TODO: map properly
|
||||||
|
}));
|
||||||
|
|
||||||
reset() {
|
const racesById: { [raceId: string]: RaceDTO } = {};
|
||||||
this.result = null;
|
for (const raceId in output.racesById) {
|
||||||
}
|
const race = output.racesById[raceId];
|
||||||
|
racesById[raceId] = {
|
||||||
present(dto: GetLeagueProtestsResultDTO) {
|
id: race.id,
|
||||||
const racesById = {};
|
name: race.track, // assuming name is track
|
||||||
dto.races.forEach(race => {
|
date: race.scheduledAt,
|
||||||
racesById[race.id] = race;
|
leagueName: undefined, // TODO: get league name if needed
|
||||||
});
|
|
||||||
const driversById = {};
|
|
||||||
dto.drivers.forEach(driver => {
|
|
||||||
driversById[driver.id] = driver;
|
|
||||||
});
|
|
||||||
this.result = {
|
|
||||||
protests: dto.protests,
|
|
||||||
racesById,
|
|
||||||
driversById,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): GetLeagueProtestsViewModel {
|
const driversById: { [driverId: string]: DriverDTO } = {};
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
for (const driverId in output.driversById) {
|
||||||
return this.result;
|
const driver = output.driversById[driverId];
|
||||||
|
driversById[driverId] = {
|
||||||
|
id: driver.id,
|
||||||
|
iracingId: driver.iracingId,
|
||||||
|
name: driver.name,
|
||||||
|
country: driver.country,
|
||||||
|
bio: driver.bio,
|
||||||
|
joinedAt: driver.joinedAt,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
protests,
|
||||||
|
racesById,
|
||||||
|
driversById,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,14 @@
|
|||||||
import { IGetLeagueSeasonsPresenter, GetLeagueSeasonsResultDTO, GetLeagueSeasonsViewModel } from '@core/racing/application/presenters/IGetLeagueSeasonsPresenter';
|
import { GetLeagueSeasonsOutputPort } from '@core/racing/application/ports/output/GetLeagueSeasonsOutputPort';
|
||||||
|
import { LeagueSeasonSummaryDTO } from '../dtos/LeagueSeasonSummaryDTO';
|
||||||
|
|
||||||
export class GetLeagueSeasonsPresenter implements IGetLeagueSeasonsPresenter {
|
export function mapGetLeagueSeasonsOutputPortToDTO(output: GetLeagueSeasonsOutputPort): LeagueSeasonSummaryDTO[] {
|
||||||
private result: GetLeagueSeasonsViewModel | null = null;
|
return output.seasons.map(season => ({
|
||||||
|
seasonId: season.seasonId,
|
||||||
reset() {
|
name: season.name,
|
||||||
this.result = null;
|
status: season.status,
|
||||||
}
|
startDate: season.startDate,
|
||||||
|
endDate: season.endDate,
|
||||||
present(dto: GetLeagueSeasonsResultDTO) {
|
isPrimary: season.isPrimary,
|
||||||
const seasons = dto.seasons.map(season => ({
|
isParallelActive: season.isParallelActive,
|
||||||
seasonId: season.id,
|
}));
|
||||||
name: season.name,
|
|
||||||
status: season.status,
|
|
||||||
startDate: season.startDate,
|
|
||||||
endDate: season.endDate,
|
|
||||||
isPrimary: season.isPrimary,
|
|
||||||
isParallelActive: season.isParallelActive,
|
|
||||||
}));
|
|
||||||
this.result = { seasons };
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): GetLeagueSeasonsViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,9 @@
|
|||||||
import { IJoinLeaguePresenter, JoinLeagueResultDTO, JoinLeagueViewModel } from '@core/racing/application/presenters/IJoinLeaguePresenter';
|
import type { JoinLeagueOutputPort } from '@core/racing/application/ports/output/JoinLeagueOutputPort';
|
||||||
|
import type { JoinLeagueOutputDTO } from '../dtos/JoinLeagueOutputDTO';
|
||||||
|
|
||||||
export class JoinLeaguePresenter implements IJoinLeaguePresenter {
|
export function mapJoinLeagueOutputPortToDTO(port: JoinLeagueOutputPort): JoinLeagueOutputDTO {
|
||||||
private result: JoinLeagueViewModel | null = null;
|
return {
|
||||||
|
success: true,
|
||||||
reset() {
|
membershipId: port.membershipId,
|
||||||
this.result = null;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: JoinLeagueResultDTO): void {
|
|
||||||
this.result = {
|
|
||||||
success: true,
|
|
||||||
membershipId: dto.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): JoinLeagueViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import { ILeagueFullConfigPresenter, LeagueFullConfigData, LeagueConfigFormViewModel } from '@core/racing/application/presenters/ILeagueFullConfigPresenter';
|
import { LeagueFullConfigOutputPort } from '@core/racing/application/ports/output/LeagueFullConfigOutputPort';
|
||||||
import { LeagueConfigFormModelDTO } from '../dtos/LeagueConfigFormModelDTO';
|
import { LeagueConfigFormModelDTO } from '../dtos/LeagueConfigFormModelDTO';
|
||||||
|
import type { Presenter } from '@core/shared/presentation';
|
||||||
|
|
||||||
export class LeagueConfigPresenter implements ILeagueFullConfigPresenter {
|
export class LeagueConfigPresenter implements Presenter<LeagueFullConfigOutputPort, LeagueConfigFormModelDTO> {
|
||||||
private result: LeagueConfigFormViewModel | null = null;
|
private result: LeagueConfigFormModelDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: LeagueFullConfigData) {
|
present(dto: LeagueFullConfigOutputPort) {
|
||||||
// Map from LeagueFullConfigData to LeagueConfigFormViewModel
|
// Map from LeagueFullConfigOutputPort to LeagueConfigFormModelDTO
|
||||||
const league = dto.league;
|
const league = dto.league;
|
||||||
const settings = league.settings;
|
const settings = league.settings;
|
||||||
const stewarding = settings.stewarding;
|
const stewarding = settings.stewarding;
|
||||||
@@ -20,64 +21,9 @@ export class LeagueConfigPresenter implements ILeagueFullConfigPresenter {
|
|||||||
name: league.name,
|
name: league.name,
|
||||||
description: league.description,
|
description: league.description,
|
||||||
visibility: 'public', // TODO: Map visibility from league
|
visibility: 'public', // TODO: Map visibility from league
|
||||||
gameId: 'iracing', // TODO: Map from game
|
|
||||||
},
|
},
|
||||||
structure: {
|
structure: {
|
||||||
mode: 'solo', // TODO: Map from league settings
|
mode: 'solo', // TODO: Map from league settings
|
||||||
maxDrivers: settings.maxDrivers || 32,
|
|
||||||
multiClassEnabled: false, // TODO: Map
|
|
||||||
},
|
|
||||||
championships: {
|
|
||||||
enableDriverChampionship: true, // TODO: Map
|
|
||||||
enableTeamChampionship: false,
|
|
||||||
enableNationsChampionship: false,
|
|
||||||
enableTrophyChampionship: false,
|
|
||||||
},
|
|
||||||
scoring: {
|
|
||||||
customScoringEnabled: false, // TODO: Map
|
|
||||||
},
|
|
||||||
dropPolicy: {
|
|
||||||
strategy: 'none', // TODO: Map
|
|
||||||
},
|
|
||||||
timings: {
|
|
||||||
practiceMinutes: 30, // TODO: Map
|
|
||||||
qualifyingMinutes: 15,
|
|
||||||
mainRaceMinutes: 60,
|
|
||||||
sessionCount: 1,
|
|
||||||
roundsPlanned: 10, // TODO: Map
|
|
||||||
},
|
|
||||||
stewarding: {
|
|
||||||
decisionMode: stewarding?.decisionMode || 'admin_only',
|
|
||||||
requireDefense: stewarding?.requireDefense || false,
|
|
||||||
defenseTimeLimit: stewarding?.defenseTimeLimit || 48,
|
|
||||||
voteTimeLimit: stewarding?.voteTimeLimit || 72,
|
|
||||||
protestDeadlineHours: stewarding?.protestDeadlineHours || 48,
|
|
||||||
stewardingClosesHours: stewarding?.stewardingClosesHours || 168,
|
|
||||||
notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest || true,
|
|
||||||
notifyOnVoteRequired: stewarding?.notifyOnVoteRequired || true,
|
|
||||||
requiredVotes: stewarding?.requiredVotes,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): LeagueConfigFormViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// API-specific method to get the DTO
|
|
||||||
get viewModel(): LeagueConfigFormModelDTO | null {
|
|
||||||
if (!this.result) return null;
|
|
||||||
|
|
||||||
// Map from LeagueConfigFormViewModel to LeagueConfigFormModelDto
|
|
||||||
return {
|
|
||||||
leagueId: this.result.leagueId,
|
|
||||||
basics: {
|
|
||||||
name: this.result.basics.name,
|
|
||||||
description: this.result.basics.description,
|
|
||||||
visibility: this.result.basics.visibility as 'public' | 'private',
|
|
||||||
},
|
|
||||||
structure: {
|
|
||||||
mode: this.result.structure.mode as 'solo' | 'team',
|
|
||||||
},
|
},
|
||||||
championships: [], // TODO: Map championships
|
championships: [], // TODO: Map championships
|
||||||
scoring: {
|
scoring: {
|
||||||
@@ -85,8 +31,8 @@ export class LeagueConfigPresenter implements ILeagueFullConfigPresenter {
|
|||||||
points: 25, // TODO: Map points
|
points: 25, // TODO: Map points
|
||||||
},
|
},
|
||||||
dropPolicy: {
|
dropPolicy: {
|
||||||
strategy: this.result.dropPolicy.strategy as 'none' | 'worst_n',
|
strategy: 'none', // TODO: Map
|
||||||
n: this.result.dropPolicy.n,
|
n: 0,
|
||||||
},
|
},
|
||||||
timings: {
|
timings: {
|
||||||
raceDayOfWeek: 'sunday', // TODO: Map from timings
|
raceDayOfWeek: 'sunday', // TODO: Map from timings
|
||||||
@@ -94,16 +40,20 @@ export class LeagueConfigPresenter implements ILeagueFullConfigPresenter {
|
|||||||
raceTimeMinute: 0,
|
raceTimeMinute: 0,
|
||||||
},
|
},
|
||||||
stewarding: {
|
stewarding: {
|
||||||
decisionMode: this.result.stewarding.decisionMode === 'steward_vote' ? 'committee_vote' : 'single_steward',
|
decisionMode: stewarding?.decisionMode === 'steward_vote' ? 'committee_vote' : 'single_steward',
|
||||||
requireDefense: this.result.stewarding.requireDefense,
|
requireDefense: stewarding?.requireDefense || false,
|
||||||
defenseTimeLimit: this.result.stewarding.defenseTimeLimit,
|
defenseTimeLimit: stewarding?.defenseTimeLimit || 48,
|
||||||
voteTimeLimit: this.result.stewarding.voteTimeLimit,
|
voteTimeLimit: stewarding?.voteTimeLimit || 72,
|
||||||
protestDeadlineHours: this.result.stewarding.protestDeadlineHours,
|
protestDeadlineHours: stewarding?.protestDeadlineHours || 48,
|
||||||
stewardingClosesHours: this.result.stewarding.stewardingClosesHours,
|
stewardingClosesHours: stewarding?.stewardingClosesHours || 168,
|
||||||
notifyAccusedOnProtest: this.result.stewarding.notifyAccusedOnProtest,
|
notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest || true,
|
||||||
notifyOnVoteRequired: this.result.stewarding.notifyOnVoteRequired,
|
notifyOnVoteRequired: stewarding?.notifyOnVoteRequired || true,
|
||||||
requiredVotes: this.result.stewarding.requiredVotes,
|
requiredVotes: stewarding?.requiredVotes || 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getViewModel(): LeagueConfigFormModelDTO | null {
|
||||||
|
return this.result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { IGetLeagueJoinRequestsPresenter, GetLeagueJoinRequestsResultDTO, GetLeagueJoinRequestsViewModel } from '@core/racing/application/presenters/IGetLeagueJoinRequestsPresenter';
|
|
||||||
|
|
||||||
export class LeagueJoinRequestsPresenter implements IGetLeagueJoinRequestsPresenter {
|
|
||||||
private result: GetLeagueJoinRequestsViewModel | null = null;
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: GetLeagueJoinRequestsResultDTO) {
|
|
||||||
const driverMap = new Map(dto.drivers.map(d => [d.id, d]));
|
|
||||||
const joinRequests = dto.joinRequests.map(request => ({
|
|
||||||
id: request.id,
|
|
||||||
leagueId: request.leagueId,
|
|
||||||
driverId: request.driverId,
|
|
||||||
requestedAt: request.requestedAt,
|
|
||||||
message: request.message,
|
|
||||||
driver: driverMap.get(request.driverId) || null,
|
|
||||||
}));
|
|
||||||
this.result = { joinRequests };
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): GetLeagueJoinRequestsViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,14 @@
|
|||||||
import { IGetLeagueSchedulePresenter, GetLeagueScheduleResultDTO, LeagueScheduleViewModel } from '@core/racing/application/presenters/IGetLeagueSchedulePresenter';
|
import { GetLeagueScheduleOutputPort } from '@core/racing/application/ports/output/GetLeagueScheduleOutputPort';
|
||||||
|
import { LeagueScheduleDTO } from '../dtos/LeagueScheduleDTO';
|
||||||
|
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
||||||
|
|
||||||
export class LeagueSchedulePresenter implements IGetLeagueSchedulePresenter {
|
export function mapGetLeagueScheduleOutputPortToDTO(output: GetLeagueScheduleOutputPort): LeagueScheduleDTO {
|
||||||
private result: LeagueScheduleViewModel | null = null;
|
return {
|
||||||
|
races: output.races.map(race => ({
|
||||||
reset() {
|
id: race.id,
|
||||||
this.result = null;
|
name: race.name,
|
||||||
}
|
date: race.scheduledAt.toISOString(),
|
||||||
|
leagueName: undefined, // TODO: get league name if needed
|
||||||
present(dto: GetLeagueScheduleResultDTO) {
|
})),
|
||||||
this.result = {
|
};
|
||||||
races: dto.races.map(race => ({
|
|
||||||
id: race.id,
|
|
||||||
name: race.name,
|
|
||||||
date: race.scheduledAt.toISOString(),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): LeagueScheduleViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,37 @@
|
|||||||
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
|
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
|
||||||
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
|
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
|
||||||
import type {
|
import type { LeagueScoringConfigOutputPort } from '@core/racing/application/ports/output/LeagueScoringConfigOutputPort';
|
||||||
ILeagueScoringConfigPresenter,
|
import type { LeagueScoringPresetOutputPort } from '@core/racing/application/ports/output/LeagueScoringPresetOutputPort';
|
||||||
LeagueScoringConfigData,
|
|
||||||
LeagueScoringConfigViewModel,
|
|
||||||
LeagueScoringChampionshipViewModel,
|
|
||||||
} from '@core/racing/application/presenters/ILeagueScoringConfigPresenter';
|
|
||||||
|
|
||||||
export class LeagueScoringConfigPresenter implements ILeagueScoringConfigPresenter {
|
export interface LeagueScoringChampionshipViewModel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
sessionTypes: string[];
|
||||||
|
pointsPreview: Array<{ sessionType: string; position: number; points: number }>;
|
||||||
|
bonusSummary: string[];
|
||||||
|
dropPolicyDescription: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeagueScoringConfigViewModel {
|
||||||
|
leagueId: string;
|
||||||
|
seasonId: string;
|
||||||
|
gameId: string;
|
||||||
|
gameName: string;
|
||||||
|
scoringPresetId?: string;
|
||||||
|
scoringPresetName?: string;
|
||||||
|
dropPolicySummary: string;
|
||||||
|
championships: LeagueScoringChampionshipViewModel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LeagueScoringConfigPresenter {
|
||||||
private viewModel: LeagueScoringConfigViewModel | null = null;
|
private viewModel: LeagueScoringConfigViewModel | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.viewModel = null;
|
this.viewModel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(data: LeagueScoringConfigData): LeagueScoringConfigViewModel {
|
present(data: LeagueScoringConfigOutputPort): LeagueScoringConfigViewModel {
|
||||||
const championships: LeagueScoringChampionshipViewModel[] =
|
const championships: LeagueScoringChampionshipViewModel[] =
|
||||||
data.championships.map((champ) => this.mapChampionship(champ));
|
data.championships.map((champ) => this.mapChampionship(champ));
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import type {
|
import type { LeagueScoringPresetsOutputPort } from '@core/racing/application/ports/output/LeagueScoringPresetsOutputPort';
|
||||||
ILeagueScoringPresetsPresenter,
|
import type { LeagueScoringPresetOutputPort } from '@core/racing/application/ports/output/LeagueScoringPresetOutputPort';
|
||||||
LeagueScoringPresetsResultDTO,
|
|
||||||
LeagueScoringPresetsViewModel,
|
|
||||||
} from '@core/racing/application/presenters/ILeagueScoringPresetsPresenter';
|
|
||||||
|
|
||||||
export class LeagueScoringPresetsPresenter implements ILeagueScoringPresetsPresenter {
|
export interface LeagueScoringPresetsViewModel {
|
||||||
|
presets: LeagueScoringPresetOutputPort[];
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LeagueScoringPresetsPresenter {
|
||||||
private viewModel: LeagueScoringPresetsViewModel | null = null;
|
private viewModel: LeagueScoringPresetsViewModel | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.viewModel = null;
|
this.viewModel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: LeagueScoringPresetsResultDTO): void {
|
present(output: LeagueScoringPresetsOutputPort): void {
|
||||||
this.viewModel = {
|
this.viewModel = {
|
||||||
presets: dto.presets,
|
presets: output.presets,
|
||||||
|
totalCount: output.presets.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
import { ILeagueStandingsPresenter, LeagueStandingsResultDTO, LeagueStandingsViewModel } from '@core/racing/application/presenters/ILeagueStandingsPresenter';
|
import { LeagueStandingsOutputPort } from '@core/racing/application/ports/output/LeagueStandingsOutputPort';
|
||||||
|
import { LeagueStandingsDTO } from '../dtos/LeagueStandingsDTO';
|
||||||
|
import type { Presenter } from '@core/shared/presentation';
|
||||||
|
|
||||||
export class LeagueStandingsPresenter implements ILeagueStandingsPresenter {
|
export class LeagueStandingsPresenter implements Presenter<LeagueStandingsOutputPort, LeagueStandingsDTO> {
|
||||||
private result: LeagueStandingsViewModel | null = null;
|
private result: LeagueStandingsDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: LeagueStandingsResultDTO) {
|
present(dto: LeagueStandingsOutputPort) {
|
||||||
const driverMap = new Map(dto.drivers.map(d => [d.id, { id: d.id, name: d.name }]));
|
const standings = dto.standings.map(standing => ({
|
||||||
const standings = dto.standings
|
driverId: standing.driverId,
|
||||||
.sort((a, b) => a.position - b.position)
|
driver: {
|
||||||
.map(standing => ({
|
id: standing.driver.id,
|
||||||
driverId: standing.driverId,
|
name: standing.driver.name,
|
||||||
driver: driverMap.get(standing.driverId)!,
|
// Add other DriverDto fields if needed, but for now just id and name
|
||||||
points: standing.points,
|
},
|
||||||
rank: standing.position,
|
points: standing.points,
|
||||||
}));
|
rank: standing.rank,
|
||||||
|
}));
|
||||||
this.result = { standings };
|
this.result = { standings };
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): LeagueStandingsViewModel {
|
getViewModel(): LeagueStandingsDTO {
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
if (!this.result) throw new Error('Presenter not presented');
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import { ILeagueStatsPresenter, LeagueStatsResultDTO, LeagueStatsViewModel } from '@core/racing/application/presenters/ILeagueStatsPresenter';
|
import { LeagueStatsOutputPort } from '@core/racing/application/ports/output/LeagueStatsOutputPort';
|
||||||
|
import { LeagueStatsDTO } from '../dtos/LeagueStatsDTO';
|
||||||
|
import type { Presenter } from '@core/shared/presentation';
|
||||||
|
|
||||||
export class LeagueStatsPresenter implements ILeagueStatsPresenter {
|
export class LeagueStatsPresenter implements Presenter<LeagueStatsOutputPort, LeagueStatsDTO> {
|
||||||
private result: LeagueStatsViewModel | null = null;
|
private result: LeagueStatsDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: LeagueStatsResultDTO) {
|
present(dto: LeagueStatsOutputPort) {
|
||||||
this.result = dto;
|
this.result = dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): LeagueStatsViewModel | null {
|
getViewModel(): LeagueStatsDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,9 @@
|
|||||||
import { IRejectLeagueJoinRequestPresenter, RejectLeagueJoinRequestResultDTO, RejectLeagueJoinRequestViewModel } from '@core/racing/application/presenters/IRejectLeagueJoinRequestPresenter';
|
import type { RejectLeagueJoinRequestOutputPort } from '@core/racing/application/ports/output/RejectLeagueJoinRequestOutputPort';
|
||||||
|
import type { RejectJoinRequestOutputDTO } from '../dtos/RejectJoinRequestOutputDTO';
|
||||||
|
|
||||||
export class RejectLeagueJoinRequestPresenter implements IRejectLeagueJoinRequestPresenter {
|
export function mapRejectLeagueJoinRequestOutputPortToDTO(port: RejectLeagueJoinRequestOutputPort): RejectJoinRequestOutputDTO {
|
||||||
private result: RejectLeagueJoinRequestViewModel | null = null;
|
return {
|
||||||
|
success: port.success,
|
||||||
reset() {
|
message: port.message,
|
||||||
this.result = null;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: RejectLeagueJoinRequestResultDTO) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): RejectLeagueJoinRequestViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,8 @@
|
|||||||
import { IRemoveLeagueMemberPresenter, RemoveLeagueMemberResultDTO, RemoveLeagueMemberViewModel } from '@core/racing/application/presenters/IRemoveLeagueMemberPresenter';
|
import type { RemoveLeagueMemberOutputPort } from '@core/racing/application/ports/output/RemoveLeagueMemberOutputPort';
|
||||||
|
import type { RemoveLeagueMemberOutputDTO } from '../dtos/RemoveLeagueMemberOutputDTO';
|
||||||
|
|
||||||
export class RemoveLeagueMemberPresenter implements IRemoveLeagueMemberPresenter {
|
export function mapRemoveLeagueMemberOutputPortToDTO(port: RemoveLeagueMemberOutputPort): RemoveLeagueMemberOutputDTO {
|
||||||
private result: RemoveLeagueMemberViewModel | null = null;
|
return {
|
||||||
|
success: port.success,
|
||||||
reset() {
|
};
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: RemoveLeagueMemberResultDTO) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): RemoveLeagueMemberViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import { IGetTotalLeaguesPresenter, GetTotalLeaguesResultDTO, GetTotalLeaguesViewModel } from '@core/racing/application/presenters/IGetTotalLeaguesPresenter';
|
import { GetTotalLeaguesOutputPort } from '@core/racing/application/ports/output/GetTotalLeaguesOutputPort';
|
||||||
import { LeagueStatsDTO } from '../dtos/LeagueStatsDTO';
|
import { TotalLeaguesDTO } from '../dtos/TotalLeaguesDTO';
|
||||||
|
|
||||||
export class TotalLeaguesPresenter implements IGetTotalLeaguesPresenter {
|
export class TotalLeaguesPresenter {
|
||||||
private result: LeagueStatsDto | null = null;
|
private result: TotalLeaguesDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: GetTotalLeaguesResultDTO) {
|
present(output: GetTotalLeaguesOutputPort) {
|
||||||
this.result = {
|
this.result = {
|
||||||
totalLeagues: dto.totalLeagues,
|
totalLeagues: output.totalLeagues,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): LeagueStatsDto | null {
|
getViewModel(): TotalLeaguesDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,8 @@
|
|||||||
import { ITransferLeagueOwnershipPresenter, TransferLeagueOwnershipResultDTO, TransferLeagueOwnershipViewModel } from '@core/racing/application/presenters/ITransferLeagueOwnershipPresenter';
|
import type { TransferLeagueOwnershipOutputPort } from '@core/racing/application/ports/output/TransferLeagueOwnershipOutputPort';
|
||||||
|
import type { TransferLeagueOwnershipOutputDTO } from '../dtos/TransferLeagueOwnershipOutputDTO';
|
||||||
|
|
||||||
export class TransferLeagueOwnershipPresenter implements ITransferLeagueOwnershipPresenter {
|
export function mapTransferLeagueOwnershipOutputPortToDTO(port: TransferLeagueOwnershipOutputPort): TransferLeagueOwnershipOutputDTO {
|
||||||
private result: TransferLeagueOwnershipViewModel | null = null;
|
return {
|
||||||
|
success: port.success,
|
||||||
reset() {
|
};
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: TransferLeagueOwnershipResultDTO): void {
|
|
||||||
this.result = {
|
|
||||||
success: dto.success,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): TransferLeagueOwnershipViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,8 @@
|
|||||||
import { IUpdateLeagueMemberRolePresenter, UpdateLeagueMemberRoleResultDTO, UpdateLeagueMemberRoleViewModel } from '@core/racing/application/presenters/IUpdateLeagueMemberRolePresenter';
|
import type { UpdateLeagueMemberRoleOutputPort } from '@core/racing/application/ports/output/UpdateLeagueMemberRoleOutputPort';
|
||||||
|
import type { UpdateLeagueMemberRoleOutputDTO } from '../dtos/UpdateLeagueMemberRoleOutputDTO';
|
||||||
|
|
||||||
export class UpdateLeagueMemberRolePresenter implements IUpdateLeagueMemberRolePresenter {
|
export function mapUpdateLeagueMemberRoleOutputPortToDTO(port: UpdateLeagueMemberRoleOutputPort): UpdateLeagueMemberRoleOutputDTO {
|
||||||
private result: UpdateLeagueMemberRoleViewModel | null = null;
|
return {
|
||||||
|
success: port.success,
|
||||||
reset() {
|
};
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: UpdateLeagueMemberRoleResultDTO) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): UpdateLeagueMemberRoleViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -46,8 +46,8 @@ export class RaceController {
|
|||||||
|
|
||||||
@Get('all/page-data')
|
@Get('all/page-data')
|
||||||
@ApiOperation({ summary: 'Get all races page data' })
|
@ApiOperation({ summary: 'Get all races page data' })
|
||||||
@ApiResponse({ status: 200, description: 'All races page data', type: RacesPageDataDTO })
|
@ApiResponse({ status: 200, description: 'All races page data', type: AllRacesPageDTO })
|
||||||
async getAllRacesPageData(): Promise<RacesPageDataDTO> {
|
async getAllRacesPageData(): Promise<AllRacesPageDTO> {
|
||||||
return this.raceService.getAllRacesPageData();
|
return this.raceService.getAllRacesPageData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,8 +146,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRegRepo: IRaceRegistrationRepository,
|
raceRegRepo: IRaceRegistrationRepository,
|
||||||
resultRepo: IResultRepository,
|
resultRepo: IResultRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
driverRatingProvider: DriverRatingProvider,
|
|
||||||
imageService: IImageServicePort,
|
|
||||||
) => new GetRaceDetailUseCase(
|
) => new GetRaceDetailUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
leagueRepo,
|
leagueRepo,
|
||||||
@@ -155,8 +153,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRegRepo,
|
raceRegRepo,
|
||||||
resultRepo,
|
resultRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
driverRatingProvider,
|
|
||||||
imageService,
|
|
||||||
),
|
),
|
||||||
inject: [
|
inject: [
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
@@ -165,8 +161,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||||
RESULT_REPOSITORY_TOKEN,
|
RESULT_REPOSITORY_TOKEN,
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
DRIVER_RATING_PROVIDER_TOKEN,
|
|
||||||
IMAGE_SERVICE_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Injectable, Inject } from '@nestjs/common';
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
import type { AllRacesPageViewModel } from '@core/racing/application/presenters/IGetAllRacesPresenter';
|
import type { AllRacesPageViewModel } from '@core/racing/application/presenters/IGetAllRacesPresenter';
|
||||||
import type { GetTotalRacesViewModel } from '@core/racing/application/presenters/IGetTotalRacesPresenter';
|
import type { RaceDetailOutputPort } from '@core/racing/application/ports/output/RaceDetailOutputPort';
|
||||||
import type { RaceDetailViewModel } from '@core/racing/application/presenters/IRaceDetailPresenter';
|
import type { RacesPageOutputPort } from '@core/racing/application/ports/output/RacesPageOutputPort';
|
||||||
import type { RacesPageViewModel } from '@core/racing/application/presenters/IRacesPagePresenter';
|
import type { RaceResultsDetailOutputPort } from '@core/racing/application/ports/output/RaceResultsDetailOutputPort';
|
||||||
import type { RaceResultsDetailViewModel } from '@core/racing/application/presenters/IRaceResultsDetailPresenter';
|
import type { RaceWithSOFOutputPort } from '@core/racing/application/ports/output/RaceWithSOFOutputPort';
|
||||||
import type { RaceWithSOFViewModel } from '@core/racing/application/presenters/IRaceWithSOFPresenter';
|
import type { RaceProtestsOutputPort } from '@core/racing/application/ports/output/RaceProtestsOutputPort';
|
||||||
import type { RaceProtestsViewModel } from '@core/racing/application/presenters/IRaceProtestsPresenter';
|
import type { RacePenaltiesOutputPort } from '@core/racing/application/ports/output/RacePenaltiesOutputPort';
|
||||||
import type { RacePenaltiesViewModel } from '@core/racing/application/presenters/IRacePenaltiesPresenter';
|
|
||||||
|
|
||||||
// DTOs
|
// DTOs
|
||||||
import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO';
|
import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO';
|
||||||
@@ -14,9 +13,18 @@ import { RegisterForRaceParamsDTO } from './dtos/RegisterForRaceParamsDTO';
|
|||||||
import { WithdrawFromRaceParamsDTO } from './dtos/WithdrawFromRaceParamsDTO';
|
import { WithdrawFromRaceParamsDTO } from './dtos/WithdrawFromRaceParamsDTO';
|
||||||
import { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO';
|
import { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO';
|
||||||
import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO';
|
import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO';
|
||||||
|
import { AllRacesPageDTO } from './dtos/AllRacesPageDTO';
|
||||||
|
import { RaceStatsDTO } from './dtos/RaceStatsDTO';
|
||||||
|
import { RacePenaltiesDTO } from './dtos/RacePenaltiesDTO';
|
||||||
|
import { RaceProtestsDTO } from './dtos/RaceProtestsDTO';
|
||||||
|
import { RaceResultsDetailDTO } from './dtos/RaceResultsDetailDTO';
|
||||||
|
|
||||||
// Core imports
|
// Core imports
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
import type { Logger } from '@core/shared/application/Logger';
|
||||||
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
import { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
|
||||||
|
import { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
|
||||||
|
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||||
|
|
||||||
// Use cases
|
// Use cases
|
||||||
import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||||
@@ -53,7 +61,7 @@ import { RequestProtestDefenseCommandDTO } from './dtos/RequestProtestDefenseCom
|
|||||||
import { ReviewProtestCommandDTO } from './dtos/ReviewProtestCommandDTO';
|
import { ReviewProtestCommandDTO } from './dtos/ReviewProtestCommandDTO';
|
||||||
|
|
||||||
// Tokens
|
// Tokens
|
||||||
import { LOGGER_TOKEN } from './RaceProviders';
|
import { LOGGER_TOKEN, DRIVER_RATING_PROVIDER_TOKEN, IMAGE_SERVICE_TOKEN, LEAGUE_REPOSITORY_TOKEN } from './RaceProviders';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RaceService {
|
export class RaceService {
|
||||||
@@ -77,7 +85,10 @@ export class RaceService {
|
|||||||
private readonly applyPenaltyUseCase: ApplyPenaltyUseCase,
|
private readonly applyPenaltyUseCase: ApplyPenaltyUseCase,
|
||||||
private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase,
|
private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase,
|
||||||
private readonly reviewProtestUseCase: ReviewProtestUseCase,
|
private readonly reviewProtestUseCase: ReviewProtestUseCase,
|
||||||
|
@Inject(LEAGUE_REPOSITORY_TOKEN) private readonly leagueRepository: ILeagueRepository,
|
||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||||
|
@Inject(DRIVER_RATING_PROVIDER_TOKEN) private readonly driverRatingProvider: DriverRatingProvider,
|
||||||
|
@Inject(IMAGE_SERVICE_TOKEN) private readonly imageService: IImageServicePort,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getAllRaces(): Promise<AllRacesPageViewModel> {
|
async getAllRaces(): Promise<AllRacesPageViewModel> {
|
||||||
@@ -88,21 +99,29 @@ export class RaceService {
|
|||||||
return presenter.getViewModel()!;
|
return presenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTotalRaces(): Promise<GetTotalRacesViewModel> {
|
async getTotalRaces(): Promise<RaceStatsDTO> {
|
||||||
this.logger.debug('[RaceService] Fetching total races count.');
|
this.logger.debug('[RaceService] Fetching total races count.');
|
||||||
|
const result = await this.getTotalRacesUseCase.execute();
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
const presenter = new GetTotalRacesPresenter();
|
const presenter = new GetTotalRacesPresenter();
|
||||||
await this.getTotalRacesUseCase.execute({}, presenter);
|
presenter.present(result.unwrap());
|
||||||
return presenter.getViewModel()!;
|
return presenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async importRaceResults(input: ImportRaceResultsDTO): Promise<ImportRaceResultsSummaryDTO> {
|
async importRaceResults(input: ImportRaceResultsDTO): Promise<ImportRaceResultsSummaryDTO> {
|
||||||
this.logger.debug('Importing race results:', input);
|
this.logger.debug('Importing race results:', input);
|
||||||
|
const result = await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent });
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
const presenter = new ImportRaceResultsApiPresenter();
|
const presenter = new ImportRaceResultsApiPresenter();
|
||||||
await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent }, presenter);
|
presenter.present(result.unwrap());
|
||||||
return presenter.getViewModel()!;
|
return presenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailViewModel> {
|
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailDTO> {
|
||||||
this.logger.debug('[RaceService] Fetching race detail:', params);
|
this.logger.debug('[RaceService] Fetching race detail:', params);
|
||||||
|
|
||||||
const result = await this.getRaceDetailUseCase.execute(params);
|
const result = await this.getRaceDetailUseCase.execute(params);
|
||||||
@@ -111,10 +130,71 @@ export class RaceService {
|
|||||||
throw new Error('Failed to get race detail');
|
throw new Error('Failed to get race detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value;
|
const outputPort = result.value;
|
||||||
|
|
||||||
|
// Map to DTO
|
||||||
|
const raceDTO = outputPort.race ? {
|
||||||
|
id: outputPort.race.id,
|
||||||
|
leagueId: outputPort.race.leagueId,
|
||||||
|
track: outputPort.race.track,
|
||||||
|
car: outputPort.race.car,
|
||||||
|
scheduledAt: outputPort.race.scheduledAt.toISOString(),
|
||||||
|
sessionType: outputPort.race.sessionType,
|
||||||
|
status: outputPort.race.status,
|
||||||
|
strengthOfField: outputPort.race.strengthOfField ?? null,
|
||||||
|
registeredCount: outputPort.race.registeredCount ?? undefined,
|
||||||
|
maxParticipants: outputPort.race.maxParticipants ?? undefined,
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
const leagueDTO = outputPort.league ? {
|
||||||
|
id: outputPort.league.id.toString(),
|
||||||
|
name: outputPort.league.name.toString(),
|
||||||
|
description: outputPort.league.description.toString(),
|
||||||
|
settings: {
|
||||||
|
maxDrivers: outputPort.league.settings.maxDrivers ?? undefined,
|
||||||
|
qualifyingFormat: outputPort.league.settings.qualifyingFormat ?? undefined,
|
||||||
|
},
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
const entryListDTO = await Promise.all(outputPort.drivers.map(async driver => {
|
||||||
|
const ratingResult = await this.driverRatingProvider.getDriverRating({ driverId: driver.id });
|
||||||
|
const avatarResult = await this.imageService.getDriverAvatar({ driverId: driver.id });
|
||||||
|
return {
|
||||||
|
id: driver.id,
|
||||||
|
name: driver.name.toString(),
|
||||||
|
country: driver.country.toString(),
|
||||||
|
avatarUrl: avatarResult.avatarUrl,
|
||||||
|
rating: ratingResult.rating,
|
||||||
|
isCurrentUser: driver.id === params.driverId,
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
const registrationDTO = {
|
||||||
|
isUserRegistered: outputPort.isUserRegistered,
|
||||||
|
canRegister: outputPort.canRegister,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userResultDTO = outputPort.userResult ? {
|
||||||
|
position: outputPort.userResult.position.toNumber(),
|
||||||
|
startPosition: outputPort.userResult.startPosition.toNumber(),
|
||||||
|
incidents: outputPort.userResult.incidents.toNumber(),
|
||||||
|
fastestLap: outputPort.userResult.fastestLap.toNumber(),
|
||||||
|
positionChange: outputPort.userResult.getPositionChange(),
|
||||||
|
isPodium: outputPort.userResult.isPodium(),
|
||||||
|
isClean: outputPort.userResult.isClean(),
|
||||||
|
ratingChange: this.calculateRatingChange(outputPort.userResult.position.toNumber()),
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
race: raceDTO,
|
||||||
|
league: leagueDTO,
|
||||||
|
entryList: entryListDTO,
|
||||||
|
registration: registrationDTO,
|
||||||
|
userResult: userResultDTO,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRacesPageData(): Promise<RacesPageViewModel> {
|
async getRacesPageData(): Promise<RacesPageDataDTO> {
|
||||||
this.logger.debug('[RaceService] Fetching races page data.');
|
this.logger.debug('[RaceService] Fetching races page data.');
|
||||||
|
|
||||||
const result = await this.getRacesPageDataUseCase.execute();
|
const result = await this.getRacesPageDataUseCase.execute();
|
||||||
@@ -123,10 +203,33 @@ export class RaceService {
|
|||||||
throw new Error('Failed to get races page data');
|
throw new Error('Failed to get races page data');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value;
|
const outputPort = result.value;
|
||||||
|
|
||||||
|
// Fetch leagues for league names
|
||||||
|
const allLeagues = await this.leagueRepository.findAll();
|
||||||
|
const leagueMap = new Map(allLeagues.map(l => [l.id, l.name]));
|
||||||
|
|
||||||
|
// Map to DTO
|
||||||
|
const racesDTO = outputPort.races.map(race => ({
|
||||||
|
id: race.id,
|
||||||
|
track: race.track,
|
||||||
|
car: race.car,
|
||||||
|
scheduledAt: race.scheduledAt.toISOString(),
|
||||||
|
status: race.status,
|
||||||
|
leagueId: race.leagueId,
|
||||||
|
leagueName: leagueMap.get(race.leagueId) ?? 'Unknown League',
|
||||||
|
strengthOfField: race.strengthOfField,
|
||||||
|
isUpcoming: race.scheduledAt > new Date(),
|
||||||
|
isLive: race.status === 'running',
|
||||||
|
isPast: race.scheduledAt < new Date() && race.status === 'completed',
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
races: racesDTO,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllRacesPageData(): Promise<RacesPageViewModel> {
|
async getAllRacesPageData(): Promise<AllRacesPageDTO> {
|
||||||
this.logger.debug('[RaceService] Fetching all races page data.');
|
this.logger.debug('[RaceService] Fetching all races page data.');
|
||||||
|
|
||||||
const result = await this.getAllRacesPageDataUseCase.execute();
|
const result = await this.getAllRacesPageDataUseCase.execute();
|
||||||
@@ -135,10 +238,10 @@ export class RaceService {
|
|||||||
throw new Error('Failed to get all races page data');
|
throw new Error('Failed to get all races page data');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value;
|
return result.value as AllRacesPageDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRaceResultsDetail(raceId: string): Promise<RaceResultsDetailViewModel> {
|
async getRaceResultsDetail(raceId: string): Promise<RaceResultsDetailDTO> {
|
||||||
this.logger.debug('[RaceService] Fetching race results detail:', { raceId });
|
this.logger.debug('[RaceService] Fetching race results detail:', { raceId });
|
||||||
|
|
||||||
const result = await this.getRaceResultsDetailUseCase.execute({ raceId });
|
const result = await this.getRaceResultsDetailUseCase.execute({ raceId });
|
||||||
@@ -147,10 +250,41 @@ export class RaceService {
|
|||||||
throw new Error('Failed to get race results detail');
|
throw new Error('Failed to get race results detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value;
|
const outputPort = result.value;
|
||||||
|
|
||||||
|
// Create a map of driverId to driver for easy lookup
|
||||||
|
const driverMap = new Map(outputPort.drivers.map(driver => [driver.id, driver]));
|
||||||
|
|
||||||
|
const resultsDTO = await Promise.all(outputPort.results.map(async (result) => {
|
||||||
|
const driver = driverMap.get(result.driverId.toString());
|
||||||
|
if (!driver) {
|
||||||
|
throw new Error(`Driver not found for result: ${result.driverId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarResult = await this.imageService.getDriverAvatar({ driverId: driver.id });
|
||||||
|
|
||||||
|
return {
|
||||||
|
driverId: result.driverId.toString(),
|
||||||
|
driverName: driver.name.toString(),
|
||||||
|
avatarUrl: avatarResult.avatarUrl,
|
||||||
|
position: result.position.toNumber(),
|
||||||
|
startPosition: result.startPosition.toNumber(),
|
||||||
|
incidents: result.incidents.toNumber(),
|
||||||
|
fastestLap: result.fastestLap.toNumber(),
|
||||||
|
positionChange: result.getPositionChange(),
|
||||||
|
isPodium: result.isPodium(),
|
||||||
|
isClean: result.isClean(),
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
raceId: outputPort.race.id,
|
||||||
|
track: outputPort.race.track,
|
||||||
|
results: resultsDTO,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRaceWithSOF(raceId: string): Promise<RaceWithSOFViewModel> {
|
async getRaceWithSOF(raceId: string): Promise<RaceWithSOFDTO> {
|
||||||
this.logger.debug('[RaceService] Fetching race with SOF:', { raceId });
|
this.logger.debug('[RaceService] Fetching race with SOF:', { raceId });
|
||||||
|
|
||||||
const result = await this.getRaceWithSOFUseCase.execute({ raceId });
|
const result = await this.getRaceWithSOFUseCase.execute({ raceId });
|
||||||
@@ -159,10 +293,17 @@ export class RaceService {
|
|||||||
throw new Error('Failed to get race with SOF');
|
throw new Error('Failed to get race with SOF');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value;
|
const outputPort = result.value;
|
||||||
|
|
||||||
|
// Map to DTO
|
||||||
|
return {
|
||||||
|
id: outputPort.id,
|
||||||
|
track: outputPort.track,
|
||||||
|
strengthOfField: outputPort.strengthOfField,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRaceProtests(raceId: string): Promise<RaceProtestsViewModel> {
|
async getRaceProtests(raceId: string): Promise<RaceProtestsDTO> {
|
||||||
this.logger.debug('[RaceService] Fetching race protests:', { raceId });
|
this.logger.debug('[RaceService] Fetching race protests:', { raceId });
|
||||||
|
|
||||||
const result = await this.getRaceProtestsUseCase.execute({ raceId });
|
const result = await this.getRaceProtestsUseCase.execute({ raceId });
|
||||||
@@ -171,10 +312,32 @@ export class RaceService {
|
|||||||
throw new Error('Failed to get race protests');
|
throw new Error('Failed to get race protests');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value;
|
const outputPort = result.value;
|
||||||
|
|
||||||
|
const protestsDTO = outputPort.protests.map(protest => ({
|
||||||
|
id: protest.id,
|
||||||
|
protestingDriverId: protest.protestingDriverId,
|
||||||
|
accusedDriverId: protest.accusedDriverId,
|
||||||
|
incident: {
|
||||||
|
lap: protest.incident.lap,
|
||||||
|
description: protest.incident.description,
|
||||||
|
},
|
||||||
|
status: protest.status,
|
||||||
|
filedAt: protest.filedAt.toISOString(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const driverMap: Record<string, string> = {};
|
||||||
|
outputPort.drivers.forEach(driver => {
|
||||||
|
driverMap[driver.id] = driver.name.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
protests: protestsDTO,
|
||||||
|
driverMap,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRacePenalties(raceId: string): Promise<RacePenaltiesViewModel> {
|
async getRacePenalties(raceId: string): Promise<RacePenaltiesDTO> {
|
||||||
this.logger.debug('[RaceService] Fetching race penalties:', { raceId });
|
this.logger.debug('[RaceService] Fetching race penalties:', { raceId });
|
||||||
|
|
||||||
const result = await this.getRacePenaltiesUseCase.execute({ raceId });
|
const result = await this.getRacePenaltiesUseCase.execute({ raceId });
|
||||||
@@ -183,7 +346,28 @@ export class RaceService {
|
|||||||
throw new Error('Failed to get race penalties');
|
throw new Error('Failed to get race penalties');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value;
|
const outputPort = result.value;
|
||||||
|
|
||||||
|
const penaltiesDTO = outputPort.penalties.map(penalty => ({
|
||||||
|
id: penalty.id,
|
||||||
|
driverId: penalty.driverId,
|
||||||
|
type: penalty.type,
|
||||||
|
value: penalty.value ?? 0,
|
||||||
|
reason: penalty.reason,
|
||||||
|
issuedBy: penalty.issuedBy,
|
||||||
|
issuedAt: penalty.issuedAt.toISOString(),
|
||||||
|
notes: penalty.notes,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const driverMap: Record<string, string> = {};
|
||||||
|
outputPort.drivers.forEach(driver => {
|
||||||
|
driverMap[driver.id] = driver.name.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
penalties: penaltiesDTO,
|
||||||
|
driverMap,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerForRace(params: RegisterForRaceParamsDTO): Promise<void> {
|
async registerForRace(params: RegisterForRaceParamsDTO): Promise<void> {
|
||||||
@@ -277,4 +461,10 @@ export class RaceService {
|
|||||||
throw new Error('Failed to review protest');
|
throw new Error('Failed to review protest');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private calculateRatingChange(position: number): number {
|
||||||
|
const baseChange = position <= 3 ? 25 : position <= 10 ? 10 : -5;
|
||||||
|
const positionBonus = Math.max(0, (20 - position) * 2);
|
||||||
|
return baseChange + positionBonus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,45 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { RaceViewModel } from '@core/racing/application/presenters/IGetAllRacesPresenter';
|
|
||||||
|
|
||||||
export class AllRacesPageDTO {
|
export type AllRacesStatus = 'scheduled' | 'running' | 'completed' | 'cancelled' | 'all';
|
||||||
@ApiProperty({ type: [RaceViewModel] })
|
|
||||||
races!: RaceViewModel[];
|
export class AllRacesListItemDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
totalCount!: number;
|
track!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
car!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
scheduledAt!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
status!: 'scheduled' | 'running' | 'completed' | 'cancelled';
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
leagueId!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
leagueName!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
strengthOfField!: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AllRacesFilterOptionsDTO {
|
||||||
|
@ApiProperty({ type: [{ value: String, label: String }] })
|
||||||
|
statuses!: { value: AllRacesStatus; label: string }[];
|
||||||
|
|
||||||
|
@ApiProperty({ type: [{ id: String, name: String }] })
|
||||||
|
leagues!: { id: string; name: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AllRacesPageDTO {
|
||||||
|
@ApiProperty({ type: [AllRacesListItemDTO] })
|
||||||
|
races!: AllRacesListItemDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ type: AllRacesFilterOptionsDTO })
|
||||||
|
filters!: AllRacesFilterOptionsDTO;
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,39 @@
|
|||||||
import { IGetAllRacesPresenter, GetAllRacesResultDTO, AllRacesPageViewModel } from '@core/racing/application/presenters/IGetAllRacesPresenter';
|
import { GetAllRacesOutputPort } from '@core/racing/application/ports/output/GetAllRacesOutputPort';
|
||||||
|
import { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
||||||
|
|
||||||
export class GetAllRacesPresenter implements IGetAllRacesPresenter {
|
export class GetAllRacesPresenter {
|
||||||
private result: AllRacesPageViewModel | null = null;
|
private result: AllRacesPageDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: GetAllRacesResultDTO) {
|
async present(output: GetAllRacesOutputPort) {
|
||||||
this.result = dto;
|
this.result = {
|
||||||
|
races: output.races.map(race => ({
|
||||||
|
id: race.id,
|
||||||
|
track: race.track,
|
||||||
|
car: race.car,
|
||||||
|
scheduledAt: race.scheduledAt,
|
||||||
|
status: race.status,
|
||||||
|
leagueId: race.leagueId,
|
||||||
|
leagueName: race.leagueName,
|
||||||
|
strengthOfField: race.strengthOfField,
|
||||||
|
})),
|
||||||
|
filters: {
|
||||||
|
statuses: [
|
||||||
|
{ value: 'all', label: 'All' },
|
||||||
|
{ value: 'scheduled', label: 'Scheduled' },
|
||||||
|
{ value: 'running', label: 'Running' },
|
||||||
|
{ value: 'completed', label: 'Completed' },
|
||||||
|
{ value: 'cancelled', label: 'Cancelled' },
|
||||||
|
],
|
||||||
|
leagues: [], // TODO: populate if needed
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): AllRacesPageViewModel | null {
|
getViewModel(): AllRacesPageDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
import { IGetTotalRacesPresenter, GetTotalRacesResultDTO, GetTotalRacesViewModel } from '@core/racing/application/presenters/IGetTotalRacesPresenter';
|
import { GetTotalRacesOutputPort } from '@core/racing/application/ports/output/GetTotalRacesOutputPort';
|
||||||
|
import { RaceStatsDTO } from '../dtos/RaceStatsDTO';
|
||||||
|
|
||||||
export class GetTotalRacesPresenter implements IGetTotalRacesPresenter {
|
export class GetTotalRacesPresenter {
|
||||||
private result: GetTotalRacesViewModel | null = null;
|
private result: RaceStatsDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: GetTotalRacesResultDTO) {
|
present(output: GetTotalRacesOutputPort) {
|
||||||
this.result = {
|
this.result = {
|
||||||
totalRaces: dto.totalRaces,
|
totalRaces: output.totalRaces,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): GetTotalRacesViewModel | null {
|
getViewModel(): RaceStatsDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,24 @@
|
|||||||
import { IImportRaceResultsApiPresenter, ImportRaceResultsApiResultDTO, ImportRaceResultsSummaryViewModel } from '@core/racing/application/presenters/IImportRaceResultsApiPresenter';
|
import { ImportRaceResultsApiOutputPort } from '@core/racing/application/ports/output/ImportRaceResultsApiOutputPort';
|
||||||
|
import { ImportRaceResultsSummaryDTO } from '../dtos/ImportRaceResultsSummaryDTO';
|
||||||
|
|
||||||
export class ImportRaceResultsApiPresenter implements IImportRaceResultsApiPresenter {
|
export class ImportRaceResultsApiPresenter {
|
||||||
private result: ImportRaceResultsSummaryViewModel | null = null;
|
private result: ImportRaceResultsSummaryDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: ImportRaceResultsApiResultDTO) {
|
present(output: ImportRaceResultsApiOutputPort) {
|
||||||
this.result = dto;
|
this.result = {
|
||||||
|
success: output.success,
|
||||||
|
raceId: output.raceId,
|
||||||
|
driversProcessed: output.driversProcessed,
|
||||||
|
resultsRecorded: output.resultsRecorded,
|
||||||
|
errors: output.errors,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): ImportRaceResultsSummaryViewModel | null {
|
getViewModel(): ImportRaceResultsSummaryDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,12 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||||||
import { SponsorshipPricingItemDTO } from './SponsorshipPricingItemDTO';
|
import { SponsorshipPricingItemDTO } from './SponsorshipPricingItemDTO';
|
||||||
|
|
||||||
export class GetEntitySponsorshipPricingResultDTO {
|
export class GetEntitySponsorshipPricingResultDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
entityType: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
entityId: string;
|
||||||
|
|
||||||
@ApiProperty({ type: [SponsorshipPricingItemDTO] })
|
@ApiProperty({ type: [SponsorshipPricingItemDTO] })
|
||||||
pricing: SponsorshipPricingItemDTO[];
|
pricing: SponsorshipPricingItemDTO[];
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,15 @@ export class SponsorDTO {
|
|||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
contactEmail?: string;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
logoUrl?: string;
|
logoUrl?: string;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
websiteUrl?: string;
|
websiteUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
createdAt?: Date;
|
||||||
}
|
}
|
||||||
@@ -10,9 +10,9 @@ describe('CreateSponsorPresenter', () => {
|
|||||||
|
|
||||||
describe('reset', () => {
|
describe('reset', () => {
|
||||||
it('should reset the result to null', () => {
|
it('should reset the result to null', () => {
|
||||||
const mockResult = { id: 'sponsor-1', name: 'Test Sponsor' };
|
const mockPort = { sponsor: { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com', createdAt: new Date() } };
|
||||||
presenter.present(mockResult);
|
presenter.present(mockPort);
|
||||||
expect(presenter.viewModel).toEqual(mockResult);
|
expect(presenter.viewModel).toEqual({ sponsor: mockPort.sponsor });
|
||||||
|
|
||||||
presenter.reset();
|
presenter.reset();
|
||||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||||
@@ -21,11 +21,11 @@ describe('CreateSponsorPresenter', () => {
|
|||||||
|
|
||||||
describe('present', () => {
|
describe('present', () => {
|
||||||
it('should store the result', () => {
|
it('should store the result', () => {
|
||||||
const mockResult = { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com' };
|
const mockPort = { sponsor: { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com', createdAt: new Date() } };
|
||||||
|
|
||||||
presenter.present(mockResult);
|
presenter.present(mockPort);
|
||||||
|
|
||||||
expect(presenter.viewModel).toEqual(mockResult);
|
expect(presenter.viewModel).toEqual({ sponsor: mockPort.sponsor });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -35,10 +35,10 @@ describe('CreateSponsorPresenter', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return the result when presented', () => {
|
it('should return the result when presented', () => {
|
||||||
const mockResult = { id: 'sponsor-1', name: 'Test Sponsor' };
|
const mockPort = { sponsor: { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com', createdAt: new Date() } };
|
||||||
presenter.present(mockResult);
|
presenter.present(mockPort);
|
||||||
|
|
||||||
expect(presenter.getViewModel()).toEqual(mockResult);
|
expect(presenter.getViewModel()).toEqual({ sponsor: mockPort.sponsor });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,10 +48,10 @@ describe('CreateSponsorPresenter', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return the result when presented', () => {
|
it('should return the result when presented', () => {
|
||||||
const mockResult = { id: 'sponsor-1', name: 'Test Sponsor' };
|
const mockPort = { sponsor: { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com', createdAt: new Date() } };
|
||||||
presenter.present(mockResult);
|
presenter.present(mockPort);
|
||||||
|
|
||||||
expect(presenter.viewModel).toEqual(mockResult);
|
expect(presenter.viewModel).toEqual({ sponsor: mockPort.sponsor });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,21 +1,31 @@
|
|||||||
import { CreateSponsorViewModel, CreateSponsorOutputPort, ICreateSponsorPresenter } from '@core/racing/application/presenters/ICreateSponsorPresenter';
|
import type { CreateSponsorOutputPort } from '@core/racing/application/ports/output/CreateSponsorOutputPort';
|
||||||
|
import type { CreateSponsorOutputDTO } from '../dtos/CreateSponsorOutputDTO';
|
||||||
|
|
||||||
export class CreateSponsorPresenter implements ICreateSponsorPresenter {
|
export class CreateSponsorPresenter {
|
||||||
private result: CreateSponsorViewModel | null = null;
|
private result: CreateSponsorOutputDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: CreateSponsorOutputPort) {
|
present(port: CreateSponsorOutputPort) {
|
||||||
this.result = dto;
|
this.result = {
|
||||||
|
sponsor: {
|
||||||
|
id: port.sponsor.id,
|
||||||
|
name: port.sponsor.name,
|
||||||
|
contactEmail: port.sponsor.contactEmail,
|
||||||
|
logoUrl: port.sponsor.logoUrl,
|
||||||
|
websiteUrl: port.sponsor.websiteUrl,
|
||||||
|
createdAt: port.sponsor.createdAt,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): CreateSponsorViewModel | null {
|
getViewModel(): CreateSponsorOutputDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
get viewModel(): CreateSponsorViewModel {
|
get viewModel(): CreateSponsorOutputDTO {
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
if (!this.result) throw new Error('Presenter not presented');
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,41 @@
|
|||||||
import type { GetEntitySponsorshipPricingResultDTO } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
import type { GetEntitySponsorshipPricingOutputPort } from '@core/racing/application/ports/output/GetEntitySponsorshipPricingOutputPort';
|
||||||
import type { IEntitySponsorshipPricingPresenter } from '@core/racing/application/presenters/IEntitySponsorshipPricingPresenter';
|
import { GetEntitySponsorshipPricingResultDTO } from '../dtos/GetEntitySponsorshipPricingResultDTO';
|
||||||
|
|
||||||
export class GetEntitySponsorshipPricingPresenter implements IEntitySponsorshipPricingPresenter {
|
export class GetEntitySponsorshipPricingPresenter {
|
||||||
private result: GetEntitySponsorshipPricingResultDTO | null = null;
|
private result: GetEntitySponsorshipPricingResultDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: GetEntitySponsorshipPricingResultDTO | null) {
|
async present(output: GetEntitySponsorshipPricingOutputPort | null) {
|
||||||
this.result = dto;
|
if (!output) {
|
||||||
|
this.result = { pricing: [] };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pricing = [];
|
||||||
|
if (output.mainSlot) {
|
||||||
|
pricing.push({
|
||||||
|
id: `${output.entityType}-${output.entityId}-main`,
|
||||||
|
level: 'main',
|
||||||
|
price: output.mainSlot.price,
|
||||||
|
currency: output.mainSlot.currency,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (output.secondarySlot) {
|
||||||
|
pricing.push({
|
||||||
|
id: `${output.entityType}-${output.entityId}-secondary`,
|
||||||
|
level: 'secondary',
|
||||||
|
price: output.secondarySlot.price,
|
||||||
|
currency: output.secondarySlot.currency,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.result = { pricing };
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): GetEntitySponsorshipPricingResultDTO | null {
|
getViewModel(): GetEntitySponsorshipPricingResultDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
get viewModel(): GetEntitySponsorshipPricingResultDTO | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { PendingSponsorshipRequestsOutputPort } from '@core/racing/application/ports/output/PendingSponsorshipRequestsOutputPort';
|
||||||
|
import { GetPendingSponsorshipRequestsOutputDTO } from '../dtos/GetPendingSponsorshipRequestsOutputDTO';
|
||||||
|
|
||||||
|
export class GetPendingSponsorshipRequestsPresenter {
|
||||||
|
present(outputPort: PendingSponsorshipRequestsOutputPort): GetPendingSponsorshipRequestsOutputDTO {
|
||||||
|
return {
|
||||||
|
entityType: outputPort.entityType,
|
||||||
|
entityId: outputPort.entityId,
|
||||||
|
requests: outputPort.requests,
|
||||||
|
totalCount: outputPort.totalCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,8 @@
|
|||||||
import type { SponsorDashboardDTO } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
import type { SponsorDashboardOutputPort } from '@core/racing/application/ports/output/SponsorDashboardOutputPort';
|
||||||
import type { ISponsorDashboardPresenter, SponsorDashboardViewModel } from '@core/racing/application/presenters/ISponsorDashboardPresenter';
|
import { SponsorDashboardDTO } from '../dtos/SponsorDashboardDTO';
|
||||||
|
|
||||||
export class GetSponsorDashboardPresenter implements ISponsorDashboardPresenter {
|
export class GetSponsorDashboardPresenter {
|
||||||
private result: SponsorDashboardViewModel | null = null;
|
present(outputPort: SponsorDashboardOutputPort | null): SponsorDashboardDTO | null {
|
||||||
|
return outputPort;
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: SponsorDashboardDTO | null) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): SponsorDashboardViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): SponsorDashboardViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,8 @@
|
|||||||
import type { SponsorSponsorshipsDTO } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
import type { SponsorSponsorshipsOutputPort } from '@core/racing/application/ports/output/SponsorSponsorshipsOutputPort';
|
||||||
import type { ISponsorSponsorshipsPresenter, SponsorSponsorshipsViewModel } from '@core/racing/application/presenters/ISponsorSponsorshipsPresenter';
|
import { SponsorSponsorshipsDTO } from '../dtos/SponsorSponsorshipsDTO';
|
||||||
|
|
||||||
export class GetSponsorSponsorshipsPresenter implements ISponsorSponsorshipsPresenter {
|
export class GetSponsorSponsorshipsPresenter {
|
||||||
private result: SponsorSponsorshipsViewModel | null = null;
|
present(outputPort: SponsorSponsorshipsOutputPort | null): SponsorSponsorshipsDTO | null {
|
||||||
|
return outputPort;
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: SponsorSponsorshipsDTO | null) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): SponsorSponsorshipsViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): SponsorSponsorshipsViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,10 @@
|
|||||||
import { GetSponsorsViewModel, GetSponsorsResultDTO, IGetSponsorsPresenter } from '@core/racing/application/presenters/IGetSponsorsPresenter';
|
import type { GetSponsorsOutputPort } from '@core/racing/application/ports/output/GetSponsorsOutputPort';
|
||||||
|
import { GetSponsorsOutputDTO } from '../dtos/GetSponsorsOutputDTO';
|
||||||
|
|
||||||
export class GetSponsorsPresenter implements IGetSponsorsPresenter {
|
export class GetSponsorsPresenter {
|
||||||
private result: GetSponsorsViewModel | null = null;
|
present(outputPort: GetSponsorsOutputPort): GetSponsorsOutputDTO {
|
||||||
|
return {
|
||||||
reset() {
|
sponsors: outputPort.sponsors,
|
||||||
this.result = null;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
present(dto: GetSponsorsResultDTO) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): GetSponsorsViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): GetSponsorsViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,12 @@
|
|||||||
import { GetSponsorshipPricingViewModel, GetSponsorshipPricingResultDTO, IGetSponsorshipPricingPresenter } from '@core/racing/application/presenters/IGetSponsorshipPricingPresenter';
|
import type { GetSponsorshipPricingOutputPort } from '@core/racing/application/ports/output/GetSponsorshipPricingOutputPort';
|
||||||
|
import { GetEntitySponsorshipPricingResultDTO } from '../dtos/GetEntitySponsorshipPricingResultDTO';
|
||||||
|
|
||||||
export class GetSponsorshipPricingPresenter implements IGetSponsorshipPricingPresenter {
|
export class GetSponsorshipPricingPresenter {
|
||||||
private result: GetSponsorshipPricingViewModel | null = null;
|
present(outputPort: GetSponsorshipPricingOutputPort): GetEntitySponsorshipPricingResultDTO {
|
||||||
|
return {
|
||||||
reset() {
|
entityType: outputPort.entityType,
|
||||||
this.result = null;
|
entityId: outputPort.entityId,
|
||||||
}
|
pricing: outputPort.pricing,
|
||||||
|
};
|
||||||
present(dto: GetSponsorshipPricingResultDTO) {
|
|
||||||
this.result = dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): GetSponsorshipPricingViewModel | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): GetSponsorshipPricingViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export type SkillLevel = 'beginner' | 'intermediate' | 'advanced' | 'pro';
|
||||||
|
|
||||||
|
class TeamLeaderboardItemDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
memberCount: number;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
rating: number | null;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
totalWins: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
totalRaces: number;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: ['beginner', 'intermediate', 'advanced', 'pro'] })
|
||||||
|
performanceLevel: SkillLevel;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
isRecruiting: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
createdAt: string;
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: ['endurance', 'sprint', 'mixed'], required: false })
|
||||||
|
specialization?: 'endurance' | 'sprint' | 'mixed';
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
region?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: [String], required: false })
|
||||||
|
languages?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetTeamsLeaderboardOutputDTO {
|
||||||
|
@ApiProperty({ type: [TeamLeaderboardItemDTO] })
|
||||||
|
teams: TeamLeaderboardItemDTO[];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
recruitingCount: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'object', additionalProperties: { type: 'array', items: { $ref: '#/components/schemas/TeamLeaderboardItemDTO' } } })
|
||||||
|
groupsBySkillLevel: Record<SkillLevel, TeamLeaderboardItemDTO[]>;
|
||||||
|
|
||||||
|
@ApiProperty({ type: [TeamLeaderboardItemDTO] })
|
||||||
|
topTeams: TeamLeaderboardItemDTO[];
|
||||||
|
}
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
import { IAllTeamsPresenter, AllTeamsResultDTO, AllTeamsViewModel, TeamListItemViewModel } from '@core/racing/application/presenters/IAllTeamsPresenter';
|
import { GetAllTeamsOutputPort } from '@core/racing/application/ports/output/GetAllTeamsOutputPort';
|
||||||
|
import { GetAllTeamsOutputDTO } from '../dtos/GetAllTeamsOutputDTO';
|
||||||
|
|
||||||
export class AllTeamsPresenter implements IAllTeamsPresenter {
|
export class AllTeamsPresenter {
|
||||||
private result: AllTeamsViewModel | null = null;
|
private result: GetAllTeamsOutputDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: AllTeamsResultDTO) {
|
async present(output: GetAllTeamsOutputPort) {
|
||||||
const teams: TeamListItemViewModel[] = dto.teams.map(team => ({
|
|
||||||
id: team.id,
|
|
||||||
name: team.name,
|
|
||||||
tag: team.tag,
|
|
||||||
description: team.description,
|
|
||||||
memberCount: team.memberCount,
|
|
||||||
leagues: team.leagues || [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.result = {
|
this.result = {
|
||||||
teams,
|
teams: output.teams.map(team => ({
|
||||||
totalCount: teams.length,
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
tag: team.tag,
|
||||||
|
description: team.description,
|
||||||
|
memberCount: team.memberCount,
|
||||||
|
leagues: team.leagues || [],
|
||||||
|
// Note: specialization, region, languages not available in output port
|
||||||
|
})),
|
||||||
|
totalCount: output.totalCount || output.teams.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): AllTeamsViewModel | null {
|
getViewModel(): GetAllTeamsOutputDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
get viewModel(): AllTeamsViewModel {
|
get viewModel(): GetAllTeamsOutputDTO {
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
if (!this.result) throw new Error('Presenter not presented');
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,38 @@
|
|||||||
import { IDriverTeamPresenter, DriverTeamResultDTO, DriverTeamViewModel } from '@core/racing/application/presenters/IDriverTeamPresenter';
|
import { DriverTeamOutputPort } from '@core/racing/application/ports/output/DriverTeamOutputPort';
|
||||||
|
import { GetDriverTeamOutputDTO } from '../dtos/GetDriverTeamOutputDTO';
|
||||||
|
|
||||||
export class DriverTeamPresenter implements IDriverTeamPresenter {
|
export class DriverTeamPresenter {
|
||||||
private result: DriverTeamViewModel | null = null;
|
private result: GetDriverTeamOutputDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: DriverTeamResultDTO) {
|
async present(output: DriverTeamOutputPort) {
|
||||||
const isOwner = dto.team.ownerId === dto.driverId;
|
const isOwner = output.team.ownerId === output.driverId;
|
||||||
const canManage = isOwner || dto.membership.role === 'owner' || dto.membership.role === 'manager';
|
const canManage = isOwner || output.membership.role === 'owner' || output.membership.role === 'manager';
|
||||||
|
|
||||||
this.result = {
|
this.result = {
|
||||||
team: {
|
team: {
|
||||||
id: dto.team.id,
|
id: output.team.id,
|
||||||
name: dto.team.name,
|
name: output.team.name,
|
||||||
tag: dto.team.tag,
|
tag: output.team.tag,
|
||||||
description: dto.team.description || '',
|
description: output.team.description || '',
|
||||||
ownerId: dto.team.ownerId,
|
ownerId: output.team.ownerId,
|
||||||
leagues: dto.team.leagues || [],
|
leagues: output.team.leagues || [],
|
||||||
|
createdAt: output.team.createdAt.toISOString(),
|
||||||
},
|
},
|
||||||
membership: {
|
membership: {
|
||||||
role: dto.membership.role as 'owner' | 'manager' | 'member',
|
role: output.membership.role === 'driver' ? 'member' : output.membership.role,
|
||||||
joinedAt: dto.membership.joinedAt.toISOString(),
|
joinedAt: output.membership.joinedAt.toISOString(),
|
||||||
isActive: dto.membership.status === 'active',
|
isActive: output.membership.status === 'active',
|
||||||
},
|
},
|
||||||
isOwner,
|
isOwner,
|
||||||
canManage,
|
canManage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): DriverTeamViewModel | null {
|
getViewModel(): GetDriverTeamOutputDTO | null {
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): DriverTeamViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,50 +1,36 @@
|
|||||||
import {
|
import type { GetTeamDetailsOutputPort } from '@core/racing/application/ports/output/GetTeamDetailsOutputPort';
|
||||||
ITeamDetailsPresenter,
|
import type { GetTeamDetailsOutputDTO } from '../dtos/GetTeamDetailsOutputDTO';
|
||||||
TeamDetailsResultDTO,
|
|
||||||
TeamDetailsViewModel,
|
|
||||||
} from '@core/racing/application/presenters/ITeamDetailsPresenter';
|
|
||||||
|
|
||||||
export class TeamDetailsPresenter implements ITeamDetailsPresenter {
|
export class TeamDetailsPresenter {
|
||||||
private result: TeamDetailsViewModel | null = null;
|
private result: GetTeamDetailsOutputDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: TeamDetailsResultDTO) {
|
async present(outputPort: GetTeamDetailsOutputPort): Promise<void> {
|
||||||
const { team, membership } = dto;
|
|
||||||
|
|
||||||
const canManage =
|
|
||||||
membership !== null &&
|
|
||||||
(membership.role === 'owner' || membership.role === 'manager');
|
|
||||||
|
|
||||||
this.result = {
|
this.result = {
|
||||||
team: {
|
team: {
|
||||||
id: team.id,
|
id: outputPort.team.id,
|
||||||
name: team.name,
|
name: outputPort.team.name,
|
||||||
tag: team.tag,
|
tag: outputPort.team.tag,
|
||||||
description: team.description,
|
description: outputPort.team.description,
|
||||||
ownerId: team.ownerId,
|
ownerId: outputPort.team.ownerId,
|
||||||
leagues: team.leagues || [],
|
leagues: outputPort.team.leagues,
|
||||||
createdAt: team.createdAt?.toISOString() || new Date().toISOString(),
|
createdAt: outputPort.team.createdAt.toISOString(),
|
||||||
},
|
},
|
||||||
membership: membership
|
membership: outputPort.membership
|
||||||
? {
|
? {
|
||||||
role: membership.role as 'owner' | 'manager' | 'member',
|
role: outputPort.membership.role,
|
||||||
joinedAt: membership.joinedAt.toISOString(),
|
joinedAt: outputPort.membership.joinedAt.toISOString(),
|
||||||
isActive: membership.status === 'active',
|
isActive: outputPort.membership.isActive,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
canManage,
|
canManage: outputPort.canManage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): TeamDetailsViewModel | null {
|
getViewModel(): GetTeamDetailsOutputDTO | null {
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): TeamDetailsViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,30 @@
|
|||||||
import {
|
import type { TeamJoinRequestsOutputPort } from '@core/racing/application/ports/output/TeamJoinRequestsOutputPort';
|
||||||
ITeamJoinRequestsPresenter,
|
import type { GetTeamJoinRequestsOutputDTO } from '../dtos/GetTeamJoinRequestsOutputDTO';
|
||||||
TeamJoinRequestsResultDTO,
|
|
||||||
TeamJoinRequestsViewModel,
|
|
||||||
TeamJoinRequestViewModel,
|
|
||||||
} from '@core/racing/application/presenters/ITeamJoinRequestsPresenter';
|
|
||||||
|
|
||||||
export class TeamJoinRequestsPresenter implements ITeamJoinRequestsPresenter {
|
export class TeamJoinRequestsPresenter {
|
||||||
private result: TeamJoinRequestsViewModel | null = null;
|
private result: GetTeamJoinRequestsOutputDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: TeamJoinRequestsResultDTO) {
|
async present(outputPort: TeamJoinRequestsOutputPort): Promise<void> {
|
||||||
const { requests, driverNames, avatarUrls } = dto;
|
|
||||||
|
|
||||||
const requestViewModels: TeamJoinRequestViewModel[] = requests.map((request) => ({
|
|
||||||
requestId: request.id,
|
|
||||||
driverId: request.driverId,
|
|
||||||
driverName: driverNames[request.driverId] || 'Unknown',
|
|
||||||
teamId: request.teamId,
|
|
||||||
status: 'pending' as const,
|
|
||||||
requestedAt: request.requestedAt.toISOString(),
|
|
||||||
avatarUrl: avatarUrls[request.driverId] || '',
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.result = {
|
this.result = {
|
||||||
requests: requestViewModels,
|
requests: outputPort.requests.map(request => ({
|
||||||
pendingCount: requestViewModels.length,
|
requestId: request.requestId,
|
||||||
totalCount: requestViewModels.length,
|
driverId: request.driverId,
|
||||||
|
driverName: request.driverName,
|
||||||
|
teamId: request.teamId,
|
||||||
|
status: request.status,
|
||||||
|
requestedAt: request.requestedAt.toISOString(),
|
||||||
|
avatarUrl: request.avatarUrl,
|
||||||
|
})),
|
||||||
|
pendingCount: outputPort.pendingCount,
|
||||||
|
totalCount: outputPort.totalCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): TeamJoinRequestsViewModel | null {
|
getViewModel(): GetTeamJoinRequestsOutputDTO | null {
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): TeamJoinRequestsViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,48 +1,31 @@
|
|||||||
import {
|
import type { TeamMembersOutputPort } from '@core/racing/application/ports/output/TeamMembersOutputPort';
|
||||||
ITeamMembersPresenter,
|
import type { GetTeamMembersOutputDTO } from '../dtos/GetTeamMembersOutputDTO';
|
||||||
TeamMembersResultDTO,
|
|
||||||
TeamMembersViewModel,
|
|
||||||
TeamMemberViewModel,
|
|
||||||
} from '@core/racing/application/presenters/ITeamMembersPresenter';
|
|
||||||
|
|
||||||
export class TeamMembersPresenter implements ITeamMembersPresenter {
|
export class TeamMembersPresenter {
|
||||||
private result: TeamMembersViewModel | null = null;
|
private result: GetTeamMembersOutputDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: TeamMembersResultDTO) {
|
async present(outputPort: TeamMembersOutputPort): Promise<void> {
|
||||||
const { memberships, driverNames, avatarUrls } = dto;
|
|
||||||
|
|
||||||
const members: TeamMemberViewModel[] = memberships.map((membership) => ({
|
|
||||||
driverId: membership.driverId,
|
|
||||||
driverName: driverNames[membership.driverId] || 'Unknown',
|
|
||||||
role: membership.role as 'owner' | 'manager' | 'member',
|
|
||||||
joinedAt: membership.joinedAt.toISOString(),
|
|
||||||
isActive: membership.status === 'active',
|
|
||||||
avatarUrl: avatarUrls[membership.driverId] || '',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const ownerCount = members.filter((m) => m.role === 'owner').length;
|
|
||||||
const managerCount = members.filter((m) => m.role === 'manager').length;
|
|
||||||
const memberCount = members.filter((m) => m.role === 'member').length;
|
|
||||||
|
|
||||||
this.result = {
|
this.result = {
|
||||||
members,
|
members: outputPort.members.map(member => ({
|
||||||
totalCount: members.length,
|
driverId: member.driverId,
|
||||||
ownerCount,
|
driverName: member.driverName,
|
||||||
managerCount,
|
role: member.role,
|
||||||
memberCount,
|
joinedAt: member.joinedAt.toISOString(),
|
||||||
|
isActive: member.isActive,
|
||||||
|
avatarUrl: member.avatarUrl,
|
||||||
|
})),
|
||||||
|
totalCount: outputPort.totalCount,
|
||||||
|
ownerCount: outputPort.ownerCount,
|
||||||
|
managerCount: outputPort.managerCount,
|
||||||
|
memberCount: outputPort.memberCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): TeamMembersViewModel | null {
|
getViewModel(): GetTeamMembersOutputDTO | null {
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): TeamMembersViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,112 @@
|
|||||||
import { ITeamsLeaderboardPresenter, TeamsLeaderboardResultDTO, TeamsLeaderboardViewModel, TeamLeaderboardItemViewModel } from '@core/racing/application/presenters/ITeamsLeaderboardPresenter';
|
import type { TeamsLeaderboardOutputPort } from '@core/racing/application/ports/output/TeamsLeaderboardOutputPort';
|
||||||
|
import type { GetTeamsLeaderboardOutputDTO } from '../dtos/GetTeamsLeaderboardOutputDTO';
|
||||||
|
|
||||||
export class TeamsLeaderboardPresenter implements ITeamsLeaderboardPresenter {
|
export class TeamsLeaderboardPresenter {
|
||||||
private result: TeamsLeaderboardViewModel | null = null;
|
private result: GetTeamsLeaderboardOutputDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(dto: TeamsLeaderboardResultDTO) {
|
async present(outputPort: TeamsLeaderboardOutputPort): Promise<void> {
|
||||||
this.result = {
|
this.result = {
|
||||||
teams: dto.teams as TeamLeaderboardItemViewModel[],
|
teams: outputPort.teams.map(team => ({
|
||||||
recruitingCount: dto.recruitingCount,
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
memberCount: team.memberCount,
|
||||||
|
rating: team.rating,
|
||||||
|
totalWins: team.totalWins,
|
||||||
|
totalRaces: team.totalRaces,
|
||||||
|
performanceLevel: team.performanceLevel,
|
||||||
|
isRecruiting: team.isRecruiting,
|
||||||
|
createdAt: team.createdAt.toISOString(),
|
||||||
|
description: team.description,
|
||||||
|
specialization: team.specialization,
|
||||||
|
region: team.region,
|
||||||
|
languages: team.languages,
|
||||||
|
})),
|
||||||
|
recruitingCount: outputPort.recruitingCount,
|
||||||
groupsBySkillLevel: {
|
groupsBySkillLevel: {
|
||||||
beginner: [],
|
beginner: outputPort.groupsBySkillLevel.beginner.map(team => ({
|
||||||
intermediate: [],
|
id: team.id,
|
||||||
advanced: [],
|
name: team.name,
|
||||||
pro: [],
|
memberCount: team.memberCount,
|
||||||
|
rating: team.rating,
|
||||||
|
totalWins: team.totalWins,
|
||||||
|
totalRaces: team.totalRaces,
|
||||||
|
performanceLevel: team.performanceLevel,
|
||||||
|
isRecruiting: team.isRecruiting,
|
||||||
|
createdAt: team.createdAt.toISOString(),
|
||||||
|
description: team.description,
|
||||||
|
specialization: team.specialization,
|
||||||
|
region: team.region,
|
||||||
|
languages: team.languages,
|
||||||
|
})),
|
||||||
|
intermediate: outputPort.groupsBySkillLevel.intermediate.map(team => ({
|
||||||
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
memberCount: team.memberCount,
|
||||||
|
rating: team.rating,
|
||||||
|
totalWins: team.totalWins,
|
||||||
|
totalRaces: team.totalRaces,
|
||||||
|
performanceLevel: team.performanceLevel,
|
||||||
|
isRecruiting: team.isRecruiting,
|
||||||
|
createdAt: team.createdAt.toISOString(),
|
||||||
|
description: team.description,
|
||||||
|
specialization: team.specialization,
|
||||||
|
region: team.region,
|
||||||
|
languages: team.languages,
|
||||||
|
})),
|
||||||
|
advanced: outputPort.groupsBySkillLevel.advanced.map(team => ({
|
||||||
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
memberCount: team.memberCount,
|
||||||
|
rating: team.rating,
|
||||||
|
totalWins: team.totalWins,
|
||||||
|
totalRaces: team.totalRaces,
|
||||||
|
performanceLevel: team.performanceLevel,
|
||||||
|
isRecruiting: team.isRecruiting,
|
||||||
|
createdAt: team.createdAt.toISOString(),
|
||||||
|
description: team.description,
|
||||||
|
specialization: team.specialization,
|
||||||
|
region: team.region,
|
||||||
|
languages: team.languages,
|
||||||
|
})),
|
||||||
|
pro: outputPort.groupsBySkillLevel.pro.map(team => ({
|
||||||
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
memberCount: team.memberCount,
|
||||||
|
rating: team.rating,
|
||||||
|
totalWins: team.totalWins,
|
||||||
|
totalRaces: team.totalRaces,
|
||||||
|
performanceLevel: team.performanceLevel,
|
||||||
|
isRecruiting: team.isRecruiting,
|
||||||
|
createdAt: team.createdAt.toISOString(),
|
||||||
|
description: team.description,
|
||||||
|
specialization: team.specialization,
|
||||||
|
region: team.region,
|
||||||
|
languages: team.languages,
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
topTeams: (dto.teams as TeamLeaderboardItemViewModel[]).slice(0, 10),
|
topTeams: outputPort.topTeams.map(team => ({
|
||||||
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
memberCount: team.memberCount,
|
||||||
|
rating: team.rating,
|
||||||
|
totalWins: team.totalWins,
|
||||||
|
totalRaces: team.totalRaces,
|
||||||
|
performanceLevel: team.performanceLevel,
|
||||||
|
isRecruiting: team.isRecruiting,
|
||||||
|
createdAt: team.createdAt.toISOString(),
|
||||||
|
description: team.description,
|
||||||
|
specialization: team.specialization,
|
||||||
|
region: team.region,
|
||||||
|
languages: team.languages,
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): TeamsLeaderboardViewModel | null {
|
getViewModel(): GetTeamsLeaderboardOutputDTO | null {
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): TeamsLeaderboardViewModel {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ import PerformanceMetrics from './PerformanceMetrics';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { DriverTeamPresenter } from '@/lib/presenters/DriverTeamPresenter';
|
import { DriverTeamPresenter } from '@/lib/presenters/DriverTeamPresenter';
|
||||||
import { getPrimaryLeagueIdForDriver } from '@/lib/leagueMembership';
|
import { getPrimaryLeagueIdForDriver } from '@/lib/leagueMembership';
|
||||||
import type { ProfileOverviewViewModel } from '@core/racing/application/presenters/IProfileOverviewPresenter';
|
import type { ProfileOverviewOutputPort } from '@core/racing/application/ports/output/ProfileOverviewOutputPort';
|
||||||
import type { DriverTeamViewModel } from '@core/racing/application/presenters/IDriverTeamPresenter';
|
import type { DriverTeamViewModel } from '@core/racing/application/presenters/IDriverTeamPresenter';
|
||||||
|
|
||||||
interface DriverProfileProps {
|
interface DriverProfileProps {
|
||||||
@@ -33,7 +33,7 @@ interface DriverProfileStatsViewModel {
|
|||||||
overallRank?: number;
|
overallRank?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DriverProfileOverviewViewModel = ProfileOverviewViewModel | null;
|
type DriverProfileOverviewViewModel = ProfileOverviewOutputPort | null;
|
||||||
|
|
||||||
export default function DriverProfile({ driver, isOwnProfile = false, onEditClick }: DriverProfileProps) {
|
export default function DriverProfile({ driver, isOwnProfile = false, onEditClick }: DriverProfileProps) {
|
||||||
const [profileData, setProfileData] = useState<DriverProfileOverviewViewModel>(null);
|
const [profileData, setProfileData] = useState<DriverProfileOverviewViewModel>(null);
|
||||||
@@ -61,8 +61,8 @@ export default function DriverProfile({ driver, isOwnProfile = false, onEditClic
|
|||||||
const leagueRank = primaryLeagueId
|
const leagueRank = primaryLeagueId
|
||||||
? getLeagueRankings(driver.id, primaryLeagueId)
|
? getLeagueRankings(driver.id, primaryLeagueId)
|
||||||
: { rank: 0, totalDrivers: 0, percentile: 0 };
|
: { rank: 0, totalDrivers: 0, percentile: 0 };
|
||||||
const globalRank = profileData?.currentDriver?.globalRank ?? null;
|
const globalRank = profileData?.driver?.globalRank ?? null;
|
||||||
const totalDrivers = profileData?.currentDriver?.totalDrivers ?? 0;
|
const totalDrivers = profileData?.driver?.totalDrivers ?? 0;
|
||||||
|
|
||||||
const performanceStats = driverStats ? {
|
const performanceStats = driverStats ? {
|
||||||
winRate: driverStats.totalRaces > 0 ? (driverStats.wins / driverStats.totalRaces) * 100 : 0,
|
winRate: driverStats.totalRaces > 0 ? (driverStats.wins / driverStats.totalRaces) * 100 : 0,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Card from '../ui/Card';
|
|||||||
import RankBadge from './RankBadge';
|
import RankBadge from './RankBadge';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { getPrimaryLeagueIdForDriver } from '@/lib/leagueMembership';
|
import { getPrimaryLeagueIdForDriver } from '@/lib/leagueMembership';
|
||||||
import type { ProfileOverviewViewModel } from '@core/racing/application/presenters/IProfileOverviewPresenter';
|
import type { ProfileOverviewOutputPort } from '@core/racing/application/ports/output/ProfileOverviewOutputPort';
|
||||||
|
|
||||||
interface ProfileStatsProps {
|
interface ProfileStatsProps {
|
||||||
driverId?: string;
|
driverId?: string;
|
||||||
@@ -18,7 +18,7 @@ interface ProfileStatsProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type DriverProfileOverviewViewModel = ProfileOverviewViewModel | null;
|
type DriverProfileOverviewViewModel = ProfileOverviewOutputPort | null;
|
||||||
|
|
||||||
export default function ProfileStats({ driverId, stats }: ProfileStatsProps) {
|
export default function ProfileStats({ driverId, stats }: ProfileStatsProps) {
|
||||||
const [profileData, setProfileData] = useState<DriverProfileOverviewViewModel>(null);
|
const [profileData, setProfileData] = useState<DriverProfileOverviewViewModel>(null);
|
||||||
@@ -35,7 +35,7 @@ export default function ProfileStats({ driverId, stats }: ProfileStatsProps) {
|
|||||||
}, [driverId]);
|
}, [driverId]);
|
||||||
|
|
||||||
const driverStats = profileData?.stats || null;
|
const driverStats = profileData?.stats || null;
|
||||||
const totalDrivers = profileData?.currentDriver?.totalDrivers ?? 0;
|
const totalDrivers = profileData?.driver?.totalDrivers ?? 0;
|
||||||
const primaryLeagueId = driverId ? getPrimaryLeagueIdForDriver(driverId) : null;
|
const primaryLeagueId = driverId ? getPrimaryLeagueIdForDriver(driverId) : null;
|
||||||
const leagueRank =
|
const leagueRank =
|
||||||
driverId && primaryLeagueId ? getLeagueRankings(driverId, primaryLeagueId) : null;
|
driverId && primaryLeagueId ? getLeagueRankings(driverId, primaryLeagueId) : null;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export * from './use-cases/GetLeagueDriverSeasonStatsUseCase';
|
|||||||
export * from './use-cases/GetAllLeaguesWithCapacityUseCase';
|
export * from './use-cases/GetAllLeaguesWithCapacityUseCase';
|
||||||
export * from './use-cases/GetAllLeaguesWithCapacityAndScoringUseCase';
|
export * from './use-cases/GetAllLeaguesWithCapacityAndScoringUseCase';
|
||||||
export * from './use-cases/GetAllRacesUseCase';
|
export * from './use-cases/GetAllRacesUseCase';
|
||||||
|
export * from './use-cases/GetAllRacesPageDataUseCase';
|
||||||
export * from './use-cases/GetTotalRacesUseCase';
|
export * from './use-cases/GetTotalRacesUseCase';
|
||||||
export * from './use-cases/ImportRaceResultsApiUseCase';
|
export * from './use-cases/ImportRaceResultsApiUseCase';
|
||||||
export * from './use-cases/ListLeagueScoringPresetsUseCase';
|
export * from './use-cases/ListLeagueScoringPresetsUseCase';
|
||||||
@@ -74,6 +75,8 @@ export type {
|
|||||||
} from './dto/LeagueScheduleDTO';
|
} from './dto/LeagueScheduleDTO';
|
||||||
export type { ChampionshipStandingsOutputPort } from './ports/output/ChampionshipStandingsOutputPort';
|
export type { ChampionshipStandingsOutputPort } from './ports/output/ChampionshipStandingsOutputPort';
|
||||||
export type { ChampionshipStandingsRowOutputPort } from './ports/output/ChampionshipStandingsRowOutputPort';
|
export type { ChampionshipStandingsRowOutputPort } from './ports/output/ChampionshipStandingsRowOutputPort';
|
||||||
|
export type { AllRacesPageOutputPort } from './ports/output/AllRacesPageOutputPort';
|
||||||
|
export type { DriverRegistrationStatusOutputPort } from './ports/output/DriverRegistrationStatusOutputPort';
|
||||||
export type {
|
export type {
|
||||||
LeagueConfigFormModel,
|
LeagueConfigFormModel,
|
||||||
LeagueStructureFormDTO,
|
LeagueStructureFormDTO,
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { League } from '../../../domain/entities/League';
|
||||||
|
import type { Season } from '../../../domain/entities/season/Season';
|
||||||
|
import type { LeagueScoringConfig } from '../../../domain/entities/LeagueScoringConfig';
|
||||||
|
import type { Game } from '../../../domain/entities/Game';
|
||||||
|
import type { LeagueScoringPresetOutputPort } from './LeagueScoringPresetOutputPort';
|
||||||
|
|
||||||
|
export interface LeagueEnrichedData {
|
||||||
|
league: League;
|
||||||
|
usedDriverSlots: number;
|
||||||
|
season?: Season;
|
||||||
|
scoringConfig?: LeagueScoringConfig;
|
||||||
|
game?: Game;
|
||||||
|
preset?: LeagueScoringPresetOutputPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AllLeaguesWithCapacityAndScoringOutputPort {
|
||||||
|
leagues: LeagueEnrichedData[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import type { League } from '../../domain/entities/League';
|
||||||
|
|
||||||
|
export interface AllLeaguesWithCapacityOutputPort {
|
||||||
|
leagues: League[];
|
||||||
|
memberCounts: Record<string, number>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export type AllRacesStatus = 'scheduled' | 'running' | 'completed' | 'cancelled' | 'all';
|
||||||
|
|
||||||
|
export interface AllRacesListItem {
|
||||||
|
id: string;
|
||||||
|
track: string;
|
||||||
|
car: string;
|
||||||
|
scheduledAt: string;
|
||||||
|
status: 'scheduled' | 'running' | 'completed' | 'cancelled';
|
||||||
|
leagueId: string;
|
||||||
|
leagueName: string;
|
||||||
|
strengthOfField: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AllRacesFilterOptions {
|
||||||
|
statuses: { value: AllRacesStatus; label: string }[];
|
||||||
|
leagues: { id: string; name: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AllRacesPageOutputPort {
|
||||||
|
races: AllRacesListItem[];
|
||||||
|
filters: AllRacesFilterOptions;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface ApproveLeagueJoinRequestOutputPort {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface CompleteDriverOnboardingOutputPort {
|
||||||
|
driverId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface CreateLeagueOutputPort {
|
||||||
|
leagueId: string;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { Presenter } from '@core/shared/presentation';
|
|
||||||
import type { FeedItemType } from '@core/social/domain/types/FeedItemType';
|
import type { FeedItemType } from '@core/social/domain/types/FeedItemType';
|
||||||
|
|
||||||
export interface DashboardDriverSummaryViewModel {
|
export interface DashboardDriverSummaryOutputPort {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
country: string;
|
country: string;
|
||||||
@@ -14,7 +13,7 @@ export interface DashboardDriverSummaryViewModel {
|
|||||||
consistency: number | null;
|
consistency: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardRaceSummaryViewModel {
|
export interface DashboardRaceSummaryOutputPort {
|
||||||
id: string;
|
id: string;
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
leagueName: string;
|
leagueName: string;
|
||||||
@@ -25,7 +24,7 @@ export interface DashboardRaceSummaryViewModel {
|
|||||||
isMyLeague: boolean;
|
isMyLeague: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardRecentResultViewModel {
|
export interface DashboardRecentResultOutputPort {
|
||||||
raceId: string;
|
raceId: string;
|
||||||
raceName: string;
|
raceName: string;
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
@@ -35,7 +34,7 @@ export interface DashboardRecentResultViewModel {
|
|||||||
incidents: number;
|
incidents: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardLeagueStandingSummaryViewModel {
|
export interface DashboardLeagueStandingSummaryOutputPort {
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
leagueName: string;
|
leagueName: string;
|
||||||
position: number;
|
position: number;
|
||||||
@@ -43,7 +42,7 @@ export interface DashboardLeagueStandingSummaryViewModel {
|
|||||||
points: number;
|
points: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardFeedItemSummaryViewModel {
|
export interface DashboardFeedItemSummaryOutputPort {
|
||||||
id: string;
|
id: string;
|
||||||
type: FeedItemType;
|
type: FeedItemType;
|
||||||
headline: string;
|
headline: string;
|
||||||
@@ -53,39 +52,34 @@ export interface DashboardFeedItemSummaryViewModel {
|
|||||||
ctaHref?: string;
|
ctaHref?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardFeedSummaryViewModel {
|
export interface DashboardFeedSummaryOutputPort {
|
||||||
notificationCount: number;
|
notificationCount: number;
|
||||||
items: DashboardFeedItemSummaryViewModel[];
|
items: DashboardFeedItemSummaryOutputPort[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardFriendSummaryViewModel {
|
export interface DashboardFriendSummaryOutputPort {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
country: string;
|
country: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardOverviewViewModel {
|
export interface DashboardOverviewOutputPort {
|
||||||
currentDriver: DashboardDriverSummaryViewModel | null;
|
currentDriver: DashboardDriverSummaryOutputPort | null;
|
||||||
myUpcomingRaces: DashboardRaceSummaryViewModel[];
|
myUpcomingRaces: DashboardRaceSummaryOutputPort[];
|
||||||
otherUpcomingRaces: DashboardRaceSummaryViewModel[];
|
otherUpcomingRaces: DashboardRaceSummaryOutputPort[];
|
||||||
/**
|
/**
|
||||||
* All upcoming races for the driver, already sorted by scheduledAt ascending.
|
* All upcoming races for the driver, already sorted by scheduledAt ascending.
|
||||||
*/
|
*/
|
||||||
upcomingRaces: DashboardRaceSummaryViewModel[];
|
upcomingRaces: DashboardRaceSummaryOutputPort[];
|
||||||
/**
|
/**
|
||||||
* Count of distinct leagues that are currently "active" for the driver,
|
* Count of distinct leagues that are currently "active" for the driver,
|
||||||
* based on upcoming races and league standings.
|
* based on upcoming races and league standings.
|
||||||
*/
|
*/
|
||||||
activeLeaguesCount: number;
|
activeLeaguesCount: number;
|
||||||
nextRace: DashboardRaceSummaryViewModel | null;
|
nextRace: DashboardRaceSummaryOutputPort | null;
|
||||||
recentResults: DashboardRecentResultViewModel[];
|
recentResults: DashboardRecentResultOutputPort[];
|
||||||
leagueStandingsSummaries: DashboardLeagueStandingSummaryViewModel[];
|
leagueStandingsSummaries: DashboardLeagueStandingSummaryOutputPort[];
|
||||||
feedSummary: DashboardFeedSummaryViewModel;
|
feedSummary: DashboardFeedSummaryOutputPort;
|
||||||
friends: DashboardFriendSummaryViewModel[];
|
friends: DashboardFriendSummaryOutputPort[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DashboardOverviewResultDTO = DashboardOverviewViewModel;
|
|
||||||
|
|
||||||
export interface IDashboardOverviewPresenter
|
|
||||||
extends Presenter<DashboardOverviewResultDTO, DashboardOverviewViewModel> {}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface DriverRegistrationStatusOutputPort {
|
||||||
|
isRegistered: boolean;
|
||||||
|
raceId: string;
|
||||||
|
driverId: string;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export interface GetDriverTeamOutputPort {
|
export interface DriverTeamOutputPort {
|
||||||
|
driverId: string;
|
||||||
team: {
|
team: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -9,9 +10,10 @@ export interface GetDriverTeamOutputPort {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
};
|
};
|
||||||
membership: {
|
membership: {
|
||||||
driverId: string;
|
|
||||||
teamId: string;
|
teamId: string;
|
||||||
role: 'member' | 'captain' | 'admin';
|
driverId: string;
|
||||||
|
role: 'owner' | 'manager' | 'driver';
|
||||||
|
status: 'active' | 'pending' | 'none';
|
||||||
joinedAt: Date;
|
joinedAt: Date;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { SkillLevel } from '../../domain/services/SkillLevelService';
|
||||||
|
|
||||||
|
export interface DriverLeaderboardItemOutputPort {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
rating: number;
|
||||||
|
skillLevel: SkillLevel;
|
||||||
|
nationality: string;
|
||||||
|
racesCompleted: number;
|
||||||
|
wins: number;
|
||||||
|
podiums: number;
|
||||||
|
isActive: boolean;
|
||||||
|
rank: number;
|
||||||
|
avatarUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DriversLeaderboardOutputPort {
|
||||||
|
drivers: DriverLeaderboardItemOutputPort[];
|
||||||
|
totalRaces: number;
|
||||||
|
totalWins: number;
|
||||||
|
activeCount: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export interface GetAllRacesOutputPort {
|
||||||
|
races: {
|
||||||
|
id: string;
|
||||||
|
leagueId: string;
|
||||||
|
track: string;
|
||||||
|
car: string;
|
||||||
|
status: 'scheduled' | 'running' | 'completed' | 'cancelled';
|
||||||
|
scheduledAt: string;
|
||||||
|
strengthOfField: number | null;
|
||||||
|
leagueName: string;
|
||||||
|
}[];
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
@@ -7,5 +7,7 @@ export interface GetAllTeamsOutputPort {
|
|||||||
ownerId: string;
|
ownerId: string;
|
||||||
leagues: string[];
|
leagues: string[];
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
memberCount: number;
|
||||||
}>;
|
}>;
|
||||||
|
totalCount?: number;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
export interface GetLeagueAdminOutputPort {
|
export interface GetLeagueAdminOutputPort {
|
||||||
league: {
|
leagueId: string;
|
||||||
id: string;
|
ownerId: string;
|
||||||
ownerId: string;
|
|
||||||
};
|
|
||||||
// Additional data would be populated by combining multiple use cases
|
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
|
export interface LeagueJoinRequestOutputPort {
|
||||||
|
id: string;
|
||||||
|
leagueId: string;
|
||||||
|
driverId: string;
|
||||||
|
requestedAt: Date;
|
||||||
|
message: string;
|
||||||
|
driver: { id: string; name: string } | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetLeagueJoinRequestsOutputPort {
|
export interface GetLeagueJoinRequestsOutputPort {
|
||||||
joinRequests: Array<{
|
joinRequests: LeagueJoinRequestOutputPort[];
|
||||||
id: string;
|
|
||||||
leagueId: string;
|
|
||||||
driverId: string;
|
|
||||||
requestedAt: Date;
|
|
||||||
message?: string;
|
|
||||||
driver: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
|
export interface LeagueMembershipOutputPort {
|
||||||
|
driverId: string;
|
||||||
|
driver: { id: string; name: string };
|
||||||
|
role: string;
|
||||||
|
joinedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeagueMembershipsOutputPort {
|
||||||
|
members: LeagueMembershipOutputPort[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetLeagueMembershipsOutputPort {
|
export interface GetLeagueMembershipsOutputPort {
|
||||||
memberships: Array<{
|
memberships: LeagueMembershipsOutputPort;
|
||||||
id: string;
|
|
||||||
leagueId: string;
|
|
||||||
driverId: string;
|
|
||||||
role: 'member' | 'admin' | 'owner';
|
|
||||||
joinedAt: Date;
|
|
||||||
}>;
|
|
||||||
drivers: { id: string; name: string }[];
|
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
export interface LeagueOwnerSummaryOutputPort {
|
||||||
|
driver: { id: string; iracingId: string; name: string; country: string; bio: string | undefined; joinedAt: string };
|
||||||
|
rating: number;
|
||||||
|
rank: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetLeagueOwnerSummaryOutputPort {
|
export interface GetLeagueOwnerSummaryOutputPort {
|
||||||
summary: { driver: { id: string; name: string }; rating: number; rank: number } | null;
|
summary: LeagueOwnerSummaryOutputPort | null;
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,47 @@
|
|||||||
|
export interface ProtestOutputPort {
|
||||||
|
id: string;
|
||||||
|
raceId: string;
|
||||||
|
protestingDriverId: string;
|
||||||
|
accusedDriverId: string;
|
||||||
|
incident: { lap: number; description: string; timeInRace: number | undefined };
|
||||||
|
comment: string | undefined;
|
||||||
|
proofVideoUrl: string | undefined;
|
||||||
|
status: string;
|
||||||
|
reviewedBy: string | undefined;
|
||||||
|
decisionNotes: string | undefined;
|
||||||
|
filedAt: string;
|
||||||
|
reviewedAt: string | undefined;
|
||||||
|
defense: { statement: string; videoUrl: string | undefined; submittedAt: string } | undefined;
|
||||||
|
defenseRequestedAt: string | undefined;
|
||||||
|
defenseRequestedBy: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RaceOutputPort {
|
||||||
|
id: string;
|
||||||
|
leagueId: string;
|
||||||
|
scheduledAt: string;
|
||||||
|
track: string;
|
||||||
|
trackId: string | undefined;
|
||||||
|
car: string;
|
||||||
|
carId: string | undefined;
|
||||||
|
sessionType: string;
|
||||||
|
status: string;
|
||||||
|
strengthOfField: number | undefined;
|
||||||
|
registeredCount: number | undefined;
|
||||||
|
maxParticipants: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DriverOutputPort {
|
||||||
|
id: string;
|
||||||
|
iracingId: string;
|
||||||
|
name: string;
|
||||||
|
country: string;
|
||||||
|
bio: string | undefined;
|
||||||
|
joinedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetLeagueProtestsOutputPort {
|
export interface GetLeagueProtestsOutputPort {
|
||||||
protests: Array<{
|
protests: ProtestOutputPort[];
|
||||||
id: string;
|
racesById: Record<string, RaceOutputPort>;
|
||||||
raceId: string;
|
driversById: Record<string, DriverOutputPort>;
|
||||||
protestingDriverId: string;
|
|
||||||
accusedDriverId: string;
|
|
||||||
submittedAt: Date;
|
|
||||||
description: string;
|
|
||||||
status: string;
|
|
||||||
}>;
|
|
||||||
races: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
date: string;
|
|
||||||
}>;
|
|
||||||
drivers: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export interface LeagueSeasonSummaryOutputPort {
|
||||||
|
seasonId: string;
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
isPrimary: boolean;
|
||||||
|
isParallelActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetLeagueSeasonsOutputPort {
|
||||||
|
seasons: LeagueSeasonSummaryOutputPort[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface GetSponsorsOutputPort {
|
||||||
|
sponsors: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
contactEmail: string;
|
||||||
|
websiteUrl: string | undefined;
|
||||||
|
logoUrl: string | undefined;
|
||||||
|
createdAt: Date;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface GetSponsorshipPricingOutputPort {
|
||||||
|
entityType: string;
|
||||||
|
entityId: string;
|
||||||
|
pricing: {
|
||||||
|
id: string;
|
||||||
|
level: string;
|
||||||
|
price: number;
|
||||||
|
currency: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
@@ -9,9 +9,9 @@ export interface GetTeamDetailsOutputPort {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
};
|
};
|
||||||
membership: {
|
membership: {
|
||||||
driverId: string;
|
role: 'owner' | 'manager' | 'member';
|
||||||
teamId: string;
|
|
||||||
role: 'member' | 'captain' | 'admin';
|
|
||||||
joinedAt: Date;
|
joinedAt: Date;
|
||||||
|
isActive: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
|
canManage: boolean;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface GetTotalLeaguesOutputPort {
|
||||||
|
totalLeagues: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface GetTotalRacesOutputPort {
|
||||||
|
totalRaces: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface ImportRaceResultsApiOutputPort {
|
||||||
|
success: boolean;
|
||||||
|
raceId: string;
|
||||||
|
leagueId: string;
|
||||||
|
driversProcessed: number;
|
||||||
|
resultsRecorded: number;
|
||||||
|
errors?: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface JoinLeagueOutputPort {
|
||||||
|
membershipId: string;
|
||||||
|
leagueId: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface LeagueDriverSeasonStatsOutputPort {
|
export interface LeagueDriverSeasonStatsItemOutputPort {
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
driverId: string;
|
driverId: string;
|
||||||
position: number;
|
position: number;
|
||||||
@@ -17,4 +17,9 @@ export interface LeagueDriverSeasonStatsOutputPort {
|
|||||||
avgFinish: number | null;
|
avgFinish: number | null;
|
||||||
rating: number | null;
|
rating: number | null;
|
||||||
ratingChange: number | null;
|
ratingChange: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeagueDriverSeasonStatsOutputPort {
|
||||||
|
leagueId: string;
|
||||||
|
stats: LeagueDriverSeasonStatsItemOutputPort[];
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { League } from '../../domain/entities/League';
|
||||||
|
import type { Season } from '../../domain/entities/Season';
|
||||||
|
import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig';
|
||||||
|
import type { Game } from '../../domain/entities/Game';
|
||||||
|
|
||||||
|
export interface LeagueFullConfigOutputPort {
|
||||||
|
league: League;
|
||||||
|
activeSeason?: Season;
|
||||||
|
scoringConfig?: LeagueScoringConfig;
|
||||||
|
game?: Game;
|
||||||
|
}
|
||||||
@@ -1,18 +1,12 @@
|
|||||||
|
import type { ChampionshipConfig } from '../../domain/types/ChampionshipConfig';
|
||||||
|
import type { LeagueScoringPresetOutputPort } from './LeagueScoringPresetOutputPort';
|
||||||
|
|
||||||
export interface LeagueScoringConfigOutputPort {
|
export interface LeagueScoringConfigOutputPort {
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
seasonId: string;
|
seasonId: string;
|
||||||
gameId: string;
|
gameId: string;
|
||||||
gameName: string;
|
gameName: string;
|
||||||
scoringPresetId?: string;
|
scoringPresetId?: string;
|
||||||
scoringPresetName?: string;
|
preset?: LeagueScoringPresetOutputPort;
|
||||||
dropPolicySummary: string;
|
championships: ChampionshipConfig[];
|
||||||
championships: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: 'driver' | 'team' | 'nations' | 'trophy';
|
|
||||||
sessionTypes: string[];
|
|
||||||
pointsPreview: Array<{ sessionType: string; position: number; points: number }>;
|
|
||||||
bonusSummary: string[];
|
|
||||||
dropPolicyDescription: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { LeagueScoringPresetOutputPort } from './LeagueScoringPresetOutputPort';
|
||||||
|
|
||||||
|
export interface LeagueScoringPresetsOutputPort {
|
||||||
|
presets: LeagueScoringPresetOutputPort[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface StandingItemOutputPort {
|
||||||
|
driverId: string;
|
||||||
|
driver: { id: string; name: string };
|
||||||
|
points: number;
|
||||||
|
rank: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeagueStandingsOutputPort {
|
||||||
|
standings: StandingItemOutputPort[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface LeagueStatsOutputPort {
|
||||||
|
totalMembers: number;
|
||||||
|
totalRaces: number;
|
||||||
|
averageRating: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest';
|
||||||
|
import type { SponsorshipTier } from '../../domain/entities/SeasonSponsorship';
|
||||||
|
|
||||||
|
export interface PendingSponsorshipRequestOutput {
|
||||||
|
id: string;
|
||||||
|
sponsorId: string;
|
||||||
|
sponsorName: string;
|
||||||
|
sponsorLogo?: string;
|
||||||
|
tier: SponsorshipTier;
|
||||||
|
offeredAmount: number;
|
||||||
|
currency: string;
|
||||||
|
formattedAmount: string;
|
||||||
|
message?: string;
|
||||||
|
createdAt: Date;
|
||||||
|
platformFee: number;
|
||||||
|
netAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PendingSponsorshipRequestsOutputPort {
|
||||||
|
entityType: SponsorableEntityType;
|
||||||
|
entityId: string;
|
||||||
|
requests: PendingSponsorshipRequestOutput[];
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
export interface ProfileOverviewOutputPort {
|
||||||
|
driver: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
country: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
iracingId: string | null;
|
||||||
|
joinedAt: Date;
|
||||||
|
rating: number | null;
|
||||||
|
globalRank: number | null;
|
||||||
|
consistency: number | null;
|
||||||
|
bio: string | null;
|
||||||
|
totalDrivers: number | null;
|
||||||
|
};
|
||||||
|
stats: {
|
||||||
|
totalRaces: number;
|
||||||
|
wins: number;
|
||||||
|
podiums: number;
|
||||||
|
dnfs: number;
|
||||||
|
avgFinish: number | null;
|
||||||
|
bestFinish: number | null;
|
||||||
|
worstFinish: number | null;
|
||||||
|
finishRate: number | null;
|
||||||
|
winRate: number | null;
|
||||||
|
podiumRate: number | null;
|
||||||
|
percentile: number | null;
|
||||||
|
rating: number | null;
|
||||||
|
consistency: number | null;
|
||||||
|
overallRank: number | null;
|
||||||
|
} | null;
|
||||||
|
finishDistribution: {
|
||||||
|
totalRaces: number;
|
||||||
|
wins: number;
|
||||||
|
podiums: number;
|
||||||
|
topTen: number;
|
||||||
|
dnfs: number;
|
||||||
|
other: number;
|
||||||
|
} | null;
|
||||||
|
teamMemberships: {
|
||||||
|
teamId: string;
|
||||||
|
teamName: string;
|
||||||
|
teamTag: string | null;
|
||||||
|
role: string;
|
||||||
|
joinedAt: Date;
|
||||||
|
isCurrent: boolean;
|
||||||
|
}[];
|
||||||
|
socialSummary: {
|
||||||
|
friendsCount: number;
|
||||||
|
friends: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
country: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
extendedProfile: null;
|
||||||
|
}
|
||||||
15
core/racing/application/ports/output/RaceDetailOutputPort.ts
Normal file
15
core/racing/application/ports/output/RaceDetailOutputPort.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Race } from '../../../domain/entities/Race';
|
||||||
|
import type { League } from '../../../domain/entities/League';
|
||||||
|
import type { RaceRegistration } from '../../../domain/entities/RaceRegistration';
|
||||||
|
import type { Driver } from '../../../domain/entities/Driver';
|
||||||
|
import type { Result } from '../../../domain/entities/result/Result';
|
||||||
|
|
||||||
|
export interface RaceDetailOutputPort {
|
||||||
|
race: Race;
|
||||||
|
league: League | null;
|
||||||
|
registrations: RaceRegistration[];
|
||||||
|
drivers: Driver[];
|
||||||
|
userResult: Result | null;
|
||||||
|
isUserRegistered: boolean;
|
||||||
|
canRegister: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { Penalty } from '../../../domain/entities/Penalty';
|
||||||
|
import type { Driver } from '../../../domain/entities/Driver';
|
||||||
|
|
||||||
|
export interface RacePenaltiesOutputPort {
|
||||||
|
penalties: Penalty[];
|
||||||
|
drivers: Driver[];
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user