refactor driver module (wip)
This commit is contained in:
@@ -128,7 +128,7 @@ export const DriverProviders: Provider[] = [
|
|||||||
driverStatsService: IDriverStatsService,
|
driverStatsService: IDriverStatsService,
|
||||||
imageService: IImageServicePort,
|
imageService: IImageServicePort,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, imageService, logger),
|
) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, (driverId: string) => Promise.resolve(imageService.getDriverAvatar(driverId)), logger),
|
||||||
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, IMAGE_SERVICE_PORT_TOKEN, LOGGER_TOKEN],
|
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, IMAGE_SERVICE_PORT_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -169,7 +169,7 @@ export const DriverProviders: Provider[] = [
|
|||||||
teamRepository,
|
teamRepository,
|
||||||
teamMembershipRepository,
|
teamMembershipRepository,
|
||||||
socialRepository,
|
socialRepository,
|
||||||
(driverId: string) => Promise.resolve(imageService.getDriverAvatar(driverId)),
|
imageService,
|
||||||
driverExtendedProfileProvider,
|
driverExtendedProfileProvider,
|
||||||
(driverId: string) => {
|
(driverId: string) => {
|
||||||
const stats = driverStatsService.getDriverStats(driverId);
|
const stats = driverStatsService.getDriverStats(driverId);
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export enum DriverProfileAchievementRarity {
|
||||||
|
COMMON = 'common',
|
||||||
|
RARE = 'rare',
|
||||||
|
EPIC = 'epic',
|
||||||
|
LEGENDARY = 'legendary',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DriverProfileAchievementDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
title!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
description!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: ['trophy', 'medal', 'star', 'crown', 'target', 'zap'] })
|
||||||
|
icon!: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap';
|
||||||
|
|
||||||
|
@ApiProperty({ enum: DriverProfileAchievementRarity })
|
||||||
|
rarity!: DriverProfileAchievementRarity;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
earnedAt!: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class DriverProfileDriverSummaryDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
country!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
avatarUrl!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
iracingId!: string | null;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
joinedAt!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
rating!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
globalRank!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
consistency!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
bio!: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
totalDrivers!: number | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { DriverProfileSocialHandleDTO } from './DriverProfileSocialHandleDTO';
|
||||||
|
|
||||||
|
import { DriverProfileAchievementDTO } from './DriverProfileAchievementDTO';
|
||||||
|
|
||||||
|
export class DriverProfileExtendedProfileDTO {
|
||||||
|
@ApiProperty({ type: [DriverProfileSocialHandleDTO] })
|
||||||
|
socialHandles!: DriverProfileSocialHandleDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ type: [DriverProfileAchievementDTO] })
|
||||||
|
achievements!: DriverProfileAchievementDTO[];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
racingStyle!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
favoriteTrack!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
favoriteCar!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
timezone!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
availableHours!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
lookingForTeam!: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
openToRequests!: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class DriverProfileFinishDistributionDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
totalRaces!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
wins!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
podiums!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
topTen!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
dnfs!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
other!: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class DriverProfileSocialFriendSummaryDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
country!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
avatarUrl!: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export type DriverProfileSocialPlatform = 'twitter' | 'youtube' | 'twitch' | 'discord';
|
||||||
|
|
||||||
|
export class DriverProfileSocialHandleDTO {
|
||||||
|
@ApiProperty({ enum: ['twitter', 'youtube', 'twitch', 'discord'] })
|
||||||
|
platform!: DriverProfileSocialPlatform;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
handle!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
url!: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { DriverProfileSocialFriendSummaryDTO } from './DriverProfileSocialFriendSummaryDTO';
|
||||||
|
|
||||||
|
export class DriverProfileSocialSummaryDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
friendsCount!: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: [DriverProfileSocialFriendSummaryDTO] })
|
||||||
|
friends!: DriverProfileSocialFriendSummaryDTO[];
|
||||||
|
}
|
||||||
45
apps/api/src/domain/driver/dtos/DriverProfileStatsDTO.ts
Normal file
45
apps/api/src/domain/driver/dtos/DriverProfileStatsDTO.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class DriverProfileStatsDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
totalRaces!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
wins!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
podiums!: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
dnfs!: number;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
avgFinish!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
bestFinish!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
worstFinish!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
finishRate!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
winRate!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
podiumRate!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
percentile!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
rating!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
consistency!: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
overallRank!: number | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class DriverProfileTeamMembershipDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
teamId!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
teamName!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
teamTag!: string | null;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
role!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
joinedAt!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
isCurrent!: boolean;
|
||||||
|
}
|
||||||
@@ -1,231 +1,28 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class DriverProfileDriverSummaryDTO {
|
import { DriverProfileDriverSummaryDTO } from './DriverProfileDriverSummaryDTO';
|
||||||
@ApiProperty()
|
import { DriverProfileStatsDTO } from './DriverProfileStatsDTO';
|
||||||
id!: string;
|
import { DriverProfileFinishDistributionDTO } from './DriverProfileFinishDistributionDTO';
|
||||||
|
import { DriverProfileTeamMembershipDTO } from './DriverProfileTeamMembershipDTO';
|
||||||
@ApiProperty()
|
import { DriverProfileSocialSummaryDTO } from './DriverProfileSocialSummaryDTO';
|
||||||
name!: string;
|
import { DriverProfileExtendedProfileDTO } from './DriverProfileExtendedProfileDTO';
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
country!: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
avatarUrl!: string;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
iracingId!: string | null;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
joinedAt!: string;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
rating!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
globalRank!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
consistency!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
bio!: string | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
totalDrivers!: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriverProfileStatsDTO {
|
|
||||||
@ApiProperty()
|
|
||||||
totalRaces!: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
wins!: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
podiums!: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
dnfs!: number;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
avgFinish!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
bestFinish!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
worstFinish!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
finishRate!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
winRate!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
podiumRate!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
percentile!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
rating!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
consistency!: number | null;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
overallRank!: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriverProfileFinishDistributionDTO {
|
|
||||||
@ApiProperty()
|
|
||||||
totalRaces: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
wins: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
podiums: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
topTen: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
dnfs: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
other: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriverProfileTeamMembershipDTO {
|
|
||||||
@ApiProperty()
|
|
||||||
teamId: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
teamName: string;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
teamTag: string | null;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
role: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
joinedAt: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
isCurrent: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriverProfileSocialFriendSummaryDTO {
|
|
||||||
@ApiProperty()
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
country: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
avatarUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriverProfileSocialSummaryDTO {
|
|
||||||
@ApiProperty()
|
|
||||||
friendsCount: number;
|
|
||||||
|
|
||||||
@ApiProperty({ type: [DriverProfileSocialFriendSummaryDTO] })
|
|
||||||
friends: DriverProfileSocialFriendSummaryDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DriverProfileSocialPlatform = 'twitter' | 'youtube' | 'twitch' | 'discord';
|
|
||||||
|
|
||||||
export enum DriverProfileAchievementRarity {
|
|
||||||
COMMON = 'common',
|
|
||||||
RARE = 'rare',
|
|
||||||
EPIC = 'epic',
|
|
||||||
LEGENDARY = 'legendary',
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriverProfileAchievementDTO {
|
|
||||||
@ApiProperty()
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
description: string;
|
|
||||||
|
|
||||||
@ApiProperty({ enum: ['trophy', 'medal', 'star', 'crown', 'target', 'zap'] })
|
|
||||||
icon: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap';
|
|
||||||
|
|
||||||
@ApiProperty({ enum: DriverProfileAchievementRarity })
|
|
||||||
rarity: DriverProfileAchievementRarity;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
earnedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriverProfileSocialHandleDTO {
|
|
||||||
@ApiProperty({ enum: DriverProfileSocialPlatform })
|
|
||||||
platform: DriverProfileSocialPlatform;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
handle: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriverProfileExtendedProfileDTO {
|
|
||||||
@ApiProperty({ type: [DriverProfileSocialHandleDTO] })
|
|
||||||
socialHandles: DriverProfileSocialHandleDTO[];
|
|
||||||
|
|
||||||
@ApiProperty({ type: [DriverProfileAchievementDTO] })
|
|
||||||
achievements: DriverProfileAchievementDTO[];
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
racingStyle: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
favoriteTrack: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
favoriteCar: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
timezone: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
availableHours: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
lookingForTeam: boolean;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
openToRequests: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GetDriverProfileOutputDTO {
|
export class GetDriverProfileOutputDTO {
|
||||||
@ApiProperty({ type: DriverProfileDriverSummaryDTO, nullable: true })
|
@ApiProperty({ type: DriverProfileDriverSummaryDTO, nullable: true })
|
||||||
currentDriver: DriverProfileDriverSummaryDTO | null;
|
currentDriver!: DriverProfileDriverSummaryDTO | null;
|
||||||
|
|
||||||
@ApiProperty({ type: DriverProfileStatsDTO, nullable: true })
|
@ApiProperty({ type: DriverProfileStatsDTO, nullable: true })
|
||||||
stats: DriverProfileStatsDTO | null;
|
stats!: DriverProfileStatsDTO | null;
|
||||||
|
|
||||||
@ApiProperty({ type: DriverProfileFinishDistributionDTO, nullable: true })
|
@ApiProperty({ type: DriverProfileFinishDistributionDTO, nullable: true })
|
||||||
finishDistribution: DriverProfileFinishDistributionDTO | null;
|
finishDistribution!: DriverProfileFinishDistributionDTO | null;
|
||||||
|
|
||||||
@ApiProperty({ type: [DriverProfileTeamMembershipDTO] })
|
@ApiProperty({ type: [DriverProfileTeamMembershipDTO] })
|
||||||
teamMemberships: DriverProfileTeamMembershipDTO[];
|
teamMemberships!: DriverProfileTeamMembershipDTO[];
|
||||||
|
|
||||||
@ApiProperty({ type: DriverProfileSocialSummaryDTO })
|
@ApiProperty({ type: DriverProfileSocialSummaryDTO })
|
||||||
socialSummary: DriverProfileSocialSummaryDTO;
|
socialSummary!: DriverProfileSocialSummaryDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: DriverProfileExtendedProfileDTO, nullable: true })
|
@ApiProperty({ type: DriverProfileExtendedProfileDTO, nullable: true })
|
||||||
extendedProfile: DriverProfileExtendedProfileDTO | null;
|
extendedProfile!: DriverProfileExtendedProfileDTO | null;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import type {
|
import type {
|
||||||
GetProfileOverviewResult,
|
GetProfileOverviewResult,
|
||||||
} from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
|
} from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
|
||||||
import type { GetDriverProfileOutputDTO, DriverProfileExtendedProfileDTO } from '../dtos/GetDriverProfileOutputDTO';
|
import type { GetDriverProfileOutputDTO } from '../dtos/GetDriverProfileOutputDTO';
|
||||||
|
import type { DriverProfileExtendedProfileDTO } from '../dtos/DriverProfileExtendedProfileDTO';
|
||||||
|
|
||||||
export class DriverProfilePresenter
|
export class DriverProfilePresenter
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
import { Result } from '@core/shared/application/Result';
|
|
||||||
import { DriverStatsPresenter } from './DriverStatsPresenter';
|
import { DriverStatsPresenter } from './DriverStatsPresenter';
|
||||||
import type { GetTotalDriversResult } from '../../../../../core/racing/application/use-cases/GetTotalDriversUseCase';
|
import type { GetTotalDriversResult } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
|
||||||
|
|
||||||
describe('DriverStatsPresenter', () => {
|
describe('DriverStatsPresenter', () => {
|
||||||
let presenter: DriverStatsPresenter;
|
let presenter: DriverStatsPresenter;
|
||||||
@@ -16,31 +15,13 @@ describe('DriverStatsPresenter', () => {
|
|||||||
totalDrivers: 42,
|
totalDrivers: 42,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = Result.ok<GetTotalDriversResult, never>(output);
|
presenter.present(output);
|
||||||
|
|
||||||
presenter.present(result);
|
const response = presenter.getResponseModel();
|
||||||
|
|
||||||
const response = presenter.responseModel;
|
|
||||||
|
|
||||||
expect(response).toEqual({
|
expect(response).toEqual({
|
||||||
totalDrivers: 42,
|
totalDrivers: 42,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('reset', () => {
|
|
||||||
it('should reset the result', () => {
|
|
||||||
const output: GetTotalDriversResult = {
|
|
||||||
totalDrivers: 10,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = Result.ok<GetTotalDriversResult, never>(output);
|
|
||||||
|
|
||||||
presenter.present(result);
|
|
||||||
expect(presenter.responseModel).toBeDefined();
|
|
||||||
|
|
||||||
presenter.reset();
|
|
||||||
expect(() => presenter.responseModel).toThrow('Presenter not presented');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
@@ -2,6 +2,8 @@ import { GetDriversLeaderboardResult } from '@core/racing/application/use-cases/
|
|||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { DriversLeaderboardPresenter } from './DriversLeaderboardPresenter';
|
import { DriversLeaderboardPresenter } from './DriversLeaderboardPresenter';
|
||||||
|
import type { Driver } from '@core/racing/domain/entities/Driver';
|
||||||
|
import type { SkillLevel } from '@core/racing/domain/services/SkillLevelService';
|
||||||
|
|
||||||
// TODO fix eslint issues
|
// TODO fix eslint issues
|
||||||
|
|
||||||
@@ -19,11 +21,11 @@ describe('DriversLeaderboardPresenter', () => {
|
|||||||
{
|
{
|
||||||
driver: {
|
driver: {
|
||||||
id: 'driver-1',
|
id: 'driver-1',
|
||||||
name: 'Driver One' as unknown,
|
name: 'Driver One',
|
||||||
country: 'US' as unknown,
|
country: 'US',
|
||||||
} as unknown,
|
} as unknown as Driver,
|
||||||
rating: 2500,
|
rating: 2500,
|
||||||
skillLevel: 'advanced' as unknown,
|
skillLevel: 'advanced' as unknown as SkillLevel,
|
||||||
racesCompleted: 50,
|
racesCompleted: 50,
|
||||||
wins: 10,
|
wins: 10,
|
||||||
podiums: 20,
|
podiums: 20,
|
||||||
@@ -34,11 +36,11 @@ describe('DriversLeaderboardPresenter', () => {
|
|||||||
{
|
{
|
||||||
driver: {
|
driver: {
|
||||||
id: 'driver-2',
|
id: 'driver-2',
|
||||||
name: 'Driver Two' as unknown,
|
name: 'Driver Two',
|
||||||
country: 'DE' as unknown,
|
country: 'DE',
|
||||||
} as unknown,
|
} as unknown as Driver,
|
||||||
rating: 2400,
|
rating: 2400,
|
||||||
skillLevel: 'intermediate' as unknown,
|
skillLevel: 'intermediate' as unknown as SkillLevel,
|
||||||
racesCompleted: 40,
|
racesCompleted: 40,
|
||||||
wins: 5,
|
wins: 5,
|
||||||
podiums: 15,
|
podiums: 15,
|
||||||
@@ -56,9 +58,10 @@ describe('DriversLeaderboardPresenter', () => {
|
|||||||
|
|
||||||
presenter.present(result);
|
presenter.present(result);
|
||||||
|
|
||||||
|
const output = presenter.getResponseModel();
|
||||||
|
|
||||||
expect(result.drivers).toHaveLength(2);
|
expect(output.drivers).toHaveLength(2);
|
||||||
expect(result.drivers[0]).toEqual({
|
expect(output.drivers[0]).toEqual({
|
||||||
id: 'driver-1',
|
id: 'driver-1',
|
||||||
name: 'Driver One',
|
name: 'Driver One',
|
||||||
rating: 2500,
|
rating: 2500,
|
||||||
@@ -71,7 +74,7 @@ describe('DriversLeaderboardPresenter', () => {
|
|||||||
rank: 1,
|
rank: 1,
|
||||||
avatarUrl: 'https://example.com/avatar1.png',
|
avatarUrl: 'https://example.com/avatar1.png',
|
||||||
});
|
});
|
||||||
expect(result.drivers[1]).toEqual({
|
expect(output.drivers[1]).toEqual({
|
||||||
id: 'driver-2',
|
id: 'driver-2',
|
||||||
name: 'Driver Two',
|
name: 'Driver Two',
|
||||||
rating: 2400,
|
rating: 2400,
|
||||||
@@ -84,126 +87,10 @@ describe('DriversLeaderboardPresenter', () => {
|
|||||||
rank: 2,
|
rank: 2,
|
||||||
avatarUrl: 'https://example.com/avatar2.png',
|
avatarUrl: 'https://example.com/avatar2.png',
|
||||||
});
|
});
|
||||||
expect(result.totalRaces).toBe(90);
|
expect(output.totalRaces).toBe(90);
|
||||||
expect(result.totalWins).toBe(15);
|
expect(output.totalWins).toBe(15);
|
||||||
expect(result.activeCount).toBe(2);
|
expect(output.activeCount).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort drivers by rating descending', () => {
|
|
||||||
const dto: DriversLeaderboardResultDTO = {
|
|
||||||
drivers: [
|
|
||||||
{
|
|
||||||
id: 'driver-1',
|
|
||||||
name: 'Driver One',
|
|
||||||
country: 'US',
|
|
||||||
iracingId: '12345',
|
|
||||||
joinedAt: new Date(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'driver-2',
|
|
||||||
name: 'Driver Two',
|
|
||||||
country: 'DE',
|
|
||||||
iracingId: '67890',
|
|
||||||
joinedAt: new Date(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rankings: [
|
|
||||||
{ driverId: 'driver-1', rating: 2400, overallRank: 2 },
|
|
||||||
{ driverId: 'driver-2', rating: 2500, overallRank: 1 },
|
|
||||||
],
|
|
||||||
stats: {},
|
|
||||||
avatarUrls: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
presenter.present(dto);
|
|
||||||
|
|
||||||
const result = presenter.viewModel;
|
|
||||||
|
|
||||||
expect(result.drivers[0].id).toBe('driver-2'); // Higher rating first
|
|
||||||
expect(result.drivers[1].id).toBe('driver-1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle missing stats gracefully', () => {
|
|
||||||
const dto: DriversLeaderboardResultDTO = {
|
|
||||||
drivers: [
|
|
||||||
{
|
|
||||||
id: 'driver-1',
|
|
||||||
name: 'Driver One',
|
|
||||||
country: 'US',
|
|
||||||
iracingId: '12345',
|
|
||||||
joinedAt: new Date(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rankings: [
|
|
||||||
{ driverId: 'driver-1', rating: 2500, overallRank: 1 },
|
|
||||||
],
|
|
||||||
stats: {}, // No stats
|
|
||||||
avatarUrls: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
presenter.present(dto);
|
|
||||||
|
|
||||||
const result = presenter.viewModel;
|
|
||||||
|
|
||||||
expect(result.drivers[0].racesCompleted).toBe(0);
|
|
||||||
expect(result.drivers[0].wins).toBe(0);
|
|
||||||
expect(result.drivers[0].podiums).toBe(0);
|
|
||||||
expect(result.drivers[0].isActive).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should derive skill level from rating bands', () => {
|
|
||||||
const dto: DriversLeaderboardResultDTO = {
|
|
||||||
drivers: [
|
|
||||||
{ id: 'd1', name: 'Beginner', country: 'US', iracingId: '1', joinedAt: new Date() },
|
|
||||||
{ id: 'd2', name: 'Intermediate', country: 'US', iracingId: '2', joinedAt: new Date() },
|
|
||||||
{ id: 'd3', name: 'Advanced', country: 'US', iracingId: '3', joinedAt: new Date() },
|
|
||||||
{ id: 'd4', name: 'Pro', country: 'US', iracingId: '4', joinedAt: new Date() },
|
|
||||||
],
|
|
||||||
rankings: [
|
|
||||||
{ driverId: 'd1', rating: 1700, overallRank: 4 },
|
|
||||||
{ driverId: 'd2', rating: 2000, overallRank: 3 },
|
|
||||||
{ driverId: 'd3', rating: 2600, overallRank: 2 },
|
|
||||||
{ driverId: 'd4', rating: 3100, overallRank: 1 },
|
|
||||||
],
|
|
||||||
stats: {
|
|
||||||
d1: { racesCompleted: 5, wins: 0, podiums: 0 },
|
|
||||||
d2: { racesCompleted: 5, wins: 0, podiums: 0 },
|
|
||||||
d3: { racesCompleted: 5, wins: 0, podiums: 0 },
|
|
||||||
d4: { racesCompleted: 5, wins: 0, podiums: 0 },
|
|
||||||
},
|
|
||||||
avatarUrls: {
|
|
||||||
d1: 'avatar-1',
|
|
||||||
d2: 'avatar-2',
|
|
||||||
d3: 'avatar-3',
|
|
||||||
d4: 'avatar-4',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
presenter.present(dto);
|
|
||||||
const result = presenter.viewModel;
|
|
||||||
|
|
||||||
const levels = result.drivers
|
|
||||||
.sort((a, b) => a.rating - b.rating)
|
|
||||||
.map(d => d.skillLevel);
|
|
||||||
|
|
||||||
expect(levels).toEqual(['beginner', 'intermediate', 'advanced', 'pro']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reset', () => {
|
|
||||||
it('should reset the result', () => {
|
|
||||||
const dto: DriversLeaderboardResultDTO = {
|
|
||||||
drivers: [],
|
|
||||||
rankings: [],
|
|
||||||
stats: {},
|
|
||||||
avatarUrls: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
presenter.present(dto);
|
|
||||||
expect(presenter.viewModel).toBeDefined();
|
|
||||||
|
|
||||||
presenter.reset();
|
|
||||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -8,6 +8,8 @@ import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeason
|
|||||||
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||||
import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
||||||
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
||||||
|
import type { IProtestRepository } from '@core/racing/domain/repositories/IProtestRepository';
|
||||||
|
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||||
|
|
||||||
// Import concrete in-memory implementations
|
// Import concrete in-memory implementations
|
||||||
import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
|
import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
|
||||||
@@ -52,6 +54,7 @@ import { WithdrawFromLeagueWalletUseCase } from '@core/racing/application/use-ca
|
|||||||
|
|
||||||
// Import presenters
|
// Import presenters
|
||||||
import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter';
|
import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter';
|
||||||
|
import { GetLeagueProtestsPresenter } from './presenters/GetLeagueProtestsPresenter';
|
||||||
|
|
||||||
// Define injection tokens
|
// Define injection tokens
|
||||||
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
|
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
|
||||||
@@ -145,6 +148,10 @@ export const LeagueProviders: Provider[] = [
|
|||||||
provide: 'AllLeaguesWithCapacityPresenter',
|
provide: 'AllLeaguesWithCapacityPresenter',
|
||||||
useClass: AllLeaguesWithCapacityPresenter,
|
useClass: AllLeaguesWithCapacityPresenter,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: 'GetLeagueProtestsPresenter',
|
||||||
|
useClass: GetLeagueProtestsPresenter,
|
||||||
|
},
|
||||||
// Use cases
|
// Use cases
|
||||||
{
|
{
|
||||||
provide: GetAllLeaguesWithCapacityUseCase,
|
provide: GetAllLeaguesWithCapacityUseCase,
|
||||||
@@ -167,7 +174,17 @@ export const LeagueProviders: Provider[] = [
|
|||||||
RemoveLeagueMemberUseCase,
|
RemoveLeagueMemberUseCase,
|
||||||
UpdateLeagueMemberRoleUseCase,
|
UpdateLeagueMemberRoleUseCase,
|
||||||
GetLeagueOwnerSummaryUseCase,
|
GetLeagueOwnerSummaryUseCase,
|
||||||
GetLeagueProtestsUseCase,
|
{
|
||||||
|
provide: GetLeagueProtestsUseCase,
|
||||||
|
useFactory: (
|
||||||
|
raceRepo: IRaceRepository,
|
||||||
|
protestRepo: IProtestRepository,
|
||||||
|
driverRepo: IDriverRepository,
|
||||||
|
leagueRepo: ILeagueRepository,
|
||||||
|
presenter: GetLeagueProtestsPresenter,
|
||||||
|
) => new GetLeagueProtestsUseCase(raceRepo, protestRepo, driverRepo, leagueRepo, presenter),
|
||||||
|
inject: [RACE_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, 'GetLeagueProtestsPresenter'],
|
||||||
|
},
|
||||||
GetLeagueSeasonsUseCase,
|
GetLeagueSeasonsUseCase,
|
||||||
GetLeagueMembershipsUseCase,
|
GetLeagueMembershipsUseCase,
|
||||||
GetLeagueScheduleUseCase,
|
GetLeagueScheduleUseCase,
|
||||||
|
|||||||
@@ -257,9 +257,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 (this.getLeagueProtestsUseCase.outputPort as GetLeagueProtestsPresenter).getResponseModel()!;
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel()!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueSeasons(query: GetLeagueSeasonsQueryDTO): Promise<LeagueSeasonSummaryDTO[]> {
|
async getLeagueSeasons(query: GetLeagueSeasonsQueryDTO): Promise<LeagueSeasonSummaryDTO[]> {
|
||||||
@@ -268,9 +266,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 (this.getLeagueSeasonsUseCase.output as GetLeagueSeasonsPresenter).getResponseModel()!;
|
||||||
presenter.present(result.unwrap());
|
|
||||||
return presenter.getViewModel()!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
|
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ export class AllLeaguesWithCapacityAndScoringDTO {
|
|||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => LeagueSummaryDTO)
|
@Type(() => LeagueSummaryDTO)
|
||||||
leagues: LeagueSummaryDTO[];
|
leagues!: LeagueSummaryDTO[];
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
totalCount: number;
|
totalCount!: number;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export interface LeagueSettings {
|
||||||
|
maxDrivers: number;
|
||||||
|
sessionDuration?: number;
|
||||||
|
visibility?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SocialLinks {
|
||||||
|
discordUrl?: string;
|
||||||
|
youtubeUrl?: string;
|
||||||
|
websiteUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class LeagueWithCapacityDTO {
|
export class LeagueWithCapacityDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
id!: string;
|
id!: string;
|
||||||
@@ -14,21 +26,13 @@ export class LeagueWithCapacityDTO {
|
|||||||
ownerId!: string;
|
ownerId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
settings!: {
|
settings!: LeagueSettings;
|
||||||
maxDrivers: number;
|
|
||||||
sessionDuration?: number;
|
|
||||||
visibility?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
createdAt!: string;
|
createdAt!: string;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
socialLinks?: {
|
socialLinks?: SocialLinks;
|
||||||
discordUrl?: string;
|
|
||||||
youtubeUrl?: string;
|
|
||||||
websiteUrl?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
usedSlots!: number;
|
usedSlots!: number;
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { IsString } from 'class-validator';
|
|||||||
export class ApproveJoinRequestInputDTO {
|
export class ApproveJoinRequestInputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
requestId: string;
|
requestId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { IsString, IsBoolean } from 'class-validator';
|
|||||||
export class ApproveJoinRequestOutputDTO {
|
export class ApproveJoinRequestOutputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
success: boolean;
|
success!: boolean;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ import { IsString, IsEnum } from 'class-validator';
|
|||||||
export class CreateLeagueInputDTO {
|
export class CreateLeagueInputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
description: string;
|
description!: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['public', 'private'] })
|
@ApiProperty({ enum: ['public', 'private'] })
|
||||||
@IsEnum(['public', 'private'])
|
@IsEnum(['public', 'private'])
|
||||||
visibility: 'public' | 'private';
|
visibility!: 'public' | 'private';
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
ownerId: string;
|
ownerId!: string;
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,9 @@ import { IsString, IsBoolean } from 'class-validator';
|
|||||||
export class CreateLeagueOutputDTO {
|
export class CreateLeagueOutputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
success: boolean;
|
success!: boolean;
|
||||||
}
|
}
|
||||||
@@ -8,5 +8,5 @@ export class GetLeagueAdminConfigOutputDTO {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueConfigFormModelDTO)
|
@Type(() => LeagueConfigFormModelDTO)
|
||||||
form: LeagueConfigFormModelDTO | null;
|
form!: LeagueConfigFormModelDTO | null;
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,5 @@ import { IsString } from 'class-validator';
|
|||||||
export class GetLeagueAdminConfigQueryDTO {
|
export class GetLeagueAdminConfigQueryDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,9 @@ import { IsString } from 'class-validator';
|
|||||||
export class GetLeagueAdminPermissionsInputDTO {
|
export class GetLeagueAdminPermissionsInputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
performerDriverId: string;
|
performerDriverId!: string;
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,5 @@ import { IsString } from 'class-validator';
|
|||||||
export class GetLeagueJoinRequestsQueryDTO {
|
export class GetLeagueJoinRequestsQueryDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,9 @@ import { IsString } from 'class-validator';
|
|||||||
export class GetLeagueOwnerSummaryQueryDTO {
|
export class GetLeagueOwnerSummaryQueryDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
ownerId: string;
|
ownerId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,5 @@ import { IsString } from 'class-validator';
|
|||||||
export class GetLeagueProtestsQueryDTO {
|
export class GetLeagueProtestsQueryDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,5 @@ import { RaceDTO } from '../../race/dtos/RaceDTO';
|
|||||||
|
|
||||||
export class GetLeagueRacesOutputDTO {
|
export class GetLeagueRacesOutputDTO {
|
||||||
@ApiProperty({ type: [RaceDTO] })
|
@ApiProperty({ type: [RaceDTO] })
|
||||||
races: RaceDTO[];
|
races!: RaceDTO[];
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,5 @@ import { IsString } from 'class-validator';
|
|||||||
export class GetLeagueSeasonsQueryDTO {
|
export class GetLeagueSeasonsQueryDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
}
|
}
|
||||||
@@ -2,28 +2,28 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||||||
|
|
||||||
export class WalletTransactionDTO {
|
export class WalletTransactionDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
id: string;
|
id!: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['sponsorship', 'membership', 'withdrawal', 'prize'] })
|
@ApiProperty({ enum: ['sponsorship', 'membership', 'withdrawal', 'prize'] })
|
||||||
type: 'sponsorship' | 'membership' | 'withdrawal' | 'prize';
|
type!: 'sponsorship' | 'membership' | 'withdrawal' | 'prize';
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
description: string;
|
description!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
amount: number;
|
amount!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
fee: number;
|
fee!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
netAmount: number;
|
netAmount!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
date: string;
|
date!: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['completed', 'pending', 'failed'] })
|
@ApiProperty({ enum: ['completed', 'pending', 'failed'] })
|
||||||
status: 'completed' | 'pending' | 'failed';
|
status!: 'completed' | 'pending' | 'failed';
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
reference?: string;
|
reference?: string;
|
||||||
@@ -31,29 +31,29 @@ export class WalletTransactionDTO {
|
|||||||
|
|
||||||
export class GetLeagueWalletOutputDTO {
|
export class GetLeagueWalletOutputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
balance: number;
|
balance!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
currency: string;
|
currency!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
totalRevenue: number;
|
totalRevenue!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
totalFees: number;
|
totalFees!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
totalWithdrawals: number;
|
totalWithdrawals!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
pendingPayouts: number;
|
pendingPayouts!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
canWithdraw: boolean;
|
canWithdraw!: boolean;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
withdrawalBlockReason?: string;
|
withdrawalBlockReason?: string;
|
||||||
|
|
||||||
@ApiProperty({ type: [WalletTransactionDTO] })
|
@ApiProperty({ type: [WalletTransactionDTO] })
|
||||||
transactions: WalletTransactionDTO[];
|
transactions!: WalletTransactionDTO[];
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,5 @@ import { SponsorshipDetailDTO } from '../../sponsor/dtos/SponsorshipDetailDTO';
|
|||||||
|
|
||||||
export class GetSeasonSponsorshipsOutputDTO {
|
export class GetSeasonSponsorshipsOutputDTO {
|
||||||
@ApiProperty({ type: [SponsorshipDetailDTO] })
|
@ApiProperty({ type: [SponsorshipDetailDTO] })
|
||||||
sponsorships: SponsorshipDetailDTO[];
|
sponsorships!: SponsorshipDetailDTO[];
|
||||||
}
|
}
|
||||||
@@ -8,5 +8,5 @@ export class LeagueAdminConfigDTO {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueConfigFormModelDTO)
|
@Type(() => LeagueConfigFormModelDTO)
|
||||||
form: LeagueConfigFormModelDTO | null;
|
form!: LeagueConfigFormModelDTO | null;
|
||||||
}
|
}
|
||||||
@@ -12,27 +12,27 @@ export class LeagueAdminDTO {
|
|||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => LeagueJoinRequestDTO)
|
@Type(() => LeagueJoinRequestDTO)
|
||||||
joinRequests: LeagueJoinRequestDTO[];
|
joinRequests!: LeagueJoinRequestDTO[];
|
||||||
|
|
||||||
@ApiProperty({ type: () => LeagueOwnerSummaryDTO, nullable: true })
|
@ApiProperty({ type: () => LeagueOwnerSummaryDTO, nullable: true })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueOwnerSummaryDTO)
|
@Type(() => LeagueOwnerSummaryDTO)
|
||||||
ownerSummary: LeagueOwnerSummaryDTO | null;
|
ownerSummary!: LeagueOwnerSummaryDTO | null;
|
||||||
|
|
||||||
@ApiProperty({ type: () => LeagueAdminConfigDTO })
|
@ApiProperty({ type: () => LeagueAdminConfigDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueAdminConfigDTO)
|
@Type(() => LeagueAdminConfigDTO)
|
||||||
config: LeagueAdminConfigDTO;
|
config!: LeagueAdminConfigDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: () => LeagueAdminProtestsDTO })
|
@ApiProperty({ type: () => LeagueAdminProtestsDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueAdminProtestsDTO)
|
@Type(() => LeagueAdminProtestsDTO)
|
||||||
protests: LeagueAdminProtestsDTO;
|
protests!: LeagueAdminProtestsDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: [LeagueSeasonSummaryDTO] })
|
@ApiProperty({ type: [LeagueSeasonSummaryDTO] })
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => LeagueSeasonSummaryDTO)
|
@Type(() => LeagueSeasonSummaryDTO)
|
||||||
seasons: LeagueSeasonSummaryDTO[];
|
seasons!: LeagueSeasonSummaryDTO[];
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,9 @@ import { IsBoolean } from 'class-validator';
|
|||||||
export class LeagueAdminPermissionsDTO {
|
export class LeagueAdminPermissionsDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
canRemoveMember: boolean;
|
canRemoveMember!: boolean;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
canUpdateRoles: boolean;
|
canUpdateRoles!: boolean;
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsArray, ValidateNested } from 'class-validator';
|
import { IsArray, ValidateNested } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { DriverDto } from '../../driver/dto/DriverDto';
|
import { DriverDTO } from '../../driver/dtos/DriverDTO';
|
||||||
import { RaceDto } from '../../race/dto/RaceDto';
|
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
||||||
import { ProtestDTO } from './ProtestDTO';
|
import { ProtestDTO } from './ProtestDTO';
|
||||||
|
|
||||||
export class LeagueAdminProtestsDTO {
|
export class LeagueAdminProtestsDTO {
|
||||||
@ApiProperty({ type: [ProtestDTO] })
|
@ApiProperty({ type: [ProtestDTO] })
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => ProtestDTO)
|
@Type(() => ProtestDTO)
|
||||||
protests: ProtestDTO[];
|
protests!: ProtestDTO[];
|
||||||
|
|
||||||
@ApiProperty({ type: () => RaceDto })
|
@ApiProperty({ type: () => RaceDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => RaceDto)
|
@Type(() => RaceDTO)
|
||||||
racesById: { [raceId: string]: RaceDto };
|
racesById!: { [raceId: string]: RaceDTO };
|
||||||
|
|
||||||
@ApiProperty({ type: () => DriverDto })
|
@ApiProperty({ type: () => DriverDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => DriverDto)
|
@Type(() => DriverDTO)
|
||||||
driversById: { [driverId: string]: DriverDto };
|
driversById!: { [driverId: string]: DriverDTO };
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,13 @@ import { IsString, IsEnum } from 'class-validator';
|
|||||||
export class LeagueConfigFormModelBasicsDTO {
|
export class LeagueConfigFormModelBasicsDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
description: string;
|
description!: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['public', 'private'] })
|
@ApiProperty({ enum: ['public', 'private'] })
|
||||||
@IsEnum(['public', 'private'])
|
@IsEnum(['public', 'private'])
|
||||||
visibility: 'public' | 'private';
|
visibility!: 'public' | 'private';
|
||||||
}
|
}
|
||||||
@@ -11,39 +11,39 @@ import { LeagueConfigFormModelTimingsDTO } from './LeagueConfigFormModelTimingsD
|
|||||||
export class LeagueConfigFormModelDTO {
|
export class LeagueConfigFormModelDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty({ type: LeagueConfigFormModelBasicsDTO })
|
@ApiProperty({ type: LeagueConfigFormModelBasicsDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueConfigFormModelBasicsDTO)
|
@Type(() => LeagueConfigFormModelBasicsDTO)
|
||||||
basics: LeagueConfigFormModelBasicsDTO;
|
basics!: LeagueConfigFormModelBasicsDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: LeagueConfigFormModelStructureDTO })
|
@ApiProperty({ type: LeagueConfigFormModelStructureDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueConfigFormModelStructureDTO)
|
@Type(() => LeagueConfigFormModelStructureDTO)
|
||||||
structure: LeagueConfigFormModelStructureDTO;
|
structure!: LeagueConfigFormModelStructureDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: [Object] })
|
@ApiProperty({ type: [Object] })
|
||||||
@IsArray()
|
@IsArray()
|
||||||
championships: Object[];
|
championships!: Object[];
|
||||||
|
|
||||||
@ApiProperty({ type: LeagueConfigFormModelScoringDTO })
|
@ApiProperty({ type: LeagueConfigFormModelScoringDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueConfigFormModelScoringDTO)
|
@Type(() => LeagueConfigFormModelScoringDTO)
|
||||||
scoring: LeagueConfigFormModelScoringDTO;
|
scoring!: LeagueConfigFormModelScoringDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: LeagueConfigFormModelDropPolicyDTO })
|
@ApiProperty({ type: LeagueConfigFormModelDropPolicyDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueConfigFormModelDropPolicyDTO)
|
@Type(() => LeagueConfigFormModelDropPolicyDTO)
|
||||||
dropPolicy: LeagueConfigFormModelDropPolicyDTO;
|
dropPolicy!: LeagueConfigFormModelDropPolicyDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: LeagueConfigFormModelTimingsDTO })
|
@ApiProperty({ type: LeagueConfigFormModelTimingsDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueConfigFormModelTimingsDTO)
|
@Type(() => LeagueConfigFormModelTimingsDTO)
|
||||||
timings: LeagueConfigFormModelTimingsDTO;
|
timings!: LeagueConfigFormModelTimingsDTO;
|
||||||
|
|
||||||
@ApiProperty({ type: LeagueConfigFormModelStewardingDTO })
|
@ApiProperty({ type: LeagueConfigFormModelStewardingDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueConfigFormModelStewardingDTO)
|
@Type(() => LeagueConfigFormModelStewardingDTO)
|
||||||
stewarding: LeagueConfigFormModelStewardingDTO;
|
stewarding!: LeagueConfigFormModelStewardingDTO;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { IsNumber, IsOptional, IsEnum } from 'class-validator';
|
|||||||
export class LeagueConfigFormModelDropPolicyDTO {
|
export class LeagueConfigFormModelDropPolicyDTO {
|
||||||
@ApiProperty({ enum: ['none', 'worst_n'] })
|
@ApiProperty({ enum: ['none', 'worst_n'] })
|
||||||
@IsEnum(['none', 'worst_n'])
|
@IsEnum(['none', 'worst_n'])
|
||||||
strategy: 'none' | 'worst_n';
|
strategy!: 'none' | 'worst_n';
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { IsString, IsNumber } from 'class-validator';
|
|||||||
export class LeagueConfigFormModelScoringDTO {
|
export class LeagueConfigFormModelScoringDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
type: string;
|
type!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
points: number;
|
points!: number;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { IsNumber, IsBoolean, IsOptional, IsEnum } from 'class-validator';
|
|||||||
export class LeagueConfigFormModelStewardingDTO {
|
export class LeagueConfigFormModelStewardingDTO {
|
||||||
@ApiProperty({ enum: ['single_steward', 'committee_vote'] })
|
@ApiProperty({ enum: ['single_steward', 'committee_vote'] })
|
||||||
@IsEnum(['single_steward', 'committee_vote'])
|
@IsEnum(['single_steward', 'committee_vote'])
|
||||||
decisionMode: 'single_steward' | 'committee_vote';
|
decisionMode!: 'single_steward' | 'committee_vote';
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -13,29 +13,29 @@ export class LeagueConfigFormModelStewardingDTO {
|
|||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
requireDefense: boolean;
|
requireDefense!: boolean;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
defenseTimeLimit: number;
|
defenseTimeLimit!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
voteTimeLimit: number;
|
voteTimeLimit!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
protestDeadlineHours: number;
|
protestDeadlineHours!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
stewardingClosesHours: number;
|
stewardingClosesHours!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
notifyAccusedOnProtest: boolean;
|
notifyAccusedOnProtest!: boolean;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
notifyOnVoteRequired: boolean;
|
notifyOnVoteRequired!: boolean;
|
||||||
}
|
}
|
||||||
@@ -5,5 +5,5 @@ export class LeagueConfigFormModelStructureDTO {
|
|||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsEnum(['solo', 'team'])
|
@IsEnum(['solo', 'team'])
|
||||||
mode: 'solo' | 'team';
|
mode!: 'solo' | 'team';
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,13 @@ import { IsString, IsNumber } from 'class-validator';
|
|||||||
export class LeagueConfigFormModelTimingsDTO {
|
export class LeagueConfigFormModelTimingsDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
raceDayOfWeek: string;
|
raceDayOfWeek!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
raceTimeHour: number;
|
raceTimeHour!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
raceTimeMinute: number;
|
raceTimeMinute!: number;
|
||||||
}
|
}
|
||||||
@@ -2,23 +2,28 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||||||
import { IsString, IsDate, IsOptional } from 'class-validator';
|
import { IsString, IsDate, IsOptional } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
|
|
||||||
|
export interface DriverInfo {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class LeagueJoinRequestDTO {
|
export class LeagueJoinRequestDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
id: string;
|
id!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
driverId: string;
|
driverId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsDate()
|
@IsDate()
|
||||||
@Type(() => Date)
|
@Type(() => Date)
|
||||||
requestedAt: Date;
|
requestedAt!: Date;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -30,8 +35,5 @@ export class LeagueJoinRequestDTO {
|
|||||||
type: () => Object,
|
type: () => Object,
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
driver?: {
|
driver?: DriverInfo;
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsString, IsDate, IsEnum, ValidateNested } from 'class-validator';
|
import { IsString, IsDate, IsEnum, ValidateNested } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { DriverDto } from '../../driver/dto/DriverDto';
|
import { DriverDTO } from '../../driver/dtos/DriverDTO';
|
||||||
|
|
||||||
export class LeagueMemberDTO {
|
export class LeagueMemberDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
driverId: string;
|
driverId!: string;
|
||||||
|
|
||||||
@ApiProperty({ type: () => DriverDto })
|
@ApiProperty({ type: () => DriverDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => DriverDto)
|
@Type(() => DriverDTO)
|
||||||
driver: DriverDto;
|
driver!: DriverDTO;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
|
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
|
||||||
@IsEnum(['owner', 'manager', 'member'])
|
@IsEnum(['owner', 'manager', 'member'])
|
||||||
role: 'owner' | 'manager' | 'member';
|
role!: 'owner' | 'manager' | 'member';
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsDate()
|
@IsDate()
|
||||||
@Type(() => Date)
|
@Type(() => Date)
|
||||||
joinedAt: Date;
|
joinedAt!: Date;
|
||||||
}
|
}
|
||||||
@@ -4,25 +4,25 @@ import { IsString, IsEnum } from 'class-validator';
|
|||||||
export class LeagueMembershipDTO {
|
export class LeagueMembershipDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
id: string;
|
id!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
driverId: string;
|
driverId!: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] })
|
@ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] })
|
||||||
@IsEnum(['owner', 'admin', 'steward', 'member'])
|
@IsEnum(['owner', 'admin', 'steward', 'member'])
|
||||||
role: 'owner' | 'admin' | 'steward' | 'member';
|
role!: 'owner' | 'admin' | 'steward' | 'member';
|
||||||
|
|
||||||
@ApiProperty({ enum: ['active', 'inactive', 'pending'] })
|
@ApiProperty({ enum: ['active', 'inactive', 'pending'] })
|
||||||
@IsEnum(['active', 'inactive', 'pending'])
|
@IsEnum(['active', 'inactive', 'pending'])
|
||||||
status: 'active' | 'inactive' | 'pending';
|
status!: 'active' | 'inactive' | 'pending';
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
joinedAt: string;
|
joinedAt!: string;
|
||||||
}
|
}
|
||||||
@@ -8,5 +8,5 @@ export class LeagueMembershipsDTO {
|
|||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => LeagueMemberDTO)
|
@Type(() => LeagueMemberDTO)
|
||||||
members: LeagueMemberDTO[];
|
members!: LeagueMemberDTO[];
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsNumber, IsOptional, ValidateNested } from 'class-validator';
|
import { IsNumber, IsOptional, ValidateNested } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { DriverDto } from '../../driver/dto/DriverDto';
|
import { DriverDTO } from '../../driver/dtos/DriverDTO';
|
||||||
|
|
||||||
export class LeagueOwnerSummaryDTO {
|
export class LeagueOwnerSummaryDTO {
|
||||||
@ApiProperty({ type: () => DriverDto })
|
@ApiProperty({ type: () => DriverDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => DriverDto)
|
@Type(() => DriverDTO)
|
||||||
driver: DriverDto;
|
driver!: DriverDTO;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
rating: number | null;
|
rating!: number | null;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
rank: number | null;
|
rank!: number | null;
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,5 @@ import { IsEnum } from 'class-validator';
|
|||||||
export class LeagueRoleDTO {
|
export class LeagueRoleDTO {
|
||||||
@ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] })
|
@ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] })
|
||||||
@IsEnum(['owner', 'admin', 'steward', 'member'])
|
@IsEnum(['owner', 'admin', 'steward', 'member'])
|
||||||
value: 'owner' | 'admin' | 'steward' | 'member';
|
value!: 'owner' | 'admin' | 'steward' | 'member';
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsArray, ValidateNested } from 'class-validator';
|
import { IsArray, ValidateNested } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { RaceDto } from '../../race/dto/RaceDto';
|
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
||||||
|
|
||||||
export class LeagueScheduleDTO {
|
export class LeagueScheduleDTO {
|
||||||
@ApiProperty({ type: [RaceDto] })
|
@ApiProperty({ type: [RaceDTO] })
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => RaceDto)
|
@Type(() => RaceDTO)
|
||||||
races: RaceDto[];
|
races!: RaceDTO[];
|
||||||
}
|
}
|
||||||
@@ -4,29 +4,29 @@ import { IsString, IsEnum } from 'class-validator';
|
|||||||
export class LeagueScoringPresetDTO {
|
export class LeagueScoringPresetDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
id: string;
|
id!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
description: string;
|
description!: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['driver', 'team', 'nations', 'trophy'] })
|
@ApiProperty({ enum: ['driver', 'team', 'nations', 'trophy'] })
|
||||||
@IsEnum(['driver', 'team', 'nations', 'trophy'])
|
@IsEnum(['driver', 'team', 'nations', 'trophy'])
|
||||||
primaryChampionshipType: 'driver' | 'team' | 'nations' | 'trophy';
|
primaryChampionshipType!: 'driver' | 'team' | 'nations' | 'trophy';
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
sessionSummary: string;
|
sessionSummary!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
bonusSummary: string;
|
bonusSummary!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
dropPolicySummary: string;
|
dropPolicySummary!: string;
|
||||||
}
|
}
|
||||||
@@ -5,15 +5,15 @@ import { Type } from 'class-transformer';
|
|||||||
export class LeagueSeasonSummaryDTO {
|
export class LeagueSeasonSummaryDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
seasonId: string;
|
seasonId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
status: string;
|
status!: string;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -29,9 +29,9 @@ export class LeagueSeasonSummaryDTO {
|
|||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
isPrimary: boolean;
|
isPrimary!: boolean;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
isParallelActive: boolean;
|
isParallelActive!: boolean;
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsString, IsNumber, ValidateNested } from 'class-validator';
|
import { IsString, IsNumber, ValidateNested } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { DriverDto } from '../../driver/dto/DriverDto';
|
import { DriverDTO } from '../../driver/dtos/DriverDTO';
|
||||||
|
|
||||||
export class LeagueStandingDTO {
|
export class LeagueStandingDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
driverId: string;
|
driverId!: string;
|
||||||
|
|
||||||
@ApiProperty({ type: () => DriverDto })
|
@ApiProperty({ type: () => DriverDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => DriverDto)
|
@Type(() => DriverDTO)
|
||||||
driver: DriverDto;
|
driver!: DriverDTO;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
points: number;
|
points!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
rank: number;
|
rank!: number;
|
||||||
}
|
}
|
||||||
@@ -8,5 +8,5 @@ export class LeagueStandingsDTO {
|
|||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => LeagueStandingDTO)
|
@Type(() => LeagueStandingDTO)
|
||||||
standings: LeagueStandingDTO[];
|
standings!: LeagueStandingDTO[];
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,13 @@ import { IsNumber } from 'class-validator';
|
|||||||
export class LeagueStatsDTO {
|
export class LeagueStatsDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
totalMembers: number;
|
totalMembers!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
totalRaces: number;
|
totalRaces!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
averageRating: number;
|
averageRating!: number;
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,11 @@ import { IsString, IsNumber, IsBoolean, IsOptional } from 'class-validator';
|
|||||||
export class LeagueSummaryDTO {
|
export class LeagueSummaryDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
id: string;
|
id!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name!: string;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -27,19 +27,19 @@ export class LeagueSummaryDTO {
|
|||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
memberCount: number;
|
memberCount!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
maxMembers: number;
|
maxMembers!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
isPublic: boolean;
|
isPublic!: boolean;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
ownerId: string;
|
ownerId!: string;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import { LeagueSettingsDTO } from './LeagueSettingsDTO';
|
|||||||
export class LeagueWithCapacityDTO {
|
export class LeagueWithCapacityDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
id: string;
|
id!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name!: string;
|
||||||
|
|
||||||
// ... other properties of LeagueWithCapacityDTO
|
// ... other properties of LeagueWithCapacityDTO
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
@@ -20,20 +20,20 @@ export class LeagueWithCapacityDTO {
|
|||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
ownerId: string;
|
ownerId!: string;
|
||||||
|
|
||||||
@ApiProperty({ type: () => LeagueSettingsDTO })
|
@ApiProperty({ type: () => LeagueSettingsDTO })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => LeagueSettingsDTO)
|
@Type(() => LeagueSettingsDTO)
|
||||||
settings: LeagueSettingsDTO;
|
settings!: LeagueSettingsDTO;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
createdAt: string;
|
createdAt!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
usedSlots: number;
|
usedSlots!: number;
|
||||||
|
|
||||||
@ApiProperty({ type: () => Object, nullable: true }) // Using Object for generic social links
|
@ApiProperty({ type: () => Object, nullable: true }) // Using Object for generic social links
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import { IsEnum } from 'class-validator';
|
|||||||
export class MembershipRoleDTO {
|
export class MembershipRoleDTO {
|
||||||
@ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] })
|
@ApiProperty({ enum: ['owner', 'admin', 'steward', 'member'] })
|
||||||
@IsEnum(['owner', 'admin', 'steward', 'member'])
|
@IsEnum(['owner', 'admin', 'steward', 'member'])
|
||||||
value: 'owner' | 'admin' | 'steward' | 'member';
|
value!: 'owner' | 'admin' | 'steward' | 'member';
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,5 @@ import { IsEnum } from 'class-validator';
|
|||||||
export class MembershipStatusDTO {
|
export class MembershipStatusDTO {
|
||||||
@ApiProperty({ enum: ['active', 'inactive', 'pending'] })
|
@ApiProperty({ enum: ['active', 'inactive', 'pending'] })
|
||||||
@IsEnum(['active', 'inactive', 'pending'])
|
@IsEnum(['active', 'inactive', 'pending'])
|
||||||
value: 'active' | 'inactive' | 'pending';
|
value!: 'active' | 'inactive' | 'pending';
|
||||||
}
|
}
|
||||||
@@ -13,34 +13,34 @@ import { Type } from 'class-transformer';
|
|||||||
export class ProtestDTO {
|
export class ProtestDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
id: string;
|
id!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
raceId: string;
|
raceId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
protestingDriverId: string;
|
protestingDriverId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
accusedDriverId: string;
|
accusedDriverId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsDate()
|
@IsDate()
|
||||||
@Type(() => Date)
|
@Type(() => Date)
|
||||||
submittedAt: Date;
|
submittedAt!: Date;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
description: string;
|
description!: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['pending', 'accepted', 'rejected'] })
|
@ApiProperty({ enum: ['pending', 'accepted', 'rejected'] })
|
||||||
@IsEnum(['pending', 'accepted', 'rejected'])
|
@IsEnum(['pending', 'accepted', 'rejected'])
|
||||||
status: 'pending' | 'accepted' | 'rejected';
|
status!: 'pending' | 'accepted' | 'rejected';
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,9 @@ import { IsString } from 'class-validator';
|
|||||||
export class RejectJoinRequestInputDTO {
|
export class RejectJoinRequestInputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
requestId: string;
|
requestId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { IsString, IsBoolean } from 'class-validator';
|
|||||||
export class RejectJoinRequestOutputDTO {
|
export class RejectJoinRequestOutputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
success: boolean;
|
success!: boolean;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { IsString } from 'class-validator';
|
|||||||
export class RemoveLeagueMemberInputDTO {
|
export class RemoveLeagueMemberInputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
performerDriverId: string;
|
performerDriverId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
targetDriverId: string;
|
targetDriverId!: string;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
|||||||
export class RemoveLeagueMemberOutputDTO {
|
export class RemoveLeagueMemberOutputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
success: boolean;
|
success!: boolean;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import { Type } from 'class-transformer';
|
|||||||
export class SeasonDTO {
|
export class SeasonDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
seasonId: string;
|
seasonId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -29,11 +29,11 @@ export class SeasonDTO {
|
|||||||
|
|
||||||
@ApiProperty({ enum: ['planned', 'active', 'completed'] })
|
@ApiProperty({ enum: ['planned', 'active', 'completed'] })
|
||||||
@IsEnum(['planned', 'active', 'completed'])
|
@IsEnum(['planned', 'active', 'completed'])
|
||||||
status: 'planned' | 'active' | 'completed';
|
status!: 'planned' | 'active' | 'completed';
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
isPrimary: boolean;
|
isPrimary!: boolean;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import { IsNumber } from 'class-validator';
|
|||||||
export class TotalLeaguesDTO {
|
export class TotalLeaguesDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
totalLeagues: number;
|
totalLeagues!: number;
|
||||||
}
|
}
|
||||||
@@ -4,17 +4,17 @@ import { IsString, IsEnum } from 'class-validator';
|
|||||||
export class UpdateLeagueMemberRoleInputDTO {
|
export class UpdateLeagueMemberRoleInputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
leagueId: string;
|
leagueId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
performerDriverId: string;
|
performerDriverId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
targetDriverId: string;
|
targetDriverId!: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
|
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
|
||||||
@IsEnum(['owner', 'manager', 'member'])
|
@IsEnum(['owner', 'manager', 'member'])
|
||||||
newRole: 'owner' | 'manager' | 'member';
|
newRole!: 'owner' | 'manager' | 'member';
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
|||||||
export class UpdateLeagueMemberRoleOutputDTO {
|
export class UpdateLeagueMemberRoleOutputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
success: boolean;
|
success!: boolean;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||||||
|
|
||||||
export class WithdrawFromLeagueWalletInputDTO {
|
export class WithdrawFromLeagueWalletInputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
amount: number;
|
amount!: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
currency: string;
|
currency!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
seasonId: string;
|
seasonId!: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
destinationAccount: string;
|
destinationAccount!: string;
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||||||
|
|
||||||
export class WithdrawFromLeagueWalletOutputDTO {
|
export class WithdrawFromLeagueWalletOutputDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
success: boolean;
|
success!: boolean;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import { IsEnum } from 'class-validator';
|
|||||||
export class WizardStepDTO {
|
export class WizardStepDTO {
|
||||||
@ApiProperty({ enum: [1, 2, 3, 4, 5, 6, 7] })
|
@ApiProperty({ enum: [1, 2, 3, 4, 5, 6, 7] })
|
||||||
@IsEnum([1, 2, 3, 4, 5, 6, 7])
|
@IsEnum([1, 2, 3, 4, 5, 6, 7])
|
||||||
value: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
value!: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,26 @@
|
|||||||
import { GetLeagueOwnerSummaryOutputPort } from '@core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||||
|
import type { GetLeagueOwnerSummaryResult } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase';
|
||||||
import { LeagueOwnerSummaryDTO } from '../dtos/LeagueOwnerSummaryDTO';
|
import { LeagueOwnerSummaryDTO } from '../dtos/LeagueOwnerSummaryDTO';
|
||||||
|
|
||||||
export class GetLeagueOwnerSummaryPresenter {
|
export class GetLeagueOwnerSummaryPresenter implements UseCaseOutputPort<GetLeagueOwnerSummaryResult> {
|
||||||
private result: LeagueOwnerSummaryDTO | null = null;
|
private result: LeagueOwnerSummaryDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(output: GetLeagueOwnerSummaryOutputPort) {
|
present(output: GetLeagueOwnerSummaryResult) {
|
||||||
if (!output.summary) {
|
|
||||||
this.result = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.result = {
|
this.result = {
|
||||||
driver: {
|
driver: {
|
||||||
id: output.summary.driver.id,
|
id: output.owner.id,
|
||||||
iracingId: output.summary.driver.iracingId,
|
iracingId: output.owner.iracingId.toString(),
|
||||||
name: output.summary.driver.name,
|
name: output.owner.name.toString(),
|
||||||
country: output.summary.driver.country,
|
country: output.owner.country.toString(),
|
||||||
bio: output.summary.driver.bio,
|
joinedAt: output.owner.joinedAt.toDate().toISOString(),
|
||||||
joinedAt: output.summary.driver.joinedAt,
|
...(output.owner.bio ? { bio: output.owner.bio.toString() } : {}),
|
||||||
},
|
},
|
||||||
rating: output.summary.rating,
|
rating: output.rating,
|
||||||
rank: output.summary.rank,
|
rank: output.rank,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { GetLeagueProtestsOutputPort, type ProtestOutputPort } from '@core/racing/application/ports/output/GetLeagueProtestsOutputPort';
|
import { Presenter } from '@core/shared/presentation';
|
||||||
|
import type { GetLeagueProtestsResult } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase';
|
||||||
import { LeagueAdminProtestsDTO } from '../dtos/LeagueAdminProtestsDTO';
|
import { LeagueAdminProtestsDTO } from '../dtos/LeagueAdminProtestsDTO';
|
||||||
import { ProtestDTO } from '../dtos/ProtestDTO';
|
import { ProtestDTO } from '../dtos/ProtestDTO';
|
||||||
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
||||||
import { DriverDTO } from '../../driver/dtos/DriverDTO';
|
import { DriverDTO } from '../../driver/dtos/DriverDTO';
|
||||||
|
|
||||||
function mapProtestStatus(status: ProtestOutputPort['status']): ProtestDTO['status'] {
|
function mapProtestStatus(status: string): ProtestDTO['status'] {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
case 'awaiting_defense':
|
case 'awaiting_defense':
|
||||||
@@ -20,53 +21,63 @@ function mapProtestStatus(status: ProtestOutputPort['status']): ProtestDTO['stat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GetLeagueProtestsPresenter {
|
export class GetLeagueProtestsPresenter implements Presenter<GetLeagueProtestsResult, LeagueAdminProtestsDTO> {
|
||||||
private result: LeagueAdminProtestsDTO | null = null;
|
private result: LeagueAdminProtestsDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(output: GetLeagueProtestsOutputPort, leagueName?: string) {
|
present(input: GetLeagueProtestsResult) {
|
||||||
const protests: ProtestDTO[] = output.protests.map((protest) => {
|
const protests: ProtestDTO[] = input.protests.map((protestWithEntities) => {
|
||||||
const race = output.racesById[protest.raceId];
|
const { protest, race } = protestWithEntities;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: protest.id,
|
id: protest.id.toString(),
|
||||||
leagueId: race?.leagueId || '',
|
leagueId: race?.leagueId.toString() || '',
|
||||||
raceId: protest.raceId,
|
raceId: protest.raceId.toString(),
|
||||||
protestingDriverId: protest.protestingDriverId,
|
protestingDriverId: protest.protestingDriverId.toString(),
|
||||||
accusedDriverId: protest.accusedDriverId,
|
accusedDriverId: protest.accusedDriverId.toString(),
|
||||||
submittedAt: new Date(protest.filedAt),
|
submittedAt: new Date(protest.filedAt),
|
||||||
description: protest.incident.description,
|
description: protest.incident.description.toString(),
|
||||||
status: mapProtestStatus(protest.status),
|
status: mapProtestStatus(protest.status.toString()),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const racesById: { [raceId: string]: RaceDTO } = {};
|
const racesById: { [raceId: string]: RaceDTO } = {};
|
||||||
for (const raceId in output.racesById) {
|
for (const protestWithEntities of input.protests) {
|
||||||
const race = output.racesById[raceId];
|
const { race } = protestWithEntities;
|
||||||
if (race) {
|
if (race) {
|
||||||
racesById[raceId] = {
|
racesById[race.id.toString()] = {
|
||||||
id: race.id,
|
id: race.id.toString(),
|
||||||
name: race.track,
|
name: race.track.toString(),
|
||||||
date: race.scheduledAt.toISOString(),
|
date: race.scheduledAt.toISOString(),
|
||||||
leagueName,
|
leagueName: input.league.name.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const driversById: { [driverId: string]: DriverDTO } = {};
|
const driversById: { [driverId: string]: DriverDTO } = {};
|
||||||
for (const driverId in output.driversById) {
|
for (const protestWithEntities of input.protests) {
|
||||||
const driver = output.driversById[driverId];
|
const { protestingDriver, accusedDriver } = protestWithEntities;
|
||||||
if (driver) {
|
if (protestingDriver) {
|
||||||
driversById[driverId] = {
|
driversById[protestingDriver.id.toString()] = {
|
||||||
id: driver.id,
|
id: protestingDriver.id.toString(),
|
||||||
iracingId: driver.iracingId,
|
iracingId: protestingDriver.iracingId.toString(),
|
||||||
name: driver.name,
|
name: protestingDriver.name.toString(),
|
||||||
country: driver.country,
|
country: protestingDriver.country.toString(),
|
||||||
bio: driver.bio,
|
bio: protestingDriver.bio?.toString(),
|
||||||
joinedAt: driver.joinedAt,
|
joinedAt: protestingDriver.joinedAt.toDate().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (accusedDriver) {
|
||||||
|
driversById[accusedDriver.id.toString()] = {
|
||||||
|
id: accusedDriver.id.toString(),
|
||||||
|
iracingId: accusedDriver.iracingId.toString(),
|
||||||
|
name: accusedDriver.name.toString(),
|
||||||
|
country: accusedDriver.country.toString(),
|
||||||
|
bio: accusedDriver.bio?.toString(),
|
||||||
|
joinedAt: accusedDriver.joinedAt.toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +89,7 @@ export class GetLeagueProtestsPresenter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): LeagueAdminProtestsDTO | null {
|
getResponseModel(): LeagueAdminProtestsDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,27 @@
|
|||||||
import { GetLeagueSeasonsOutputPort } from '@core/racing/application/ports/output/GetLeagueSeasonsOutputPort';
|
import { Presenter } from '@core/shared/presentation';
|
||||||
|
import type { GetLeagueSeasonsResult } from '@core/racing/application/use-cases/GetLeagueSeasonsUseCase';
|
||||||
import { LeagueSeasonSummaryDTO } from '../dtos/LeagueSeasonSummaryDTO';
|
import { LeagueSeasonSummaryDTO } from '../dtos/LeagueSeasonSummaryDTO';
|
||||||
|
|
||||||
export class GetLeagueSeasonsPresenter {
|
export class GetLeagueSeasonsPresenter implements Presenter<GetLeagueSeasonsResult, LeagueSeasonSummaryDTO[]> {
|
||||||
private result: LeagueSeasonSummaryDTO[] | null = null;
|
private result: LeagueSeasonSummaryDTO[] | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(output: GetLeagueSeasonsOutputPort) {
|
present(input: GetLeagueSeasonsResult) {
|
||||||
this.result = output.seasons.map(season => ({
|
this.result = input.seasons.map(seasonSummary => ({
|
||||||
seasonId: season.seasonId,
|
seasonId: seasonSummary.season.id.toString(),
|
||||||
name: season.name,
|
name: seasonSummary.season.name.toString(),
|
||||||
status: season.status,
|
status: seasonSummary.season.status.toString(),
|
||||||
startDate: season.startDate,
|
startDate: seasonSummary.season.startDate.toISOString(),
|
||||||
endDate: season.endDate,
|
endDate: seasonSummary.season.endDate?.toISOString(),
|
||||||
isPrimary: season.isPrimary,
|
isPrimary: seasonSummary.isPrimary,
|
||||||
isParallelActive: season.isParallelActive,
|
isParallelActive: seasonSummary.isParallelActive,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): LeagueSeasonSummaryDTO[] | null {
|
getResponseModel(): LeagueSeasonSummaryDTO[] | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@ describe('MediaController', () => {
|
|||||||
describe('updateAvatar', () => {
|
describe('updateAvatar', () => {
|
||||||
it('should update avatar and return result', async () => {
|
it('should update avatar and return result', async () => {
|
||||||
const driverId = 'driver-123';
|
const driverId = 'driver-123';
|
||||||
const input = { mediaUrl: 'https://example.com/new-avatar.png' } as UpdateAvatarOutputDTO;
|
const input: UpdateAvatarInputDTO = { avatarUrl: 'https://example.com/new-avatar.png' };
|
||||||
const dto: UpdateAvatarOutputDTO = {
|
const dto: UpdateAvatarOutputDTO = {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
@@ -225,9 +225,9 @@ describe('MediaController', () => {
|
|||||||
|
|
||||||
const res = createMockResponse();
|
const res = createMockResponse();
|
||||||
|
|
||||||
await controller.updateAvatar(driverId, input as any, res);
|
await controller.updateAvatar(driverId, input, res);
|
||||||
|
|
||||||
expect(service.updateAvatar).toHaveBeenCalledWith(driverId, input as any);
|
expect(service.updateAvatar).toHaveBeenCalledWith(driverId, input);
|
||||||
expect(res.status).toHaveBeenCalledWith(200);
|
expect(res.status).toHaveBeenCalledWith(200);
|
||||||
expect(res.json).toHaveBeenCalledWith(dto);
|
expect(res.json).toHaveBeenCalledWith(dto);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export class MediaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async uploadMedia(
|
async uploadMedia(
|
||||||
input: UploadMediaInput & { file: Express.Multer.File } & { userId?: string; metadata?: Record<string, any> },
|
input: UploadMediaInput & { file: Express.Multer.File } & { userId?: string; metadata?: Record<string, unknown> },
|
||||||
): Promise<UploadMediaOutputDTO> {
|
): Promise<UploadMediaOutputDTO> {
|
||||||
this.logger.debug('[MediaService] Uploading media.');
|
this.logger.debug('[MediaService] Uploading media.');
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export class UpdateAvatarPresenter implements UseCaseOutputPort<UpdateAvatarResu
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: UpdateAvatarResult): void {
|
present(_result: UpdateAvatarResult): void {
|
||||||
this.model = {
|
this.model = {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,17 +44,11 @@ import type {
|
|||||||
UpdateMemberPaymentInput,
|
UpdateMemberPaymentInput,
|
||||||
UpdateMemberPaymentOutput,
|
UpdateMemberPaymentOutput,
|
||||||
GetPrizesQuery,
|
GetPrizesQuery,
|
||||||
GetPrizesOutput,
|
|
||||||
CreatePrizeInput,
|
CreatePrizeInput,
|
||||||
CreatePrizeOutput,
|
|
||||||
AwardPrizeInput,
|
AwardPrizeInput,
|
||||||
AwardPrizeOutput,
|
|
||||||
DeletePrizeInput,
|
DeletePrizeInput,
|
||||||
DeletePrizeOutput,
|
|
||||||
GetWalletQuery,
|
GetWalletQuery,
|
||||||
GetWalletOutput,
|
|
||||||
ProcessWalletTransactionInput,
|
ProcessWalletTransactionInput,
|
||||||
ProcessWalletTransactionOutput,
|
|
||||||
} from './dtos/PaymentsDto';
|
} from './dtos/PaymentsDto';
|
||||||
|
|
||||||
// Injection tokens
|
// Injection tokens
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class GetPaymentsPresenter implements UseCaseOutputPort<GetPaymentsResult
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): GetPaymentsOutput {
|
get responseModel(): GetPaymentsOutput {
|
||||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||||
return this.responseModel;
|
return this.responseModel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ import { ReviewProtestPresenter, type ReviewProtestResponseDTO } from './present
|
|||||||
// Tokens
|
// Tokens
|
||||||
import { LOGGER_TOKEN } from './ProtestsProviders';
|
import { LOGGER_TOKEN } from './ProtestsProviders';
|
||||||
|
|
||||||
|
import type { IProtestRepository } from '@core/racing/domain/repositories/IProtestRepository';
|
||||||
|
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
||||||
|
import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
||||||
|
import { PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN } from './ProtestsProviders';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProtestsService {
|
export class ProtestsService {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||||
import type { ReviewProtestResult } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
import type { ReviewProtestResult, ReviewProtestApplicationError } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||||
|
|
||||||
export interface ReviewProtestResponseDTO {
|
export interface ReviewProtestResponseDTO {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@@ -25,6 +25,14 @@ export class ReviewProtestPresenter implements UseCaseOutputPort<ReviewProtestRe
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
presentError(error: ReviewProtestApplicationError): void {
|
||||||
|
this.model = {
|
||||||
|
success: false,
|
||||||
|
errorCode: error.code,
|
||||||
|
message: error.details.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getResponseModel(): ReviewProtestResponseDTO | null {
|
getResponseModel(): ReviewProtestResponseDTO | null {
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class RaceController {
|
|||||||
@Query('driverId') driverId: string,
|
@Query('driverId') driverId: string,
|
||||||
): Promise<RaceDetailDTO> {
|
): Promise<RaceDetailDTO> {
|
||||||
const presenter = await this.raceService.getRaceDetail({ raceId, driverId });
|
const presenter = await this.raceService.getRaceDetail({ raceId, driverId });
|
||||||
return presenter.viewModel;
|
return await presenter.viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':raceId/results')
|
@Get(':raceId/results')
|
||||||
@@ -74,7 +74,7 @@ export class RaceController {
|
|||||||
@ApiResponse({ status: 200, description: 'Race results detail', type: RaceResultsDetailDTO })
|
@ApiResponse({ status: 200, description: 'Race results detail', type: RaceResultsDetailDTO })
|
||||||
async getRaceResultsDetail(@Param('raceId') raceId: string): Promise<RaceResultsDetailDTO> {
|
async getRaceResultsDetail(@Param('raceId') raceId: string): Promise<RaceResultsDetailDTO> {
|
||||||
const presenter = await this.raceService.getRaceResultsDetail(raceId);
|
const presenter = await this.raceService.getRaceResultsDetail(raceId);
|
||||||
return presenter.viewModel;
|
return await presenter.viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':raceId/sof')
|
@Get(':raceId/sof')
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { RaceDetailOutputPort } from '@core/racing/application/ports/output/RaceDetailOutputPort';
|
|
||||||
import type { RacesPageOutputPort } from '@core/racing/application/ports/output/RacesPageOutputPort';
|
import type { RacesPageOutputPort } from '@core/racing/application/ports/output/RacesPageOutputPort';
|
||||||
import type { RaceResultsDetailOutputPort } from '@core/racing/application/ports/output/RaceResultsDetailOutputPort';
|
import type { RaceResultsDetailOutputPort } from '@core/racing/application/ports/output/RaceResultsDetailOutputPort';
|
||||||
import type { RaceWithSOFOutputPort } from '@core/racing/application/ports/output/RaceWithSOFOutputPort';
|
import type { RaceWithSOFOutputPort } from '@core/racing/application/ports/output/RaceWithSOFOutputPort';
|
||||||
@@ -130,14 +129,15 @@ export class RaceService {
|
|||||||
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailPresenter> {
|
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching race detail:', params);
|
this.logger.debug('[RaceService] Fetching race detail:', params);
|
||||||
|
|
||||||
|
const presenter = new RaceDetailPresenter(this.driverRatingProvider, this.imageService, params);
|
||||||
|
this.getRaceDetailUseCase.setOutput(presenter);
|
||||||
|
|
||||||
const result = await this.getRaceDetailUseCase.execute(params);
|
const result = await this.getRaceDetailUseCase.execute(params);
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error('Failed to get race detail');
|
throw new Error('Failed to get race detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
const presenter = new RaceDetailPresenter(this.driverRatingProvider, this.imageService);
|
|
||||||
await presenter.present(result.value as RaceDetailOutputPort, params);
|
|
||||||
return presenter;
|
return presenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,4 +47,8 @@ export class AllRacesPageDataPresenter {
|
|||||||
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get viewModel(): AllRacesPageDataResponseModel {
|
||||||
|
return this.responseModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Result } from '@core/shared/application/Result';
|
|
||||||
import { GetAllRacesPresenter } from './GetAllRacesPresenter';
|
import { GetAllRacesPresenter } from './GetAllRacesPresenter';
|
||||||
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||||
|
|
||||||
@@ -6,7 +5,7 @@ describe('GetAllRacesPresenter', () => {
|
|||||||
it('should map races and distinct leagues into the DTO', async () => {
|
it('should map races and distinct leagues into the DTO', async () => {
|
||||||
const presenter = new GetAllRacesPresenter();
|
const presenter = new GetAllRacesPresenter();
|
||||||
|
|
||||||
const output: GetAllRacesOutputPort = {
|
const output: GetAllRacesResult = {
|
||||||
races: [
|
races: [
|
||||||
{
|
{
|
||||||
id: 'race-1',
|
id: 'race-1',
|
||||||
@@ -14,9 +13,8 @@ describe('GetAllRacesPresenter', () => {
|
|||||||
track: 'Track A',
|
track: 'Track A',
|
||||||
car: 'Car A',
|
car: 'Car A',
|
||||||
status: 'scheduled',
|
status: 'scheduled',
|
||||||
scheduledAt: '2025-01-01T10:00:00.000Z',
|
scheduledAt: new Date('2025-01-01T10:00:00.000Z'),
|
||||||
strengthOfField: 1500,
|
strengthOfField: 1500,
|
||||||
leagueName: 'League One',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'race-2',
|
id: 'race-2',
|
||||||
@@ -24,9 +22,8 @@ describe('GetAllRacesPresenter', () => {
|
|||||||
track: 'Track B',
|
track: 'Track B',
|
||||||
car: 'Car B',
|
car: 'Car B',
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
scheduledAt: '2025-01-02T10:00:00.000Z',
|
scheduledAt: new Date('2025-01-02T10:00:00.000Z'),
|
||||||
strengthOfField: null,
|
strengthOfField: undefined,
|
||||||
leagueName: 'League One',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'race-3',
|
id: 'race-3',
|
||||||
@@ -34,16 +31,22 @@ describe('GetAllRacesPresenter', () => {
|
|||||||
track: 'Track C',
|
track: 'Track C',
|
||||||
car: 'Car C',
|
car: 'Car C',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
scheduledAt: '2025-01-03T10:00:00.000Z',
|
scheduledAt: new Date('2025-01-03T10:00:00.000Z'),
|
||||||
strengthOfField: 1800,
|
strengthOfField: 1800,
|
||||||
leagueName: 'League Two',
|
|
||||||
},
|
},
|
||||||
],
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
] as any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
leagues: [
|
||||||
|
{ id: 'league-1', name: 'League One' },
|
||||||
|
{ id: 'league-2', name: 'League Two' },
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
] as any,
|
||||||
totalCount: 3,
|
totalCount: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
await presenter.present(output);
|
await presenter.present(output);
|
||||||
const viewModel = presenter.getViewModel();
|
const viewModel = presenter.getResponseModel();
|
||||||
|
|
||||||
expect(viewModel).not.toBeNull();
|
expect(viewModel).not.toBeNull();
|
||||||
expect(viewModel!.races).toHaveLength(3);
|
expect(viewModel!.races).toHaveLength(3);
|
||||||
@@ -61,13 +64,16 @@ describe('GetAllRacesPresenter', () => {
|
|||||||
it('should handle empty races by returning empty leagues', async () => {
|
it('should handle empty races by returning empty leagues', async () => {
|
||||||
const presenter = new GetAllRacesPresenter();
|
const presenter = new GetAllRacesPresenter();
|
||||||
|
|
||||||
const output: GetAllRacesOutputPort = {
|
const output: GetAllRacesResult = {
|
||||||
races: [],
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
races: [] as any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
leagues: [] as any,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
await presenter.present(output);
|
await presenter.present(output);
|
||||||
const viewModel = presenter.getViewModel();
|
const viewModel = presenter.getResponseModel();
|
||||||
|
|
||||||
expect(viewModel).not.toBeNull();
|
expect(viewModel).not.toBeNull();
|
||||||
expect(viewModel!.races).toHaveLength(0);
|
expect(viewModel!.races).toHaveLength(0);
|
||||||
|
|||||||
@@ -53,4 +53,8 @@ export class GetAllRacesPresenter implements UseCaseOutputPort<GetAllRacesResult
|
|||||||
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get viewModel(): GetAllRacesResponseModel {
|
||||||
|
return this.responseModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -44,4 +44,8 @@ export class GetTotalRacesPresenter {
|
|||||||
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get viewModel(): GetTotalRacesResponseModel {
|
||||||
|
return this.responseModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -50,4 +50,8 @@ export class ImportRaceResultsApiPresenter {
|
|||||||
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get viewModel(): ImportRaceResultsApiResponseModel {
|
||||||
|
return this.responseModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { GetRaceDetailResult } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
|
||||||
import type {
|
|
||||||
GetRaceDetailResult,
|
|
||||||
GetRaceDetailErrorCode,
|
|
||||||
} from '@core/racing/application/use-cases/GetRaceDetailUseCase';
|
|
||||||
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
|
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
|
||||||
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
|
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
|
||||||
import type { GetRaceDetailParamsDTO } from '../dtos/GetRaceDetailParamsDTO';
|
import type { GetRaceDetailParamsDTO } from '../dtos/GetRaceDetailParamsDTO';
|
||||||
@@ -16,47 +12,26 @@ import type { RaceDetailUserResultDTO } from '../dtos/RaceDetailUserResultDTO';
|
|||||||
|
|
||||||
export type GetRaceDetailResponseModel = RaceDetailDTO;
|
export type GetRaceDetailResponseModel = RaceDetailDTO;
|
||||||
|
|
||||||
export type GetRaceDetailApplicationError = ApplicationErrorCode<
|
export class RaceDetailPresenter implements UseCaseOutputPort<GetRaceDetailResult> {
|
||||||
GetRaceDetailErrorCode,
|
private result: GetRaceDetailResult | null = null;
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class RaceDetailPresenter {
|
|
||||||
private model: GetRaceDetailResponseModel | null = null;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly driverRatingProvider: DriverRatingProvider,
|
private readonly driverRatingProvider: DriverRatingProvider,
|
||||||
private readonly imageService: IImageServicePort,
|
private readonly imageService: IImageServicePort,
|
||||||
|
private readonly params: GetRaceDetailParamsDTO,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
reset(): void {
|
present(result: GetRaceDetailResult): void {
|
||||||
this.model = null;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async present(
|
async getResponseModel(): Promise<GetRaceDetailResponseModel | null> {
|
||||||
result: Result<GetRaceDetailResult, GetRaceDetailApplicationError>,
|
if (!this.result) {
|
||||||
params: GetRaceDetailParamsDTO,
|
return null;
|
||||||
): Promise<void> {
|
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
if (error.code === 'RACE_NOT_FOUND') {
|
|
||||||
this.model = {
|
|
||||||
race: null,
|
|
||||||
league: null,
|
|
||||||
entryList: [],
|
|
||||||
registration: {
|
|
||||||
isUserRegistered: false,
|
|
||||||
canRegister: false,
|
|
||||||
},
|
|
||||||
userResult: null,
|
|
||||||
} as RaceDetailDTO;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get race detail');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = result.unwrap();
|
const output = this.result;
|
||||||
|
const params = this.params;
|
||||||
|
|
||||||
const raceDTO: RaceDetailRaceDTO | null = output.race
|
const raceDTO: RaceDetailRaceDTO | null = output.race
|
||||||
? {
|
? {
|
||||||
@@ -118,7 +93,7 @@ export class RaceDetailPresenter {
|
|||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
this.model = {
|
return {
|
||||||
race: raceDTO,
|
race: raceDTO,
|
||||||
league: leagueDTO,
|
league: leagueDTO,
|
||||||
entryList: entryListDTO,
|
entryList: entryListDTO,
|
||||||
@@ -127,16 +102,11 @@ export class RaceDetailPresenter {
|
|||||||
} as RaceDetailDTO;
|
} as RaceDetailDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): GetRaceDetailResponseModel | null {
|
get viewModel(): Promise<GetRaceDetailResponseModel> {
|
||||||
return this.model;
|
return this.getResponseModel().then(model => {
|
||||||
}
|
if (!model) throw new Error('Presenter not presented');
|
||||||
|
return model;
|
||||||
get responseModel(): GetRaceDetailResponseModel {
|
});
|
||||||
if (!this.model) {
|
|
||||||
throw new Error('Presenter not presented');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.model;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateRatingChange(position: number): number {
|
private calculateRatingChange(position: number): number {
|
||||||
|
|||||||
@@ -62,4 +62,8 @@ export class RacePenaltiesPresenter {
|
|||||||
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get viewModel(): GetRacePenaltiesResponseModel {
|
||||||
|
return this.responseModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,4 +63,8 @@ export class RaceProtestsPresenter {
|
|||||||
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get viewModel(): GetRaceProtestsResponseModel {
|
||||||
|
return this.responseModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type {
|
import type {
|
||||||
GetRaceResultsDetailResult,
|
GetRaceResultsDetailResult,
|
||||||
@@ -16,33 +15,20 @@ export type GetRaceResultsDetailApplicationError = ApplicationErrorCode<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export class RaceResultsDetailPresenter {
|
export class RaceResultsDetailPresenter {
|
||||||
private model: GetRaceResultsDetailResponseModel | null = null;
|
private result: GetRaceResultsDetailResult | null = null;
|
||||||
|
|
||||||
constructor(private readonly imageService: IImageServicePort) {}
|
constructor(private readonly imageService: IImageServicePort) {}
|
||||||
|
|
||||||
reset(): void {
|
present(result: GetRaceResultsDetailResult): void {
|
||||||
this.model = null;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async present(
|
async getResponseModel(): Promise<GetRaceResultsDetailResponseModel | null> {
|
||||||
result: Result<GetRaceResultsDetailResult, GetRaceResultsDetailApplicationError>,
|
if (!this.result) {
|
||||||
): Promise<void> {
|
return null;
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
|
|
||||||
if (error.code === 'RACE_NOT_FOUND') {
|
|
||||||
this.model = {
|
|
||||||
raceId: '',
|
|
||||||
track: '',
|
|
||||||
results: [],
|
|
||||||
} as RaceResultsDetailDTO;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get race results detail');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = result.unwrap();
|
const output = this.result;
|
||||||
|
|
||||||
const driverMap = new Map(output.drivers.map(driver => [driver.id, driver]));
|
const driverMap = new Map(output.drivers.map(driver => [driver.id, driver]));
|
||||||
|
|
||||||
@@ -70,22 +56,17 @@ export class RaceResultsDetailPresenter {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.model = {
|
return {
|
||||||
raceId: output.race.id,
|
raceId: output.race.id,
|
||||||
track: output.race.track,
|
track: output.race.track,
|
||||||
results,
|
results,
|
||||||
} as RaceResultsDetailDTO;
|
} as RaceResultsDetailDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): GetRaceResultsDetailResponseModel | null {
|
get viewModel(): Promise<GetRaceResultsDetailResponseModel> {
|
||||||
return this.model;
|
return this.getResponseModel().then(model => {
|
||||||
}
|
if (!model) throw new Error('Presenter not presented');
|
||||||
|
return model;
|
||||||
get responseModel(): GetRaceResultsDetailResponseModel {
|
});
|
||||||
if (!this.model) {
|
|
||||||
throw new Error('Presenter not presented');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.model;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,4 +55,8 @@ export class RaceWithSOFPresenter {
|
|||||||
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get viewModel(): GetRaceWithSOFResponseModel {
|
||||||
|
return this.responseModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,4 +59,8 @@ export class RacesPageDataPresenter {
|
|||||||
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get viewModel(): GetRacesPageDataResponseModel {
|
||||||
|
return this.responseModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { SponsorService } from './SponsorService';
|
|||||||
import { NotificationService } from '@core/notifications/application/ports/NotificationService';
|
import { NotificationService } from '@core/notifications/application/ports/NotificationService';
|
||||||
import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository';
|
import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository';
|
||||||
import { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
import { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||||
import { IPaymentGateway } from '@core/racing/application/ports/IPaymentGateway';
|
import { IPaymentGateway } from '@core/payments/domain/ports/IPaymentGateway';
|
||||||
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
||||||
import { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
import { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||||
import { ILeagueWalletRepository } from '@core/racing/domain/repositories/ILeagueWalletRepository';
|
import { ILeagueWalletRepository } from '@core/racing/domain/repositories/ILeagueWalletRepository';
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { TeamService } from './TeamService';
|
import { TeamService } from './TeamService';
|
||||||
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||||
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
|
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
import { DriverTeamPresenter } from './presenters/DriverTeamPresenter';
|
import { DriverTeamPresenter } from './presenters/DriverTeamPresenter';
|
||||||
|
import { DriverTeamViewModel } from './dtos/TeamDto';
|
||||||
|
|
||||||
describe('TeamService', () => {
|
describe('TeamService', () => {
|
||||||
let service: TeamService;
|
let service: TeamService;
|
||||||
let getAllTeamsUseCase: jest.Mocked<GetAllTeamsUseCase>;
|
let getAllTeamsUseCase: jest.Mocked<GetAllTeamsUseCase>;
|
||||||
let getDriverTeamUseCase: jest.Mocked<GetDriverTeamUseCase>;
|
let getDriverTeamUseCase: jest.Mocked<GetDriverTeamUseCase>;
|
||||||
let logger: jest.Mocked<Logger>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const mockGetAllTeamsUseCase = {
|
const mockGetAllTeamsUseCase = {
|
||||||
@@ -46,7 +48,6 @@ describe('TeamService', () => {
|
|||||||
service = module.get<TeamService>(TeamService);
|
service = module.get<TeamService>(TeamService);
|
||||||
getAllTeamsUseCase = module.get(GetAllTeamsUseCase);
|
getAllTeamsUseCase = module.get(GetAllTeamsUseCase);
|
||||||
getDriverTeamUseCase = module.get(GetDriverTeamUseCase);
|
getDriverTeamUseCase = module.get(GetDriverTeamUseCase);
|
||||||
logger = module.get('Logger');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
@@ -56,12 +57,14 @@ describe('TeamService', () => {
|
|||||||
describe('getAll', () => {
|
describe('getAll', () => {
|
||||||
it('should call use case and return result', async () => {
|
it('should call use case and return result', async () => {
|
||||||
const mockResult = { isOk: () => true, value: { teams: [], totalCount: 0 } };
|
const mockResult = { isOk: () => true, value: { teams: [], totalCount: 0 } };
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
getAllTeamsUseCase.execute.mockResolvedValue(mockResult as any);
|
getAllTeamsUseCase.execute.mockResolvedValue(mockResult as any);
|
||||||
|
|
||||||
const mockPresenter = {
|
const mockPresenter = {
|
||||||
present: jest.fn(),
|
present: jest.fn(),
|
||||||
getViewModel: jest.fn().mockReturnValue({ teams: [], totalCount: 0 }),
|
getViewModel: jest.fn().mockReturnValue({ teams: [], totalCount: 0 }),
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(AllTeamsPresenter as any) = jest.fn().mockImplementation(() => mockPresenter);
|
(AllTeamsPresenter as any) = jest.fn().mockImplementation(() => mockPresenter);
|
||||||
|
|
||||||
const result = await service.getAll();
|
const result = await service.getAll();
|
||||||
@@ -74,12 +77,14 @@ describe('TeamService', () => {
|
|||||||
describe('getDriverTeam', () => {
|
describe('getDriverTeam', () => {
|
||||||
it('should call use case and return result', async () => {
|
it('should call use case and return result', async () => {
|
||||||
const mockResult = { isOk: () => true, value: {} };
|
const mockResult = { isOk: () => true, value: {} };
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
getDriverTeamUseCase.execute.mockResolvedValue(mockResult as any);
|
getDriverTeamUseCase.execute.mockResolvedValue(mockResult as any);
|
||||||
|
|
||||||
const mockPresenter = {
|
const mockPresenter = {
|
||||||
present: jest.fn(),
|
present: jest.fn(),
|
||||||
getViewModel: jest.fn().mockReturnValue({} as DriverTeamViewModel),
|
getViewModel: jest.fn().mockReturnValue({} as DriverTeamViewModel),
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(DriverTeamPresenter as any) = jest.fn().mockImplementation(() => mockPresenter);
|
(DriverTeamPresenter as any) = jest.fn().mockImplementation(() => mockPresenter);
|
||||||
|
|
||||||
const result = await service.getDriverTeam('driver1');
|
const result = await service.getDriverTeam('driver1');
|
||||||
@@ -90,6 +95,7 @@ describe('TeamService', () => {
|
|||||||
|
|
||||||
it('should return null on error', async () => {
|
it('should return null on error', async () => {
|
||||||
const mockResult = { isErr: () => true, error: {} };
|
const mockResult = { isErr: () => true, error: {} };
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
getDriverTeamUseCase.execute.mockResolvedValue(mockResult as any);
|
getDriverTeamUseCase.execute.mockResolvedValue(mockResult as any);
|
||||||
|
|
||||||
const result = await service.getDriverTeam('driver1');
|
const result = await service.getDriverTeam('driver1');
|
||||||
|
|||||||
@@ -12,14 +12,18 @@ import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO';
|
|||||||
|
|
||||||
// Core imports
|
// Core imports
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
import type { Logger } from '@core/shared/application/Logger';
|
||||||
|
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
|
||||||
|
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
|
||||||
|
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||||
|
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
|
||||||
|
|
||||||
// Use cases
|
// Use cases
|
||||||
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||||
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
|
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
|
||||||
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
|
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
|
||||||
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
|
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
|
||||||
import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase';
|
import { CreateTeamUseCase, CreateTeamInput } from '@core/racing/application/use-cases/CreateTeamUseCase';
|
||||||
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
|
import { UpdateTeamUseCase, UpdateTeamInput } from '@core/racing/application/use-cases/UpdateTeamUseCase';
|
||||||
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
||||||
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
|
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
|
||||||
|
|
||||||
@@ -34,97 +38,90 @@ import { CreateTeamPresenter } from './presenters/CreateTeamPresenter';
|
|||||||
import { UpdateTeamPresenter } from './presenters/UpdateTeamPresenter';
|
import { UpdateTeamPresenter } from './presenters/UpdateTeamPresenter';
|
||||||
|
|
||||||
// Tokens
|
// Tokens
|
||||||
import { LOGGER_TOKEN } from './TeamProviders';
|
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, LOGGER_TOKEN } from './TeamProviders';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamService {
|
export class TeamService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly getAllTeamsUseCase: GetAllTeamsUseCase,
|
@Inject(TEAM_REPOSITORY_TOKEN) private readonly teamRepository: ITeamRepository,
|
||||||
private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase,
|
@Inject(TEAM_MEMBERSHIP_REPOSITORY_TOKEN) private readonly membershipRepository: ITeamMembershipRepository,
|
||||||
private readonly getTeamMembersUseCase: GetTeamMembersUseCase,
|
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
|
||||||
private readonly getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase,
|
@Inject(IMAGE_SERVICE_TOKEN) private readonly imageService: IImageServicePort,
|
||||||
private readonly createTeamUseCase: CreateTeamUseCase,
|
|
||||||
private readonly updateTeamUseCase: UpdateTeamUseCase,
|
|
||||||
private readonly getDriverTeamUseCase: GetDriverTeamUseCase,
|
|
||||||
private readonly getTeamMembershipUseCase: GetTeamMembershipUseCase,
|
|
||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getAll(): Promise<any> { // TODO: type
|
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||||
this.logger.debug('[TeamService] Fetching all teams.');
|
this.logger.debug('[TeamService] Fetching all teams.');
|
||||||
|
|
||||||
const result = await this.getAllTeamsUseCase.execute();
|
|
||||||
const presenter = new AllTeamsPresenter();
|
const presenter = new AllTeamsPresenter();
|
||||||
|
const useCase = new GetAllTeamsUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
||||||
|
const result = await useCase.execute();
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error('Error fetching all teams', result.error);
|
this.logger.error('Error fetching all teams', result.error?.details?.message || 'Unknown error');
|
||||||
await presenter.present({ teams: [], totalCount: 0 });
|
return { teams: [], totalCount: 0 };
|
||||||
return presenter.responseModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await presenter.present(result.value);
|
|
||||||
return presenter.responseModel;
|
return presenter.responseModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDetails(teamId: string, userId?: string): Promise<TeamDetailsPresenter> {
|
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
|
||||||
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);
|
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);
|
||||||
|
|
||||||
const presenter = new TeamDetailsPresenter();
|
const presenter = new TeamDetailsPresenter();
|
||||||
const result = await this.getTeamDetailsUseCase.execute({ teamId, driverId: userId || '' });
|
const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository, presenter);
|
||||||
|
const result = await useCase.execute({ teamId, driverId: userId || '' });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team details for teamId: ${teamId}`, result.error);
|
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
return presenter;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await presenter.present(result.value);
|
return presenter.getResponseModel();
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMembers(teamId: string): Promise<TeamMembersPresenter> {
|
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
|
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
|
||||||
|
|
||||||
const presenter = new TeamMembersPresenter();
|
const presenter = new TeamMembersPresenter();
|
||||||
const result = await this.getTeamMembersUseCase.execute({ teamId });
|
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.imageService, this.logger, presenter);
|
||||||
|
const result = await useCase.execute({ teamId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team members for teamId: ${teamId}`, result.error);
|
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
await presenter.present({
|
return {
|
||||||
members: [],
|
members: [],
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
ownerCount: 0,
|
ownerCount: 0,
|
||||||
managerCount: 0,
|
managerCount: 0,
|
||||||
memberCount: 0,
|
memberCount: 0,
|
||||||
} as unknown as any);
|
};
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await presenter.present(result.value);
|
return presenter.getResponseModel()!;
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getJoinRequests(teamId: string): Promise<TeamJoinRequestsPresenter> {
|
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`);
|
this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`);
|
||||||
|
|
||||||
const presenter = new TeamJoinRequestsPresenter();
|
const presenter = new TeamJoinRequestsPresenter();
|
||||||
const result = await this.getTeamJoinRequestsUseCase.execute({ teamId });
|
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, presenter);
|
||||||
|
const result = await useCase.execute({ teamId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, result.error);
|
this.logger.error(new Error(`Error fetching team join requests for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`));
|
||||||
await presenter.present({
|
return {
|
||||||
requests: [],
|
requests: [],
|
||||||
pendingCount: 0,
|
pendingCount: 0,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
} as unknown as any);
|
};
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await presenter.present(result.value);
|
return presenter.getResponseModel()!;
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(input: CreateTeamInputDTO, userId?: string): Promise<CreateTeamPresenter> {
|
async create(input: CreateTeamInputDTO, userId?: string): Promise<CreateTeamOutputDTO> {
|
||||||
this.logger.debug('[TeamService] Creating team', { input, userId });
|
this.logger.debug('[TeamService] Creating team', { input, userId });
|
||||||
|
|
||||||
const presenter = new CreateTeamPresenter();
|
const presenter = new CreateTeamPresenter();
|
||||||
|
|
||||||
const command = {
|
const command: CreateTeamInput = {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
tag: input.tag,
|
tag: input.tag,
|
||||||
description: input.description ?? '',
|
description: input.description ?? '',
|
||||||
@@ -132,68 +129,66 @@ export class TeamService {
|
|||||||
leagues: [],
|
leagues: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.createTeamUseCase.execute(command as any);
|
const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
||||||
|
const result = await useCase.execute(command);
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error('Error creating team', result.error);
|
this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
presenter.presentError();
|
return { id: '', success: false };
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
presenter.presentSuccess(result.value);
|
return presenter.responseModel;
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise<UpdateTeamPresenter> {
|
async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise<UpdateTeamOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Updating team ${teamId}`, { input, userId });
|
this.logger.debug(`[TeamService] Updating team ${teamId}`, { input, userId });
|
||||||
|
|
||||||
const presenter = new UpdateTeamPresenter();
|
const presenter = new UpdateTeamPresenter();
|
||||||
|
|
||||||
const command = {
|
const command: UpdateTeamInput = {
|
||||||
teamId,
|
teamId,
|
||||||
updates: {
|
updates: {
|
||||||
name: input.name,
|
...(input.name !== undefined && { name: input.name }),
|
||||||
tag: input.tag,
|
...(input.tag !== undefined && { tag: input.tag }),
|
||||||
description: input.description,
|
...(input.description !== undefined && { description: input.description }),
|
||||||
},
|
},
|
||||||
updatedBy: userId || '',
|
updatedBy: userId || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.updateTeamUseCase.execute(command as any);
|
const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository, presenter);
|
||||||
|
const result = await useCase.execute(command);
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error updating team ${teamId}`, result.error);
|
this.logger.error(`Error updating team ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
presenter.presentError();
|
return { success: false };
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
presenter.presentSuccess();
|
return presenter.responseModel;
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDriverTeam(driverId: string): Promise<DriverTeamPresenter> {
|
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||||
this.logger.debug(`[TeamService] Fetching driver team for driverId: ${driverId}`);
|
this.logger.debug(`[TeamService] Fetching driver team for driverId: ${driverId}`);
|
||||||
|
|
||||||
const presenter = new DriverTeamPresenter();
|
const presenter = new DriverTeamPresenter();
|
||||||
const result = await this.getDriverTeamUseCase.execute({ driverId });
|
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
||||||
|
const result = await useCase.execute({ driverId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching driver team for driverId: ${driverId}`, result.error);
|
this.logger.error(`Error fetching driver team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
return presenter;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await presenter.present(result.value);
|
return presenter.getResponseModel();
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMembership(teamId: string, driverId: string): Promise<TeamMembershipPresenter> {
|
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||||
this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`);
|
this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`);
|
||||||
|
|
||||||
const presenter = new TeamMembershipPresenter();
|
const presenter = new TeamMembershipPresenter();
|
||||||
const result = await this.getTeamMembershipUseCase.execute({ teamId, driverId });
|
const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger, presenter);
|
||||||
|
const result = await useCase.execute({ teamId, driverId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}`, result.error);
|
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
return presenter;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
presenter.present(result.value as any);
|
return presenter.responseModel;
|
||||||
return presenter;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,26 @@
|
|||||||
import type { GetAllTeamsErrorCode, GetAllTeamsResult } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import type { GetAllTeamsResult } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import { GetAllTeamsOutputDTO } from '../dtos/GetAllTeamsOutputDTO';
|
import { GetAllTeamsOutputDTO } from '../dtos/GetAllTeamsOutputDTO';
|
||||||
|
|
||||||
export type GetAllTeamsError = ApplicationErrorCode<GetAllTeamsErrorCode, { message: string }>;
|
export class AllTeamsPresenter implements UseCaseOutputPort<GetAllTeamsResult> {
|
||||||
|
|
||||||
export class AllTeamsPresenter {
|
|
||||||
private model: GetAllTeamsOutputDTO | null = null;
|
private model: GetAllTeamsOutputDTO | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: Result<GetAllTeamsResult, GetAllTeamsError>): void {
|
present(result: GetAllTeamsResult): void {
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get teams');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
this.model = {
|
this.model = {
|
||||||
teams: output.teams.map(team => ({
|
teams: result.teams.map(team => ({
|
||||||
id: team.id,
|
id: team.id,
|
||||||
name: team.name,
|
name: team.name.toString(),
|
||||||
tag: team.tag,
|
tag: team.tag.toString(),
|
||||||
description: team.description,
|
description: team.description?.toString() || '',
|
||||||
memberCount: team.memberCount,
|
memberCount: team.memberCount,
|
||||||
leagues: team.leagues || [],
|
leagues: team.leagues?.map(l => l.toString()) || [],
|
||||||
// Note: specialization, region, languages not available in output
|
// Note: specialization, region, languages not available in output
|
||||||
})),
|
})),
|
||||||
totalCount: output.totalCount ?? output.teams.length,
|
totalCount: result.totalCount ?? result.teams.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,17 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { CreateTeamResult } from '@core/racing/application/use-cases/CreateTeamUseCase';
|
||||||
import type { CreateTeamErrorCode, CreateTeamResult } from '@core/racing/application/use-cases/CreateTeamUseCase';
|
|
||||||
import type { CreateTeamOutputDTO } from '../dtos/CreateTeamOutputDTO';
|
import type { CreateTeamOutputDTO } from '../dtos/CreateTeamOutputDTO';
|
||||||
|
|
||||||
export type CreateTeamError = ApplicationErrorCode<CreateTeamErrorCode, { message: string }>;
|
export class CreateTeamPresenter implements UseCaseOutputPort<CreateTeamResult> {
|
||||||
|
|
||||||
export class CreateTeamPresenter {
|
|
||||||
private model: CreateTeamOutputDTO | null = null;
|
private model: CreateTeamOutputDTO | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: Result<CreateTeamResult, CreateTeamError>): void {
|
present(result: CreateTeamResult): void {
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
// Validation and expected domain errors map to an unsuccessful DTO
|
|
||||||
if (error.code === 'VALIDATION_ERROR' || error.code === 'LEAGUE_NOT_FOUND') {
|
|
||||||
this.model = {
|
|
||||||
id: '',
|
|
||||||
success: false,
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to create team');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
this.model = {
|
this.model = {
|
||||||
id: output.team.id,
|
id: result.team.id,
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,39 @@
|
|||||||
import { DriverTeamOutputPort } from '@core/racing/application/ports/output/DriverTeamOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
|
import type { GetDriverTeamResult } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
||||||
import { GetDriverTeamOutputDTO } from '../dtos/GetDriverTeamOutputDTO';
|
import { GetDriverTeamOutputDTO } from '../dtos/GetDriverTeamOutputDTO';
|
||||||
|
|
||||||
export class DriverTeamPresenter {
|
export class DriverTeamPresenter implements UseCaseOutputPort<GetDriverTeamResult> {
|
||||||
private result: GetDriverTeamOutputDTO | null = null;
|
private result: GetDriverTeamOutputDTO | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async present(output: DriverTeamOutputPort) {
|
present(result: GetDriverTeamResult): void {
|
||||||
const isOwner = output.team.ownerId === output.driverId;
|
const isOwner = result.team.ownerId.toString() === result.driverId;
|
||||||
const canManage = isOwner || output.membership.role === 'owner' || output.membership.role === 'manager';
|
const canManage = isOwner || result.membership.role === 'owner' || result.membership.role === 'manager';
|
||||||
|
|
||||||
this.result = {
|
this.result = {
|
||||||
team: {
|
team: {
|
||||||
id: output.team.id,
|
id: result.team.id,
|
||||||
name: output.team.name,
|
name: result.team.name.toString(),
|
||||||
tag: output.team.tag,
|
tag: result.team.tag.toString(),
|
||||||
description: output.team.description || '',
|
description: result.team.description?.toString() || '',
|
||||||
ownerId: output.team.ownerId,
|
ownerId: result.team.ownerId.toString(),
|
||||||
leagues: output.team.leagues || [],
|
leagues: result.team.leagues?.map(l => l.toString()) || [],
|
||||||
createdAt: output.team.createdAt.toISOString(),
|
createdAt: result.team.createdAt.toDate().toISOString(),
|
||||||
},
|
},
|
||||||
membership: {
|
membership: {
|
||||||
role: output.membership.role === 'driver' ? 'member' : output.membership.role,
|
role: result.membership.role === 'driver' ? 'member' : result.membership.role,
|
||||||
joinedAt: output.membership.joinedAt.toISOString(),
|
joinedAt: result.membership.joinedAt.toISOString(),
|
||||||
isActive: output.membership.status === 'active',
|
isActive: result.membership.status === 'active',
|
||||||
},
|
},
|
||||||
isOwner,
|
isOwner,
|
||||||
canManage,
|
canManage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewModel(): GetDriverTeamOutputDTO | null {
|
getResponseModel(): GetDriverTeamOutputDTO | null {
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user