refactor api modules
This commit is contained in:
@@ -1,24 +1,24 @@
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { HelloController } from './presentation/hello.controller';
|
||||
import { HelloService } from './application/hello/hello.service';
|
||||
import { AnalyticsModule } from './domain/analytics/AnalyticsModule';
|
||||
import { DatabaseModule } from './infrastructure/database/database.module';
|
||||
import { LoggingModule } from './infrastructure/logging/LoggingModule';
|
||||
import { BootstrapModule } from './infrastructure/bootstrap/BootstrapModule';
|
||||
import { AuthModule } from './domain/auth/AuthModule';
|
||||
import { BootstrapModule } from './domain/bootstrap/BootstrapModule';
|
||||
import { DashboardModule } from './domain/dashboard/DashboardModule';
|
||||
import { LeagueModule } from './domain/league/LeagueModule';
|
||||
import { RaceModule } from './domain/race/RaceModule';
|
||||
import { ProtestsModule } from './domain/protests/ProtestsModule';
|
||||
import { TeamModule } from './domain/team/TeamModule';
|
||||
import { SponsorModule } from './domain/sponsor/SponsorModule';
|
||||
import { DatabaseModule } from './domain/database/DatabaseModule';
|
||||
import { DriverModule } from './domain/driver/DriverModule';
|
||||
import { HelloModule } from './domain/hello/HelloModule';
|
||||
import { LeagueModule } from './domain/league/LeagueModule';
|
||||
import { LoggingModule } from './domain/logging/LoggingModule';
|
||||
import { MediaModule } from './domain/media/MediaModule';
|
||||
import { PaymentsModule } from './domain/payments/PaymentsModule';
|
||||
import { ProtestsModule } from './domain/protests/ProtestsModule';
|
||||
import { RaceModule } from './domain/race/RaceModule';
|
||||
import { SponsorModule } from './domain/sponsor/SponsorModule';
|
||||
import { TeamModule } from './domain/team/TeamModule';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
HelloModule,
|
||||
DatabaseModule,
|
||||
LoggingModule,
|
||||
BootstrapModule,
|
||||
@@ -34,7 +34,5 @@ import { PaymentsModule } from './domain/payments/PaymentsModule';
|
||||
MediaModule,
|
||||
PaymentsModule,
|
||||
],
|
||||
controllers: [HelloController],
|
||||
providers: [HelloService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { HelloService } from './hello.service';
|
||||
import { HelloPresenter } from './presenters/HelloPresenter';
|
||||
|
||||
describe('HelloService', () => {
|
||||
let service: HelloService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [HelloService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<HelloService>(HelloService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return "Hello World!"', () => {
|
||||
const presenter = service.getHello();
|
||||
expect(presenter.responseModel).toEqual({ message: 'Hello World!' });
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import { HelloPresenter, HelloResponseModel } from './presenters/HelloPresenter';
|
||||
|
||||
@Injectable()
|
||||
export class HelloService {
|
||||
constructor(private readonly presenter: HelloPresenter) {}
|
||||
|
||||
getHello(): HelloResponseModel {
|
||||
const result = Result.ok('Hello World!');
|
||||
this.presenter.present(result);
|
||||
return this.presenter.responseModel;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
|
||||
export interface HelloResponseModel {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export class HelloPresenter {
|
||||
private result: HelloResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(result: Result<string, Error>): void {
|
||||
if (result.isErr()) {
|
||||
throw result.unwrapErr();
|
||||
}
|
||||
const message = result.unwrap();
|
||||
this.result = { message };
|
||||
}
|
||||
|
||||
get responseModel(): HelloResponseModel {
|
||||
if (!this.result) {
|
||||
throw new Error('HelloPresenter not presented');
|
||||
}
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EnsureInitialData } from '@/adapters/bootstrap/EnsureInitialData';
|
||||
import { Module, OnModuleInit } from '@nestjs/common';
|
||||
import { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData';
|
||||
import { BootstrapProviders } from './BootstrapProviders';
|
||||
|
||||
@Module({
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AnalyticsSnapshotOrmEntity } from '../../../../..//persistence/typeorm/analytics/AnalyticsSnapshotOrmEntity';
|
||||
import { EngagementOrmEntity } from '../../../../..//persistence/typeorm/analytics/EngagementOrmEntity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -12,7 +10,7 @@ import { EngagementOrmEntity } from '../../../../..//persistence/typeorm/analyti
|
||||
username: process.env.DATABASE_USER || 'user',
|
||||
password: process.env.DATABASE_PASSWORD || 'password',
|
||||
database: process.env.DATABASE_NAME || 'gridpilot',
|
||||
entities: [AnalyticsSnapshotOrmEntity, EngagementOrmEntity],
|
||||
// entities: [AnalyticsSnapshotOrmEntity, EngagementOrmEntity],
|
||||
synchronize: true, // Use carefully in production
|
||||
}),
|
||||
],
|
||||
14
apps/api/src/domain/hello/HelloController.ts
Normal file
14
apps/api/src/domain/hello/HelloController.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { HelloService } from './HelloService';
|
||||
|
||||
@Controller()
|
||||
export class HelloController {
|
||||
constructor(private readonly helloService: HelloService) {}
|
||||
|
||||
@Get()
|
||||
getHello() {
|
||||
return this.helloService.getHello();
|
||||
}
|
||||
}
|
||||
9
apps/api/src/domain/hello/HelloModule.ts
Normal file
9
apps/api/src/domain/hello/HelloModule.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { HelloController } from "./HelloController";
|
||||
import { HelloService } from "./HelloService";
|
||||
|
||||
@Module({
|
||||
controllers: [HelloController],
|
||||
exports: [HelloService],
|
||||
})
|
||||
export class HelloModule {}
|
||||
11
apps/api/src/domain/hello/HelloService.ts
Normal file
11
apps/api/src/domain/hello/HelloService.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class HelloService {
|
||||
|
||||
getHello() {
|
||||
return "Hello World";
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,15 @@ import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
// Import use cases
|
||||
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||
|
||||
// Import presenters
|
||||
import { ReviewProtestPresenter } from './presenters/ReviewProtestPresenter';
|
||||
|
||||
// Define injection tokens
|
||||
export const PROTEST_REPOSITORY_TOKEN = 'IProtestRepository';
|
||||
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
||||
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
export const REVIEW_PROTEST_PRESENTER_TOKEN = 'ReviewProtestPresenter';
|
||||
|
||||
export const ProtestsProviders: Provider[] = [
|
||||
ProtestsService, // Provide the service itself
|
||||
@@ -40,6 +44,26 @@ export const ProtestsProviders: Provider[] = [
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
{
|
||||
provide: REVIEW_PROTEST_PRESENTER_TOKEN,
|
||||
useClass: ReviewProtestPresenter,
|
||||
},
|
||||
// Use cases
|
||||
ReviewProtestUseCase,
|
||||
{
|
||||
provide: ReviewProtestUseCase,
|
||||
useFactory: (
|
||||
protestRepo: any,
|
||||
raceRepo: any,
|
||||
leagueMembershipRepo: any,
|
||||
logger: Logger,
|
||||
output: ReviewProtestPresenter,
|
||||
) => new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger, output),
|
||||
inject: [
|
||||
PROTEST_REPOSITORY_TOKEN,
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
REVIEW_PROTEST_PRESENTER_TOKEN,
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -3,10 +3,10 @@ import { Result } from '@core/shared/application/Result';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type {
|
||||
ReviewProtestUseCase,
|
||||
ReviewProtestResult,
|
||||
ReviewProtestApplicationError,
|
||||
} from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||
import { ProtestsService } from './ProtestsService';
|
||||
import type { ReviewProtestPresenter } from './presenters/ReviewProtestPresenter';
|
||||
import type { ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';
|
||||
|
||||
describe('ProtestsService', () => {
|
||||
@@ -17,6 +17,21 @@ describe('ProtestsService', () => {
|
||||
beforeEach(() => {
|
||||
executeMock = vi.fn();
|
||||
const reviewProtestUseCase = { execute: executeMock } as unknown as ReviewProtestUseCase;
|
||||
const reviewProtestPresenter = {
|
||||
reset: vi.fn(),
|
||||
setCommand: vi.fn(),
|
||||
present: vi.fn(),
|
||||
presentError: vi.fn(),
|
||||
getResponseModel: vi.fn(),
|
||||
get responseModel() {
|
||||
return {
|
||||
success: true,
|
||||
protestId: 'test',
|
||||
stewardId: 'test',
|
||||
decision: 'uphold' as const,
|
||||
};
|
||||
},
|
||||
} as unknown as ReviewProtestPresenter;
|
||||
logger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
@@ -24,7 +39,7 @@ describe('ProtestsService', () => {
|
||||
error: vi.fn(),
|
||||
} as unknown as Logger;
|
||||
|
||||
service = new ProtestsService(reviewProtestUseCase, logger);
|
||||
service = new ProtestsService(reviewProtestUseCase, reviewProtestPresenter, logger);
|
||||
});
|
||||
|
||||
const baseCommand = {
|
||||
@@ -35,15 +50,7 @@ describe('ProtestsService', () => {
|
||||
};
|
||||
|
||||
it('returns DTO with success model on success', async () => {
|
||||
const coreResult: ReviewProtestResult = {
|
||||
leagueId: 'league-1',
|
||||
protestId: baseCommand.protestId,
|
||||
status: 'upheld',
|
||||
stewardId: baseCommand.stewardId,
|
||||
decision: baseCommand.decision,
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.ok<ReviewProtestResult, ReviewProtestApplicationError>(coreResult));
|
||||
executeMock.mockResolvedValue(Result.ok(undefined));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
@@ -62,7 +69,7 @@ describe('ProtestsService', () => {
|
||||
details: { message: 'Protest not found' },
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error));
|
||||
executeMock.mockResolvedValue(Result.err<void, ReviewProtestApplicationError>(error));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
@@ -79,7 +86,7 @@ describe('ProtestsService', () => {
|
||||
details: { message: 'Race not found for protest' },
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error));
|
||||
executeMock.mockResolvedValue(Result.err<void, ReviewProtestApplicationError>(error));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
@@ -96,7 +103,7 @@ describe('ProtestsService', () => {
|
||||
details: { message: 'Steward is not authorized to review this protest' },
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error));
|
||||
executeMock.mockResolvedValue(Result.err<void, ReviewProtestApplicationError>(error));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
@@ -114,7 +121,7 @@ describe('ProtestsService', () => {
|
||||
details: { message: 'Failed to review protest' },
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error));
|
||||
executeMock.mockResolvedValue(Result.err<void, ReviewProtestApplicationError>(error));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
|
||||
@@ -8,17 +8,14 @@ import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewP
|
||||
import { ReviewProtestPresenter, type ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';
|
||||
|
||||
// Tokens
|
||||
import { LOGGER_TOKEN } from './ProtestsProviders';
|
||||
import { LOGGER_TOKEN, REVIEW_PROTEST_PRESENTER_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()
|
||||
export class ProtestsService {
|
||||
constructor(
|
||||
private readonly reviewProtestUseCase: ReviewProtestUseCase,
|
||||
@Inject(REVIEW_PROTEST_PRESENTER_TOKEN) private readonly reviewProtestPresenter: ReviewProtestPresenter,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -30,11 +27,14 @@ export class ProtestsService {
|
||||
}): Promise<ReviewProtestResponseDTO> {
|
||||
this.logger.debug('[ProtestsService] Reviewing protest:', command);
|
||||
|
||||
const result = await this.reviewProtestUseCase.execute(command);
|
||||
const presenter = new ReviewProtestPresenter();
|
||||
// Set the command on the presenter so it can include stewardId and decision in the response
|
||||
this.reviewProtestPresenter.setCommand({
|
||||
stewardId: command.stewardId,
|
||||
decision: command.decision,
|
||||
});
|
||||
|
||||
presenter.present(result);
|
||||
await this.reviewProtestUseCase.execute(command);
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.reviewProtestPresenter.responseModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,27 @@ export interface ReviewProtestResponseDTO {
|
||||
|
||||
export class ReviewProtestPresenter implements UseCaseOutputPort<ReviewProtestResult> {
|
||||
private model: ReviewProtestResponseDTO | null = null;
|
||||
private command: { stewardId: string; decision: 'uphold' | 'dismiss' } | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
this.command = null;
|
||||
}
|
||||
|
||||
setCommand(command: { stewardId: string; decision: 'uphold' | 'dismiss' }): void {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
present(result: ReviewProtestResult): void {
|
||||
if (!this.command) {
|
||||
throw new Error('Command must be set before presenting result');
|
||||
}
|
||||
|
||||
this.model = {
|
||||
success: true,
|
||||
protestId: result.protestId,
|
||||
decision: result.status === 'upheld' ? 'uphold' : 'dismiss',
|
||||
stewardId: this.command.stewardId,
|
||||
decision: this.command.decision,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { RaceController } from './RaceController';
|
||||
import { RaceService } from './RaceService';
|
||||
import { vi, Mocked } from 'vitest';
|
||||
|
||||
describe('RaceController', () => {
|
||||
let controller: RaceController;
|
||||
let service: jest.Mocked<RaceService>;
|
||||
let service: Mocked<RaceService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockService = {
|
||||
getAllRaces: jest.fn(),
|
||||
getTotalRaces: jest.fn(),
|
||||
getRacesPageData: jest.fn(),
|
||||
getAllRacesPageData: jest.fn(),
|
||||
getRaceDetail: jest.fn(),
|
||||
getRaceResultsDetail: jest.fn(),
|
||||
getRaceWithSOF: jest.fn(),
|
||||
getRaceProtests: jest.fn(),
|
||||
getRacePenalties: jest.fn(),
|
||||
registerForRace: jest.fn(),
|
||||
withdrawFromRace: jest.fn(),
|
||||
cancelRace: jest.fn(),
|
||||
completeRace: jest.fn(),
|
||||
importRaceResults: jest.fn(),
|
||||
fileProtest: jest.fn(),
|
||||
applyQuickPenalty: jest.fn(),
|
||||
applyPenalty: jest.fn(),
|
||||
requestProtestDefense: jest.fn(),
|
||||
} as unknown as jest.Mocked<RaceService>;
|
||||
getAllRaces: vi.fn(),
|
||||
getTotalRaces: vi.fn(),
|
||||
getRacesPageData: vi.fn(),
|
||||
getAllRacesPageData: vi.fn(),
|
||||
getRaceDetail: vi.fn(),
|
||||
getRaceResultsDetail: vi.fn(),
|
||||
getRaceWithSOF: vi.fn(),
|
||||
getRaceProtests: vi.fn(),
|
||||
getRacePenalties: vi.fn(),
|
||||
registerForRace: vi.fn(),
|
||||
withdrawFromRace: vi.fn(),
|
||||
cancelRace: vi.fn(),
|
||||
completeRace: vi.fn(),
|
||||
importRaceResults: vi.fn(),
|
||||
fileProtest: vi.fn(),
|
||||
applyQuickPenalty: vi.fn(),
|
||||
applyPenalty: vi.fn(),
|
||||
requestProtestDefense: vi.fn(),
|
||||
} as unknown as Mocked<RaceService>;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [RaceController],
|
||||
@@ -39,7 +40,7 @@ describe('RaceController', () => {
|
||||
}).compile();
|
||||
|
||||
controller = module.get<RaceController>(RaceController);
|
||||
service = module.get(RaceService) as jest.Mocked<RaceService>;
|
||||
service = module.get(RaceService) as Mocked<RaceService>;
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
@@ -48,25 +49,25 @@ describe('RaceController', () => {
|
||||
|
||||
describe('getAllRaces', () => {
|
||||
it('should return all races view model', async () => {
|
||||
const mockViewModel = { races: [], filters: { statuses: [], leagues: [] } } as { races: unknown[]; filters: { statuses: unknown[]; leagues: unknown[] } };
|
||||
service.getAllRaces.mockResolvedValue({ viewModel: mockViewModel } as unknown as ReturnType<RaceService['getAllRaces']>);
|
||||
const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as any;
|
||||
service.getAllRaces.mockResolvedValue(mockPresenter);
|
||||
|
||||
const result = await controller.getAllRaces();
|
||||
|
||||
expect(service.getAllRaces).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockViewModel);
|
||||
expect(result).toEqual(mockPresenter.viewModel);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTotalRaces', () => {
|
||||
it('should return total races count view model', async () => {
|
||||
const mockViewModel = { totalRaces: 5 } as { totalRaces: number };
|
||||
service.getTotalRaces.mockResolvedValue({ viewModel: mockViewModel } as unknown as ReturnType<RaceService['getTotalRaces']>);
|
||||
const mockPresenter = { viewModel: { totalRaces: 5 } } as any;
|
||||
service.getTotalRaces.mockResolvedValue(mockPresenter);
|
||||
|
||||
const result = await controller.getTotalRaces();
|
||||
|
||||
expect(service.getTotalRaces).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockViewModel);
|
||||
expect(result).toEqual(mockPresenter.viewModel);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -41,9 +41,10 @@ export class RaceController {
|
||||
|
||||
@Get('page-data')
|
||||
@ApiOperation({ summary: 'Get races page data' })
|
||||
@ApiQuery({ name: 'leagueId', description: 'League ID' })
|
||||
@ApiResponse({ status: 200, description: 'Races page data', type: RacesPageDataDTO })
|
||||
async getRacesPageData(): Promise<RacesPageDataDTO> {
|
||||
const presenter = await this.raceService.getRacesPageData();
|
||||
async getRacesPageData(@Query('leagueId') leagueId: string): Promise<RacesPageDataDTO> {
|
||||
const presenter = await this.raceService.getRacesPageData(leagueId);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ export class RaceController {
|
||||
@ApiParam({ name: 'raceId', description: 'Race ID' })
|
||||
@ApiResponse({ status: 200, description: 'Successfully cancelled race' })
|
||||
async cancelRace(@Param('raceId') raceId: string): Promise<void> {
|
||||
const presenter = await this.raceService.cancelRace({ raceId });
|
||||
const presenter = await this.raceService.cancelRace({ raceId }, '');
|
||||
const viewModel = presenter.viewModel;
|
||||
|
||||
if (!viewModel.success) {
|
||||
@@ -172,7 +173,7 @@ export class RaceController {
|
||||
@ApiParam({ name: 'raceId', description: 'Race ID' })
|
||||
@ApiResponse({ status: 200, description: 'Successfully re-opened race' })
|
||||
async reopenRace(@Param('raceId') raceId: string): Promise<void> {
|
||||
const presenter = await this.raceService.reopenRace({ raceId });
|
||||
const presenter = await this.raceService.reopenRace({ raceId }, '');
|
||||
const viewModel = presenter.viewModel;
|
||||
|
||||
if (!viewModel.success) {
|
||||
|
||||
@@ -2,54 +2,68 @@ import type { Provider } from '@nestjs/common';
|
||||
import { RaceService } from './RaceService';
|
||||
|
||||
// Import core interfaces
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
||||
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
|
||||
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
|
||||
import type { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
|
||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
||||
import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
||||
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||
import type { IPenaltyRepository } from '@core/racing/domain/repositories/IPenaltyRepository';
|
||||
import type { IProtestRepository } from '@core/racing/domain/repositories/IProtestRepository';
|
||||
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
|
||||
import type { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
|
||||
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
||||
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
|
||||
// Import concrete in-memory implementations
|
||||
import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRepository';
|
||||
import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { getPointsSystems } from '@adapters/bootstrap/PointsSystems';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
|
||||
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
|
||||
import { InMemoryRaceRegistrationRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRegistrationRepository';
|
||||
import { InMemoryResultRepository } from '@adapters/racing/persistence/inmemory/InMemoryResultRepository';
|
||||
import { InMemoryLeagueMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository';
|
||||
import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryPenaltyRepository } from '@adapters/racing/persistence/inmemory/InMemoryPenaltyRepository';
|
||||
import { InMemoryProtestRepository } from '@adapters/racing/persistence/inmemory/InMemoryProtestRepository';
|
||||
import { InMemoryRaceRegistrationRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRegistrationRepository';
|
||||
import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRepository';
|
||||
import { InMemoryResultRepository } from '@adapters/racing/persistence/inmemory/InMemoryResultRepository';
|
||||
import { InMemoryStandingRepository } from '@adapters/racing/persistence/inmemory/InMemoryStandingRepository';
|
||||
import { InMemoryDriverRatingProvider } from '@adapters/racing/ports/InMemoryDriverRatingProvider';
|
||||
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
|
||||
// Import use cases
|
||||
import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||
import { GetTotalRacesUseCase } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
|
||||
import { ImportRaceResultsApiUseCase } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
|
||||
import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
|
||||
import { GetRacesPageDataUseCase } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
|
||||
import { GetAllRacesPageDataUseCase } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
|
||||
import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
|
||||
import { GetRaceWithSOFUseCase } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
|
||||
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
||||
import { GetRacePenaltiesUseCase } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
|
||||
import { RegisterForRaceUseCase } from '@core/racing/application/use-cases/RegisterForRaceUseCase';
|
||||
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
|
||||
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
|
||||
import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
|
||||
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
|
||||
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
|
||||
import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
|
||||
import { GetAllRacesPageDataUseCase } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
|
||||
import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||
import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
|
||||
import { GetRacePenaltiesUseCase } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
|
||||
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
||||
import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
|
||||
import { GetRacesPageDataUseCase } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
|
||||
import { GetRaceWithSOFUseCase } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
|
||||
import { GetTotalRacesUseCase } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
|
||||
import { ImportRaceResultsApiUseCase } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
|
||||
import { ImportRaceResultsUseCase } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
|
||||
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
|
||||
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
|
||||
import { RegisterForRaceUseCase } from '@core/racing/application/use-cases/RegisterForRaceUseCase';
|
||||
import { ReopenRaceUseCase } from '@core/racing/application/use-cases/ReopenRaceUseCase';
|
||||
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
|
||||
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||
import { ReopenRaceUseCase } from '@core/racing/application/use-cases/ReopenRaceUseCase';
|
||||
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
|
||||
|
||||
// Import presenters
|
||||
import { AllRacesPageDataPresenter } from './presenters/AllRacesPageDataPresenter';
|
||||
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
|
||||
import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter';
|
||||
import { GetTotalRacesPresenter } from './presenters/GetTotalRacesPresenter';
|
||||
import { ImportRaceResultsApiPresenter } from './presenters/ImportRaceResultsApiPresenter';
|
||||
import { RaceDetailPresenter } from './presenters/RaceDetailPresenter';
|
||||
import { RacePenaltiesPresenter } from './presenters/RacePenaltiesPresenter';
|
||||
import { RaceProtestsPresenter } from './presenters/RaceProtestsPresenter';
|
||||
import { RaceResultsDetailPresenter } from './presenters/RaceResultsDetailPresenter';
|
||||
import { RacesPageDataPresenter } from './presenters/RacesPageDataPresenter';
|
||||
import { RaceWithSOFPresenter } from './presenters/RaceWithSOFPresenter';
|
||||
|
||||
// Define injection tokens
|
||||
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
||||
@@ -65,6 +79,19 @@ export const DRIVER_RATING_PROVIDER_TOKEN = 'DriverRatingProvider';
|
||||
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
|
||||
// Presenter tokens
|
||||
export const GET_ALL_RACES_PRESENTER_TOKEN = 'GetAllRacesPresenter';
|
||||
export const GET_TOTAL_RACES_PRESENTER_TOKEN = 'GetTotalRacesPresenter';
|
||||
export const IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN = 'ImportRaceResultsApiPresenter';
|
||||
export const RACE_DETAIL_PRESENTER_TOKEN = 'RaceDetailPresenter';
|
||||
export const RACES_PAGE_DATA_PRESENTER_TOKEN = 'RacesPageDataPresenter';
|
||||
export const ALL_RACES_PAGE_DATA_PRESENTER_TOKEN = 'AllRacesPageDataPresenter';
|
||||
export const RACE_RESULTS_DETAIL_PRESENTER_TOKEN = 'RaceResultsDetailPresenter';
|
||||
export const RACE_WITH_SOF_PRESENTER_TOKEN = 'RaceWithSOFPresenter';
|
||||
export const RACE_PROTESTS_PRESENTER_TOKEN = 'RaceProtestsPresenter';
|
||||
export const RACE_PENALTIES_PRESENTER_TOKEN = 'RacePenaltiesPresenter';
|
||||
export const COMMAND_RESULT_PRESENTER_TOKEN = 'CommandResultPresenter';
|
||||
|
||||
export const RaceProviders: Provider[] = [
|
||||
RaceService,
|
||||
{
|
||||
@@ -109,7 +136,7 @@ export const RaceProviders: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: STANDING_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryStandingRepository(logger),
|
||||
useFactory: (logger: Logger) => new InMemoryStandingRepository(logger, getPointsSystems()),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
@@ -126,18 +153,105 @@ export const RaceProviders: Provider[] = [
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
// Use cases
|
||||
// Presenters
|
||||
{
|
||||
provide: GET_ALL_RACES_PRESENTER_TOKEN,
|
||||
useClass: GetAllRacesPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_TOTAL_RACES_PRESENTER_TOKEN,
|
||||
useClass: GetTotalRacesPresenter,
|
||||
},
|
||||
{
|
||||
provide: IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN,
|
||||
useClass: ImportRaceResultsApiPresenter,
|
||||
},
|
||||
{
|
||||
provide: RACE_DETAIL_PRESENTER_TOKEN,
|
||||
useFactory: (driverRatingProvider: DriverRatingProvider, imageService: any) =>
|
||||
new RaceDetailPresenter(driverRatingProvider, imageService, { raceId: '', driverId: '' }),
|
||||
inject: [DRIVER_RATING_PROVIDER_TOKEN, IMAGE_SERVICE_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: RACES_PAGE_DATA_PRESENTER_TOKEN,
|
||||
useClass: RacesPageDataPresenter,
|
||||
},
|
||||
{
|
||||
provide: ALL_RACES_PAGE_DATA_PRESENTER_TOKEN,
|
||||
useClass: AllRacesPageDataPresenter,
|
||||
},
|
||||
{
|
||||
provide: RACE_RESULTS_DETAIL_PRESENTER_TOKEN,
|
||||
useFactory: (imageService: any) => new RaceResultsDetailPresenter(imageService),
|
||||
inject: [IMAGE_SERVICE_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: RACE_WITH_SOF_PRESENTER_TOKEN,
|
||||
useClass: RaceWithSOFPresenter,
|
||||
},
|
||||
{
|
||||
provide: RACE_PROTESTS_PRESENTER_TOKEN,
|
||||
useClass: RaceProtestsPresenter,
|
||||
},
|
||||
{
|
||||
provide: RACE_PENALTIES_PRESENTER_TOKEN,
|
||||
useClass: RacePenaltiesPresenter,
|
||||
},
|
||||
{
|
||||
provide: COMMAND_RESULT_PRESENTER_TOKEN,
|
||||
useClass: CommandResultPresenter,
|
||||
},
|
||||
// Use cases - using simplified approach since presenters need to be adapted
|
||||
{
|
||||
provide: GetAllRacesUseCase,
|
||||
useFactory: (raceRepo: IRaceRepository, leagueRepo: ILeagueRepository, logger: Logger) =>
|
||||
new GetAllRacesUseCase(raceRepo, leagueRepo, logger),
|
||||
useFactory: (
|
||||
raceRepo: IRaceRepository,
|
||||
leagueRepo: ILeagueRepository,
|
||||
logger: Logger,
|
||||
) => {
|
||||
const useCase = new GetAllRacesUseCase(raceRepo, leagueRepo, logger);
|
||||
// Create a simple wrapper that calls the presenter
|
||||
const wrapper = {
|
||||
present: (data: any) => {
|
||||
const presenter = new GetAllRacesPresenter();
|
||||
presenter.present(data);
|
||||
}
|
||||
};
|
||||
useCase.setOutput(wrapper as any);
|
||||
return useCase;
|
||||
},
|
||||
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetTotalRacesUseCase,
|
||||
useFactory: (raceRepo: IRaceRepository, logger: Logger) => new GetTotalRacesUseCase(raceRepo, logger),
|
||||
useFactory: (raceRepo: IRaceRepository, logger: Logger) => {
|
||||
const presenter = new GetTotalRacesPresenter();
|
||||
return new GetTotalRacesUseCase(raceRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: ImportRaceResultsApiUseCase,
|
||||
useFactory: (
|
||||
raceRepo: IRaceRepository,
|
||||
leagueRepo: ILeagueRepository,
|
||||
resultRepo: IResultRepository,
|
||||
driverRepo: IDriverRepository,
|
||||
standingRepo: IStandingRepository,
|
||||
logger: Logger,
|
||||
) => {
|
||||
const presenter = new ImportRaceResultsApiPresenter();
|
||||
return new ImportRaceResultsApiUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, standingRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
RESULT_REPOSITORY_TOKEN,
|
||||
DRIVER_REPOSITORY_TOKEN,
|
||||
STANDING_REPOSITORY_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: GetRaceDetailUseCase,
|
||||
useFactory: (
|
||||
@@ -147,15 +261,24 @@ export const RaceProviders: Provider[] = [
|
||||
raceRegRepo: IRaceRegistrationRepository,
|
||||
resultRepo: IResultRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
) =>
|
||||
new GetRaceDetailUseCase(
|
||||
) => {
|
||||
const useCase = new GetRaceDetailUseCase(
|
||||
raceRepo,
|
||||
leagueRepo,
|
||||
driverRepo,
|
||||
raceRegRepo,
|
||||
resultRepo,
|
||||
leagueMembershipRepo,
|
||||
),
|
||||
);
|
||||
const wrapper = {
|
||||
present: (data: any) => {
|
||||
const presenter = new RaceDetailPresenter({} as any, {} as any, {} as any);
|
||||
presenter.present(data);
|
||||
}
|
||||
};
|
||||
useCase.setOutput(wrapper as any);
|
||||
return useCase;
|
||||
},
|
||||
inject: [
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
@@ -167,15 +290,27 @@ export const RaceProviders: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: GetRacesPageDataUseCase,
|
||||
useFactory: (raceRepo: IRaceRepository, leagueRepo: ILeagueRepository) =>
|
||||
new GetRacesPageDataUseCase(raceRepo, leagueRepo),
|
||||
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN],
|
||||
useFactory: (
|
||||
raceRepo: IRaceRepository,
|
||||
leagueRepo: ILeagueRepository,
|
||||
logger: Logger,
|
||||
) => {
|
||||
const presenter = new RacesPageDataPresenter();
|
||||
return new GetRacesPageDataUseCase(raceRepo, leagueRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetAllRacesPageDataUseCase,
|
||||
useFactory: (raceRepo: IRaceRepository, leagueRepo: ILeagueRepository) =>
|
||||
new GetAllRacesPageDataUseCase(raceRepo, leagueRepo),
|
||||
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN],
|
||||
useFactory: (
|
||||
raceRepo: IRaceRepository,
|
||||
leagueRepo: ILeagueRepository,
|
||||
logger: Logger,
|
||||
) => {
|
||||
const presenter = new AllRacesPageDataPresenter();
|
||||
return new GetAllRacesPageDataUseCase(raceRepo, leagueRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetRaceResultsDetailUseCase,
|
||||
@@ -185,7 +320,10 @@ export const RaceProviders: Provider[] = [
|
||||
resultRepo: IResultRepository,
|
||||
driverRepo: IDriverRepository,
|
||||
penaltyRepo: IPenaltyRepository,
|
||||
) => new GetRaceResultsDetailUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, penaltyRepo),
|
||||
) => {
|
||||
const presenter = new RaceResultsDetailPresenter({} as any);
|
||||
return new GetRaceResultsDetailUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, penaltyRepo, presenter as any);
|
||||
},
|
||||
inject: [
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
@@ -201,7 +339,10 @@ export const RaceProviders: Provider[] = [
|
||||
raceRegRepo: IRaceRegistrationRepository,
|
||||
resultRepo: IResultRepository,
|
||||
driverRatingProvider: DriverRatingProvider,
|
||||
) => new GetRaceWithSOFUseCase(raceRepo, raceRegRepo, resultRepo, driverRatingProvider),
|
||||
) => {
|
||||
const presenter = new RaceWithSOFPresenter();
|
||||
return new GetRaceWithSOFUseCase(raceRepo, raceRegRepo, resultRepo, driverRatingProvider as any, presenter as any);
|
||||
},
|
||||
inject: [
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||
@@ -211,14 +352,18 @@ export const RaceProviders: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: GetRaceProtestsUseCase,
|
||||
useFactory: (protestRepo: IProtestRepository, driverRepo: IDriverRepository) =>
|
||||
new GetRaceProtestsUseCase(protestRepo, driverRepo),
|
||||
useFactory: (protestRepo: IProtestRepository, driverRepo: IDriverRepository) => {
|
||||
const presenter = new RaceProtestsPresenter();
|
||||
return new GetRaceProtestsUseCase(protestRepo, driverRepo, presenter as any);
|
||||
},
|
||||
inject: [PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetRacePenaltiesUseCase,
|
||||
useFactory: (penaltyRepo: IPenaltyRepository, driverRepo: IDriverRepository) =>
|
||||
new GetRacePenaltiesUseCase(penaltyRepo, driverRepo),
|
||||
useFactory: (penaltyRepo: IPenaltyRepository, driverRepo: IDriverRepository) => {
|
||||
const presenter = new RacePenaltiesPresenter();
|
||||
return new GetRacePenaltiesUseCase(penaltyRepo, driverRepo, presenter as any);
|
||||
},
|
||||
inject: [PENALTY_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
@@ -227,17 +372,30 @@ export const RaceProviders: Provider[] = [
|
||||
raceRegRepo: IRaceRegistrationRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
logger: Logger,
|
||||
) => new RegisterForRaceUseCase(raceRegRepo, leagueMembershipRepo, logger),
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new RegisterForRaceUseCase(raceRegRepo, leagueMembershipRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: WithdrawFromRaceUseCase,
|
||||
useFactory: (raceRegRepo: IRaceRegistrationRepository) => new WithdrawFromRaceUseCase(raceRegRepo),
|
||||
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN],
|
||||
useFactory: (
|
||||
raceRepo: IRaceRepository,
|
||||
raceRegRepo: IRaceRegistrationRepository,
|
||||
logger: Logger,
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new WithdrawFromRaceUseCase(raceRepo, raceRegRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [RACE_REPOSITORY_TOKEN, RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: CancelRaceUseCase,
|
||||
useFactory: (raceRepo: IRaceRepository, logger: Logger) => new CancelRaceUseCase(raceRepo, logger),
|
||||
useFactory: (raceRepo: IRaceRepository, logger: Logger) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new CancelRaceUseCase(raceRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
@@ -248,7 +406,10 @@ export const RaceProviders: Provider[] = [
|
||||
resultRepo: IResultRepository,
|
||||
standingRepo: IStandingRepository,
|
||||
driverRatingProvider: DriverRatingProvider,
|
||||
) => new CompleteRaceUseCase(raceRepo, raceRegRepo, resultRepo, standingRepo, driverRatingProvider),
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new CompleteRaceUseCase(raceRepo, raceRegRepo, resultRepo, standingRepo, driverRatingProvider as any, presenter as any);
|
||||
},
|
||||
inject: [
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||
@@ -259,28 +420,12 @@ export const RaceProviders: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: ReopenRaceUseCase,
|
||||
useFactory: (raceRepo: IRaceRepository, logger: Logger) => new ReopenRaceUseCase(raceRepo, logger),
|
||||
useFactory: (raceRepo: IRaceRepository, logger: Logger) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new ReopenRaceUseCase(raceRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: ImportRaceResultsApiUseCase,
|
||||
useFactory: (
|
||||
raceRepo: IRaceRepository,
|
||||
leagueRepo: ILeagueRepository,
|
||||
resultRepo: IResultRepository,
|
||||
driverRepo: IDriverRepository,
|
||||
standingRepo: IStandingRepository,
|
||||
logger: Logger,
|
||||
) => new ImportRaceResultsApiUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, standingRepo, logger),
|
||||
inject: [
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
RESULT_REPOSITORY_TOKEN,
|
||||
DRIVER_REPOSITORY_TOKEN,
|
||||
STANDING_REPOSITORY_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: ImportRaceResultsUseCase,
|
||||
useFactory: (
|
||||
@@ -290,7 +435,10 @@ export const RaceProviders: Provider[] = [
|
||||
driverRepo: IDriverRepository,
|
||||
standingRepo: IStandingRepository,
|
||||
logger: Logger,
|
||||
) => new ImportRaceResultsUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, standingRepo, logger),
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new ImportRaceResultsUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, standingRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
@@ -306,7 +454,10 @@ export const RaceProviders: Provider[] = [
|
||||
protestRepo: IProtestRepository,
|
||||
raceRepo: IRaceRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
) => new FileProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo),
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new FileProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, presenter as any);
|
||||
},
|
||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
@@ -316,13 +467,11 @@ export const RaceProviders: Provider[] = [
|
||||
raceRepo: IRaceRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
logger: Logger,
|
||||
) => new QuickPenaltyUseCase(penaltyRepo, raceRepo, leagueMembershipRepo, logger),
|
||||
inject: [
|
||||
PENALTY_REPOSITORY_TOKEN,
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
],
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new QuickPenaltyUseCase(penaltyRepo, raceRepo, leagueMembershipRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [PENALTY_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: ApplyPenaltyUseCase,
|
||||
@@ -332,14 +481,11 @@ export const RaceProviders: Provider[] = [
|
||||
raceRepo: IRaceRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
logger: Logger,
|
||||
) => new ApplyPenaltyUseCase(penaltyRepo, protestRepo, raceRepo, leagueMembershipRepo, logger),
|
||||
inject: [
|
||||
PENALTY_REPOSITORY_TOKEN,
|
||||
PROTEST_REPOSITORY_TOKEN,
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
],
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new ApplyPenaltyUseCase(penaltyRepo, protestRepo, raceRepo, leagueMembershipRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [PENALTY_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: RequestProtestDefenseUseCase,
|
||||
@@ -347,8 +493,12 @@ export const RaceProviders: Provider[] = [
|
||||
protestRepo: IProtestRepository,
|
||||
raceRepo: IRaceRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
) => new RequestProtestDefenseUseCase(protestRepo, raceRepo, leagueMembershipRepo),
|
||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||
logger: Logger,
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new RequestProtestDefenseUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: ReviewProtestUseCase,
|
||||
@@ -356,7 +506,11 @@ export const RaceProviders: Provider[] = [
|
||||
protestRepo: IProtestRepository,
|
||||
raceRepo: IRaceRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
) => new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo),
|
||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||
logger: Logger,
|
||||
) => {
|
||||
const presenter = new CommandResultPresenter();
|
||||
return new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger, presenter as any);
|
||||
},
|
||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
];
|
||||
];
|
||||
@@ -1,168 +0,0 @@
|
||||
import { RaceService } from './RaceService';
|
||||
import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||
import { GetTotalRacesUseCase } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
|
||||
import { ImportRaceResultsApiUseCase } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
|
||||
import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
|
||||
import { GetRacesPageDataUseCase } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
|
||||
import { GetAllRacesPageDataUseCase } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
|
||||
import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
|
||||
import { GetRaceWithSOFUseCase } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
|
||||
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
||||
import { GetRacePenaltiesUseCase } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
|
||||
import { RegisterForRaceUseCase } from '@core/racing/application/use-cases/RegisterForRaceUseCase';
|
||||
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
|
||||
import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
|
||||
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
|
||||
import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
|
||||
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
|
||||
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
|
||||
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
|
||||
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||
import { ReopenRaceUseCase } from '@core/racing/application/use-cases/ReopenRaceUseCase';
|
||||
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
|
||||
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
|
||||
// Minimal happy-path coverage to assert presenter usage
|
||||
|
||||
describe('RaceService', () => {
|
||||
let service: RaceService;
|
||||
let getAllRacesUseCase: jest.Mocked<GetAllRacesUseCase>;
|
||||
let getTotalRacesUseCase: jest.Mocked<GetTotalRacesUseCase>;
|
||||
let importRaceResultsApiUseCase: jest.Mocked<ImportRaceResultsApiUseCase>;
|
||||
let getRaceDetailUseCase: jest.Mocked<GetRaceDetailUseCase>;
|
||||
let getRacesPageDataUseCase: jest.Mocked<GetRacesPageDataUseCase>;
|
||||
let getAllRacesPageDataUseCase: jest.Mocked<GetAllRacesPageDataUseCase>;
|
||||
let getRaceResultsDetailUseCase: jest.Mocked<GetRaceResultsDetailUseCase>;
|
||||
let getRaceWithSOFUseCase: jest.Mocked<GetRaceWithSOFUseCase>;
|
||||
let getRaceProtestsUseCase: jest.Mocked<GetRaceProtestsUseCase>;
|
||||
let getRacePenaltiesUseCase: jest.Mocked<GetRacePenaltiesUseCase>;
|
||||
let registerForRaceUseCase: jest.Mocked<RegisterForRaceUseCase>;
|
||||
let withdrawFromRaceUseCase: jest.Mocked<WithdrawFromRaceUseCase>;
|
||||
let cancelRaceUseCase: jest.Mocked<CancelRaceUseCase>;
|
||||
let completeRaceUseCase: jest.Mocked<CompleteRaceUseCase>;
|
||||
let fileProtestUseCase: jest.Mocked<FileProtestUseCase>;
|
||||
let quickPenaltyUseCase: jest.Mocked<QuickPenaltyUseCase>;
|
||||
let applyPenaltyUseCase: jest.Mocked<ApplyPenaltyUseCase>;
|
||||
let requestProtestDefenseUseCase: jest.Mocked<RequestProtestDefenseUseCase>;
|
||||
let reviewProtestUseCase: jest.Mocked<ReviewProtestUseCase>;
|
||||
let reopenRaceUseCase: jest.Mocked<ReopenRaceUseCase>;
|
||||
let leagueRepository: jest.Mocked<ILeagueRepository>;
|
||||
let logger: jest.Mocked<Logger>;
|
||||
let driverRatingProvider: jest.Mocked<DriverRatingProvider>;
|
||||
let imageService: jest.Mocked<IImageServicePort>;
|
||||
|
||||
beforeEach(() => {
|
||||
getAllRacesUseCase = { execute: jest.fn() } as jest.Mocked<GetAllRacesUseCase>;
|
||||
getTotalRacesUseCase = { execute: jest.fn() } as jest.Mocked<GetTotalRacesUseCase>;
|
||||
importRaceResultsApiUseCase = { execute: jest.fn() } as jest.Mocked<ImportRaceResultsApiUseCase>;
|
||||
getRaceDetailUseCase = { execute: jest.fn() } as jest.Mocked<GetRaceDetailUseCase>;
|
||||
getRacesPageDataUseCase = { execute: jest.fn() } as jest.Mocked<GetRacesPageDataUseCase>;
|
||||
getAllRacesPageDataUseCase = { execute: jest.fn() } as jest.Mocked<GetAllRacesPageDataUseCase>;
|
||||
getRaceResultsDetailUseCase = { execute: jest.fn() } as jest.Mocked<GetRaceResultsDetailUseCase>;
|
||||
getRaceWithSOFUseCase = { execute: jest.fn() } as jest.Mocked<GetRaceWithSOFUseCase>;
|
||||
getRaceProtestsUseCase = { execute: jest.fn() } as jest.Mocked<GetRaceProtestsUseCase>;
|
||||
getRacePenaltiesUseCase = { execute: jest.fn() } as jest.Mocked<GetRacePenaltiesUseCase>;
|
||||
registerForRaceUseCase = { execute: jest.fn() } as jest.Mocked<RegisterForRaceUseCase>;
|
||||
withdrawFromRaceUseCase = { execute: jest.fn() } as jest.Mocked<WithdrawFromRaceUseCase>;
|
||||
cancelRaceUseCase = { execute: jest.fn() } as jest.Mocked<CancelRaceUseCase>;
|
||||
completeRaceUseCase = { execute: jest.fn() } as jest.Mocked<CompleteRaceUseCase>;
|
||||
fileProtestUseCase = { execute: jest.fn() } as jest.Mocked<FileProtestUseCase>;
|
||||
quickPenaltyUseCase = { execute: jest.fn() } as jest.Mocked<QuickPenaltyUseCase>;
|
||||
applyPenaltyUseCase = { execute: jest.fn() } as jest.Mocked<ApplyPenaltyUseCase>;
|
||||
requestProtestDefenseUseCase = { execute: jest.fn() } as jest.Mocked<RequestProtestDefenseUseCase>;
|
||||
reviewProtestUseCase = { execute: jest.fn() } as jest.Mocked<ReviewProtestUseCase>;
|
||||
reopenRaceUseCase = { execute: jest.fn() } as jest.Mocked<ReopenRaceUseCase>;
|
||||
|
||||
leagueRepository = {
|
||||
findAll: jest.fn(),
|
||||
} as jest.Mocked<ILeagueRepository>;
|
||||
|
||||
logger = {
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as jest.Mocked<Logger>;
|
||||
|
||||
driverRatingProvider = {
|
||||
getDriverRating: jest.fn(),
|
||||
} as jest.Mocked<DriverRatingProvider>;
|
||||
|
||||
imageService = {
|
||||
getDriverAvatar: jest.fn(),
|
||||
getTeamLogo: jest.fn(),
|
||||
getLeagueCover: jest.fn(),
|
||||
getLeagueLogo: jest.fn(),
|
||||
} as jest.Mocked<IImageServicePort>;
|
||||
|
||||
service = new RaceService(
|
||||
getAllRacesUseCase,
|
||||
getTotalRacesUseCase,
|
||||
importRaceResultsApiUseCase,
|
||||
getRaceDetailUseCase,
|
||||
getRacesPageDataUseCase,
|
||||
getAllRacesPageDataUseCase,
|
||||
getRaceResultsDetailUseCase,
|
||||
getRaceWithSOFUseCase,
|
||||
getRaceProtestsUseCase,
|
||||
getRacePenaltiesUseCase,
|
||||
registerForRaceUseCase,
|
||||
withdrawFromRaceUseCase,
|
||||
cancelRaceUseCase,
|
||||
completeRaceUseCase,
|
||||
fileProtestUseCase,
|
||||
quickPenaltyUseCase,
|
||||
applyPenaltyUseCase,
|
||||
requestProtestDefenseUseCase,
|
||||
reviewProtestUseCase,
|
||||
reopenRaceUseCase,
|
||||
leagueRepository,
|
||||
logger,
|
||||
driverRatingProvider,
|
||||
imageService,
|
||||
);
|
||||
});
|
||||
|
||||
it('getAllRaces should return presenter with view model', async () => {
|
||||
const output = {
|
||||
races: [],
|
||||
totalCount: 0,
|
||||
};
|
||||
|
||||
(getAllRacesUseCase.execute as jest.Mock).mockResolvedValue(Result.ok(output));
|
||||
|
||||
const presenter = await service.getAllRaces();
|
||||
const viewModel = presenter.getViewModel();
|
||||
|
||||
expect(getAllRacesUseCase.execute).toHaveBeenCalledWith();
|
||||
expect(viewModel).not.toBeNull();
|
||||
expect(viewModel).toMatchObject({ totalCount: 0 });
|
||||
});
|
||||
|
||||
it('registerForRace should map success into CommandResultPresenter', async () => {
|
||||
(registerForRaceUseCase.execute as jest.Mock).mockResolvedValue(Result.ok({}));
|
||||
|
||||
const presenter = await service.registerForRace({
|
||||
raceId: 'race-1',
|
||||
driverId: 'driver-1',
|
||||
} as { raceId: string; driverId: string });
|
||||
|
||||
expect(registerForRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'race-1', driverId: 'driver-1' });
|
||||
expect(presenter.viewModel.success).toBe(true);
|
||||
});
|
||||
|
||||
it('registerForRace should map error into CommandResultPresenter', async () => {
|
||||
(registerForRaceUseCase.execute as jest.Mock).mockResolvedValue(Result.err({ code: 'FAILED_TO_REGISTER_FOR_RACE' as const }));
|
||||
|
||||
const presenter = await service.registerForRace({
|
||||
raceId: 'race-1',
|
||||
driverId: 'driver-1',
|
||||
} as { raceId: string; driverId: string });
|
||||
|
||||
expect(presenter.viewModel.success).toBe(false);
|
||||
expect(presenter.viewModel.errorCode).toBe('FAILED_TO_REGISTER_FOR_RACE');
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { RacesPageOutputPort } from '@core/racing/application/ports/output/RacesPageOutputPort';
|
||||
import type { RaceResultsDetailOutputPort } from '@core/racing/application/ports/output/RaceResultsDetailOutputPort';
|
||||
import type { RaceWithSOFOutputPort } from '@core/racing/application/ports/output/RaceWithSOFOutputPort';
|
||||
import type { RaceProtestsOutputPort } from '@core/racing/application/ports/output/RaceProtestsOutputPort';
|
||||
import type { RacePenaltiesOutputPort } from '@core/racing/application/ports/output/RacePenaltiesOutputPort';
|
||||
|
||||
// DTOs
|
||||
import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO';
|
||||
@@ -61,7 +56,23 @@ import { RequestProtestDefenseCommandDTO } from './dtos/RequestProtestDefenseCom
|
||||
import { ReviewProtestCommandDTO } from './dtos/ReviewProtestCommandDTO';
|
||||
|
||||
// Tokens
|
||||
import { DRIVER_RATING_PROVIDER_TOKEN, IMAGE_SERVICE_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN } from './RaceProviders';
|
||||
import {
|
||||
DRIVER_RATING_PROVIDER_TOKEN,
|
||||
IMAGE_SERVICE_TOKEN,
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
GET_ALL_RACES_PRESENTER_TOKEN,
|
||||
GET_TOTAL_RACES_PRESENTER_TOKEN,
|
||||
IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN,
|
||||
RACE_DETAIL_PRESENTER_TOKEN,
|
||||
RACES_PAGE_DATA_PRESENTER_TOKEN,
|
||||
ALL_RACES_PAGE_DATA_PRESENTER_TOKEN,
|
||||
RACE_RESULTS_DETAIL_PRESENTER_TOKEN,
|
||||
RACE_WITH_SOF_PRESENTER_TOKEN,
|
||||
RACE_PROTESTS_PRESENTER_TOKEN,
|
||||
RACE_PENALTIES_PRESENTER_TOKEN,
|
||||
COMMAND_RESULT_PRESENTER_TOKEN
|
||||
} from './RaceProviders';
|
||||
|
||||
@Injectable()
|
||||
export class RaceService {
|
||||
@@ -90,305 +101,137 @@ export class RaceService {
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
@Inject(DRIVER_RATING_PROVIDER_TOKEN) private readonly driverRatingProvider: DriverRatingProvider,
|
||||
@Inject(IMAGE_SERVICE_TOKEN) private readonly imageService: IImageServicePort,
|
||||
// Injected presenters
|
||||
@Inject(GET_ALL_RACES_PRESENTER_TOKEN) private readonly getAllRacesPresenter: GetAllRacesPresenter,
|
||||
@Inject(GET_TOTAL_RACES_PRESENTER_TOKEN) private readonly getTotalRacesPresenter: GetTotalRacesPresenter,
|
||||
@Inject(IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN) private readonly importRaceResultsApiPresenter: ImportRaceResultsApiPresenter,
|
||||
@Inject(RACE_DETAIL_PRESENTER_TOKEN) private readonly raceDetailPresenter: RaceDetailPresenter,
|
||||
@Inject(RACES_PAGE_DATA_PRESENTER_TOKEN) private readonly racesPageDataPresenter: RacesPageDataPresenter,
|
||||
@Inject(ALL_RACES_PAGE_DATA_PRESENTER_TOKEN) private readonly allRacesPageDataPresenter: AllRacesPageDataPresenter,
|
||||
@Inject(RACE_RESULTS_DETAIL_PRESENTER_TOKEN) private readonly raceResultsDetailPresenter: RaceResultsDetailPresenter,
|
||||
@Inject(RACE_WITH_SOF_PRESENTER_TOKEN) private readonly raceWithSOFPresenter: RaceWithSOFPresenter,
|
||||
@Inject(RACE_PROTESTS_PRESENTER_TOKEN) private readonly raceProtestsPresenter: RaceProtestsPresenter,
|
||||
@Inject(RACE_PENALTIES_PRESENTER_TOKEN) private readonly racePenaltiesPresenter: RacePenaltiesPresenter,
|
||||
@Inject(COMMAND_RESULT_PRESENTER_TOKEN) private readonly commandResultPresenter: CommandResultPresenter,
|
||||
) {}
|
||||
|
||||
async getAllRaces(): Promise<GetAllRacesPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching all races.');
|
||||
|
||||
const presenter = new GetAllRacesPresenter();
|
||||
this.getAllRacesUseCase.setOutput(presenter);
|
||||
|
||||
const result = await this.getAllRacesUseCase.execute({});
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().code);
|
||||
}
|
||||
|
||||
return presenter;
|
||||
await this.getAllRacesUseCase.execute({});
|
||||
return this.getAllRacesPresenter;
|
||||
}
|
||||
|
||||
async getTotalRaces(): Promise<GetTotalRacesPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching total races count.');
|
||||
const result = await this.getTotalRacesUseCase.execute({});
|
||||
const presenter = new GetTotalRacesPresenter();
|
||||
presenter.present(result);
|
||||
return presenter;
|
||||
await this.getTotalRacesUseCase.execute({});
|
||||
return this.getTotalRacesPresenter;
|
||||
}
|
||||
|
||||
async importRaceResults(input: ImportRaceResultsDTO): Promise<ImportRaceResultsApiPresenter> {
|
||||
this.logger.debug('Importing race results:', input);
|
||||
const result = await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent });
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().code);
|
||||
}
|
||||
const presenter = new ImportRaceResultsApiPresenter();
|
||||
presenter.present(result.unwrap());
|
||||
return presenter;
|
||||
await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent });
|
||||
return this.importRaceResultsApiPresenter;
|
||||
}
|
||||
|
||||
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailPresenter> {
|
||||
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);
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error('Failed to get race detail');
|
||||
}
|
||||
|
||||
return presenter;
|
||||
await this.getRaceDetailUseCase.execute(params);
|
||||
return this.raceDetailPresenter;
|
||||
}
|
||||
|
||||
async getRacesPageData(): Promise<RacesPageDataPresenter> {
|
||||
async getRacesPageData(leagueId: string): Promise<RacesPageDataPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching races page data.');
|
||||
|
||||
const result = await this.getRacesPageDataUseCase.execute();
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error('Failed to get races page data');
|
||||
}
|
||||
|
||||
const presenter = new RacesPageDataPresenter(this.leagueRepository);
|
||||
await presenter.present(result.value as RacesPageOutputPort);
|
||||
return presenter;
|
||||
await this.getRacesPageDataUseCase.execute({ leagueId });
|
||||
return this.racesPageDataPresenter;
|
||||
}
|
||||
|
||||
async getAllRacesPageData(): Promise<AllRacesPageDataPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching all races page data.');
|
||||
|
||||
const result = await this.getAllRacesPageDataUseCase.execute();
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error('Failed to get all races page data');
|
||||
}
|
||||
|
||||
const presenter = new AllRacesPageDataPresenter();
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
await this.getAllRacesPageDataUseCase.execute({});
|
||||
return this.allRacesPageDataPresenter;
|
||||
}
|
||||
|
||||
async getRaceResultsDetail(raceId: string): Promise<RaceResultsDetailPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching race results detail:', { raceId });
|
||||
|
||||
const result = await this.getRaceResultsDetailUseCase.execute({ raceId });
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error('Failed to get race results detail');
|
||||
}
|
||||
|
||||
const presenter = new RaceResultsDetailPresenter(this.imageService);
|
||||
await presenter.present(result.value as RaceResultsDetailOutputPort);
|
||||
return presenter;
|
||||
await this.getRaceResultsDetailUseCase.execute({ raceId });
|
||||
return this.raceResultsDetailPresenter;
|
||||
}
|
||||
|
||||
async getRaceWithSOF(raceId: string): Promise<RaceWithSOFPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching race with SOF:', { raceId });
|
||||
|
||||
const result = await this.getRaceWithSOFUseCase.execute({ raceId });
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error('Failed to get race with SOF');
|
||||
}
|
||||
|
||||
const presenter = new RaceWithSOFPresenter();
|
||||
presenter.present(result.value as RaceWithSOFOutputPort);
|
||||
return presenter;
|
||||
await this.getRaceWithSOFUseCase.execute({ raceId });
|
||||
return this.raceWithSOFPresenter;
|
||||
}
|
||||
|
||||
async getRaceProtests(raceId: string): Promise<RaceProtestsPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching race protests:', { raceId });
|
||||
|
||||
const result = await this.getRaceProtestsUseCase.execute({ raceId });
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error('Failed to get race protests');
|
||||
}
|
||||
|
||||
const presenter = new RaceProtestsPresenter();
|
||||
presenter.present(result.value as RaceProtestsOutputPort);
|
||||
return presenter;
|
||||
await this.getRaceProtestsUseCase.execute({ raceId });
|
||||
return this.raceProtestsPresenter;
|
||||
}
|
||||
|
||||
async getRacePenalties(raceId: string): Promise<RacePenaltiesPresenter> {
|
||||
this.logger.debug('[RaceService] Fetching race penalties:', { raceId });
|
||||
|
||||
const result = await this.getRacePenaltiesUseCase.execute({ raceId });
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error('Failed to get race penalties');
|
||||
}
|
||||
|
||||
const presenter = new RacePenaltiesPresenter();
|
||||
presenter.present(result.value as RacePenaltiesOutputPort);
|
||||
return presenter;
|
||||
await this.getRacePenaltiesUseCase.execute({ raceId });
|
||||
return this.racePenaltiesPresenter;
|
||||
}
|
||||
|
||||
async registerForRace(params: RegisterForRaceParamsDTO): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Registering for race:', params);
|
||||
|
||||
const result = await this.registerForRaceUseCase.execute(params);
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_REGISTER_FOR_RACE', 'Failed to register for race');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.registerForRaceUseCase.execute(params);
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async withdrawFromRace(params: WithdrawFromRaceParamsDTO): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Withdrawing from race:', params);
|
||||
|
||||
const result = await this.withdrawFromRaceUseCase.execute(params);
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_WITHDRAW_FROM_RACE', 'Failed to withdraw from race');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.withdrawFromRaceUseCase.execute(params);
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async cancelRace(params: RaceActionParamsDTO): Promise<CommandResultPresenter> {
|
||||
async cancelRace(params: RaceActionParamsDTO, cancelledById: string): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Cancelling race:', params);
|
||||
|
||||
const result = await this.cancelRaceUseCase.execute({ raceId: params.raceId });
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_CANCEL_RACE', 'Failed to cancel race');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.cancelRaceUseCase.execute({ raceId: params.raceId, cancelledById });
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async completeRace(params: RaceActionParamsDTO): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Completing race:', params);
|
||||
|
||||
const result = await this.completeRaceUseCase.execute({ raceId: params.raceId });
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_COMPLETE_RACE', 'Failed to complete race');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.completeRaceUseCase.execute({ raceId: params.raceId });
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async reopenRace(params: RaceActionParamsDTO): Promise<CommandResultPresenter> {
|
||||
async reopenRace(params: RaceActionParamsDTO, reopenedById: string): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Re-opening race:', params);
|
||||
|
||||
const result = await this.reopenRaceUseCase.execute({ raceId: params.raceId });
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const errorCode = result.unwrapErr().code;
|
||||
|
||||
if (errorCode === 'RACE_ALREADY_SCHEDULED') {
|
||||
this.logger.debug('[RaceService] Race is already scheduled, treating reopen as success.');
|
||||
presenter.presentSuccess('Race already scheduled');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentFailure(errorCode ?? 'UNEXPECTED_ERROR', 'Unexpected error while reopening race');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.reopenRaceUseCase.execute({ raceId: params.raceId, reopenedById });
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async fileProtest(command: FileProtestCommandDTO): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Filing protest:', command);
|
||||
|
||||
const result = await this.fileProtestUseCase.execute(command);
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_FILE_PROTEST', 'Failed to file protest');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.fileProtestUseCase.execute(command);
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async applyQuickPenalty(command: QuickPenaltyCommandDTO): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Applying quick penalty:', command);
|
||||
|
||||
const result = await this.quickPenaltyUseCase.execute(command);
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_APPLY_QUICK_PENALTY', 'Failed to apply quick penalty');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.quickPenaltyUseCase.execute(command);
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async applyPenalty(command: ApplyPenaltyCommandDTO): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Applying penalty:', command);
|
||||
|
||||
const result = await this.applyPenaltyUseCase.execute(command);
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_APPLY_PENALTY', 'Failed to apply penalty');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.applyPenaltyUseCase.execute(command);
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async requestProtestDefense(command: RequestProtestDefenseCommandDTO): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Requesting protest defense:', command);
|
||||
|
||||
const result = await this.requestProtestDefenseUseCase.execute(command);
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_REQUEST_PROTEST_DEFENSE', 'Failed to request protest defense');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.requestProtestDefenseUseCase.execute(command);
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
|
||||
async reviewProtest(command: ReviewProtestCommandDTO): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[RaceService] Reviewing protest:', command);
|
||||
|
||||
const result = await this.reviewProtestUseCase.execute(command);
|
||||
|
||||
const presenter = new CommandResultPresenter();
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
presenter.presentFailure(error.code ?? 'FAILED_TO_REVIEW_PROTEST', 'Failed to review protest');
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.presentSuccess();
|
||||
return presenter;
|
||||
await this.reviewProtestUseCase.execute(command);
|
||||
return this.commandResultPresenter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,28 @@ export class AllRacesListItemDTO {
|
||||
strengthOfField!: number | null;
|
||||
}
|
||||
|
||||
export class AllRacesFilterOptionsDTO {
|
||||
@ApiProperty({ type: [{ value: String, label: String }] })
|
||||
statuses!: { value: AllRacesStatus; label: string }[];
|
||||
export class AllRacesStatusFilterDTO {
|
||||
@ApiProperty()
|
||||
value!: AllRacesStatus;
|
||||
|
||||
@ApiProperty({ type: [{ id: String, name: String }] })
|
||||
leagues!: { id: string; name: string }[];
|
||||
@ApiProperty()
|
||||
label!: string;
|
||||
}
|
||||
|
||||
export class AllRacesLeagueFilterDTO {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name!: string;
|
||||
}
|
||||
|
||||
export class AllRacesFilterOptionsDTO {
|
||||
@ApiProperty({ type: [AllRacesStatusFilterDTO] })
|
||||
statuses!: AllRacesStatusFilterDTO[];
|
||||
|
||||
@ApiProperty({ type: [AllRacesLeagueFilterDTO] })
|
||||
leagues!: AllRacesLeagueFilterDTO[];
|
||||
}
|
||||
|
||||
export class AllRacesPageDTO {
|
||||
|
||||
@@ -7,19 +7,19 @@ export interface CommandResultDTO {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export type CommandApplicationError<E extends string = string> = ApplicationErrorCode<
|
||||
E,
|
||||
export type CommandApplicationError = ApplicationErrorCode<
|
||||
string,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class CommandResultPresenter<E extends string = string> {
|
||||
export class CommandResultPresenter {
|
||||
private model: CommandResultDTO | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<unknown, CommandApplicationError<E>>): void {
|
||||
present(result: Result<unknown, CommandApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
this.model = {
|
||||
@@ -36,7 +36,7 @@ export class CommandResultPresenter<E extends string = string> {
|
||||
presentSuccess(message?: string): void {
|
||||
this.model = {
|
||||
success: true,
|
||||
message,
|
||||
...(message !== undefined && { message }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export class CommandResultPresenter<E extends string = string> {
|
||||
this.model = {
|
||||
success: false,
|
||||
errorCode,
|
||||
message,
|
||||
...(message !== undefined && { message }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,4 +59,8 @@ export class CommandResultPresenter<E extends string = string> {
|
||||
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get viewModel(): CommandResultDTO {
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,11 +40,11 @@ export class RaceDetailPresenter implements UseCaseOutputPort<GetRaceDetailResul
|
||||
track: output.race.track,
|
||||
car: output.race.car,
|
||||
scheduledAt: output.race.scheduledAt.toISOString(),
|
||||
sessionType: output.race.sessionType,
|
||||
status: output.race.status,
|
||||
sessionType: output.race.sessionType.toString(),
|
||||
status: output.race.status.toString(),
|
||||
strengthOfField: output.race.strengthOfField ?? null,
|
||||
registeredCount: output.race.registeredCount ?? undefined,
|
||||
maxParticipants: output.race.maxParticipants ?? undefined,
|
||||
...(output.race.registeredCount !== undefined && { registeredCount: output.race.registeredCount }),
|
||||
...(output.race.maxParticipants !== undefined && { maxParticipants: output.race.maxParticipants }),
|
||||
}
|
||||
: null;
|
||||
|
||||
@@ -54,22 +54,22 @@ export class RaceDetailPresenter implements UseCaseOutputPort<GetRaceDetailResul
|
||||
name: output.league.name.toString(),
|
||||
description: output.league.description.toString(),
|
||||
settings: {
|
||||
maxDrivers: output.league.settings.maxDrivers ?? undefined,
|
||||
qualifyingFormat: output.league.settings.qualifyingFormat ?? undefined,
|
||||
...(output.league.settings.maxDrivers !== undefined && { maxDrivers: output.league.settings.maxDrivers }),
|
||||
...(output.league.settings.qualifyingFormat !== undefined && { qualifyingFormat: output.league.settings.qualifyingFormat.toString() }),
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
const entryListDTO: RaceDetailEntryDTO[] = await Promise.all(
|
||||
output.drivers.map(async driver => {
|
||||
const ratingResult = await this.driverRatingProvider.getDriverRating({ driverId: driver.id });
|
||||
const avatarResult = await this.imageService.getDriverAvatar({ driverId: driver.id });
|
||||
const rating = this.driverRatingProvider.getRating(driver.id);
|
||||
const avatarUrl = this.imageService.getDriverAvatar(driver.id);
|
||||
return {
|
||||
id: driver.id,
|
||||
name: driver.name.toString(),
|
||||
country: driver.country.toString(),
|
||||
avatarUrl: avatarResult.avatarUrl,
|
||||
rating: ratingResult.rating,
|
||||
avatarUrl,
|
||||
rating,
|
||||
isCurrentUser: driver.id === params.driverId,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -34,10 +34,10 @@ export class RaceProtestsPresenter {
|
||||
protestingDriverId: protest.protestingDriverId,
|
||||
accusedDriverId: protest.accusedDriverId,
|
||||
incident: {
|
||||
lap: protest.incident.lap,
|
||||
description: protest.incident.description,
|
||||
lap: protest.incident.lap.toNumber(),
|
||||
description: protest.incident.description.toString(),
|
||||
},
|
||||
status: protest.status,
|
||||
status: protest.status.toString(),
|
||||
filedAt: protest.filedAt.toISOString(),
|
||||
} as RaceProtestDTO));
|
||||
|
||||
|
||||
@@ -39,12 +39,12 @@ export class RaceResultsDetailPresenter {
|
||||
throw new Error(`Driver not found for result: ${singleResult.driverId}`);
|
||||
}
|
||||
|
||||
const avatarResult = await this.imageService.getDriverAvatar({ driverId: driver.id });
|
||||
const avatarUrl = this.imageService.getDriverAvatar(driver.id);
|
||||
|
||||
return {
|
||||
driverId: singleResult.driverId.toString(),
|
||||
driverName: driver.name.toString(),
|
||||
avatarUrl: avatarResult.avatarUrl,
|
||||
avatarUrl,
|
||||
position: singleResult.position.toNumber(),
|
||||
startPosition: singleResult.startPosition.toNumber(),
|
||||
incidents: singleResult.incidents.toNumber(),
|
||||
|
||||
@@ -24,7 +24,7 @@ import { SponsorProfileDTO } from './dtos/SponsorProfileDTO';
|
||||
import { NotificationSettingsDTO } from './dtos/NotificationSettingsDTO';
|
||||
import { PrivacySettingsDTO } from './dtos/PrivacySettingsDTO';
|
||||
import type { AcceptSponsorshipRequestResultViewModel } from './presenters/AcceptSponsorshipRequestPresenter';
|
||||
import type { RejectSponsorshipRequestResultDTO } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||
import type { RejectSponsorshipRequestResult } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||
|
||||
@ApiTags('sponsors')
|
||||
@Controller('sponsors')
|
||||
@@ -39,8 +39,7 @@ export class SponsorController {
|
||||
type: GetEntitySponsorshipPricingResultDTO,
|
||||
})
|
||||
async getEntitySponsorshipPricing(): Promise<GetEntitySponsorshipPricingResultDTO> {
|
||||
const presenter = await this.sponsorService.getEntitySponsorshipPricing();
|
||||
return presenter.viewModel;
|
||||
return await this.sponsorService.getEntitySponsorshipPricing();
|
||||
}
|
||||
|
||||
@Get()
|
||||
@@ -51,8 +50,7 @@ export class SponsorController {
|
||||
type: GetSponsorsOutputDTO,
|
||||
})
|
||||
async getSponsors(): Promise<GetSponsorsOutputDTO> {
|
||||
const presenter = await this.sponsorService.getSponsors();
|
||||
return presenter.viewModel;
|
||||
return await this.sponsorService.getSponsors();
|
||||
}
|
||||
|
||||
@Post()
|
||||
@@ -64,8 +62,7 @@ export class SponsorController {
|
||||
type: CreateSponsorOutputDTO,
|
||||
})
|
||||
async createSponsor(@Body() input: CreateSponsorInputDTO): Promise<CreateSponsorOutputDTO> {
|
||||
const presenter = await this.sponsorService.createSponsor(input);
|
||||
return presenter.viewModel;
|
||||
return await this.sponsorService.createSponsor(input);
|
||||
}
|
||||
|
||||
@Get('dashboard/:sponsorId')
|
||||
@@ -78,11 +75,10 @@ export class SponsorController {
|
||||
@ApiResponse({ status: 404, description: 'Sponsor not found' })
|
||||
async getSponsorDashboard(
|
||||
@Param('sponsorId') sponsorId: string,
|
||||
): Promise<SponsorDashboardDTO | null> {
|
||||
const presenter = await this.sponsorService.getSponsorDashboard({
|
||||
): Promise<SponsorDashboardDTO> {
|
||||
return await this.sponsorService.getSponsorDashboard({
|
||||
sponsorId,
|
||||
} as GetSponsorDashboardQueryParamsDTO);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get(':sponsorId/sponsorships')
|
||||
@@ -97,11 +93,10 @@ export class SponsorController {
|
||||
@ApiResponse({ status: 404, description: 'Sponsor not found' })
|
||||
async getSponsorSponsorships(
|
||||
@Param('sponsorId') sponsorId: string,
|
||||
): Promise<SponsorSponsorshipsDTO | null> {
|
||||
const presenter = await this.sponsorService.getSponsorSponsorships({
|
||||
): Promise<SponsorSponsorshipsDTO> {
|
||||
return await this.sponsorService.getSponsorSponsorships({
|
||||
sponsorId,
|
||||
} as GetSponsorSponsorshipsQueryParamsDTO);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get(':sponsorId')
|
||||
@@ -112,9 +107,8 @@ export class SponsorController {
|
||||
type: GetSponsorOutputDTO,
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'Sponsor not found' })
|
||||
async getSponsor(@Param('sponsorId') sponsorId: string): Promise<GetSponsorOutputDTO | null> {
|
||||
const presenter = await this.sponsorService.getSponsor(sponsorId);
|
||||
return presenter.viewModel;
|
||||
async getSponsor(@Param('sponsorId') sponsorId: string): Promise<GetSponsorOutputDTO> {
|
||||
return await this.sponsorService.getSponsor(sponsorId);
|
||||
}
|
||||
|
||||
@Get('requests')
|
||||
@@ -126,14 +120,13 @@ export class SponsorController {
|
||||
})
|
||||
async getPendingSponsorshipRequests(
|
||||
@Query() query: { entityType: string; entityId: string },
|
||||
): Promise<GetPendingSponsorshipRequestsOutputDTO | null> {
|
||||
const presenter = await this.sponsorService.getPendingSponsorshipRequests(
|
||||
): Promise<GetPendingSponsorshipRequestsOutputDTO> {
|
||||
return await this.sponsorService.getPendingSponsorshipRequests(
|
||||
query as {
|
||||
entityType: import('@core/racing/domain/entities/SponsorshipRequest').SponsorableEntityType;
|
||||
entityId: string;
|
||||
},
|
||||
);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Post('requests/:requestId/accept')
|
||||
@@ -146,11 +139,10 @@ export class SponsorController {
|
||||
@Param('requestId') requestId: string,
|
||||
@Body() input: AcceptSponsorshipRequestInputDTO,
|
||||
): Promise<AcceptSponsorshipRequestResultViewModel | null> {
|
||||
const presenter = await this.sponsorService.acceptSponsorshipRequest(
|
||||
return await this.sponsorService.acceptSponsorshipRequest(
|
||||
requestId,
|
||||
input.respondedBy,
|
||||
);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Post('requests/:requestId/reject')
|
||||
@@ -162,13 +154,12 @@ export class SponsorController {
|
||||
async rejectSponsorshipRequest(
|
||||
@Param('requestId') requestId: string,
|
||||
@Body() input: RejectSponsorshipRequestInputDTO,
|
||||
): Promise<RejectSponsorshipRequestResultDTO | null> {
|
||||
const presenter = await this.sponsorService.rejectSponsorshipRequest(
|
||||
): Promise<RejectSponsorshipRequestResult | null> {
|
||||
return await this.sponsorService.rejectSponsorshipRequest(
|
||||
requestId,
|
||||
input.respondedBy,
|
||||
input.reason,
|
||||
);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get('billing/:sponsorId')
|
||||
@@ -181,8 +172,7 @@ export class SponsorController {
|
||||
invoices: InvoiceDTO[];
|
||||
stats: BillingStatsDTO;
|
||||
}> {
|
||||
const presenter = await this.sponsorService.getSponsorBilling(sponsorId);
|
||||
return presenter.viewModel;
|
||||
return await this.sponsorService.getSponsorBilling(sponsorId);
|
||||
}
|
||||
|
||||
@Get('leagues/available')
|
||||
|
||||
@@ -5,7 +5,8 @@ import { SponsorService } from './SponsorService';
|
||||
import { NotificationService } from '@core/notifications/application/ports/NotificationService';
|
||||
import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository';
|
||||
import { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||
import { IPaymentGateway } from '@core/payments/domain/ports/IPaymentGateway';
|
||||
// Remove the missing import
|
||||
// import { IPaymentGateway } from '@core/payments/domain/ports/IPaymentGateway';
|
||||
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
||||
import { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||
import { ILeagueWalletRepository } from '@core/racing/domain/repositories/ILeagueWalletRepository';
|
||||
@@ -15,7 +16,7 @@ import { ISeasonSponsorshipRepository } from '@core/racing/domain/repositories/I
|
||||
import { ISponsorRepository } from '@core/racing/domain/repositories/ISponsorRepository';
|
||||
import { ISponsorshipPricingRepository } from '@core/racing/domain/repositories/ISponsorshipPricingRepository';
|
||||
import { ISponsorshipRequestRepository } from '@core/racing/domain/repositories/ISponsorshipRequestRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
|
||||
import { GetSponsorBillingUseCase } from '@core/payments/application/use-cases/GetSponsorBillingUseCase';
|
||||
import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
|
||||
@@ -40,6 +41,18 @@ import { InMemorySponsorRepository } from '@adapters/racing/persistence/inmemory
|
||||
import { InMemorySponsorshipPricingRepository } from '@adapters/racing/persistence/inmemory/InMemorySponsorshipPricingRepository';
|
||||
import { InMemorySponsorshipRequestRepository } from '@adapters/racing/persistence/inmemory/InMemorySponsorshipRequestRepository';
|
||||
|
||||
// Import presenters
|
||||
import { GetEntitySponsorshipPricingPresenter } from './presenters/GetEntitySponsorshipPricingPresenter';
|
||||
import { GetSponsorsPresenter } from './presenters/GetSponsorsPresenter';
|
||||
import { CreateSponsorPresenter } from './presenters/CreateSponsorPresenter';
|
||||
import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPresenter';
|
||||
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
|
||||
import { GetSponsorPresenter } from './presenters/GetSponsorPresenter';
|
||||
import { GetPendingSponsorshipRequestsPresenter } from './presenters/GetPendingSponsorshipRequestsPresenter';
|
||||
import { AcceptSponsorshipRequestPresenter } from './presenters/AcceptSponsorshipRequestPresenter';
|
||||
import { RejectSponsorshipRequestPresenter } from './presenters/RejectSponsorshipRequestPresenter';
|
||||
import { SponsorBillingPresenter } from './presenters/SponsorBillingPresenter';
|
||||
|
||||
// Define injection tokens
|
||||
export const SPONSOR_REPOSITORY_TOKEN = 'ISponsorRepository';
|
||||
export const SEASON_SPONSORSHIP_REPOSITORY_TOKEN = 'ISeasonSponsorshipRepository';
|
||||
@@ -51,6 +64,18 @@ export const SPONSORSHIP_PRICING_REPOSITORY_TOKEN = 'ISponsorshipPricingReposito
|
||||
export const SPONSORSHIP_REQUEST_REPOSITORY_TOKEN = 'ISponsorshipRequestRepository';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
|
||||
// Presenter tokens
|
||||
export const GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN = 'GetEntitySponsorshipPricingPresenter';
|
||||
export const GET_SPONSORS_PRESENTER_TOKEN = 'GetSponsorsPresenter';
|
||||
export const CREATE_SPONSOR_PRESENTER_TOKEN = 'CreateSponsorPresenter';
|
||||
export const GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN = 'GetSponsorDashboardPresenter';
|
||||
export const GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN = 'GetSponsorSponsorshipsPresenter';
|
||||
export const GET_SPONSOR_PRESENTER_TOKEN = 'GetSponsorPresenter';
|
||||
export const GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN = 'GetPendingSponsorshipRequestsPresenter';
|
||||
export const ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN = 'AcceptSponsorshipRequestPresenter';
|
||||
export const REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN = 'RejectSponsorshipRequestPresenter';
|
||||
export const GET_SPONSOR_BILLING_PRESENTER_TOKEN = 'SponsorBillingPresenter';
|
||||
|
||||
// Use case / application service tokens
|
||||
export const GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetSponsorshipPricingUseCase';
|
||||
export const GET_SPONSORS_USE_CASE_TOKEN = 'GetSponsorsUseCase';
|
||||
@@ -64,6 +89,19 @@ export const ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'AcceptSponsorshipReque
|
||||
export const REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'RejectSponsorshipRequestUseCase';
|
||||
export const GET_SPONSOR_BILLING_USE_CASE_TOKEN = 'GetSponsorBillingUseCase';
|
||||
|
||||
// Output port tokens
|
||||
export const GET_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN = 'GetSponsorshipPricingOutputPort_TOKEN';
|
||||
export const GET_SPONSORS_OUTPUT_PORT_TOKEN = 'GetSponsorsOutputPort_TOKEN';
|
||||
export const CREATE_SPONSOR_OUTPUT_PORT_TOKEN = 'CreateSponsorOutputPort_TOKEN';
|
||||
export const GET_SPONSOR_DASHBOARD_OUTPUT_PORT_TOKEN = 'GetSponsorDashboardOutputPort_TOKEN';
|
||||
export const GET_SPONSOR_SPONSORSHIPS_OUTPUT_PORT_TOKEN = 'GetSponsorSponsorshipsOutputPort_TOKEN';
|
||||
export const GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN = 'GetEntitySponsorshipPricingOutputPort_TOKEN';
|
||||
export const GET_SPONSOR_OUTPUT_PORT_TOKEN = 'GetSponsorOutputPort_TOKEN';
|
||||
export const GET_PENDING_SPONSORSHIP_REQUESTS_OUTPUT_PORT_TOKEN = 'GetPendingSponsorshipRequestsOutputPort_TOKEN';
|
||||
export const ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN = 'AcceptSponsorshipRequestOutputPort_TOKEN';
|
||||
export const REJECT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN = 'RejectSponsorshipRequestOutputPort_TOKEN';
|
||||
export const GET_SPONSOR_BILLING_OUTPUT_PORT_TOKEN = 'GetSponsorBillingOutputPort_TOKEN';
|
||||
|
||||
export const SponsorProviders: Provider[] = [
|
||||
SponsorService,
|
||||
// Repositories
|
||||
@@ -111,27 +149,94 @@ export const SponsorProviders: Provider[] = [
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
// Presenters
|
||||
GetEntitySponsorshipPricingPresenter,
|
||||
GetSponsorsPresenter,
|
||||
CreateSponsorPresenter,
|
||||
GetSponsorDashboardPresenter,
|
||||
GetSponsorSponsorshipsPresenter,
|
||||
GetSponsorPresenter,
|
||||
GetPendingSponsorshipRequestsPresenter,
|
||||
AcceptSponsorshipRequestPresenter,
|
||||
RejectSponsorshipRequestPresenter,
|
||||
SponsorBillingPresenter,
|
||||
// Output ports
|
||||
{
|
||||
provide: GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetEntitySponsorshipPricingPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSORS_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetSponsorsPresenter,
|
||||
},
|
||||
{
|
||||
provide: CREATE_SPONSOR_OUTPUT_PORT_TOKEN,
|
||||
useExisting: CreateSponsorPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSOR_DASHBOARD_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetSponsorDashboardPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSOR_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetSponsorSponsorshipsPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSOR_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetSponsorPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_PENDING_SPONSORSHIP_REQUESTS_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetPendingSponsorshipRequestsPresenter,
|
||||
},
|
||||
{
|
||||
provide: ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN,
|
||||
useExisting: AcceptSponsorshipRequestPresenter,
|
||||
},
|
||||
{
|
||||
provide: REJECT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN,
|
||||
useExisting: RejectSponsorshipRequestPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSOR_BILLING_OUTPUT_PORT_TOKEN,
|
||||
useExisting: SponsorBillingPresenter,
|
||||
},
|
||||
// Use cases
|
||||
{
|
||||
provide: GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN,
|
||||
useFactory: () => new GetSponsorshipPricingUseCase(),
|
||||
inject: [],
|
||||
useFactory: (output: UseCaseOutputPort<any>) => new GetSponsorshipPricingUseCase(output),
|
||||
inject: [GET_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSORS_USE_CASE_TOKEN,
|
||||
useFactory: (sponsorRepo: ISponsorRepository) => new GetSponsorsUseCase(sponsorRepo),
|
||||
inject: [SPONSOR_REPOSITORY_TOKEN],
|
||||
useFactory: (sponsorRepo: ISponsorRepository, output: UseCaseOutputPort<any>) => new GetSponsorsUseCase(sponsorRepo, output),
|
||||
inject: [SPONSOR_REPOSITORY_TOKEN, GET_SPONSORS_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: CREATE_SPONSOR_USE_CASE_TOKEN,
|
||||
useFactory: (sponsorRepo: ISponsorRepository) => new CreateSponsorUseCase(sponsorRepo),
|
||||
inject: [SPONSOR_REPOSITORY_TOKEN],
|
||||
useFactory: (sponsorRepo: ISponsorRepository, logger: Logger, output: UseCaseOutputPort<any>) => new CreateSponsorUseCase(sponsorRepo, logger, output),
|
||||
inject: [SPONSOR_REPOSITORY_TOKEN, LOGGER_TOKEN, CREATE_SPONSOR_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN,
|
||||
useFactory: (sponsorRepo: ISponsorRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository, seasonRepo: ISeasonRepository, leagueRepo: ILeagueRepository, leagueMembershipRepo: ILeagueMembershipRepository, raceRepo: IRaceRepository) =>
|
||||
new GetSponsorDashboardUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo),
|
||||
inject: [SPONSOR_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, SEASON_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN],
|
||||
useFactory: (
|
||||
sponsorRepo: ISponsorRepository,
|
||||
seasonSponsorshipRepo: ISeasonSponsorshipRepository,
|
||||
seasonRepo: ISeasonRepository,
|
||||
leagueRepo: ILeagueRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
raceRepo: IRaceRepository,
|
||||
output: UseCaseOutputPort<any>,
|
||||
) => new GetSponsorDashboardUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo, output),
|
||||
inject: [
|
||||
SPONSOR_REPOSITORY_TOKEN,
|
||||
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
||||
SEASON_REPOSITORY_TOKEN,
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
GET_SPONSOR_DASHBOARD_OUTPUT_PORT_TOKEN,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN,
|
||||
@@ -142,7 +247,8 @@ export const SponsorProviders: Provider[] = [
|
||||
leagueRepo: ILeagueRepository,
|
||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||
raceRepo: IRaceRepository,
|
||||
) => new GetSponsorSponsorshipsUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo),
|
||||
output: UseCaseOutputPort<any>,
|
||||
) => new GetSponsorSponsorshipsUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo, output),
|
||||
inject: [
|
||||
SPONSOR_REPOSITORY_TOKEN,
|
||||
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
||||
@@ -150,41 +256,97 @@ export const SponsorProviders: Provider[] = [
|
||||
LEAGUE_REPOSITORY_TOKEN,
|
||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
GET_SPONSOR_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSOR_BILLING_USE_CASE_TOKEN,
|
||||
useFactory: (paymentRepo: IPaymentRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository) =>
|
||||
new GetSponsorBillingUseCase(paymentRepo, seasonSponsorshipRepo),
|
||||
useFactory: (
|
||||
paymentRepo: IPaymentRepository,
|
||||
seasonSponsorshipRepo: ISeasonSponsorshipRepository,
|
||||
) => {
|
||||
return new GetSponsorBillingUseCase(paymentRepo, seasonSponsorshipRepo);
|
||||
},
|
||||
inject: ['IPaymentRepository', SEASON_SPONSORSHIP_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN,
|
||||
useFactory: (sponsorshipPricingRepo: ISponsorshipPricingRepository, sponsorshipRequestRepo: ISponsorshipRequestRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository, logger: Logger) =>
|
||||
new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, sponsorshipRequestRepo, seasonSponsorshipRepo, logger),
|
||||
inject: [SPONSORSHIP_PRICING_REPOSITORY_TOKEN, SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (
|
||||
sponsorshipPricingRepo: ISponsorshipPricingRepository,
|
||||
sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||
seasonSponsorshipRepo: ISeasonSponsorshipRepository,
|
||||
logger: Logger,
|
||||
output: UseCaseOutputPort<any>,
|
||||
) => new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, sponsorshipRequestRepo, seasonSponsorshipRepo, logger, output),
|
||||
inject: [
|
||||
SPONSORSHIP_PRICING_REPOSITORY_TOKEN,
|
||||
SPONSORSHIP_REQUEST_REPOSITORY_TOKEN,
|
||||
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: GET_SPONSOR_USE_CASE_TOKEN,
|
||||
useFactory: (sponsorRepo: ISponsorRepository) => new GetSponsorUseCase(sponsorRepo),
|
||||
inject: [SPONSOR_REPOSITORY_TOKEN],
|
||||
useFactory: (sponsorRepo: ISponsorRepository, output: UseCaseOutputPort<any>) => new GetSponsorUseCase(sponsorRepo, output),
|
||||
inject: [SPONSOR_REPOSITORY_TOKEN, GET_SPONSOR_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN,
|
||||
useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, sponsorRepo: ISponsorRepository) =>
|
||||
new GetPendingSponsorshipRequestsUseCase(sponsorshipRequestRepo, sponsorRepo),
|
||||
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SPONSOR_REPOSITORY_TOKEN],
|
||||
useFactory: (
|
||||
sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||
sponsorRepo: ISponsorRepository,
|
||||
output: UseCaseOutputPort<any>,
|
||||
) => new GetPendingSponsorshipRequestsUseCase(sponsorshipRequestRepo, sponsorRepo, output),
|
||||
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SPONSOR_REPOSITORY_TOKEN, GET_PENDING_SPONSORSHIP_REQUESTS_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
||||
useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository, seasonRepo: ISeasonRepository, notificationService: NotificationService, paymentGateway: IPaymentGateway, walletRepository: IWalletRepository, leagueWalletRepository: ILeagueWalletRepository, logger: Logger) =>
|
||||
new AcceptSponsorshipRequestUseCase(sponsorshipRequestRepo, seasonSponsorshipRepo, seasonRepo, notificationService, paymentGateway, walletRepository, leagueWalletRepository, logger),
|
||||
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, SEASON_REPOSITORY_TOKEN, 'INotificationService', 'IPaymentGateway', 'IWalletRepository', 'ILeagueWalletRepository', LOGGER_TOKEN],
|
||||
useFactory: (
|
||||
sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||
seasonSponsorshipRepo: ISeasonSponsorshipRepository,
|
||||
seasonRepo: ISeasonRepository,
|
||||
notificationService: NotificationService,
|
||||
walletRepository: IWalletRepository,
|
||||
leagueWalletRepository: ILeagueWalletRepository,
|
||||
logger: Logger,
|
||||
output: UseCaseOutputPort<any>,
|
||||
) => {
|
||||
// Create a mock payment processor function
|
||||
const paymentProcessor = async (_input: any) => {
|
||||
return { success: true, transactionId: `txn_${Date.now()}` };
|
||||
};
|
||||
|
||||
return new AcceptSponsorshipRequestUseCase(
|
||||
sponsorshipRequestRepo,
|
||||
seasonSponsorshipRepo,
|
||||
seasonRepo,
|
||||
notificationService,
|
||||
paymentProcessor,
|
||||
walletRepository,
|
||||
leagueWalletRepository,
|
||||
logger,
|
||||
output
|
||||
);
|
||||
},
|
||||
inject: [
|
||||
SPONSORSHIP_REQUEST_REPOSITORY_TOKEN,
|
||||
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
||||
SEASON_REPOSITORY_TOKEN,
|
||||
'INotificationService',
|
||||
'IWalletRepository',
|
||||
'ILeagueWalletRepository',
|
||||
LOGGER_TOKEN,
|
||||
ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
||||
useFactory: (sponsorshipRequestRepo: ISponsorshipRequestRepository, logger: Logger) =>
|
||||
new RejectSponsorshipRequestUseCase(sponsorshipRequestRepo, logger),
|
||||
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (
|
||||
sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||
logger: Logger,
|
||||
output: UseCaseOutputPort<any>,
|
||||
) => new RejectSponsorshipRequestUseCase(sponsorshipRequestRepo, logger, output),
|
||||
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, LOGGER_TOKEN, REJECT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
];
|
||||
];
|
||||
@@ -1,15 +1,27 @@
|
||||
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { GetSponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
||||
import type { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
||||
import type { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
|
||||
import type { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||
import type { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
||||
import type { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
|
||||
import type { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
|
||||
import type { GetSponsorBillingUseCase } from '@core/payments/application/use-cases/GetSponsorBillingUseCase';
|
||||
import type { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
|
||||
import type { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
|
||||
import type { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
|
||||
import type { GetSponsorDashboardInput, GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||
import type { GetSponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
||||
import type { GetSponsorSponsorshipsInput, GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
||||
import type { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
||||
import type { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
|
||||
import type { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
|
||||
import type { CreateSponsorInputDTO } from './dtos/CreateSponsorInputDTO';
|
||||
import { AcceptSponsorshipRequestPresenter } from './presenters/AcceptSponsorshipRequestPresenter';
|
||||
import { CreateSponsorPresenter } from './presenters/CreateSponsorPresenter';
|
||||
import { GetEntitySponsorshipPricingPresenter } from './presenters/GetEntitySponsorshipPricingPresenter';
|
||||
import { GetPendingSponsorshipRequestsPresenter } from './presenters/GetPendingSponsorshipRequestsPresenter';
|
||||
import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPresenter';
|
||||
import { GetSponsorPresenter } from './presenters/GetSponsorPresenter';
|
||||
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
|
||||
import { GetSponsorsPresenter } from './presenters/GetSponsorsPresenter';
|
||||
import { RejectSponsorshipRequestPresenter } from './presenters/RejectSponsorshipRequestPresenter';
|
||||
import { SponsorBillingPresenter } from './presenters/SponsorBillingPresenter';
|
||||
import { SponsorService } from './SponsorService';
|
||||
|
||||
describe('SponsorService', () => {
|
||||
@@ -23,8 +35,21 @@ describe('SponsorService', () => {
|
||||
let getPendingSponsorshipRequestsUseCase: { execute: Mock };
|
||||
let acceptSponsorshipRequestUseCase: { execute: Mock };
|
||||
let rejectSponsorshipRequestUseCase: { execute: Mock };
|
||||
let getSponsorBillingUseCase: { execute: Mock };
|
||||
let logger: Logger;
|
||||
|
||||
// Presenters
|
||||
let getEntitySponsorshipPricingPresenter: GetEntitySponsorshipPricingPresenter;
|
||||
let getSponsorsPresenter: GetSponsorsPresenter;
|
||||
let createSponsorPresenter: CreateSponsorPresenter;
|
||||
let getSponsorDashboardPresenter: GetSponsorDashboardPresenter;
|
||||
let getSponsorSponsorshipsPresenter: GetSponsorSponsorshipsPresenter;
|
||||
let getSponsorPresenter: GetSponsorPresenter;
|
||||
let getPendingSponsorshipRequestsPresenter: GetPendingSponsorshipRequestsPresenter;
|
||||
let acceptSponsorshipRequestPresenter: AcceptSponsorshipRequestPresenter;
|
||||
let rejectSponsorshipRequestPresenter: RejectSponsorshipRequestPresenter;
|
||||
let sponsorBillingPresenter: SponsorBillingPresenter;
|
||||
|
||||
beforeEach(() => {
|
||||
getSponsorshipPricingUseCase = { execute: vi.fn() };
|
||||
getSponsorsUseCase = { execute: vi.fn() };
|
||||
@@ -35,6 +60,7 @@ describe('SponsorService', () => {
|
||||
getPendingSponsorshipRequestsUseCase = { execute: vi.fn() };
|
||||
acceptSponsorshipRequestUseCase = { execute: vi.fn() };
|
||||
rejectSponsorshipRequestUseCase = { execute: vi.fn() };
|
||||
getSponsorBillingUseCase = { execute: vi.fn() };
|
||||
logger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
@@ -42,6 +68,18 @@ describe('SponsorService', () => {
|
||||
error: vi.fn(),
|
||||
} as unknown as Logger;
|
||||
|
||||
// Initialize presenters
|
||||
getEntitySponsorshipPricingPresenter = new GetEntitySponsorshipPricingPresenter();
|
||||
getSponsorsPresenter = new GetSponsorsPresenter();
|
||||
createSponsorPresenter = new CreateSponsorPresenter();
|
||||
getSponsorDashboardPresenter = new GetSponsorDashboardPresenter();
|
||||
getSponsorSponsorshipsPresenter = new GetSponsorSponsorshipsPresenter();
|
||||
getSponsorPresenter = new GetSponsorPresenter();
|
||||
getPendingSponsorshipRequestsPresenter = new GetPendingSponsorshipRequestsPresenter();
|
||||
acceptSponsorshipRequestPresenter = new AcceptSponsorshipRequestPresenter();
|
||||
rejectSponsorshipRequestPresenter = new RejectSponsorshipRequestPresenter();
|
||||
sponsorBillingPresenter = new SponsorBillingPresenter();
|
||||
|
||||
service = new SponsorService(
|
||||
getSponsorshipPricingUseCase as unknown as GetSponsorshipPricingUseCase,
|
||||
getSponsorsUseCase as unknown as GetSponsorsUseCase,
|
||||
@@ -52,28 +90,39 @@ describe('SponsorService', () => {
|
||||
getPendingSponsorshipRequestsUseCase as unknown as GetPendingSponsorshipRequestsUseCase,
|
||||
acceptSponsorshipRequestUseCase as unknown as AcceptSponsorshipRequestUseCase,
|
||||
rejectSponsorshipRequestUseCase as unknown as RejectSponsorshipRequestUseCase,
|
||||
getSponsorBillingUseCase as unknown as GetSponsorBillingUseCase,
|
||||
logger,
|
||||
getEntitySponsorshipPricingPresenter,
|
||||
getSponsorsPresenter,
|
||||
createSponsorPresenter,
|
||||
getSponsorDashboardPresenter,
|
||||
getSponsorSponsorshipsPresenter,
|
||||
getSponsorPresenter,
|
||||
getPendingSponsorshipRequestsPresenter,
|
||||
acceptSponsorshipRequestPresenter,
|
||||
rejectSponsorshipRequestPresenter,
|
||||
sponsorBillingPresenter,
|
||||
);
|
||||
});
|
||||
|
||||
describe('getEntitySponsorshipPricing', () => {
|
||||
it('returns presenter with pricing data on success', async () => {
|
||||
it('returns pricing data on success', async () => {
|
||||
const outputPort = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: [
|
||||
{ id: 'tier-gold', level: 'Gold', price: 500, currency: 'USD' },
|
||||
tiers: [
|
||||
{ name: 'Gold', price: 500, benefits: ['Main slot'] },
|
||||
],
|
||||
};
|
||||
getSponsorshipPricingUseCase.execute.mockResolvedValue(Result.ok(outputPort));
|
||||
|
||||
const presenter = await service.getEntitySponsorshipPricing();
|
||||
const result = await service.getEntitySponsorshipPricing();
|
||||
|
||||
expect(presenter.viewModel).toEqual({
|
||||
expect(result).toEqual({
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: [
|
||||
{ id: 'tier-gold', level: 'Gold', price: 500, currency: 'USD' },
|
||||
{ id: 'Gold', level: 'Gold', price: 500, currency: 'USD' },
|
||||
],
|
||||
});
|
||||
});
|
||||
@@ -81,9 +130,9 @@ describe('SponsorService', () => {
|
||||
it('returns empty pricing on error', async () => {
|
||||
getSponsorshipPricingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
|
||||
|
||||
const presenter = await service.getEntitySponsorshipPricing();
|
||||
const result = await service.getEntitySponsorshipPricing();
|
||||
|
||||
expect(presenter.viewModel).toEqual({
|
||||
expect(result).toEqual({
|
||||
entityType: 'season',
|
||||
entityId: '',
|
||||
pricing: [],
|
||||
@@ -92,82 +141,93 @@ describe('SponsorService', () => {
|
||||
});
|
||||
|
||||
describe('getSponsors', () => {
|
||||
it('returns sponsors in presenter on success', async () => {
|
||||
const outputPort = { sponsors: [{ id: 's1', name: 'S1', contactEmail: 's1@test', createdAt: new Date() }] };
|
||||
it('returns sponsors on success', async () => {
|
||||
const sponsors = [{ id: 's1', name: 'S1', contactEmail: 's1@test', createdAt: new Date() }];
|
||||
const outputPort = { sponsors };
|
||||
getSponsorsUseCase.execute.mockResolvedValue(Result.ok(outputPort));
|
||||
|
||||
const presenter = await service.getSponsors();
|
||||
const result = await service.getSponsors();
|
||||
|
||||
expect(presenter.viewModel).toEqual({ sponsors: outputPort.sponsors });
|
||||
expect(result).toEqual({ sponsors });
|
||||
});
|
||||
|
||||
it('returns empty list on error', async () => {
|
||||
getSponsorsUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
|
||||
|
||||
const presenter = await service.getSponsors();
|
||||
const result = await service.getSponsors();
|
||||
|
||||
expect(presenter.viewModel).toEqual({ sponsors: [] });
|
||||
expect(result).toEqual({ sponsors: [] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSponsor', () => {
|
||||
it('returns created sponsor in presenter on success', async () => {
|
||||
const input = { name: 'Test', contactEmail: 'test@example.com' };
|
||||
const outputPort = {
|
||||
sponsor: {
|
||||
id: 's1',
|
||||
name: 'Test',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
it('returns created sponsor on success', async () => {
|
||||
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' };
|
||||
const sponsor = {
|
||||
id: 's1',
|
||||
name: 'Test',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date(),
|
||||
};
|
||||
const outputPort = { sponsor };
|
||||
createSponsorUseCase.execute.mockResolvedValue(Result.ok(outputPort));
|
||||
|
||||
const presenter = await service.createSponsor(input as any);
|
||||
const result = await service.createSponsor(input);
|
||||
|
||||
expect(presenter.viewModel).toEqual({ sponsor: outputPort.sponsor });
|
||||
expect(result).toEqual({ sponsor });
|
||||
});
|
||||
|
||||
it('throws on error', async () => {
|
||||
const input = { name: 'Test', contactEmail: 'test@example.com' };
|
||||
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' };
|
||||
createSponsorUseCase.execute.mockResolvedValue(
|
||||
Result.err({ code: 'VALIDATION_ERROR', details: { message: 'Invalid' } }),
|
||||
);
|
||||
|
||||
await expect(service.createSponsor(input as any)).rejects.toThrow('Invalid');
|
||||
await expect(service.createSponsor(input)).rejects.toThrow('Invalid');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSponsorDashboard', () => {
|
||||
it('returns dashboard in presenter on success', async () => {
|
||||
const params = { sponsorId: 's1' };
|
||||
it('returns dashboard on success', async () => {
|
||||
const params: GetSponsorDashboardInput = { sponsorId: 's1' };
|
||||
const outputPort = {
|
||||
sponsorId: 's1',
|
||||
sponsorName: 'S1',
|
||||
metrics: {} as any,
|
||||
metrics: {
|
||||
impressions: 0,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 0,
|
||||
viewersChange: 0,
|
||||
races: 0,
|
||||
drivers: 0,
|
||||
exposure: 0,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {} as any,
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
};
|
||||
getSponsorDashboardUseCase.execute.mockResolvedValue(Result.ok(outputPort));
|
||||
|
||||
const presenter = await service.getSponsorDashboard(params as any);
|
||||
const result = await service.getSponsorDashboard(params);
|
||||
|
||||
expect(presenter.viewModel).toEqual(outputPort);
|
||||
expect(result).toEqual(outputPort);
|
||||
});
|
||||
|
||||
it('returns null in presenter on error', async () => {
|
||||
const params = { sponsorId: 's1' };
|
||||
it('throws on error', async () => {
|
||||
const params: GetSponsorDashboardInput = { sponsorId: 's1' };
|
||||
getSponsorDashboardUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
|
||||
|
||||
const presenter = await service.getSponsorDashboard(params as any);
|
||||
|
||||
expect(presenter.viewModel).toBeNull();
|
||||
await expect(service.getSponsorDashboard(params)).rejects.toThrow('Sponsor dashboard not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSponsorSponsorships', () => {
|
||||
it('returns sponsorships in presenter on success', async () => {
|
||||
const params = { sponsorId: 's1' };
|
||||
it('returns sponsorships on success', async () => {
|
||||
const params: GetSponsorSponsorshipsInput = { sponsorId: 's1' };
|
||||
const outputPort = {
|
||||
sponsorId: 's1',
|
||||
sponsorName: 'S1',
|
||||
@@ -182,46 +242,43 @@ describe('SponsorService', () => {
|
||||
};
|
||||
getSponsorSponsorshipsUseCase.execute.mockResolvedValue(Result.ok(outputPort));
|
||||
|
||||
const presenter = await service.getSponsorSponsorships(params as any);
|
||||
const result = await service.getSponsorSponsorships(params);
|
||||
|
||||
expect(presenter.viewModel).toEqual(outputPort);
|
||||
expect(result).toEqual(outputPort);
|
||||
});
|
||||
|
||||
it('returns null in presenter on error', async () => {
|
||||
const params = { sponsorId: 's1' };
|
||||
it('throws on error', async () => {
|
||||
const params: GetSponsorSponsorshipsInput = { sponsorId: 's1' };
|
||||
getSponsorSponsorshipsUseCase.execute.mockResolvedValue(
|
||||
Result.err({ code: 'REPOSITORY_ERROR' }),
|
||||
);
|
||||
|
||||
const presenter = await service.getSponsorSponsorships(params as any);
|
||||
|
||||
expect(presenter.viewModel).toBeNull();
|
||||
await expect(service.getSponsorSponsorships(params)).rejects.toThrow('Sponsor sponsorships not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSponsor', () => {
|
||||
it('returns sponsor in presenter when found', async () => {
|
||||
it('returns sponsor when found', async () => {
|
||||
const sponsorId = 's1';
|
||||
const output = { sponsor: { id: sponsorId, name: 'S1' } };
|
||||
const sponsor = { id: sponsorId, name: 'S1' };
|
||||
const output = { sponsor };
|
||||
getSponsorUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||
|
||||
const presenter = await service.getSponsor(sponsorId);
|
||||
const result = await service.getSponsor(sponsorId);
|
||||
|
||||
expect(presenter.viewModel).toEqual({ sponsor: output.sponsor });
|
||||
expect(result).toEqual({ sponsor });
|
||||
});
|
||||
|
||||
it('returns null in presenter when not found', async () => {
|
||||
it('throws when not found', async () => {
|
||||
const sponsorId = 's1';
|
||||
getSponsorUseCase.execute.mockResolvedValue(Result.ok(null));
|
||||
|
||||
const presenter = await service.getSponsor(sponsorId);
|
||||
|
||||
expect(presenter.viewModel).toBeNull();
|
||||
await expect(service.getSponsor(sponsorId)).rejects.toThrow('Sponsor not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPendingSponsorshipRequests', () => {
|
||||
it('returns requests in presenter on success', async () => {
|
||||
it('returns requests on success', async () => {
|
||||
const params = { entityType: 'season' as const, entityId: 'season-1' };
|
||||
const outputPort = {
|
||||
entityType: 'season',
|
||||
@@ -231,9 +288,9 @@ describe('SponsorService', () => {
|
||||
};
|
||||
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(Result.ok(outputPort));
|
||||
|
||||
const presenter = await service.getPendingSponsorshipRequests(params);
|
||||
const result = await service.getPendingSponsorshipRequests(params);
|
||||
|
||||
expect(presenter.viewModel).toEqual(outputPort);
|
||||
expect(result).toEqual(outputPort);
|
||||
});
|
||||
|
||||
it('returns empty result on error', async () => {
|
||||
@@ -242,9 +299,9 @@ describe('SponsorService', () => {
|
||||
Result.err({ code: 'REPOSITORY_ERROR' }),
|
||||
);
|
||||
|
||||
const presenter = await service.getPendingSponsorshipRequests(params);
|
||||
const result = await service.getPendingSponsorshipRequests(params);
|
||||
|
||||
expect(presenter.viewModel).toEqual({
|
||||
expect(result).toEqual({
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
requests: [],
|
||||
@@ -253,8 +310,8 @@ describe('SponsorService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('acceptSponsorshipRequest', () => {
|
||||
it('returns accept result in presenter on success', async () => {
|
||||
describe('SponsorshipRequest', () => {
|
||||
it('returns accept result on success', async () => {
|
||||
const requestId = 'r1';
|
||||
const respondedBy = 'u1';
|
||||
const outputPort = {
|
||||
@@ -267,100 +324,114 @@ describe('SponsorService', () => {
|
||||
};
|
||||
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(outputPort));
|
||||
|
||||
const presenter = await service.acceptSponsorshipRequest(requestId, respondedBy);
|
||||
const result = await service.acceptSponsorshipRequest(requestId, respondedBy);
|
||||
|
||||
expect(presenter.viewModel).toEqual(outputPort);
|
||||
expect(result).toEqual(outputPort);
|
||||
});
|
||||
|
||||
it('returns null in presenter on error', async () => {
|
||||
it('throws on error', async () => {
|
||||
const requestId = 'r1';
|
||||
const respondedBy = 'u1';
|
||||
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(
|
||||
Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_FOUND' }),
|
||||
);
|
||||
|
||||
const presenter = await service.acceptSponsorshipRequest(requestId, respondedBy);
|
||||
|
||||
expect(presenter.viewModel).toBeNull();
|
||||
await expect(service.acceptSponsorshipRequest(requestId, respondedBy)).rejects.toThrow('Accept sponsorship request failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('rejectSponsorshipRequest', () => {
|
||||
it('returns reject result in presenter on success', async () => {
|
||||
it('returns reject result on success', async () => {
|
||||
const requestId = 'r1';
|
||||
const respondedBy = 'u1';
|
||||
const reason = 'Not interested';
|
||||
const output = {
|
||||
requestId,
|
||||
status: 'rejected' as const,
|
||||
rejectedAt: new Date(),
|
||||
reason,
|
||||
respondedAt: new Date(),
|
||||
rejectionReason: reason,
|
||||
};
|
||||
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||
|
||||
const presenter = await service.rejectSponsorshipRequest(requestId, respondedBy, reason);
|
||||
const result = await service.rejectSponsorshipRequest(requestId, respondedBy, reason);
|
||||
|
||||
expect(presenter.viewModel).toEqual(output);
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
|
||||
it('returns null in presenter on error', async () => {
|
||||
it('throws on error', async () => {
|
||||
const requestId = 'r1';
|
||||
const respondedBy = 'u1';
|
||||
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(
|
||||
Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_FOUND' }),
|
||||
);
|
||||
|
||||
const presenter = await service.rejectSponsorshipRequest(requestId, respondedBy);
|
||||
|
||||
expect(presenter.viewModel).toBeNull();
|
||||
await expect(service.rejectSponsorshipRequest(requestId, respondedBy)).rejects.toThrow('Reject sponsorship request failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSponsorBilling', () => {
|
||||
it('returns mock billing data in presenter', async () => {
|
||||
const presenter = await service.getSponsorBilling('s1');
|
||||
it('returns billing data', async () => {
|
||||
// Mock the use case to set up the presenter
|
||||
getSponsorBillingUseCase.execute.mockImplementation(async () => {
|
||||
sponsorBillingPresenter.present({
|
||||
paymentMethods: [],
|
||||
invoices: [],
|
||||
stats: {
|
||||
totalSpent: 0,
|
||||
pendingAmount: 0,
|
||||
nextPaymentDate: '',
|
||||
nextPaymentAmount: 0,
|
||||
activeSponsorships: 0,
|
||||
averageMonthlySpend: 0,
|
||||
},
|
||||
});
|
||||
return Result.ok(undefined);
|
||||
});
|
||||
|
||||
expect(presenter.viewModel).not.toBeNull();
|
||||
expect(presenter.viewModel?.paymentMethods).toBeInstanceOf(Array);
|
||||
expect(presenter.viewModel?.invoices).toBeInstanceOf(Array);
|
||||
expect(presenter.viewModel?.stats).toBeDefined();
|
||||
const result = await service.getSponsorBilling('s1');
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.paymentMethods).toBeInstanceOf(Array);
|
||||
expect(result.invoices).toBeInstanceOf(Array);
|
||||
expect(result.stats).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvailableLeagues', () => {
|
||||
it('returns mock leagues in presenter', async () => {
|
||||
const presenter = await service.getAvailableLeagues();
|
||||
it('returns mock leagues', async () => {
|
||||
const result = await service.getAvailableLeagues();
|
||||
|
||||
expect(presenter.viewModel).not.toBeNull();
|
||||
expect(presenter.viewModel?.length).toBeGreaterThan(0);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.viewModel).not.toBeNull();
|
||||
expect(result.viewModel?.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLeagueDetail', () => {
|
||||
it('returns league detail in presenter', async () => {
|
||||
const presenter = await service.getLeagueDetail('league-1');
|
||||
it('returns league detail', async () => {
|
||||
const result = await service.getLeagueDetail('league-1');
|
||||
|
||||
expect(presenter.viewModel).not.toBeNull();
|
||||
expect(presenter.viewModel?.league.id).toBe('league-1');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.viewModel?.league.id).toBe('league-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSponsorSettings', () => {
|
||||
it('returns settings in presenter', async () => {
|
||||
const presenter = await service.getSponsorSettings('s1');
|
||||
it('returns settings', async () => {
|
||||
const result = await service.getSponsorSettings('s1');
|
||||
|
||||
expect(presenter.viewModel).not.toBeNull();
|
||||
expect(presenter.viewModel?.profile).toBeDefined();
|
||||
expect(presenter.viewModel?.notifications).toBeDefined();
|
||||
expect(presenter.viewModel?.privacy).toBeDefined();
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.viewModel?.profile).toBeDefined();
|
||||
expect(result.viewModel?.notifications).toBeDefined();
|
||||
expect(result.viewModel?.privacy).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSponsorSettings', () => {
|
||||
it('returns success result in presenter', async () => {
|
||||
const presenter = await service.updateSponsorSettings('s1', {});
|
||||
it('returns success result', async () => {
|
||||
const result = await service.updateSponsorSettings('s1', {});
|
||||
|
||||
expect(presenter.viewModel).toEqual({ success: true });
|
||||
expect(result.viewModel).toEqual({ success: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { CreateSponsorInputDTO } from './dtos/CreateSponsorInputDTO';
|
||||
import { CreateSponsorOutputDTO } from './dtos/CreateSponsorOutputDTO';
|
||||
import { GetSponsorDashboardQueryParamsDTO } from './dtos/GetSponsorDashboardQueryParamsDTO';
|
||||
import { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO';
|
||||
import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO';
|
||||
import { AcceptSponsorshipRequestInputDTO } from './dtos/AcceptSponsorshipRequestInputDTO';
|
||||
import { RejectSponsorshipRequestInputDTO } from './dtos/RejectSponsorshipRequestInputDTO';
|
||||
import { PaymentMethodDTO } from './dtos/PaymentMethodDTO';
|
||||
import { InvoiceDTO } from './dtos/InvoiceDTO';
|
||||
import { BillingStatsDTO } from './dtos/BillingStatsDTO';
|
||||
import { SponsorSponsorshipsDTO } from './dtos/SponsorSponsorshipsDTO';
|
||||
import { GetSponsorOutputDTO } from './dtos/GetSponsorOutputDTO';
|
||||
import { GetPendingSponsorshipRequestsOutputDTO } from './dtos/GetPendingSponsorshipRequestsOutputDTO';
|
||||
import { GetEntitySponsorshipPricingResultDTO } from './dtos/GetEntitySponsorshipPricingResultDTO';
|
||||
import { GetSponsorsOutputDTO } from './dtos/GetSponsorsOutputDTO';
|
||||
import { AvailableLeagueDTO } from './dtos/AvailableLeagueDTO';
|
||||
import { LeagueDetailDTO } from './dtos/LeagueDetailDTO';
|
||||
import { DriverDTO } from './dtos/DriverDTO';
|
||||
@@ -14,6 +16,9 @@ import { RaceDTO } from './dtos/RaceDTO';
|
||||
import { SponsorProfileDTO } from './dtos/SponsorProfileDTO';
|
||||
import { NotificationSettingsDTO } from './dtos/NotificationSettingsDTO';
|
||||
import { PrivacySettingsDTO } from './dtos/PrivacySettingsDTO';
|
||||
import { PaymentMethodDTO } from './dtos/PaymentMethodDTO';
|
||||
import { InvoiceDTO } from './dtos/InvoiceDTO';
|
||||
import { BillingStatsDTO } from './dtos/BillingStatsDTO';
|
||||
|
||||
// Use cases
|
||||
import { GetSponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
||||
@@ -24,7 +29,7 @@ import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-case
|
||||
import { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
|
||||
import {
|
||||
GetPendingSponsorshipRequestsUseCase,
|
||||
GetPendingSponsorshipRequestsDTO,
|
||||
GetPendingSponsorshipRequestsInput,
|
||||
} from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
|
||||
import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
|
||||
import { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||
@@ -48,6 +53,8 @@ import { AvailableLeaguesPresenter } from './presenters/AvailableLeaguesPresente
|
||||
import { LeagueDetailPresenter } from './presenters/LeagueDetailPresenter';
|
||||
import { SponsorSettingsPresenter } from './presenters/SponsorSettingsPresenter';
|
||||
import { SponsorSettingsUpdatePresenter } from './presenters/SponsorSettingsUpdatePresenter';
|
||||
import { AcceptSponsorshipRequestResultViewModel } from './presenters/AcceptSponsorshipRequestPresenter';
|
||||
import type { RejectSponsorshipRequestResult } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||
|
||||
// Tokens
|
||||
import {
|
||||
@@ -61,6 +68,16 @@ import {
|
||||
ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
||||
REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN,
|
||||
GET_SPONSORS_PRESENTER_TOKEN,
|
||||
CREATE_SPONSOR_PRESENTER_TOKEN,
|
||||
GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN,
|
||||
GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN,
|
||||
GET_SPONSOR_PRESENTER_TOKEN,
|
||||
GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN,
|
||||
ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN,
|
||||
REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN,
|
||||
GET_SPONSOR_BILLING_PRESENTER_TOKEN,
|
||||
} from './SponsorProviders';
|
||||
|
||||
@Injectable()
|
||||
@@ -88,194 +105,152 @@ export class SponsorService {
|
||||
private readonly getSponsorBillingUseCase: GetSponsorBillingUseCase,
|
||||
@Inject(LOGGER_TOKEN)
|
||||
private readonly logger: Logger,
|
||||
// Injected presenters
|
||||
@Inject(GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN)
|
||||
private readonly getEntitySponsorshipPricingPresenter: GetEntitySponsorshipPricingPresenter,
|
||||
@Inject(GET_SPONSORS_PRESENTER_TOKEN)
|
||||
private readonly getSponsorsPresenter: GetSponsorsPresenter,
|
||||
@Inject(CREATE_SPONSOR_PRESENTER_TOKEN)
|
||||
private readonly createSponsorPresenter: CreateSponsorPresenter,
|
||||
@Inject(GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN)
|
||||
private readonly getSponsorDashboardPresenter: GetSponsorDashboardPresenter,
|
||||
@Inject(GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN)
|
||||
private readonly getSponsorSponsorshipsPresenter: GetSponsorSponsorshipsPresenter,
|
||||
@Inject(GET_SPONSOR_PRESENTER_TOKEN)
|
||||
private readonly getSponsorPresenter: GetSponsorPresenter,
|
||||
@Inject(GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN)
|
||||
private readonly getPendingSponsorshipRequestsPresenter: GetPendingSponsorshipRequestsPresenter,
|
||||
@Inject(ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN)
|
||||
private readonly acceptSponsorshipRequestPresenter: AcceptSponsorshipRequestPresenter,
|
||||
@Inject(REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN)
|
||||
private readonly rejectSponsorshipRequestPresenter: RejectSponsorshipRequestPresenter,
|
||||
@Inject(GET_SPONSOR_BILLING_PRESENTER_TOKEN)
|
||||
private readonly sponsorBillingPresenter: SponsorBillingPresenter,
|
||||
) {}
|
||||
|
||||
async getEntitySponsorshipPricing(): Promise<GetEntitySponsorshipPricingPresenter> {
|
||||
async getEntitySponsorshipPricing(): Promise<GetEntitySponsorshipPricingResultDTO> {
|
||||
this.logger.debug('[SponsorService] Fetching sponsorship pricing.');
|
||||
|
||||
const presenter = new GetEntitySponsorshipPricingPresenter();
|
||||
const result = await this.getSponsorshipPricingUseCase.execute();
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to fetch sponsorship pricing.', result.error);
|
||||
presenter.present(null);
|
||||
return presenter;
|
||||
}
|
||||
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
await this.getSponsorshipPricingUseCase.execute({});
|
||||
return this.getEntitySponsorshipPricingPresenter.viewModel;
|
||||
}
|
||||
|
||||
async getSponsors(): Promise<GetSponsorsOutputDTO> {
|
||||
this.logger.debug('[SponsorService] Fetching sponsors.');
|
||||
|
||||
const presenter = new GetSponsorsPresenter();
|
||||
const result = await this.getSponsorsUseCase.execute();
|
||||
|
||||
presenter.present(result);
|
||||
|
||||
return presenter.responseModel;
|
||||
await this.getSponsorsUseCase.execute();
|
||||
return this.getSponsorsPresenter.responseModel;
|
||||
}
|
||||
|
||||
async createSponsor(input: CreateSponsorInputDTO): Promise<CreateSponsorPresenter> {
|
||||
async createSponsor(input: CreateSponsorInputDTO): Promise<CreateSponsorOutputDTO> {
|
||||
this.logger.debug('[SponsorService] Creating sponsor.', { input });
|
||||
|
||||
const presenter = new CreateSponsorPresenter();
|
||||
const result = await this.createSponsorUseCase.execute(input);
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to create sponsor.', result.error);
|
||||
throw new Error(result.error.details?.message || 'Failed to create sponsor');
|
||||
}
|
||||
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
await this.createSponsorUseCase.execute(input);
|
||||
return this.createSponsorPresenter.viewModel;
|
||||
}
|
||||
|
||||
async getSponsorDashboard(
|
||||
params: GetSponsorDashboardQueryParamsDTO,
|
||||
): Promise<GetSponsorDashboardPresenter> {
|
||||
): Promise<SponsorDashboardDTO> {
|
||||
this.logger.debug('[SponsorService] Fetching sponsor dashboard.', { params });
|
||||
|
||||
const presenter = new GetSponsorDashboardPresenter();
|
||||
const result = await this.getSponsorDashboardUseCase.execute(params);
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to fetch sponsor dashboard.', result.error);
|
||||
presenter.present(null);
|
||||
return presenter;
|
||||
await this.getSponsorDashboardUseCase.execute(params);
|
||||
const result = this.getSponsorDashboardPresenter.viewModel;
|
||||
if (!result) {
|
||||
throw new Error('Sponsor dashboard not found');
|
||||
}
|
||||
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
return result;
|
||||
}
|
||||
|
||||
async getSponsorSponsorships(
|
||||
params: GetSponsorSponsorshipsQueryParamsDTO,
|
||||
): Promise<GetSponsorSponsorshipsPresenter> {
|
||||
): Promise<SponsorSponsorshipsDTO> {
|
||||
this.logger.debug('[SponsorService] Fetching sponsor sponsorships.', { params });
|
||||
|
||||
const presenter = new GetSponsorSponsorshipsPresenter();
|
||||
const result = await this.getSponsorSponsorshipsUseCase.execute(params);
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to fetch sponsor sponsorships.', result.error);
|
||||
presenter.present(null);
|
||||
return presenter;
|
||||
await this.getSponsorSponsorshipsUseCase.execute(params);
|
||||
const result = this.getSponsorSponsorshipsPresenter.viewModel;
|
||||
if (!result) {
|
||||
throw new Error('Sponsor sponsorships not found');
|
||||
}
|
||||
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
return result;
|
||||
}
|
||||
|
||||
async getSponsor(sponsorId: string): Promise<GetSponsorPresenter> {
|
||||
async getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO> {
|
||||
this.logger.debug('[SponsorService] Fetching sponsor.', { sponsorId });
|
||||
|
||||
const presenter = new GetSponsorPresenter();
|
||||
const result = await this.getSponsorUseCase.execute({ sponsorId });
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to fetch sponsor.', result.error);
|
||||
presenter.present(null);
|
||||
return presenter;
|
||||
await this.getSponsorUseCase.execute({ sponsorId });
|
||||
const result = this.getSponsorPresenter.viewModel;
|
||||
if (!result) {
|
||||
throw new Error('Sponsor not found');
|
||||
}
|
||||
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
return result;
|
||||
}
|
||||
|
||||
async getPendingSponsorshipRequests(params: {
|
||||
entityType: SponsorableEntityType;
|
||||
entityId: string;
|
||||
}): Promise<GetPendingSponsorshipRequestsPresenter> {
|
||||
}): Promise<GetPendingSponsorshipRequestsOutputDTO> {
|
||||
this.logger.debug('[SponsorService] Fetching pending sponsorship requests.', { params });
|
||||
|
||||
const presenter = new GetPendingSponsorshipRequestsPresenter();
|
||||
const result = await this.getPendingSponsorshipRequestsUseCase.execute(
|
||||
params as GetPendingSponsorshipRequestsDTO,
|
||||
await this.getPendingSponsorshipRequestsUseCase.execute(
|
||||
params as GetPendingSponsorshipRequestsInput,
|
||||
);
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to fetch pending sponsorship requests.', result.error);
|
||||
presenter.present({
|
||||
entityType: params.entityType,
|
||||
entityId: params.entityId,
|
||||
requests: [],
|
||||
totalCount: 0,
|
||||
});
|
||||
return presenter;
|
||||
const result = this.getPendingSponsorshipRequestsPresenter.viewModel;
|
||||
if (!result) {
|
||||
throw new Error('Pending sponsorship requests not found');
|
||||
}
|
||||
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
return result;
|
||||
}
|
||||
|
||||
async acceptSponsorshipRequest(
|
||||
requestId: string,
|
||||
respondedBy: string,
|
||||
): Promise<AcceptSponsorshipRequestPresenter> {
|
||||
): Promise<AcceptSponsorshipRequestResultViewModel> {
|
||||
this.logger.debug('[SponsorService] Accepting sponsorship request.', {
|
||||
requestId,
|
||||
respondedBy,
|
||||
});
|
||||
|
||||
const presenter = new AcceptSponsorshipRequestPresenter();
|
||||
const result = await this.acceptSponsorshipRequestUseCase.execute({
|
||||
await this.acceptSponsorshipRequestUseCase.execute({
|
||||
requestId,
|
||||
respondedBy,
|
||||
} as AcceptSponsorshipRequestInputDTO);
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to accept sponsorship request.', result.error);
|
||||
presenter.present(null);
|
||||
return presenter;
|
||||
});
|
||||
const result = this.acceptSponsorshipRequestPresenter.viewModel;
|
||||
if (!result) {
|
||||
throw new Error('Accept sponsorship request failed');
|
||||
}
|
||||
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
return result;
|
||||
}
|
||||
|
||||
async rejectSponsorshipRequest(
|
||||
requestId: string,
|
||||
respondedBy: string,
|
||||
reason?: string,
|
||||
): Promise<RejectSponsorshipRequestPresenter> {
|
||||
): Promise<RejectSponsorshipRequestResult> {
|
||||
this.logger.debug('[SponsorService] Rejecting sponsorship request.', {
|
||||
requestId,
|
||||
respondedBy,
|
||||
reason,
|
||||
});
|
||||
|
||||
const presenter = new RejectSponsorshipRequestPresenter();
|
||||
const result = await this.rejectSponsorshipRequestUseCase.execute({
|
||||
const input: { requestId: string; respondedBy: string; reason?: string } = {
|
||||
requestId,
|
||||
respondedBy,
|
||||
reason,
|
||||
} as RejectSponsorshipRequestInputDTO);
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to reject sponsorship request.', result.error);
|
||||
presenter.present(null);
|
||||
return presenter;
|
||||
};
|
||||
if (reason !== undefined) {
|
||||
input.reason = reason;
|
||||
}
|
||||
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
await this.rejectSponsorshipRequestUseCase.execute(input);
|
||||
const result = this.rejectSponsorshipRequestPresenter.viewModel;
|
||||
if (!result) {
|
||||
throw new Error('Reject sponsorship request failed');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async getSponsorBilling(sponsorId: string): Promise<GetSponsorBillingPresenter> {
|
||||
async getSponsorBilling(sponsorId: string): Promise<{
|
||||
paymentMethods: PaymentMethodDTO[];
|
||||
invoices: InvoiceDTO[];
|
||||
stats: BillingStatsDTO;
|
||||
}> {
|
||||
this.logger.debug('[SponsorService] Fetching sponsor billing.', { sponsorId });
|
||||
|
||||
const result = await this.getSponsorBillingUseCase.execute({ sponsorId });
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error('[SponsorService] Failed to fetch sponsor billing.', result.error);
|
||||
throw new Error(result.error.details?.message || 'Failed to fetch sponsor billing');
|
||||
await this.getSponsorBillingUseCase.execute({ sponsorId });
|
||||
const result = this.sponsorBillingPresenter.viewModel;
|
||||
if (!result) {
|
||||
throw new Error('Sponsor billing not found');
|
||||
}
|
||||
|
||||
const presenter = new GetSponsorBillingPresenter();
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
return result;
|
||||
}
|
||||
|
||||
async getAvailableLeagues(): Promise<AvailableLeaguesPresenter> {
|
||||
@@ -426,7 +401,7 @@ export class SponsorService {
|
||||
website: 'https://acme-racing.com',
|
||||
description:
|
||||
'Premium sim racing equipment and accessories for competitive drivers.',
|
||||
logoUrl: null,
|
||||
logoUrl: '',
|
||||
industry: 'Racing Equipment',
|
||||
address: {
|
||||
street: '123 Racing Boulevard',
|
||||
@@ -479,4 +454,4 @@ export class SponsorService {
|
||||
presenter.present({ success: true });
|
||||
return presenter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,19 @@ import { IsString, IsEnum, IsOptional, IsNumber } from 'class-validator';
|
||||
export class ActivityItemDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty({ enum: ['race', 'league', 'team', 'driver', 'platform'] })
|
||||
@IsEnum(['race', 'league', 'team', 'driver', 'platform'])
|
||||
type: 'race' | 'league' | 'team' | 'driver' | 'platform';
|
||||
type: 'race' | 'league' | 'team' | 'driver' | 'platform' = 'race';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
message: string;
|
||||
message: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
time: string;
|
||||
time: string = '';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsEnum, IsNumber, IsBoolean, IsOptional, IsDateString } from 'class-validator';
|
||||
import { IsString, IsEnum, IsNumber, IsOptional, IsDateString } from 'class-validator';
|
||||
|
||||
export class AvailableLeagueDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
name: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
game: string;
|
||||
game: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
drivers: number;
|
||||
drivers: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
avgViewsPerRace: number;
|
||||
avgViewsPerRace: number = 0;
|
||||
|
||||
@ApiProperty({ type: Object })
|
||||
mainSponsorSlot: {
|
||||
available: boolean;
|
||||
price: number;
|
||||
};
|
||||
} = { available: false, price: 0 };
|
||||
|
||||
@ApiProperty({ type: Object })
|
||||
secondarySlots: {
|
||||
available: number;
|
||||
total: number;
|
||||
price: number;
|
||||
};
|
||||
} = { available: 0, total: 0, price: 0 };
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
rating: number;
|
||||
rating: number = 0;
|
||||
|
||||
@ApiProperty({ enum: ['premium', 'standard', 'starter'] })
|
||||
@IsEnum(['premium', 'standard', 'starter'])
|
||||
tier: 'premium' | 'standard' | 'starter';
|
||||
tier: 'premium' | 'standard' | 'starter' = 'standard';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@@ -50,9 +50,9 @@ export class AvailableLeagueDTO {
|
||||
|
||||
@ApiProperty({ enum: ['active', 'upcoming', 'completed'] })
|
||||
@IsEnum(['active', 'upcoming', 'completed'])
|
||||
seasonStatus: 'active' | 'upcoming' | 'completed';
|
||||
seasonStatus: 'active' | 'upcoming' | 'completed' = 'active';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
description: string;
|
||||
description: string = '';
|
||||
}
|
||||
@@ -4,25 +4,25 @@ import { IsNumber, IsDateString } from 'class-validator';
|
||||
export class BillingStatsDTO {
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
totalSpent: number;
|
||||
totalSpent: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
pendingAmount: number;
|
||||
pendingAmount: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsDateString()
|
||||
nextPaymentDate: string;
|
||||
nextPaymentDate: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
nextPaymentAmount: number;
|
||||
nextPaymentAmount: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
activeSponsorships: number;
|
||||
activeSponsorships: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
averageMonthlySpend: number;
|
||||
averageMonthlySpend: number = 0;
|
||||
}
|
||||
@@ -3,5 +3,5 @@ import { SponsorDTO } from './SponsorDTO';
|
||||
|
||||
export class CreateSponsorOutputDTO {
|
||||
@ApiProperty({ type: SponsorDTO })
|
||||
sponsor: SponsorDTO;
|
||||
sponsor: SponsorDTO = new SponsorDTO();
|
||||
}
|
||||
@@ -4,29 +4,29 @@ import { IsString, IsNumber } from 'class-validator';
|
||||
export class DriverDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
name: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
country: string;
|
||||
country: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
position: number;
|
||||
position: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
races: number;
|
||||
races: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
impressions: number;
|
||||
impressions: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
team: string;
|
||||
team: string = '';
|
||||
}
|
||||
@@ -3,11 +3,11 @@ import { SponsorshipPricingItemDTO } from './SponsorshipPricingItemDTO';
|
||||
|
||||
export class GetEntitySponsorshipPricingResultDTO {
|
||||
@ApiProperty()
|
||||
entityType: string;
|
||||
entityType: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
entityId: string;
|
||||
entityId: string = '';
|
||||
|
||||
@ApiProperty({ type: [SponsorshipPricingItemDTO] })
|
||||
pricing: SponsorshipPricingItemDTO[];
|
||||
pricing: SponsorshipPricingItemDTO[] = [];
|
||||
}
|
||||
@@ -3,14 +3,14 @@ import { SponsorshipRequestDTO } from './SponsorshipRequestDTO';
|
||||
|
||||
export class GetPendingSponsorshipRequestsOutputDTO {
|
||||
@ApiProperty()
|
||||
entityType: string;
|
||||
entityType: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
entityId: string;
|
||||
entityId: string = '';
|
||||
|
||||
@ApiProperty({ type: [SponsorshipRequestDTO] })
|
||||
requests: SponsorshipRequestDTO[];
|
||||
requests: SponsorshipRequestDTO[] = [];
|
||||
|
||||
@ApiProperty()
|
||||
totalCount: number;
|
||||
totalCount: number = 0;
|
||||
}
|
||||
@@ -4,5 +4,5 @@ import { IsString } from 'class-validator';
|
||||
export class GetSponsorDashboardQueryParamsDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
sponsorId: string;
|
||||
sponsorId: string = '';
|
||||
}
|
||||
@@ -3,5 +3,5 @@ import { SponsorDTO } from './SponsorDTO';
|
||||
|
||||
export class GetSponsorOutputDTO {
|
||||
@ApiProperty({ type: SponsorDTO })
|
||||
sponsor: SponsorDTO;
|
||||
sponsor: SponsorDTO = new SponsorDTO();
|
||||
}
|
||||
@@ -4,5 +4,5 @@ import { IsString } from 'class-validator';
|
||||
export class GetSponsorSponsorshipsQueryParamsDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
sponsorId: string;
|
||||
sponsorId: string = '';
|
||||
}
|
||||
@@ -3,5 +3,5 @@ import { SponsorDTO } from './SponsorDTO';
|
||||
|
||||
export class GetSponsorsOutputDTO {
|
||||
@ApiProperty({ type: [SponsorDTO] })
|
||||
sponsors: SponsorDTO[];
|
||||
sponsors: SponsorDTO[] = [];
|
||||
}
|
||||
@@ -4,45 +4,45 @@ import { IsString, IsEnum, IsNumber, IsDateString } from 'class-validator';
|
||||
export class InvoiceDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
invoiceNumber: string;
|
||||
invoiceNumber: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsDateString()
|
||||
date: string;
|
||||
date: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsDateString()
|
||||
dueDate: string;
|
||||
dueDate: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
amount: number;
|
||||
amount: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
vatAmount: number;
|
||||
vatAmount: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
totalAmount: number;
|
||||
totalAmount: number = 0;
|
||||
|
||||
@ApiProperty({ enum: ['paid', 'pending', 'overdue', 'failed'] })
|
||||
@IsEnum(['paid', 'pending', 'overdue', 'failed'])
|
||||
status: 'paid' | 'pending' | 'overdue' | 'failed';
|
||||
status: 'paid' | 'pending' | 'overdue' | 'failed' = 'pending';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
description: string;
|
||||
description: string = '';
|
||||
|
||||
@ApiProperty({ enum: ['league', 'team', 'driver', 'race', 'platform'] })
|
||||
@IsEnum(['league', 'team', 'driver', 'race', 'platform'])
|
||||
sponsorshipType: 'league' | 'team' | 'driver' | 'race' | 'platform';
|
||||
sponsorshipType: 'league' | 'team' | 'driver' | 'race' | 'platform' = 'league';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
pdfUrl: string;
|
||||
pdfUrl: string = '';
|
||||
}
|
||||
@@ -1,68 +1,68 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsEnum, IsNumber, IsDateString, IsOptional } from 'class-validator';
|
||||
import { IsString, IsEnum, IsNumber, IsOptional } from 'class-validator';
|
||||
|
||||
export class LeagueDetailDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
name: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
game: string;
|
||||
game: string = '';
|
||||
|
||||
@ApiProperty({ enum: ['premium', 'standard', 'starter'] })
|
||||
@IsEnum(['premium', 'standard', 'starter'])
|
||||
tier: 'premium' | 'standard' | 'starter';
|
||||
tier: 'premium' | 'standard' | 'starter' = 'standard';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
season: string;
|
||||
season: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
description: string;
|
||||
description: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
drivers: number;
|
||||
drivers: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
races: number;
|
||||
races: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
completedRaces: number;
|
||||
completedRaces: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
totalImpressions: number;
|
||||
totalImpressions: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
avgViewsPerRace: number;
|
||||
avgViewsPerRace: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
engagement: number;
|
||||
engagement: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
rating: number;
|
||||
rating: number = 0;
|
||||
|
||||
@ApiProperty({ enum: ['active', 'upcoming', 'completed'] })
|
||||
@IsEnum(['active', 'upcoming', 'completed'])
|
||||
seasonStatus: 'active' | 'upcoming' | 'completed';
|
||||
seasonStatus: 'active' | 'upcoming' | 'completed' = 'active';
|
||||
|
||||
@ApiProperty({ type: Object })
|
||||
seasonDates: {
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
} = { start: '', end: '' };
|
||||
|
||||
@ApiProperty({ type: Object, required: false })
|
||||
@IsOptional()
|
||||
@@ -84,5 +84,8 @@ export class LeagueDetailDTO {
|
||||
price: number;
|
||||
benefits: string[];
|
||||
};
|
||||
} = {
|
||||
main: { available: false, price: 0, benefits: [] },
|
||||
secondary: { available: 0, total: 0, price: 0, benefits: [] }
|
||||
};
|
||||
}
|
||||
@@ -4,25 +4,25 @@ import { IsBoolean } from 'class-validator';
|
||||
export class NotificationSettingsDTO {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
emailNewSponsorships: boolean;
|
||||
emailNewSponsorships: boolean = false;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
emailWeeklyReport: boolean;
|
||||
emailWeeklyReport: boolean = false;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
emailRaceAlerts: boolean;
|
||||
emailRaceAlerts: boolean = false;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
emailPaymentAlerts: boolean;
|
||||
emailPaymentAlerts: boolean = false;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
emailNewOpportunities: boolean;
|
||||
emailNewOpportunities: boolean = false;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
emailContractExpiry: boolean;
|
||||
emailContractExpiry: boolean = false;
|
||||
}
|
||||
@@ -4,15 +4,15 @@ import { IsString, IsEnum, IsBoolean, IsOptional, IsNumber } from 'class-validat
|
||||
export class PaymentMethodDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty({ enum: ['card', 'bank', 'sepa'] })
|
||||
@IsEnum(['card', 'bank', 'sepa'])
|
||||
type: 'card' | 'bank' | 'sepa';
|
||||
type: 'card' | 'bank' | 'sepa' = 'card';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
last4: string;
|
||||
last4: string = '';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@@ -21,7 +21,7 @@ export class PaymentMethodDTO {
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
isDefault: boolean;
|
||||
isDefault: boolean = false;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
|
||||
@@ -4,17 +4,17 @@ import { IsBoolean } from 'class-validator';
|
||||
export class PrivacySettingsDTO {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
publicProfile: boolean;
|
||||
publicProfile: boolean = false;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
showStats: boolean;
|
||||
showStats: boolean = false;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
showActiveSponsorships: boolean;
|
||||
showActiveSponsorships: boolean = false;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
allowDirectContact: boolean;
|
||||
allowDirectContact: boolean = false;
|
||||
}
|
||||
@@ -4,21 +4,21 @@ import { IsString, IsEnum, IsNumber, IsDateString } from 'class-validator';
|
||||
export class RaceDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
name: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsDateString()
|
||||
date: string;
|
||||
date: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
views: number;
|
||||
views: number = 0;
|
||||
|
||||
@ApiProperty({ enum: ['upcoming', 'completed'] })
|
||||
@IsEnum(['upcoming', 'completed'])
|
||||
status: 'upcoming' | 'completed';
|
||||
status: 'upcoming' | 'completed' = 'upcoming';
|
||||
}
|
||||
@@ -5,7 +5,7 @@ export class RejectSponsorshipRequestInputDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
respondedBy: string;
|
||||
respondedBy: string = '';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
|
||||
@@ -4,21 +4,21 @@ import { IsString, IsEnum, IsNumber, IsDateString } from 'class-validator';
|
||||
export class RenewalAlertDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
name: string = '';
|
||||
|
||||
@ApiProperty({ enum: ['league', 'team', 'driver', 'race', 'platform'] })
|
||||
@IsEnum(['league', 'team', 'driver', 'race', 'platform'])
|
||||
type: 'league' | 'team' | 'driver' | 'race' | 'platform';
|
||||
type: 'league' | 'team' | 'driver' | 'race' | 'platform' = 'league';
|
||||
|
||||
@ApiProperty()
|
||||
@IsDateString()
|
||||
renewDate: string;
|
||||
renewDate: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
price: number;
|
||||
price: number = 0;
|
||||
}
|
||||
@@ -2,10 +2,10 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class SponsorDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
name: string = '';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
contactEmail?: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsArray, IsObject } from 'class-validator';
|
||||
import { IsString } from 'class-validator';
|
||||
import { SponsorDashboardMetricsDTO } from './SponsorDashboardMetricsDTO';
|
||||
import { SponsoredLeagueDTO } from './SponsoredLeagueDTO';
|
||||
import { SponsorDashboardInvestmentDTO } from './SponsorDashboardInvestmentDTO';
|
||||
@@ -10,20 +10,20 @@ import { RenewalAlertDTO } from './RenewalAlertDTO';
|
||||
export class SponsorDashboardDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
sponsorId: string;
|
||||
sponsorId: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
sponsorName: string;
|
||||
sponsorName: string = '';
|
||||
|
||||
@ApiProperty({ type: SponsorDashboardMetricsDTO })
|
||||
metrics: SponsorDashboardMetricsDTO;
|
||||
metrics: SponsorDashboardMetricsDTO = new SponsorDashboardMetricsDTO();
|
||||
|
||||
@ApiProperty({ type: [SponsoredLeagueDTO] })
|
||||
sponsoredLeagues: SponsoredLeagueDTO[];
|
||||
sponsoredLeagues: SponsoredLeagueDTO[] = [];
|
||||
|
||||
@ApiProperty({ type: SponsorDashboardInvestmentDTO })
|
||||
investment: SponsorDashboardInvestmentDTO;
|
||||
investment: SponsorDashboardInvestmentDTO = new SponsorDashboardInvestmentDTO();
|
||||
|
||||
@ApiProperty({ type: Object })
|
||||
sponsorships: {
|
||||
@@ -32,11 +32,17 @@ export class SponsorDashboardDTO {
|
||||
drivers: SponsorshipDTO[];
|
||||
races: SponsorshipDTO[];
|
||||
platform: SponsorshipDTO[];
|
||||
} = {
|
||||
leagues: [],
|
||||
teams: [],
|
||||
drivers: [],
|
||||
races: [],
|
||||
platform: []
|
||||
};
|
||||
|
||||
@ApiProperty({ type: [ActivityItemDTO] })
|
||||
recentActivity: ActivityItemDTO[];
|
||||
recentActivity: ActivityItemDTO[] = [];
|
||||
|
||||
@ApiProperty({ type: [RenewalAlertDTO] })
|
||||
upcomingRenewals: RenewalAlertDTO[];
|
||||
upcomingRenewals: RenewalAlertDTO[] = [];
|
||||
}
|
||||
@@ -4,13 +4,13 @@ import { IsNumber } from 'class-validator';
|
||||
export class SponsorDashboardInvestmentDTO {
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
activeSponsorships: number;
|
||||
activeSponsorships: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
totalInvestment: number;
|
||||
totalInvestment: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
costPerThousandViews: number;
|
||||
costPerThousandViews: number = 0;
|
||||
}
|
||||
@@ -4,33 +4,33 @@ import { IsNumber } from 'class-validator';
|
||||
export class SponsorDashboardMetricsDTO {
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
impressions: number;
|
||||
impressions: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
impressionsChange: number;
|
||||
impressionsChange: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
uniqueViewers: number;
|
||||
uniqueViewers: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
viewersChange: number;
|
||||
viewersChange: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
races: number;
|
||||
races: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
drivers: number;
|
||||
drivers: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
exposure: number;
|
||||
exposure: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
exposureChange: number;
|
||||
exposureChange: number = 0;
|
||||
}
|
||||
@@ -1,30 +1,30 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsOptional, IsObject } from 'class-validator';
|
||||
import { IsString, IsOptional } from 'class-validator';
|
||||
|
||||
export class SponsorProfileDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
companyName: string;
|
||||
companyName: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
contactName: string;
|
||||
contactName: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
contactEmail: string;
|
||||
contactEmail: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
contactPhone: string;
|
||||
contactPhone: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
website: string;
|
||||
website: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
description: string;
|
||||
description: string = '';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@@ -33,7 +33,7 @@ export class SponsorProfileDTO {
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
industry: string;
|
||||
industry: string = '';
|
||||
|
||||
@ApiProperty({ type: Object })
|
||||
address: {
|
||||
@@ -41,16 +41,16 @@ export class SponsorProfileDTO {
|
||||
city: string;
|
||||
country: string;
|
||||
postalCode: string;
|
||||
};
|
||||
} = { street: '', city: '', country: '', postalCode: '' };
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
taxId: string;
|
||||
taxId: string = '';
|
||||
|
||||
@ApiProperty({ type: Object })
|
||||
socialLinks: {
|
||||
twitter: string;
|
||||
linkedin: string;
|
||||
instagram: string;
|
||||
};
|
||||
} = { twitter: '', linkedin: '', instagram: '' };
|
||||
}
|
||||
@@ -5,14 +5,14 @@ import { SponsorshipDetailDTO } from './SponsorshipDetailDTO';
|
||||
export class SponsorSponsorshipsDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
sponsorId: string;
|
||||
sponsorId: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
sponsorName: string;
|
||||
sponsorName: string = '';
|
||||
|
||||
@ApiProperty({ type: [SponsorshipDetailDTO] })
|
||||
sponsorships: SponsorshipDetailDTO[];
|
||||
sponsorships: SponsorshipDetailDTO[] = [];
|
||||
|
||||
@ApiProperty()
|
||||
summary: {
|
||||
@@ -21,5 +21,11 @@ export class SponsorSponsorshipsDTO {
|
||||
totalInvestment: number;
|
||||
totalPlatformFees: number;
|
||||
currency: string;
|
||||
} = {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
totalPlatformFees: 0,
|
||||
currency: ''
|
||||
};
|
||||
}
|
||||
@@ -4,29 +4,29 @@ import { IsString, IsEnum, IsNumber } from 'class-validator';
|
||||
export class SponsoredLeagueDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
name: string = '';
|
||||
|
||||
@ApiProperty({ enum: ['main', 'secondary'] })
|
||||
@IsEnum(['main', 'secondary'])
|
||||
tier: 'main' | 'secondary';
|
||||
tier: 'main' | 'secondary' = 'main';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
drivers: number;
|
||||
drivers: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
races: number;
|
||||
races: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
impressions: number;
|
||||
impressions: number = 0;
|
||||
|
||||
@ApiProperty({ enum: ['active', 'upcoming', 'completed'] })
|
||||
@IsEnum(['active', 'upcoming', 'completed'])
|
||||
status: 'active' | 'upcoming' | 'completed';
|
||||
status: 'active' | 'upcoming' | 'completed' = 'active';
|
||||
}
|
||||
@@ -4,19 +4,19 @@ import { IsString, IsEnum, IsNumber, IsOptional, IsDateString } from 'class-vali
|
||||
export class SponsorshipDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty({ enum: ['leagues', 'teams', 'drivers', 'races', 'platform'] })
|
||||
@IsEnum(['leagues', 'teams', 'drivers', 'races', 'platform'])
|
||||
type: 'leagues' | 'teams' | 'drivers' | 'races' | 'platform';
|
||||
type: 'leagues' | 'teams' | 'drivers' | 'races' | 'platform' = 'leagues';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
entityId: string;
|
||||
entityId: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
entityName: string;
|
||||
entityName: string = '';
|
||||
|
||||
@ApiProperty({ enum: ['main', 'secondary'], required: false })
|
||||
@IsOptional()
|
||||
@@ -25,7 +25,7 @@ export class SponsorshipDTO {
|
||||
|
||||
@ApiProperty({ enum: ['active', 'pending_approval', 'approved', 'rejected', 'expired'] })
|
||||
@IsEnum(['active', 'pending_approval', 'approved', 'rejected', 'expired'])
|
||||
status: 'active' | 'pending_approval' | 'approved' | 'rejected' | 'expired';
|
||||
status: 'active' | 'pending_approval' | 'approved' | 'rejected' | 'expired' = 'pending_approval';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@@ -44,19 +44,19 @@ export class SponsorshipDTO {
|
||||
|
||||
@ApiProperty()
|
||||
@IsDateString()
|
||||
startDate: string;
|
||||
startDate: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsDateString()
|
||||
endDate: string;
|
||||
endDate: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
price: number;
|
||||
price: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
impressions: number;
|
||||
impressions: number = 0;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
|
||||
@@ -4,23 +4,23 @@ import { IsString, IsEnum, IsOptional, IsDate } from 'class-validator';
|
||||
export class SponsorshipDetailDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
leagueId: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueName: string;
|
||||
leagueName: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
seasonId: string;
|
||||
seasonId: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
seasonName: string;
|
||||
seasonName: string = '';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@@ -34,29 +34,29 @@ export class SponsorshipDetailDTO {
|
||||
|
||||
@ApiProperty({ enum: ['main', 'secondary'] })
|
||||
@IsEnum(['main', 'secondary'])
|
||||
tier: 'main' | 'secondary';
|
||||
tier: 'main' | 'secondary' = 'main';
|
||||
|
||||
@ApiProperty({ enum: ['pending', 'active', 'expired', 'cancelled'] })
|
||||
@IsEnum(['pending', 'active', 'expired', 'cancelled'])
|
||||
status: 'pending' | 'active' | 'expired' | 'cancelled';
|
||||
status: 'pending' | 'active' | 'expired' | 'cancelled' = 'pending';
|
||||
|
||||
@ApiProperty()
|
||||
pricing: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
};
|
||||
} = { amount: 0, currency: '' };
|
||||
|
||||
@ApiProperty()
|
||||
platformFee: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
};
|
||||
} = { amount: 0, currency: '' };
|
||||
|
||||
@ApiProperty()
|
||||
netAmount: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
};
|
||||
} = { amount: 0, currency: '' };
|
||||
|
||||
@ApiProperty()
|
||||
metrics: {
|
||||
@@ -64,10 +64,10 @@ export class SponsorshipDetailDTO {
|
||||
races: number;
|
||||
completedRaces: number;
|
||||
impressions: number;
|
||||
};
|
||||
} = { drivers: 0, races: 0, completedRaces: 0, impressions: 0 };
|
||||
|
||||
@ApiProperty()
|
||||
createdAt: Date;
|
||||
createdAt: Date = new Date();
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
|
||||
@@ -4,17 +4,17 @@ import { IsString, IsNumber } from 'class-validator';
|
||||
export class SponsorshipPricingItemDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
level: string;
|
||||
level: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
price: number;
|
||||
price: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
currency: string;
|
||||
currency: string = '';
|
||||
}
|
||||
@@ -2,38 +2,38 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class SponsorshipRequestDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
sponsorId: string;
|
||||
sponsorId: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
sponsorName: string;
|
||||
sponsorName: string = '';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
sponsorLogo?: string;
|
||||
|
||||
@ApiProperty()
|
||||
tier: string;
|
||||
tier: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
offeredAmount: number;
|
||||
offeredAmount: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
currency: string;
|
||||
currency: string = '';
|
||||
|
||||
@ApiProperty()
|
||||
formattedAmount: string;
|
||||
formattedAmount: string = '';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
message?: string;
|
||||
|
||||
@ApiProperty()
|
||||
createdAt: Date;
|
||||
createdAt: Date = new Date();
|
||||
|
||||
@ApiProperty()
|
||||
platformFee: number;
|
||||
platformFee: number = 0;
|
||||
|
||||
@ApiProperty()
|
||||
netAmount: number;
|
||||
netAmount: number = 0;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AcceptSponsorshipOutputPort } from '@core/racing/application/ports/output/AcceptSponsorshipOutputPort';
|
||||
import type { AcceptSponsorshipResult } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
|
||||
|
||||
export interface AcceptSponsorshipRequestResultViewModel {
|
||||
requestId: string;
|
||||
@@ -16,7 +16,7 @@ export class AcceptSponsorshipRequestPresenter {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: AcceptSponsorshipOutputPort | null) {
|
||||
present(output: AcceptSponsorshipResult | null) {
|
||||
if (!output) {
|
||||
this.result = null;
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { CreateSponsorPresenter } from './CreateSponsorPresenter';
|
||||
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||
|
||||
describe('CreateSponsorPresenter', () => {
|
||||
let presenter: CreateSponsorPresenter;
|
||||
@@ -10,9 +11,23 @@ describe('CreateSponsorPresenter', () => {
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the result to null', () => {
|
||||
const mockPort = { sponsor: { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com', createdAt: new Date() } };
|
||||
presenter.present(mockPort);
|
||||
expect(presenter.viewModel).toEqual({ sponsor: mockPort.sponsor });
|
||||
const mockSponsor = Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date()
|
||||
});
|
||||
presenter.present(mockSponsor);
|
||||
|
||||
const expectedViewModel = {
|
||||
sponsor: {
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: mockSponsor.createdAt.toDate()
|
||||
}
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
|
||||
presenter.reset();
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
@@ -21,11 +36,24 @@ describe('CreateSponsorPresenter', () => {
|
||||
|
||||
describe('present', () => {
|
||||
it('should store the result', () => {
|
||||
const mockPort = { sponsor: { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com', createdAt: new Date() } };
|
||||
const mockSponsor = Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
presenter.present(mockPort);
|
||||
presenter.present(mockSponsor);
|
||||
|
||||
expect(presenter.viewModel).toEqual({ sponsor: mockPort.sponsor });
|
||||
const expectedViewModel = {
|
||||
sponsor: {
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: mockSponsor.createdAt.toDate()
|
||||
}
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,10 +63,23 @@ describe('CreateSponsorPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockPort = { sponsor: { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com', createdAt: new Date() } };
|
||||
presenter.present(mockPort);
|
||||
const mockSponsor = Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date()
|
||||
});
|
||||
presenter.present(mockSponsor);
|
||||
|
||||
expect(presenter.getViewModel()).toEqual({ sponsor: mockPort.sponsor });
|
||||
const expectedViewModel = {
|
||||
sponsor: {
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: mockSponsor.createdAt.toDate()
|
||||
}
|
||||
};
|
||||
expect(presenter.getViewModel()).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,10 +89,23 @@ describe('CreateSponsorPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockPort = { sponsor: { id: 'sponsor-1', name: 'Test Sponsor', contactEmail: 'test@example.com', createdAt: new Date() } };
|
||||
presenter.present(mockPort);
|
||||
const mockSponsor = Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date()
|
||||
});
|
||||
presenter.present(mockSponsor);
|
||||
|
||||
expect(presenter.viewModel).toEqual({ sponsor: mockPort.sponsor });
|
||||
const expectedViewModel = {
|
||||
sponsor: {
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: mockSponsor.createdAt.toDate()
|
||||
}
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CreateSponsorOutputPort } from '@core/racing/application/ports/output/CreateSponsorOutputPort';
|
||||
import type { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||
import type { CreateSponsorOutputDTO } from '../dtos/CreateSponsorOutputDTO';
|
||||
|
||||
export class CreateSponsorPresenter {
|
||||
@@ -8,16 +8,24 @@ export class CreateSponsorPresenter {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(port: CreateSponsorOutputPort) {
|
||||
present(sponsor: Sponsor) {
|
||||
const sponsorData: any = {
|
||||
id: sponsor.id.toString(),
|
||||
name: sponsor.name.toString(),
|
||||
contactEmail: sponsor.contactEmail.toString(),
|
||||
createdAt: sponsor.createdAt.toDate(),
|
||||
};
|
||||
|
||||
if (sponsor.logoUrl) {
|
||||
sponsorData.logoUrl = sponsor.logoUrl.toString();
|
||||
}
|
||||
|
||||
if (sponsor.websiteUrl) {
|
||||
sponsorData.websiteUrl = sponsor.websiteUrl.toString();
|
||||
}
|
||||
|
||||
this.result = {
|
||||
sponsor: {
|
||||
id: port.sponsor.id,
|
||||
name: port.sponsor.name,
|
||||
contactEmail: port.sponsor.contactEmail,
|
||||
logoUrl: port.sponsor.logoUrl,
|
||||
websiteUrl: port.sponsor.websiteUrl,
|
||||
createdAt: port.sponsor.createdAt,
|
||||
},
|
||||
sponsor: sponsorData,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { GetEntitySponsorshipPricingPresenter } from './GetEntitySponsorshipPricingPresenter';
|
||||
import type { GetEntitySponsorshipPricingResult } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
||||
|
||||
describe('GetEntitySponsorshipPricingPresenter', () => {
|
||||
let presenter: GetEntitySponsorshipPricingPresenter;
|
||||
@@ -10,9 +11,20 @@ describe('GetEntitySponsorshipPricingPresenter', () => {
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the result to null', () => {
|
||||
const mockResult = { entityType: 'season', entityId: 'season-1', pricing: [] };
|
||||
const mockResult: GetEntitySponsorshipPricingResult = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
acceptingApplications: true,
|
||||
tiers: []
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
|
||||
const expectedViewModel = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
|
||||
presenter.reset();
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
@@ -21,11 +33,21 @@ describe('GetEntitySponsorshipPricingPresenter', () => {
|
||||
|
||||
describe('present', () => {
|
||||
it('should store the result', () => {
|
||||
const mockResult = { entityType: 'season', entityId: 'season-1', pricing: [] };
|
||||
const mockResult: GetEntitySponsorshipPricingResult = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
acceptingApplications: true,
|
||||
tiers: []
|
||||
};
|
||||
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,10 +57,20 @@ describe('GetEntitySponsorshipPricingPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockResult = { entityType: 'season', entityId: 'season-1', pricing: [] };
|
||||
const mockResult: GetEntitySponsorshipPricingResult = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
acceptingApplications: true,
|
||||
tiers: []
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.getViewModel()).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
expect(presenter.getViewModel()).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,10 +80,20 @@ describe('GetEntitySponsorshipPricingPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockResult = { entityType: 'season', entityId: 'season-1', pricing: [] };
|
||||
const mockResult: GetEntitySponsorshipPricingResult = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
acceptingApplications: true,
|
||||
tiers: []
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GetSponsorshipPricingOutputPort } from '@core/racing/application/ports/output/GetSponsorshipPricingOutputPort';
|
||||
import type { GetEntitySponsorshipPricingResult } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
||||
import { GetEntitySponsorshipPricingResultDTO } from '../dtos/GetEntitySponsorshipPricingResultDTO';
|
||||
|
||||
export class GetEntitySponsorshipPricingPresenter {
|
||||
@@ -8,7 +8,7 @@ export class GetEntitySponsorshipPricingPresenter {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: GetSponsorshipPricingOutputPort | null) {
|
||||
present(output: GetEntitySponsorshipPricingResult | null) {
|
||||
if (!output) {
|
||||
this.result = {
|
||||
entityType: 'season',
|
||||
@@ -21,11 +21,11 @@ export class GetEntitySponsorshipPricingPresenter {
|
||||
this.result = {
|
||||
entityType: output.entityType,
|
||||
entityId: output.entityId,
|
||||
pricing: output.pricing.map(item => ({
|
||||
id: item.id,
|
||||
level: item.level,
|
||||
pricing: output.tiers.map(item => ({
|
||||
id: item.name,
|
||||
level: item.name,
|
||||
price: item.price,
|
||||
currency: item.currency,
|
||||
currency: 'USD',
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PendingSponsorshipRequestsOutputPort } from '@core/racing/application/ports/output/PendingSponsorshipRequestsOutputPort';
|
||||
import type { GetPendingSponsorshipRequestsResult } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
|
||||
import { GetPendingSponsorshipRequestsOutputDTO } from '../dtos/GetPendingSponsorshipRequestsOutputDTO';
|
||||
|
||||
export class GetPendingSponsorshipRequestsPresenter {
|
||||
@@ -8,7 +8,7 @@ export class GetPendingSponsorshipRequestsPresenter {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(outputPort: PendingSponsorshipRequestsOutputPort | null) {
|
||||
present(outputPort: GetPendingSponsorshipRequestsResult | null) {
|
||||
if (!outputPort) {
|
||||
this.result = null;
|
||||
return;
|
||||
@@ -17,7 +17,30 @@ export class GetPendingSponsorshipRequestsPresenter {
|
||||
this.result = {
|
||||
entityType: outputPort.entityType,
|
||||
entityId: outputPort.entityId,
|
||||
requests: outputPort.requests,
|
||||
requests: outputPort.requests.map(r => {
|
||||
const request: any = {
|
||||
id: r.request.id,
|
||||
sponsorId: r.request.sponsorId,
|
||||
sponsorName: r.sponsor?.name?.toString() || 'Unknown Sponsor',
|
||||
tier: r.request.tier,
|
||||
offeredAmount: r.financials.offeredAmount.amount,
|
||||
currency: r.financials.offeredAmount.currency,
|
||||
formattedAmount: `${r.financials.offeredAmount.amount} ${r.financials.offeredAmount.currency}`,
|
||||
createdAt: r.request.createdAt,
|
||||
platformFee: r.financials.platformFee.amount,
|
||||
netAmount: r.financials.netAmount.amount,
|
||||
};
|
||||
|
||||
if (r.sponsor?.logoUrl) {
|
||||
request.sponsorLogo = r.sponsor.logoUrl.toString();
|
||||
}
|
||||
|
||||
if (r.request.message) {
|
||||
request.message = r.request.message;
|
||||
}
|
||||
|
||||
return request;
|
||||
}),
|
||||
totalCount: outputPort.totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { GetSponsorDashboardPresenter } from './GetSponsorDashboardPresenter';
|
||||
import type { GetSponsorDashboardResult } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||
|
||||
describe('GetSponsorDashboardPresenter', () => {
|
||||
let presenter: GetSponsorDashboardPresenter;
|
||||
@@ -10,9 +12,58 @@ describe('GetSponsorDashboardPresenter', () => {
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the result to null', () => {
|
||||
const mockResult = { sponsorId: 'sponsor-1', metrics: {}, sponsoredLeagues: [] };
|
||||
const mockResult: GetSponsorDashboardResult = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
metrics: {
|
||||
impressions: 1000,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 700,
|
||||
viewersChange: 0,
|
||||
races: 5,
|
||||
drivers: 10,
|
||||
exposure: 50,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: Money.create(0, 'USD'),
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
|
||||
const expectedViewModel = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
metrics: {
|
||||
impressions: 1000,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 700,
|
||||
viewersChange: 0,
|
||||
races: 5,
|
||||
drivers: 10,
|
||||
exposure: 50,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
sponsorships: {
|
||||
leagues: [],
|
||||
teams: [],
|
||||
drivers: [],
|
||||
races: [],
|
||||
platform: [],
|
||||
},
|
||||
recentActivity: [],
|
||||
upcomingRenewals: [],
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
|
||||
presenter.reset();
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
@@ -21,11 +72,59 @@ describe('GetSponsorDashboardPresenter', () => {
|
||||
|
||||
describe('present', () => {
|
||||
it('should store the result', () => {
|
||||
const mockResult = { sponsorId: 'sponsor-1', metrics: {}, sponsoredLeagues: [] };
|
||||
const mockResult: GetSponsorDashboardResult = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
metrics: {
|
||||
impressions: 1000,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 700,
|
||||
viewersChange: 0,
|
||||
races: 5,
|
||||
drivers: 10,
|
||||
exposure: 50,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: Money.create(0, 'USD'),
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
};
|
||||
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
metrics: {
|
||||
impressions: 1000,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 700,
|
||||
viewersChange: 0,
|
||||
races: 5,
|
||||
drivers: 10,
|
||||
exposure: 50,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
sponsorships: {
|
||||
leagues: [],
|
||||
teams: [],
|
||||
drivers: [],
|
||||
races: [],
|
||||
platform: [],
|
||||
},
|
||||
recentActivity: [],
|
||||
upcomingRenewals: [],
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,10 +134,58 @@ describe('GetSponsorDashboardPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockResult = { sponsorId: 'sponsor-1', metrics: {}, sponsoredLeagues: [] };
|
||||
const mockResult: GetSponsorDashboardResult = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
metrics: {
|
||||
impressions: 1000,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 700,
|
||||
viewersChange: 0,
|
||||
races: 5,
|
||||
drivers: 10,
|
||||
exposure: 50,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: Money.create(0, 'USD'),
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.getViewModel()).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
metrics: {
|
||||
impressions: 1000,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 700,
|
||||
viewersChange: 0,
|
||||
races: 5,
|
||||
drivers: 10,
|
||||
exposure: 50,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
sponsorships: {
|
||||
leagues: [],
|
||||
teams: [],
|
||||
drivers: [],
|
||||
races: [],
|
||||
platform: [],
|
||||
},
|
||||
recentActivity: [],
|
||||
upcomingRenewals: [],
|
||||
};
|
||||
expect(presenter.getViewModel()).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,10 +195,58 @@ describe('GetSponsorDashboardPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockResult = { sponsorId: 'sponsor-1', metrics: {}, sponsoredLeagues: [] };
|
||||
const mockResult: GetSponsorDashboardResult = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
metrics: {
|
||||
impressions: 1000,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 700,
|
||||
viewersChange: 0,
|
||||
races: 5,
|
||||
drivers: 10,
|
||||
exposure: 50,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: Money.create(0, 'USD'),
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
metrics: {
|
||||
impressions: 1000,
|
||||
impressionsChange: 0,
|
||||
uniqueViewers: 700,
|
||||
viewersChange: 0,
|
||||
races: 5,
|
||||
drivers: 10,
|
||||
exposure: 50,
|
||||
exposureChange: 0,
|
||||
},
|
||||
sponsoredLeagues: [],
|
||||
investment: {
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
costPerThousandViews: 0,
|
||||
},
|
||||
sponsorships: {
|
||||
leagues: [],
|
||||
teams: [],
|
||||
drivers: [],
|
||||
races: [],
|
||||
platform: [],
|
||||
},
|
||||
recentActivity: [],
|
||||
upcomingRenewals: [],
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SponsorDashboardOutputPort } from '@core/racing/application/ports/output/SponsorDashboardOutputPort';
|
||||
import type { GetSponsorDashboardResult } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||
import { SponsorDashboardDTO } from '../dtos/SponsorDashboardDTO';
|
||||
|
||||
export class GetSponsorDashboardPresenter {
|
||||
@@ -8,8 +8,40 @@ export class GetSponsorDashboardPresenter {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(outputPort: SponsorDashboardOutputPort | null) {
|
||||
this.result = outputPort ?? null;
|
||||
present(outputPort: GetSponsorDashboardResult | null) {
|
||||
if (!outputPort) {
|
||||
this.result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.result = {
|
||||
sponsorId: outputPort.sponsorId,
|
||||
sponsorName: outputPort.sponsorName,
|
||||
metrics: outputPort.metrics,
|
||||
sponsoredLeagues: outputPort.sponsoredLeagues.map(league => ({
|
||||
id: league.leagueId,
|
||||
name: league.leagueName,
|
||||
tier: league.tier,
|
||||
drivers: league.metrics.drivers,
|
||||
races: league.metrics.races,
|
||||
impressions: league.metrics.impressions,
|
||||
status: league.status,
|
||||
})),
|
||||
investment: {
|
||||
activeSponsorships: outputPort.investment.activeSponsorships,
|
||||
totalInvestment: outputPort.investment.totalInvestment.amount,
|
||||
costPerThousandViews: outputPort.investment.costPerThousandViews,
|
||||
},
|
||||
sponsorships: {
|
||||
leagues: [],
|
||||
teams: [],
|
||||
drivers: [],
|
||||
races: [],
|
||||
platform: [],
|
||||
},
|
||||
recentActivity: [],
|
||||
upcomingRenewals: [],
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): SponsorDashboardDTO | null {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { GetSponsorSponsorshipsPresenter } from './GetSponsorSponsorshipsPresenter';
|
||||
import type { GetSponsorSponsorshipsResult } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
||||
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||
|
||||
describe('GetSponsorSponsorshipsPresenter', () => {
|
||||
let presenter: GetSponsorSponsorshipsPresenter;
|
||||
@@ -10,9 +13,38 @@ describe('GetSponsorSponsorshipsPresenter', () => {
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the result to null', () => {
|
||||
const mockResult = { sponsorId: 'sponsor-1', sponsorName: 'Test Sponsor', sponsorships: [] };
|
||||
const mockSponsor = Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
const mockResult: GetSponsorSponsorshipsResult = {
|
||||
sponsor: mockSponsor,
|
||||
sponsorships: [],
|
||||
summary: {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: Money.create(0, 'USD'),
|
||||
totalPlatformFees: Money.create(0, 'USD'),
|
||||
},
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
|
||||
const expectedViewModel = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
sponsorships: [],
|
||||
summary: {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
totalPlatformFees: 0,
|
||||
currency: 'USD',
|
||||
},
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
|
||||
presenter.reset();
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
@@ -21,11 +53,39 @@ describe('GetSponsorSponsorshipsPresenter', () => {
|
||||
|
||||
describe('present', () => {
|
||||
it('should store the result', () => {
|
||||
const mockResult = { sponsorId: 'sponsor-1', sponsorName: 'Test Sponsor', sponsorships: [] };
|
||||
const mockSponsor = Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
const mockResult: GetSponsorSponsorshipsResult = {
|
||||
sponsor: mockSponsor,
|
||||
sponsorships: [],
|
||||
summary: {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: Money.create(0, 'USD'),
|
||||
totalPlatformFees: Money.create(0, 'USD'),
|
||||
},
|
||||
};
|
||||
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
sponsorships: [],
|
||||
summary: {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
totalPlatformFees: 0,
|
||||
currency: 'USD',
|
||||
},
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,10 +95,38 @@ describe('GetSponsorSponsorshipsPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockResult = { sponsorId: 'sponsor-1', sponsorName: 'Test Sponsor', sponsorships: [] };
|
||||
const mockSponsor = Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
const mockResult: GetSponsorSponsorshipsResult = {
|
||||
sponsor: mockSponsor,
|
||||
sponsorships: [],
|
||||
summary: {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: Money.create(0, 'USD'),
|
||||
totalPlatformFees: Money.create(0, 'USD'),
|
||||
},
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.getViewModel()).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
sponsorships: [],
|
||||
summary: {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
totalPlatformFees: 0,
|
||||
currency: 'USD',
|
||||
},
|
||||
};
|
||||
expect(presenter.getViewModel()).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,10 +136,38 @@ describe('GetSponsorSponsorshipsPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockResult = { sponsorId: 'sponsor-1', sponsorName: 'Test Sponsor', sponsorships: [] };
|
||||
const mockSponsor = Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Test Sponsor',
|
||||
contactEmail: 'test@example.com',
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
const mockResult: GetSponsorSponsorshipsResult = {
|
||||
sponsor: mockSponsor,
|
||||
sponsorships: [],
|
||||
summary: {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: Money.create(0, 'USD'),
|
||||
totalPlatformFees: Money.create(0, 'USD'),
|
||||
},
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
sponsorId: 'sponsor-1',
|
||||
sponsorName: 'Test Sponsor',
|
||||
sponsorships: [],
|
||||
summary: {
|
||||
totalSponsorships: 0,
|
||||
activeSponsorships: 0,
|
||||
totalInvestment: 0,
|
||||
totalPlatformFees: 0,
|
||||
currency: 'USD',
|
||||
},
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SponsorSponsorshipsOutputPort } from '@core/racing/application/ports/output/SponsorSponsorshipsOutputPort';
|
||||
import type { GetSponsorSponsorshipsResult } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
||||
import { SponsorSponsorshipsDTO } from '../dtos/SponsorSponsorshipsDTO';
|
||||
|
||||
export class GetSponsorSponsorshipsPresenter {
|
||||
@@ -8,8 +8,58 @@ export class GetSponsorSponsorshipsPresenter {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(outputPort: SponsorSponsorshipsOutputPort | null) {
|
||||
this.result = outputPort ?? null;
|
||||
present(outputPort: GetSponsorSponsorshipsResult | null) {
|
||||
if (!outputPort) {
|
||||
this.result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.result = {
|
||||
sponsorId: outputPort.sponsor.id.toString(),
|
||||
sponsorName: outputPort.sponsor.name.toString(),
|
||||
sponsorships: outputPort.sponsorships.map(s => {
|
||||
// Map status to DTO expected values
|
||||
let status: 'pending' | 'active' | 'expired' | 'cancelled';
|
||||
if (s.sponsorship.status === 'ended') {
|
||||
status = 'expired';
|
||||
} else if (s.sponsorship.status === 'pending' || s.sponsorship.status === 'active' || s.sponsorship.status === 'cancelled') {
|
||||
status = s.sponsorship.status;
|
||||
} else {
|
||||
status = 'pending';
|
||||
}
|
||||
|
||||
return {
|
||||
id: s.sponsorship.id.toString(),
|
||||
leagueId: s.league.id.toString(),
|
||||
leagueName: s.league.name.toString(),
|
||||
seasonId: s.season.id.toString(),
|
||||
seasonName: s.season.name.toString(),
|
||||
tier: s.sponsorship.tier,
|
||||
status,
|
||||
pricing: {
|
||||
amount: s.financials.pricing.amount,
|
||||
currency: s.financials.pricing.currency,
|
||||
},
|
||||
platformFee: {
|
||||
amount: s.financials.platformFee.amount,
|
||||
currency: s.financials.platformFee.currency,
|
||||
},
|
||||
netAmount: {
|
||||
amount: s.financials.netAmount.amount,
|
||||
currency: s.financials.netAmount.currency,
|
||||
},
|
||||
metrics: s.metrics,
|
||||
createdAt: s.sponsorship.createdAt,
|
||||
};
|
||||
}),
|
||||
summary: {
|
||||
totalSponsorships: outputPort.summary.totalSponsorships,
|
||||
activeSponsorships: outputPort.summary.activeSponsorships,
|
||||
totalInvestment: outputPort.summary.totalInvestment.amount,
|
||||
totalPlatformFees: outputPort.summary.totalPlatformFees.amount,
|
||||
currency: outputPort.summary.totalInvestment.currency,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): SponsorSponsorshipsDTO | null {
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type {
|
||||
GetSponsorsResult,
|
||||
GetSponsorsErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import { GetSponsorsPresenter } from './GetSponsorsPresenter';
|
||||
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||
|
||||
describe('GetSponsorsPresenter', () => {
|
||||
let presenter: GetSponsorsPresenter;
|
||||
@@ -16,8 +11,8 @@ describe('GetSponsorsPresenter', () => {
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the model to null and cause responseModel to throw', () => {
|
||||
const result = Result.ok<GetSponsorsResult, never>({ sponsors: [] });
|
||||
presenter.present(result);
|
||||
const sponsors: Sponsor[] = [];
|
||||
presenter.present(sponsors);
|
||||
expect(presenter.responseModel).toEqual({ sponsors: [] });
|
||||
|
||||
presenter.reset();
|
||||
@@ -26,29 +21,24 @@ describe('GetSponsorsPresenter', () => {
|
||||
});
|
||||
|
||||
describe('present', () => {
|
||||
it('should map Result.ok sponsors to DTO responseModel', () => {
|
||||
const result = Result.ok<GetSponsorsResult, never>({
|
||||
sponsors: [
|
||||
{
|
||||
id: 'sponsor-1',
|
||||
name: 'Sponsor One',
|
||||
contactEmail: 's1@example.com',
|
||||
logoUrl: 'logo1.png',
|
||||
websiteUrl: 'https://one.example.com',
|
||||
createdAt: new Date('2024-01-01T00:00:00Z'),
|
||||
},
|
||||
{
|
||||
id: 'sponsor-2',
|
||||
name: 'Sponsor Two',
|
||||
contactEmail: 's2@example.com',
|
||||
logoUrl: undefined,
|
||||
websiteUrl: undefined,
|
||||
createdAt: undefined,
|
||||
},
|
||||
],
|
||||
});
|
||||
it('should map sponsors to DTO responseModel', () => {
|
||||
const sponsors: Sponsor[] = [
|
||||
Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Sponsor One',
|
||||
contactEmail: 's1@example.com',
|
||||
logoUrl: 'logo1.png',
|
||||
websiteUrl: 'https://one.example.com',
|
||||
createdAt: new Date('2024-01-01T00:00:00Z'),
|
||||
}),
|
||||
Sponsor.create({
|
||||
id: 'sponsor-2',
|
||||
name: 'Sponsor Two',
|
||||
contactEmail: 's2@example.com',
|
||||
}),
|
||||
];
|
||||
|
||||
presenter.present(result);
|
||||
presenter.present(sponsors);
|
||||
|
||||
expect(presenter.responseModel).toEqual({
|
||||
sponsors: [
|
||||
@@ -64,9 +54,7 @@ describe('GetSponsorsPresenter', () => {
|
||||
id: 'sponsor-2',
|
||||
name: 'Sponsor Two',
|
||||
contactEmail: 's2@example.com',
|
||||
logoUrl: undefined,
|
||||
websiteUrl: undefined,
|
||||
createdAt: undefined,
|
||||
createdAt: expect.any(Date),
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -79,8 +67,8 @@ describe('GetSponsorsPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the model when presented', () => {
|
||||
const result = Result.ok<GetSponsorsResult, never>({ sponsors: [] });
|
||||
presenter.present(result);
|
||||
const sponsors: Sponsor[] = [];
|
||||
presenter.present(sponsors);
|
||||
|
||||
expect(presenter.getResponseModel()).toEqual({ sponsors: [] });
|
||||
});
|
||||
@@ -91,16 +79,26 @@ describe('GetSponsorsPresenter', () => {
|
||||
expect(() => presenter.responseModel).toThrow('Presenter not presented');
|
||||
});
|
||||
|
||||
it('should fallback to empty sponsors list on error', () => {
|
||||
const error = {
|
||||
code: 'REPOSITORY_ERROR' as GetSponsorsErrorCode,
|
||||
details: { message: 'DB error' },
|
||||
} satisfies ApplicationErrorCode<GetSponsorsErrorCode, { message: string }>;
|
||||
const result = Result.err<GetSponsorsResult, typeof error>(error);
|
||||
it('should return the model when presented', () => {
|
||||
const sponsors: Sponsor[] = [
|
||||
Sponsor.create({
|
||||
id: 'sponsor-1',
|
||||
name: 'Sponsor One',
|
||||
contactEmail: 's1@example.com',
|
||||
}),
|
||||
];
|
||||
presenter.present(sponsors);
|
||||
|
||||
presenter.present(result);
|
||||
|
||||
expect(presenter.responseModel).toEqual({ sponsors: [] });
|
||||
expect(presenter.responseModel).toEqual({
|
||||
sponsors: [
|
||||
{
|
||||
id: 'sponsor-1',
|
||||
name: 'Sponsor One',
|
||||
contactEmail: 's1@example.com',
|
||||
createdAt: expect.any(Date),
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,4 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetSponsorsResult,
|
||||
GetSponsorsErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
||||
import type { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { GetSponsorsOutputDTO } from '../dtos/GetSponsorsOutputDTO';
|
||||
import type { SponsorDTO } from '../dtos/SponsorDTO';
|
||||
|
||||
@@ -14,29 +9,26 @@ export class GetSponsorsPresenter {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(
|
||||
result: Result<
|
||||
GetSponsorsResult,
|
||||
ApplicationErrorCode<GetSponsorsErrorCode, { message: string }>
|
||||
>,
|
||||
): void {
|
||||
if (result.isErr()) {
|
||||
// For sponsor listing, fall back to empty list on error
|
||||
this.model = { sponsors: [] };
|
||||
return;
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
present(sponsors: Sponsor[]): void {
|
||||
this.model = {
|
||||
sponsors: output.sponsors.map<SponsorDTO>((sponsor) => ({
|
||||
id: sponsor.id,
|
||||
name: sponsor.name,
|
||||
contactEmail: sponsor.contactEmail,
|
||||
logoUrl: sponsor.logoUrl,
|
||||
websiteUrl: sponsor.websiteUrl,
|
||||
createdAt: sponsor.createdAt,
|
||||
})),
|
||||
sponsors: sponsors.map<SponsorDTO>((sponsor) => {
|
||||
const sponsorData: any = {
|
||||
id: sponsor.id.toString(),
|
||||
name: sponsor.name.toString(),
|
||||
contactEmail: sponsor.contactEmail.toString(),
|
||||
createdAt: sponsor.createdAt.toDate(),
|
||||
};
|
||||
|
||||
if (sponsor.logoUrl) {
|
||||
sponsorData.logoUrl = sponsor.logoUrl.toString();
|
||||
}
|
||||
|
||||
if (sponsor.websiteUrl) {
|
||||
sponsorData.websiteUrl = sponsor.websiteUrl.toString();
|
||||
}
|
||||
|
||||
return sponsorData;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { GetSponsorshipPricingPresenter } from './GetSponsorshipPricingPresenter';
|
||||
import type { GetSponsorshipPricingResult } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
||||
|
||||
describe('GetSponsorshipPricingPresenter', () => {
|
||||
let presenter: GetSponsorshipPricingPresenter;
|
||||
@@ -10,9 +11,19 @@ describe('GetSponsorshipPricingPresenter', () => {
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the result to null', () => {
|
||||
const mockResult = { tiers: [] };
|
||||
const mockResult: GetSponsorshipPricingResult = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
|
||||
const expectedViewModel = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
|
||||
presenter.reset();
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
@@ -21,11 +32,20 @@ describe('GetSponsorshipPricingPresenter', () => {
|
||||
|
||||
describe('present', () => {
|
||||
it('should store the result', () => {
|
||||
const mockResult = { tiers: [] };
|
||||
const mockResult: GetSponsorshipPricingResult = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,10 +55,19 @@ describe('GetSponsorshipPricingPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockResult = { tiers: [] };
|
||||
const mockResult: GetSponsorshipPricingResult = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.getViewModel()).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
expect(presenter.getViewModel()).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,10 +77,19 @@ describe('GetSponsorshipPricingPresenter', () => {
|
||||
});
|
||||
|
||||
it('should return the result when presented', () => {
|
||||
const mockResult = { tiers: [] };
|
||||
const mockResult: GetSponsorshipPricingResult = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
presenter.present(mockResult);
|
||||
|
||||
expect(presenter.viewModel).toEqual(mockResult);
|
||||
const expectedViewModel = {
|
||||
entityType: 'season',
|
||||
entityId: 'season-1',
|
||||
pricing: []
|
||||
};
|
||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,32 @@
|
||||
import type { GetSponsorshipPricingOutputPort } from '@core/racing/application/ports/output/GetSponsorshipPricingOutputPort';
|
||||
import type { GetSponsorshipPricingResult } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
||||
import { GetEntitySponsorshipPricingResultDTO } from '../dtos/GetEntitySponsorshipPricingResultDTO';
|
||||
|
||||
export class GetSponsorshipPricingPresenter {
|
||||
present(outputPort: GetSponsorshipPricingOutputPort): GetEntitySponsorshipPricingResultDTO {
|
||||
return {
|
||||
private result: GetEntitySponsorshipPricingResultDTO | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(outputPort: GetSponsorshipPricingResult): void {
|
||||
this.result = {
|
||||
entityType: outputPort.entityType,
|
||||
entityId: outputPort.entityId,
|
||||
pricing: outputPort.pricing,
|
||||
pricing: outputPort.pricing.map(item => ({
|
||||
id: item.id,
|
||||
level: item.level,
|
||||
price: item.price,
|
||||
currency: item.currency,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): GetEntitySponsorshipPricingResultDTO | null {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
get viewModel(): GetEntitySponsorshipPricingResultDTO {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { RejectSponsorshipRequestResultDTO } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||
import type { RejectSponsorshipRequestResult } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||
|
||||
export class RejectSponsorshipRequestPresenter {
|
||||
private result: RejectSponsorshipRequestResultDTO | null = null;
|
||||
private result: RejectSponsorshipRequestResult | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: RejectSponsorshipRequestResultDTO | null) {
|
||||
present(output: RejectSponsorshipRequestResult | null) {
|
||||
this.result = output ?? null;
|
||||
}
|
||||
|
||||
getViewModel(): RejectSponsorshipRequestResultDTO | null {
|
||||
getViewModel(): RejectSponsorshipRequestResult | null {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
get viewModel(): RejectSponsorshipRequestResultDTO | null {
|
||||
get viewModel(): RejectSponsorshipRequestResult | null {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { vi } from 'vitest';
|
||||
import { TeamController } from './TeamController';
|
||||
import { TeamService } from './TeamService';
|
||||
import type { Request } from 'express';
|
||||
import { CreateTeamInputDTO, UpdateTeamInputDTO } from './dtos/CreateTeamInputDTO';
|
||||
import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO';
|
||||
import { UpdateTeamInput } from './dtos/TeamDto';
|
||||
|
||||
describe('TeamController', () => {
|
||||
let controller: TeamController;
|
||||
@@ -35,7 +36,7 @@ describe('TeamController', () => {
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should return all teams', async () => {
|
||||
const result = { teams: [] };
|
||||
const result = { teams: [], totalCount: 0 };
|
||||
service.getAll.mockResolvedValue(result);
|
||||
|
||||
const response = await controller.getAll();
|
||||
@@ -49,12 +50,12 @@ describe('TeamController', () => {
|
||||
it('should return team details', async () => {
|
||||
const teamId = 'team-123';
|
||||
const userId = 'user-456';
|
||||
const result = { id: teamId, name: 'Team' };
|
||||
const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [] }, membership: null, canManage: false };
|
||||
service.getDetails.mockResolvedValue(result);
|
||||
|
||||
const mockReq: Partial<Request> = { ['user']: { userId } };
|
||||
const mockReq = { user: { userId } } as any;
|
||||
|
||||
const response = await controller.getDetails(teamId, mockReq as Request);
|
||||
const response = await controller.getDetails(teamId, mockReq);
|
||||
|
||||
expect(service.getDetails).toHaveBeenCalledWith(teamId, userId);
|
||||
expect(response).toEqual(result);
|
||||
@@ -64,7 +65,7 @@ describe('TeamController', () => {
|
||||
describe('getMembers', () => {
|
||||
it('should return team members', async () => {
|
||||
const teamId = 'team-123';
|
||||
const result = { members: [] };
|
||||
const result = { members: [], totalCount: 0, ownerCount: 0, managerCount: 0, memberCount: 0 };
|
||||
service.getMembers.mockResolvedValue(result);
|
||||
|
||||
const response = await controller.getMembers(teamId);
|
||||
@@ -77,7 +78,7 @@ describe('TeamController', () => {
|
||||
describe('getJoinRequests', () => {
|
||||
it('should return join requests', async () => {
|
||||
const teamId = 'team-123';
|
||||
const result = { requests: [] };
|
||||
const result = { requests: [], pendingCount: 0, totalCount: 0 };
|
||||
service.getJoinRequests.mockResolvedValue(result);
|
||||
|
||||
const response = await controller.getJoinRequests(teamId);
|
||||
@@ -89,14 +90,14 @@ describe('TeamController', () => {
|
||||
|
||||
describe('create', () => {
|
||||
it('should create team', async () => {
|
||||
const input: CreateTeamInputDTO = { name: 'New Team' };
|
||||
const input: CreateTeamInputDTO = { name: 'New Team', tag: 'TAG' };
|
||||
const userId = 'user-123';
|
||||
const result = { teamId: 'team-456' };
|
||||
const result = { id: 'team-456', success: true };
|
||||
service.create.mockResolvedValue(result);
|
||||
|
||||
const mockReq: Partial<Request> = { ['user']: { userId } };
|
||||
const mockReq = { user: { userId } } as any;
|
||||
|
||||
const response = await controller.create(input, mockReq as Request);
|
||||
const response = await controller.create(input, mockReq);
|
||||
|
||||
expect(service.create).toHaveBeenCalledWith(input, userId);
|
||||
expect(response).toEqual(result);
|
||||
@@ -106,14 +107,14 @@ describe('TeamController', () => {
|
||||
describe('update', () => {
|
||||
it('should update team', async () => {
|
||||
const teamId = 'team-123';
|
||||
const input: UpdateTeamInputDTO = { name: 'Updated Team' };
|
||||
const userId = 'user-456';
|
||||
const input: UpdateTeamInput = { name: 'Updated Team', updatedBy: userId };
|
||||
const result = { success: true };
|
||||
service.update.mockResolvedValue(result);
|
||||
|
||||
const mockReq: Partial<Request> = { ['user']: { userId } };
|
||||
const mockReq = { user: { userId } } as any;
|
||||
|
||||
const response = await controller.update(teamId, input, mockReq as Request);
|
||||
const response = await controller.update(teamId, input, mockReq);
|
||||
|
||||
expect(service.update).toHaveBeenCalledWith(teamId, input, userId);
|
||||
expect(response).toEqual(result);
|
||||
@@ -123,7 +124,7 @@ describe('TeamController', () => {
|
||||
describe('getDriverTeam', () => {
|
||||
it('should return driver team', async () => {
|
||||
const driverId = 'driver-123';
|
||||
const result = { teamId: 'team-456' };
|
||||
const result = { team: { id: 'team-456', name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [] }, membership: { role: 'member' as const, joinedAt: '2023-01-01', isActive: true }, isOwner: false, canManage: false };
|
||||
service.getDriverTeam.mockResolvedValue(result);
|
||||
|
||||
const response = await controller.getDriverTeam(driverId);
|
||||
@@ -137,7 +138,7 @@ describe('TeamController', () => {
|
||||
it('should return team membership', async () => {
|
||||
const teamId = 'team-123';
|
||||
const driverId = 'driver-456';
|
||||
const result = { role: 'member' };
|
||||
const result = { role: 'member' as const, joinedAt: '2023-01-01', isActive: true };
|
||||
service.getMembership.mockResolvedValue(result);
|
||||
|
||||
const response = await controller.getMembership(teamId, driverId);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { GetTeamMembersOutputDTO } from './dtos/GetTeamMembersOutputDTO';
|
||||
import { GetTeamJoinRequestsOutputDTO } from './dtos/GetTeamJoinRequestsOutputDTO';
|
||||
import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO';
|
||||
import { CreateTeamOutputDTO } from './dtos/CreateTeamOutputDTO';
|
||||
import { UpdateTeamInputDTO } from './dtos/UpdateTeamInputDTO';
|
||||
import { UpdateTeamInput } from './dtos/TeamDto';
|
||||
import { UpdateTeamOutputDTO } from './dtos/UpdateTeamOutputDTO';
|
||||
import { GetDriverTeamOutputDTO } from './dtos/GetDriverTeamOutputDTO';
|
||||
import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO';
|
||||
@@ -22,8 +22,7 @@ export class TeamController {
|
||||
@ApiOperation({ summary: 'Get all teams' })
|
||||
@ApiResponse({ status: 200, description: 'List of all teams', type: GetAllTeamsOutputDTO })
|
||||
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||
const presenter = await this.teamService.getAll();
|
||||
return presenter.responseModel;
|
||||
return await this.teamService.getAll();
|
||||
}
|
||||
|
||||
@Get(':teamId')
|
||||
@@ -31,43 +30,38 @@ export class TeamController {
|
||||
@ApiResponse({ status: 200, description: 'Team details', type: GetTeamDetailsOutputDTO })
|
||||
@ApiResponse({ status: 404, description: 'Team not found' })
|
||||
async getDetails(@Param('teamId') teamId: string, @Req() req: Request): Promise<GetTeamDetailsOutputDTO | null> {
|
||||
const userId = req['user']?.userId;
|
||||
const presenter = await this.teamService.getDetails(teamId, userId);
|
||||
return presenter.getResponseModel();
|
||||
const userId = (req as any)['user']?.userId;
|
||||
return await this.teamService.getDetails(teamId, userId);
|
||||
}
|
||||
|
||||
@Get(':teamId/members')
|
||||
@ApiOperation({ summary: 'Get team members' })
|
||||
@ApiResponse({ status: 200, description: 'Team members', type: GetTeamMembersOutputDTO })
|
||||
async getMembers(@Param('teamId') teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||
const presenter = await this.teamService.getMembers(teamId);
|
||||
return presenter.getResponseModel()!;
|
||||
return await this.teamService.getMembers(teamId);
|
||||
}
|
||||
|
||||
@Get(':teamId/join-requests')
|
||||
@ApiOperation({ summary: 'Get team join requests' })
|
||||
@ApiResponse({ status: 200, description: 'Team join requests', type: GetTeamJoinRequestsOutputDTO })
|
||||
async getJoinRequests(@Param('teamId') teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||
const presenter = await this.teamService.getJoinRequests(teamId);
|
||||
return presenter.getResponseModel()!;
|
||||
return await this.teamService.getJoinRequests(teamId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new team' })
|
||||
@ApiResponse({ status: 201, description: 'Team created', type: CreateTeamOutputDTO })
|
||||
async create(@Body() input: CreateTeamInputDTO, @Req() req: Request): Promise<CreateTeamOutputDTO> {
|
||||
const userId = req['user']?.userId;
|
||||
const presenter = await this.teamService.create(input, userId);
|
||||
return presenter.responseModel;
|
||||
const userId = (req as any)['user']?.userId;
|
||||
return await this.teamService.create(input, userId);
|
||||
}
|
||||
|
||||
@Patch(':teamId')
|
||||
@ApiOperation({ summary: 'Update team' })
|
||||
@ApiResponse({ status: 200, description: 'Team updated', type: UpdateTeamOutputDTO })
|
||||
async update(@Param('teamId') teamId: string, @Body() input: UpdateTeamInputDTO, @Req() req: Request): Promise<UpdateTeamOutputDTO> {
|
||||
const userId = req['user']?.userId;
|
||||
const presenter = await this.teamService.update(teamId, input, userId);
|
||||
return presenter.responseModel;
|
||||
async update(@Param('teamId') teamId: string, @Body() input: UpdateTeamInput, @Req() req: Request): Promise<UpdateTeamOutputDTO> {
|
||||
const userId = (req as any)['user']?.userId;
|
||||
return await this.teamService.update(teamId, input, userId);
|
||||
}
|
||||
|
||||
@Get('driver/:driverId')
|
||||
@@ -75,8 +69,7 @@ export class TeamController {
|
||||
@ApiResponse({ status: 200, description: 'Driver\'s team', type: GetDriverTeamOutputDTO })
|
||||
@ApiResponse({ status: 404, description: 'Team not found' })
|
||||
async getDriverTeam(@Param('driverId') driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||
const presenter = await this.teamService.getDriverTeam(driverId);
|
||||
return presenter.getResponseModel();
|
||||
return await this.teamService.getDriverTeam(driverId);
|
||||
}
|
||||
|
||||
@Get(':teamId/members/:driverId')
|
||||
@@ -84,7 +77,6 @@ export class TeamController {
|
||||
@ApiResponse({ status: 200, description: 'Team membership', type: GetTeamMembershipOutputDTO })
|
||||
@ApiResponse({ status: 404, description: 'Membership not found' })
|
||||
async getMembership(@Param('teamId') teamId: string, @Param('driverId') driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||
const presenter = await this.teamService.getMembership(teamId, driverId);
|
||||
return presenter.responseModel;
|
||||
return await this.teamService.getMembership(teamId, driverId);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,6 @@ import { TeamService } from './TeamService';
|
||||
|
||||
// Import core interfaces
|
||||
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';
|
||||
|
||||
// Import concrete in-memory implementations
|
||||
import { InMemoryTeamRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamRepository';
|
||||
@@ -15,15 +11,7 @@ import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/
|
||||
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
|
||||
// Import use cases
|
||||
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
|
||||
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
|
||||
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
|
||||
import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase';
|
||||
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
|
||||
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
||||
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
|
||||
// Use cases are imported and used directly in the service
|
||||
|
||||
// Define injection tokens
|
||||
export const TEAM_REPOSITORY_TOKEN = 'ITeamRepository';
|
||||
@@ -58,53 +46,5 @@ export const TeamProviders: Provider[] = [
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
// Use cases
|
||||
{
|
||||
provide: GetAllTeamsUseCase,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new GetAllTeamsUseCase(teamRepo, membershipRepo, logger),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetTeamDetailsUseCase,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
|
||||
new GetTeamDetailsUseCase(teamRepo, membershipRepo),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetTeamMembersUseCase,
|
||||
useFactory: (membershipRepo: ITeamMembershipRepository, driverRepo: IDriverRepository, imageService: IImageServicePort, logger: Logger) =>
|
||||
new GetTeamMembersUseCase(membershipRepo, driverRepo, imageService, logger),
|
||||
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetTeamJoinRequestsUseCase,
|
||||
useFactory: (membershipRepo: ITeamMembershipRepository, driverRepo: IDriverRepository, imageService: IImageServicePort, logger: Logger) =>
|
||||
new GetTeamJoinRequestsUseCase(membershipRepo, driverRepo, imageService, logger),
|
||||
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: CreateTeamUseCase,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
|
||||
new CreateTeamUseCase(teamRepo, membershipRepo),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UpdateTeamUseCase,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
|
||||
new UpdateTeamUseCase(teamRepo, membershipRepo),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetDriverTeamUseCase,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new GetDriverTeamUseCase(teamRepo, membershipRepo, logger),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetTeamMembershipUseCase,
|
||||
useFactory: (membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new GetTeamMembershipUseCase(membershipRepo, logger),
|
||||
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
// Use cases are created directly in the service
|
||||
];
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { vi } from 'vitest';
|
||||
import { TeamService } from './TeamService';
|
||||
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
||||
@@ -11,20 +12,20 @@ import { DriverTeamViewModel } from './dtos/TeamDto';
|
||||
|
||||
describe('TeamService', () => {
|
||||
let service: TeamService;
|
||||
let getAllTeamsUseCase: jest.Mocked<GetAllTeamsUseCase>;
|
||||
let getDriverTeamUseCase: jest.Mocked<GetDriverTeamUseCase>;
|
||||
let getAllTeamsUseCase: ReturnType<typeof vi.mocked<GetAllTeamsUseCase>>;
|
||||
let getDriverTeamUseCase: ReturnType<typeof vi.mocked<GetDriverTeamUseCase>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockGetAllTeamsUseCase = {
|
||||
execute: jest.fn(),
|
||||
execute: vi.fn(),
|
||||
};
|
||||
const mockGetDriverTeamUseCase = {
|
||||
execute: jest.fn(),
|
||||
execute: vi.fn(),
|
||||
};
|
||||
const mockLogger = {
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -61,11 +62,11 @@ describe('TeamService', () => {
|
||||
getAllTeamsUseCase.execute.mockResolvedValue(mockResult as any);
|
||||
|
||||
const mockPresenter = {
|
||||
present: jest.fn(),
|
||||
getViewModel: jest.fn().mockReturnValue({ teams: [], totalCount: 0 }),
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn().mockReturnValue({ teams: [], totalCount: 0 }),
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(AllTeamsPresenter as any) = jest.fn().mockImplementation(() => mockPresenter);
|
||||
(AllTeamsPresenter as any) = vi.fn().mockImplementation(() => mockPresenter);
|
||||
|
||||
const result = await service.getAll();
|
||||
|
||||
@@ -81,11 +82,11 @@ describe('TeamService', () => {
|
||||
getDriverTeamUseCase.execute.mockResolvedValue(mockResult as any);
|
||||
|
||||
const mockPresenter = {
|
||||
present: jest.fn(),
|
||||
getViewModel: jest.fn().mockReturnValue({} as DriverTeamViewModel),
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn().mockReturnValue({} as DriverTeamViewModel),
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(DriverTeamPresenter as any) = jest.fn().mockImplementation(() => mockPresenter);
|
||||
(DriverTeamPresenter as any) = vi.fn().mockImplementation(() => mockPresenter);
|
||||
|
||||
const result = await service.getDriverTeam('driver1');
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ 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
|
||||
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
@@ -38,7 +37,7 @@ import { CreateTeamPresenter } from './presenters/CreateTeamPresenter';
|
||||
import { UpdateTeamPresenter } from './presenters/UpdateTeamPresenter';
|
||||
|
||||
// Tokens
|
||||
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, LOGGER_TOKEN } from './TeamProviders';
|
||||
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN } from './TeamProviders';
|
||||
|
||||
@Injectable()
|
||||
export class TeamService {
|
||||
@@ -46,7 +45,6 @@ export class TeamService {
|
||||
@Inject(TEAM_REPOSITORY_TOKEN) private readonly teamRepository: ITeamRepository,
|
||||
@Inject(TEAM_MEMBERSHIP_REPOSITORY_TOKEN) private readonly membershipRepository: ITeamMembershipRepository,
|
||||
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
|
||||
@Inject(IMAGE_SERVICE_TOKEN) private readonly imageService: IImageServicePort,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -57,11 +55,11 @@ export class TeamService {
|
||||
const useCase = new GetAllTeamsUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
||||
const result = await useCase.execute();
|
||||
if (result.isErr()) {
|
||||
this.logger.error('Error fetching all teams', result.error?.details?.message || 'Unknown error');
|
||||
this.logger.error('Error fetching all teams', new Error(result.error?.details?.message || 'Unknown error'));
|
||||
return { teams: [], totalCount: 0 };
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
return presenter.getResponseModel()!;
|
||||
}
|
||||
|
||||
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
|
||||
@@ -75,14 +73,14 @@ export class TeamService {
|
||||
return null;
|
||||
}
|
||||
|
||||
return presenter.getResponseModel();
|
||||
return presenter.getResponseModel()!;
|
||||
}
|
||||
|
||||
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
|
||||
|
||||
const presenter = new TeamMembersPresenter();
|
||||
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.imageService, this.logger, presenter);
|
||||
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger, presenter);
|
||||
const result = await useCase.execute({ teamId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
@@ -105,7 +103,7 @@ export class TeamService {
|
||||
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, presenter);
|
||||
const result = await useCase.execute({ teamId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(new Error(`Error fetching team join requests for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`));
|
||||
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.error?.details?.message || 'Unknown error'));
|
||||
return {
|
||||
requests: [],
|
||||
pendingCount: 0,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateTeamInputDTO {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
tag: string;
|
||||
tag!: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CreateTeamOutputDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
success: boolean;
|
||||
success!: boolean;
|
||||
}
|
||||
@@ -2,22 +2,22 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
class TeamListItemDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
tag: string;
|
||||
tag!: string;
|
||||
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
description!: string;
|
||||
|
||||
@ApiProperty()
|
||||
memberCount: number;
|
||||
memberCount!: number;
|
||||
|
||||
@ApiProperty({ type: [String] })
|
||||
leagues: string[];
|
||||
leagues!: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
specialization?: 'endurance' | 'sprint' | 'mixed';
|
||||
@@ -31,8 +31,8 @@ class TeamListItemDTO {
|
||||
|
||||
export class GetAllTeamsOutputDTO {
|
||||
@ApiProperty({ type: [TeamListItemDTO] })
|
||||
teams: TeamListItemDTO[];
|
||||
teams!: TeamListItemDTO[];
|
||||
|
||||
@ApiProperty()
|
||||
totalCount: number;
|
||||
totalCount!: number;
|
||||
}
|
||||
@@ -2,22 +2,22 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
class TeamDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
tag: string;
|
||||
tag!: string;
|
||||
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
description!: string;
|
||||
|
||||
@ApiProperty()
|
||||
ownerId: string;
|
||||
ownerId!: string;
|
||||
|
||||
@ApiProperty({ type: [String] })
|
||||
leagues: string[];
|
||||
leagues!: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
createdAt?: string;
|
||||
@@ -34,25 +34,25 @@ class TeamDTO {
|
||||
|
||||
class MembershipDTO {
|
||||
@ApiProperty()
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
role!: 'owner' | 'manager' | 'member';
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt: string;
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
isActive: boolean;
|
||||
isActive!: boolean;
|
||||
}
|
||||
|
||||
export class GetDriverTeamOutputDTO {
|
||||
@ApiProperty({ type: TeamDTO })
|
||||
team: TeamDTO;
|
||||
team!: TeamDTO;
|
||||
|
||||
@ApiProperty({ type: MembershipDTO })
|
||||
membership: MembershipDTO;
|
||||
membership!: MembershipDTO;
|
||||
|
||||
@ApiProperty()
|
||||
isOwner: boolean;
|
||||
isOwner!: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
canManage: boolean;
|
||||
canManage!: boolean;
|
||||
}
|
||||
@@ -2,22 +2,22 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
class TeamDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
tag: string;
|
||||
tag!: string;
|
||||
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
description!: string;
|
||||
|
||||
@ApiProperty()
|
||||
ownerId: string;
|
||||
ownerId!: string;
|
||||
|
||||
@ApiProperty({ type: [String] })
|
||||
leagues: string[];
|
||||
leagues!: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
createdAt?: string;
|
||||
@@ -34,22 +34,22 @@ class TeamDTO {
|
||||
|
||||
class MembershipDTO {
|
||||
@ApiProperty()
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
role!: 'owner' | 'manager' | 'member';
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt: string;
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
isActive: boolean;
|
||||
isActive!: boolean;
|
||||
}
|
||||
|
||||
export class GetTeamDetailsOutputDTO {
|
||||
@ApiProperty({ type: TeamDTO })
|
||||
team: TeamDTO;
|
||||
team!: TeamDTO;
|
||||
|
||||
@ApiProperty({ type: MembershipDTO, nullable: true })
|
||||
membership: MembershipDTO | null;
|
||||
membership!: MembershipDTO | null;
|
||||
|
||||
@ApiProperty()
|
||||
canManage: boolean;
|
||||
canManage!: boolean;
|
||||
}
|
||||
@@ -2,34 +2,34 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
class TeamJoinRequestDTO {
|
||||
@ApiProperty()
|
||||
requestId: string;
|
||||
requestId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
driverId: string;
|
||||
driverId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
driverName: string;
|
||||
driverName!: string;
|
||||
|
||||
@ApiProperty()
|
||||
teamId: string;
|
||||
teamId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
status: 'pending' | 'approved' | 'rejected';
|
||||
status!: 'pending' | 'approved' | 'rejected';
|
||||
|
||||
@ApiProperty()
|
||||
requestedAt: string;
|
||||
requestedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
avatarUrl: string;
|
||||
avatarUrl!: string;
|
||||
}
|
||||
|
||||
export class GetTeamJoinRequestsOutputDTO {
|
||||
@ApiProperty({ type: [TeamJoinRequestDTO] })
|
||||
requests: TeamJoinRequestDTO[];
|
||||
requests!: TeamJoinRequestDTO[];
|
||||
|
||||
@ApiProperty()
|
||||
pendingCount: number;
|
||||
pendingCount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
totalCount: number;
|
||||
totalCount!: number;
|
||||
}
|
||||
@@ -2,37 +2,37 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
class TeamMemberDTO {
|
||||
@ApiProperty()
|
||||
driverId: string;
|
||||
driverId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
driverName: string;
|
||||
driverName!: string;
|
||||
|
||||
@ApiProperty()
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
role!: 'owner' | 'manager' | 'member';
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt: string;
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
isActive: boolean;
|
||||
isActive!: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
avatarUrl: string;
|
||||
avatarUrl!: string;
|
||||
}
|
||||
|
||||
export class GetTeamMembersOutputDTO {
|
||||
@ApiProperty({ type: [TeamMemberDTO] })
|
||||
members: TeamMemberDTO[];
|
||||
members!: TeamMemberDTO[];
|
||||
|
||||
@ApiProperty()
|
||||
totalCount: number;
|
||||
totalCount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
ownerCount: number;
|
||||
ownerCount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
managerCount: number;
|
||||
managerCount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
memberCount: number;
|
||||
memberCount!: number;
|
||||
}
|
||||
@@ -2,11 +2,11 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class GetTeamMembershipOutputDTO {
|
||||
@ApiProperty()
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
role!: 'owner' | 'manager' | 'member';
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt: string;
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
isActive: boolean;
|
||||
isActive!: boolean;
|
||||
}
|
||||
@@ -4,31 +4,31 @@ export type SkillLevel = 'beginner' | 'intermediate' | 'advanced' | 'pro';
|
||||
|
||||
class TeamLeaderboardItemDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
memberCount: number;
|
||||
memberCount!: number;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
rating: number | null;
|
||||
rating!: number | null;
|
||||
|
||||
@ApiProperty()
|
||||
totalWins: number;
|
||||
totalWins!: number;
|
||||
|
||||
@ApiProperty()
|
||||
totalRaces: number;
|
||||
totalRaces!: number;
|
||||
|
||||
@ApiProperty({ enum: ['beginner', 'intermediate', 'advanced', 'pro'] })
|
||||
performanceLevel: SkillLevel;
|
||||
performanceLevel!: SkillLevel;
|
||||
|
||||
@ApiProperty()
|
||||
isRecruiting: boolean;
|
||||
isRecruiting!: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
createdAt: string;
|
||||
createdAt!: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
description?: string;
|
||||
@@ -45,14 +45,14 @@ class TeamLeaderboardItemDTO {
|
||||
|
||||
export class GetTeamsLeaderboardOutputDTO {
|
||||
@ApiProperty({ type: [TeamLeaderboardItemDTO] })
|
||||
teams: TeamLeaderboardItemDTO[];
|
||||
teams!: TeamLeaderboardItemDTO[];
|
||||
|
||||
@ApiProperty()
|
||||
recruitingCount: number;
|
||||
recruitingCount!: number;
|
||||
|
||||
@ApiProperty({ type: 'object', additionalProperties: { type: 'array', items: { $ref: '#/components/schemas/TeamLeaderboardItemDTO' } } })
|
||||
groupsBySkillLevel: Record<SkillLevel, TeamLeaderboardItemDTO[]>;
|
||||
groupsBySkillLevel!: Record<SkillLevel, TeamLeaderboardItemDTO[]>;
|
||||
|
||||
@ApiProperty({ type: [TeamLeaderboardItemDTO] })
|
||||
topTeams: TeamLeaderboardItemDTO[];
|
||||
topTeams!: TeamLeaderboardItemDTO[];
|
||||
}
|
||||
@@ -3,22 +3,22 @@ import { IsString, IsNotEmpty, IsBoolean, IsOptional } from 'class-validator';
|
||||
|
||||
export class TeamListItemViewModel {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
tag: string;
|
||||
tag!: string;
|
||||
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
description!: string;
|
||||
|
||||
@ApiProperty()
|
||||
memberCount: number;
|
||||
memberCount!: number;
|
||||
|
||||
@ApiProperty({ type: [String] })
|
||||
leagues: string[];
|
||||
leagues!: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
specialization?: 'endurance' | 'sprint' | 'mixed';
|
||||
@@ -32,30 +32,30 @@ export class TeamListItemViewModel {
|
||||
|
||||
export class AllTeamsViewModel {
|
||||
@ApiProperty({ type: [TeamListItemViewModel] })
|
||||
teams: TeamListItemViewModel[];
|
||||
teams!: TeamListItemViewModel[];
|
||||
|
||||
@ApiProperty()
|
||||
totalCount: number;
|
||||
totalCount!: number;
|
||||
}
|
||||
|
||||
export class TeamViewModel {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
tag: string;
|
||||
tag!: string;
|
||||
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
description!: string;
|
||||
|
||||
@ApiProperty()
|
||||
ownerId: string;
|
||||
ownerId!: string;
|
||||
|
||||
@ApiProperty({ type: [String] })
|
||||
leagues: string[];
|
||||
leagues!: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
createdAt?: string;
|
||||
@@ -85,131 +85,131 @@ export enum MembershipStatus {
|
||||
|
||||
export class MembershipViewModel {
|
||||
@ApiProperty()
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
role!: 'owner' | 'manager' | 'member';
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt: string;
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
isActive: boolean;
|
||||
isActive!: boolean;
|
||||
}
|
||||
|
||||
export class DriverTeamViewModel {
|
||||
@ApiProperty({ type: TeamViewModel })
|
||||
team: TeamViewModel;
|
||||
team!: TeamViewModel;
|
||||
|
||||
@ApiProperty({ type: MembershipViewModel })
|
||||
membership: MembershipViewModel;
|
||||
membership!: MembershipViewModel;
|
||||
|
||||
@ApiProperty()
|
||||
isOwner: boolean;
|
||||
isOwner!: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
canManage: boolean;
|
||||
canManage!: boolean;
|
||||
}
|
||||
|
||||
export class GetDriverTeamQuery {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
teamId: string;
|
||||
teamId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
driverId: string;
|
||||
driverId!: string;
|
||||
}
|
||||
|
||||
export class TeamDetailsViewModel {
|
||||
@ApiProperty({ type: TeamViewModel })
|
||||
team: TeamViewModel;
|
||||
team!: TeamViewModel;
|
||||
|
||||
@ApiProperty({ type: MembershipViewModel, nullable: true })
|
||||
membership: MembershipViewModel | null;
|
||||
membership!: MembershipViewModel | null;
|
||||
|
||||
@ApiProperty()
|
||||
canManage: boolean;
|
||||
canManage!: boolean;
|
||||
}
|
||||
|
||||
export class TeamMemberViewModel {
|
||||
@ApiProperty()
|
||||
driverId: string;
|
||||
driverId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
driverName: string;
|
||||
driverName!: string;
|
||||
|
||||
@ApiProperty()
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
role!: 'owner' | 'manager' | 'member';
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt: string;
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
isActive: boolean;
|
||||
isActive!: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
avatarUrl: string;
|
||||
avatarUrl!: string;
|
||||
}
|
||||
|
||||
export class TeamMembersViewModel {
|
||||
@ApiProperty({ type: [TeamMemberViewModel] })
|
||||
members: TeamMemberViewModel[];
|
||||
members!: TeamMemberViewModel[];
|
||||
|
||||
@ApiProperty()
|
||||
totalCount: number;
|
||||
totalCount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
ownerCount: number;
|
||||
ownerCount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
managerCount: number;
|
||||
managerCount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
memberCount: number;
|
||||
memberCount!: number;
|
||||
}
|
||||
|
||||
export class TeamJoinRequestViewModel {
|
||||
@ApiProperty()
|
||||
requestId: string;
|
||||
requestId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
driverId: string;
|
||||
driverId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
driverName: string;
|
||||
driverName!: string;
|
||||
|
||||
@ApiProperty()
|
||||
teamId: string;
|
||||
teamId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
status: 'pending' | 'approved' | 'rejected';
|
||||
status!: 'pending' | 'approved' | 'rejected';
|
||||
|
||||
@ApiProperty()
|
||||
requestedAt: string;
|
||||
requestedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
avatarUrl: string;
|
||||
avatarUrl!: string;
|
||||
}
|
||||
|
||||
export class TeamJoinRequestsViewModel {
|
||||
@ApiProperty({ type: [TeamJoinRequestViewModel] })
|
||||
requests: TeamJoinRequestViewModel[];
|
||||
requests!: TeamJoinRequestViewModel[];
|
||||
|
||||
@ApiProperty()
|
||||
pendingCount: number;
|
||||
pendingCount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
totalCount: number;
|
||||
totalCount!: number;
|
||||
}
|
||||
|
||||
export class CreateTeamInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
tag: string;
|
||||
tag!: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@@ -218,17 +218,17 @@ export class CreateTeamInput {
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ownerId: string;
|
||||
ownerId!: string;
|
||||
}
|
||||
|
||||
export class CreateTeamOutput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
teamId: string;
|
||||
teamId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
success!: boolean;
|
||||
}
|
||||
|
||||
export class UpdateTeamInput {
|
||||
@@ -254,19 +254,19 @@ export class UpdateTeamInput {
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
updatedBy: string;
|
||||
updatedBy!: string;
|
||||
}
|
||||
|
||||
export class UpdateTeamOutput {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
success!: boolean;
|
||||
}
|
||||
|
||||
export class ApproveTeamJoinRequestInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
requestId: string;
|
||||
requestId!: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@@ -277,13 +277,13 @@ export class ApproveTeamJoinRequestInput {
|
||||
export class ApproveTeamJoinRequestOutput {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
success!: boolean;
|
||||
}
|
||||
|
||||
export class RejectTeamJoinRequestInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
requestId: string;
|
||||
requestId!: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@@ -294,5 +294,5 @@ export class RejectTeamJoinRequestInput {
|
||||
export class RejectTeamJoinRequestOutput {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
success!: boolean;
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class UpdateTeamOutputDTO {
|
||||
@ApiProperty()
|
||||
success: boolean;
|
||||
success!: boolean;
|
||||
}
|
||||
@@ -16,4 +16,11 @@ export class TeamMembershipPresenter implements UseCaseOutputPort<GetTeamMembers
|
||||
getResponseModel(): GetTeamMembershipOutputDTO | null {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
get responseModel(): GetTeamMembershipOutputDTO {
|
||||
if (!this.result) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +1,90 @@
|
||||
import type { TeamsLeaderboardOutputPort } from '@core/racing/application/ports/output/TeamsLeaderboardOutputPort';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type { GetTeamsLeaderboardResult } from '@core/racing/application/use-cases/GetTeamsLeaderboardUseCase';
|
||||
import type { GetTeamsLeaderboardOutputDTO } from '../dtos/GetTeamsLeaderboardOutputDTO';
|
||||
|
||||
export class TeamsLeaderboardPresenter {
|
||||
export class TeamsLeaderboardPresenter implements UseCaseOutputPort<GetTeamsLeaderboardResult> {
|
||||
private result: GetTeamsLeaderboardOutputDTO | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
async present(outputPort: TeamsLeaderboardOutputPort): Promise<void> {
|
||||
present(result: GetTeamsLeaderboardResult): void {
|
||||
this.result = {
|
||||
teams: outputPort.teams.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
memberCount: team.memberCount,
|
||||
rating: team.rating,
|
||||
totalWins: team.totalWins,
|
||||
totalRaces: team.totalRaces,
|
||||
performanceLevel: team.performanceLevel,
|
||||
isRecruiting: team.isRecruiting,
|
||||
createdAt: team.createdAt.toISOString(),
|
||||
description: team.description,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
teams: result.items.map(item => ({
|
||||
id: item.team.id,
|
||||
name: item.team.name.toString(),
|
||||
memberCount: item.memberCount,
|
||||
rating: item.rating,
|
||||
totalWins: item.totalWins,
|
||||
totalRaces: item.totalRaces,
|
||||
performanceLevel: item.performanceLevel,
|
||||
isRecruiting: item.isRecruiting,
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
description: item.team.description?.toString() || '',
|
||||
})),
|
||||
recruitingCount: outputPort.recruitingCount,
|
||||
recruitingCount: result.recruitingCount,
|
||||
groupsBySkillLevel: {
|
||||
beginner: outputPort.groupsBySkillLevel.beginner.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
memberCount: team.memberCount,
|
||||
rating: team.rating,
|
||||
totalWins: team.totalWins,
|
||||
totalRaces: team.totalRaces,
|
||||
performanceLevel: team.performanceLevel,
|
||||
isRecruiting: team.isRecruiting,
|
||||
createdAt: team.createdAt.toISOString(),
|
||||
description: team.description,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
beginner: result.groupsBySkillLevel.beginner.map(item => ({
|
||||
id: item.team.id,
|
||||
name: item.team.name.toString(),
|
||||
memberCount: item.memberCount,
|
||||
rating: item.rating,
|
||||
totalWins: item.totalWins,
|
||||
totalRaces: item.totalRaces,
|
||||
performanceLevel: item.performanceLevel,
|
||||
isRecruiting: item.isRecruiting,
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
description: item.team.description?.toString() || '',
|
||||
})),
|
||||
intermediate: outputPort.groupsBySkillLevel.intermediate.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
memberCount: team.memberCount,
|
||||
rating: team.rating,
|
||||
totalWins: team.totalWins,
|
||||
totalRaces: team.totalRaces,
|
||||
performanceLevel: team.performanceLevel,
|
||||
isRecruiting: team.isRecruiting,
|
||||
createdAt: team.createdAt.toISOString(),
|
||||
description: team.description,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
intermediate: result.groupsBySkillLevel.intermediate.map(item => ({
|
||||
id: item.team.id,
|
||||
name: item.team.name.toString(),
|
||||
memberCount: item.memberCount,
|
||||
rating: item.rating,
|
||||
totalWins: item.totalWins,
|
||||
totalRaces: item.totalRaces,
|
||||
performanceLevel: item.performanceLevel,
|
||||
isRecruiting: item.isRecruiting,
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
description: item.team.description?.toString() || '',
|
||||
})),
|
||||
advanced: outputPort.groupsBySkillLevel.advanced.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
memberCount: team.memberCount,
|
||||
rating: team.rating,
|
||||
totalWins: team.totalWins,
|
||||
totalRaces: team.totalRaces,
|
||||
performanceLevel: team.performanceLevel,
|
||||
isRecruiting: team.isRecruiting,
|
||||
createdAt: team.createdAt.toISOString(),
|
||||
description: team.description,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
advanced: result.groupsBySkillLevel.advanced.map(item => ({
|
||||
id: item.team.id,
|
||||
name: item.team.name.toString(),
|
||||
memberCount: item.memberCount,
|
||||
rating: item.rating,
|
||||
totalWins: item.totalWins,
|
||||
totalRaces: item.totalRaces,
|
||||
performanceLevel: item.performanceLevel,
|
||||
isRecruiting: item.isRecruiting,
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
description: item.team.description?.toString() || '',
|
||||
})),
|
||||
pro: outputPort.groupsBySkillLevel.pro.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
memberCount: team.memberCount,
|
||||
rating: team.rating,
|
||||
totalWins: team.totalWins,
|
||||
totalRaces: team.totalRaces,
|
||||
performanceLevel: team.performanceLevel,
|
||||
isRecruiting: team.isRecruiting,
|
||||
createdAt: team.createdAt.toISOString(),
|
||||
description: team.description,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
pro: result.groupsBySkillLevel.pro.map(item => ({
|
||||
id: item.team.id,
|
||||
name: item.team.name.toString(),
|
||||
memberCount: item.memberCount,
|
||||
rating: item.rating,
|
||||
totalWins: item.totalWins,
|
||||
totalRaces: item.totalRaces,
|
||||
performanceLevel: item.performanceLevel,
|
||||
isRecruiting: item.isRecruiting,
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
description: item.team.description?.toString() || '',
|
||||
})),
|
||||
},
|
||||
topTeams: outputPort.topTeams.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
memberCount: team.memberCount,
|
||||
rating: team.rating,
|
||||
totalWins: team.totalWins,
|
||||
totalRaces: team.totalRaces,
|
||||
performanceLevel: team.performanceLevel,
|
||||
isRecruiting: team.isRecruiting,
|
||||
createdAt: team.createdAt.toISOString(),
|
||||
description: team.description,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
topTeams: result.topItems.map(item => ({
|
||||
id: item.team.id,
|
||||
name: item.team.name.toString(),
|
||||
memberCount: item.memberCount,
|
||||
rating: item.rating,
|
||||
totalWins: item.totalWins,
|
||||
totalRaces: item.totalRaces,
|
||||
performanceLevel: item.performanceLevel,
|
||||
isRecruiting: item.isRecruiting,
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
description: item.team.description?.toString() || '',
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { HelloService } from '../application/hello/hello.service';
|
||||
|
||||
@Controller()
|
||||
export class HelloController {
|
||||
constructor(private readonly helloService: HelloService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): { message: string } {
|
||||
const presenter = this.helloService.getHello();
|
||||
return presenter.responseModel;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type { CreatePaymentResult } from '@core/payments/application/use-cases/CreatePaymentUseCase';
|
||||
import type { CreatePaymentViewModel, PaymentDto } from './types';
|
||||
|
||||
export class CreatePaymentPresenter implements UseCaseOutputPort<CreatePaymentResult> {
|
||||
private viewModel: CreatePaymentViewModel | null = null;
|
||||
|
||||
present(result: CreatePaymentResult): void {
|
||||
this.viewModel = {
|
||||
payment: this.mapPaymentToDto(result.payment),
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): CreatePaymentViewModel | null {
|
||||
return this.viewModel;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
}
|
||||
|
||||
private mapPaymentToDto(payment: CreatePaymentResult['payment']): PaymentDto {
|
||||
return {
|
||||
id: payment.id,
|
||||
type: payment.type,
|
||||
amount: payment.amount,
|
||||
platformFee: payment.platformFee,
|
||||
netAmount: payment.netAmount,
|
||||
payerId: payment.payerId,
|
||||
payerType: payment.payerType,
|
||||
leagueId: payment.leagueId,
|
||||
seasonId: payment.seasonId,
|
||||
status: payment.status,
|
||||
createdAt: payment.createdAt,
|
||||
completedAt: payment.completedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type { GetPaymentsResult } from '@core/payments/application/use-cases/GetPaymentsUseCase';
|
||||
import type { GetPaymentsViewModel, PaymentDto } from './types';
|
||||
|
||||
export class GetPaymentsPresenter implements UseCaseOutputPort<GetPaymentsResult> {
|
||||
private viewModel: GetPaymentsViewModel | null = null;
|
||||
|
||||
present(result: GetPaymentsResult): void {
|
||||
this.viewModel = {
|
||||
payments: result.payments.map(payment => this.mapPaymentToDto(payment)),
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): GetPaymentsViewModel | null {
|
||||
return this.viewModel;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
}
|
||||
|
||||
private mapPaymentToDto(payment: GetPaymentsResult['payments'][0]): PaymentDto {
|
||||
return {
|
||||
id: payment.id,
|
||||
type: payment.type,
|
||||
amount: payment.amount,
|
||||
platformFee: payment.platformFee,
|
||||
netAmount: payment.netAmount,
|
||||
payerId: payment.payerId,
|
||||
payerType: payment.payerType,
|
||||
leagueId: payment.leagueId,
|
||||
seasonId: payment.seasonId,
|
||||
status: payment.status,
|
||||
createdAt: payment.createdAt,
|
||||
completedAt: payment.completedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type { GetSponsorBillingResult } from '@core/payments/application/use-cases/GetSponsorBillingUseCase';
|
||||
import type { SponsorBillingSummary } from './types';
|
||||
|
||||
export class GetSponsorBillingPresenter implements UseCaseOutputPort<GetSponsorBillingResult> {
|
||||
private viewModel: SponsorBillingSummary | null = null;
|
||||
|
||||
present(result: GetSponsorBillingResult): void {
|
||||
this.viewModel = result;
|
||||
}
|
||||
|
||||
getViewModel(): SponsorBillingSummary | null {
|
||||
return this.viewModel;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './types';
|
||||
export * from './CreatePaymentPresenter';
|
||||
export * from './GetPaymentsPresenter';
|
||||
export * from './GetSponsorBillingPresenter';
|
||||
@@ -1,177 +0,0 @@
|
||||
import type { PaymentType, PayerType, PaymentStatus } from '@core/payments/domain/entities/Payment';
|
||||
import type { PrizeType } from '@core/payments/domain/entities/Prize';
|
||||
import type { TransactionType, ReferenceType } from '@core/payments/domain/entities/Wallet';
|
||||
import type { MembershipFeeType } from '@core/payments/domain/entities/MembershipFee';
|
||||
import type { MemberPaymentStatus } from '@core/payments/domain/entities/MemberPayment';
|
||||
|
||||
// DTOs for API responses
|
||||
|
||||
export interface PaymentDto {
|
||||
id: string;
|
||||
type: PaymentType;
|
||||
amount: number;
|
||||
platformFee: number;
|
||||
netAmount: number;
|
||||
payerId: string;
|
||||
payerType: PayerType;
|
||||
leagueId: string;
|
||||
seasonId: string | undefined;
|
||||
status: PaymentStatus;
|
||||
createdAt: Date;
|
||||
completedAt: Date | undefined;
|
||||
}
|
||||
|
||||
export interface PrizeDto {
|
||||
id: string;
|
||||
leagueId: string;
|
||||
seasonId: string;
|
||||
position: number;
|
||||
name: string;
|
||||
amount: number;
|
||||
type: PrizeType;
|
||||
description: string | undefined;
|
||||
awarded: boolean;
|
||||
awardedTo: string | undefined;
|
||||
awardedAt: Date | undefined;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface WalletDto {
|
||||
id: string;
|
||||
leagueId: string;
|
||||
balance: number;
|
||||
totalRevenue: number;
|
||||
totalPlatformFees: number;
|
||||
totalWithdrawn: number;
|
||||
currency: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface TransactionDto {
|
||||
id: string;
|
||||
walletId: string;
|
||||
type: TransactionType;
|
||||
amount: number;
|
||||
description: string;
|
||||
referenceId: string | undefined;
|
||||
referenceType: ReferenceType | undefined;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface MembershipFeeDto {
|
||||
id: string;
|
||||
leagueId: string;
|
||||
seasonId: string | undefined;
|
||||
type: MembershipFeeType;
|
||||
amount: number;
|
||||
enabled: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface MemberPaymentDto {
|
||||
id: string;
|
||||
feeId: string;
|
||||
driverId: string;
|
||||
amount: number;
|
||||
platformFee: number;
|
||||
netAmount: number;
|
||||
status: MemberPaymentStatus;
|
||||
dueDate: Date;
|
||||
paidAt: Date | undefined;
|
||||
}
|
||||
|
||||
// View Models
|
||||
|
||||
export interface CreatePaymentViewModel {
|
||||
payment: PaymentDto;
|
||||
}
|
||||
|
||||
export interface GetPaymentsViewModel {
|
||||
payments: PaymentDto[];
|
||||
}
|
||||
|
||||
export interface GetPrizesViewModel {
|
||||
prizes: PrizeDto[];
|
||||
}
|
||||
|
||||
export interface CreatePrizeViewModel {
|
||||
prize: PrizeDto;
|
||||
}
|
||||
|
||||
export interface AwardPrizeViewModel {
|
||||
prize: PrizeDto;
|
||||
}
|
||||
|
||||
export interface DeletePrizeViewModel {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface GetWalletViewModel {
|
||||
wallet: WalletDto;
|
||||
transactions: TransactionDto[];
|
||||
}
|
||||
|
||||
export interface ProcessWalletTransactionViewModel {
|
||||
wallet: WalletDto;
|
||||
transaction: TransactionDto;
|
||||
}
|
||||
|
||||
export interface GetMembershipFeesViewModel {
|
||||
fee: MembershipFeeDto | null;
|
||||
payments: MemberPaymentDto[];
|
||||
}
|
||||
|
||||
export interface UpsertMembershipFeeViewModel {
|
||||
fee: MembershipFeeDto;
|
||||
}
|
||||
|
||||
export interface UpdateMemberPaymentViewModel {
|
||||
payment: MemberPaymentDto;
|
||||
}
|
||||
|
||||
export interface UpdatePaymentStatusViewModel {
|
||||
payment: PaymentDto;
|
||||
}
|
||||
|
||||
// Sponsor Billing
|
||||
|
||||
export interface SponsorBillingStats {
|
||||
totalSpent: number;
|
||||
pendingAmount: number;
|
||||
nextPaymentDate: string | null;
|
||||
nextPaymentAmount: number | null;
|
||||
activeSponsorships: number;
|
||||
averageMonthlySpend: number;
|
||||
}
|
||||
|
||||
export interface SponsorInvoiceSummary {
|
||||
id: string;
|
||||
invoiceNumber: string;
|
||||
date: string;
|
||||
dueDate: string;
|
||||
amount: number;
|
||||
vatAmount: number;
|
||||
totalAmount: number;
|
||||
status: 'paid' | 'pending' | 'overdue' | 'failed';
|
||||
description: string;
|
||||
sponsorshipType: 'league' | 'team' | 'driver' | 'race' | 'platform';
|
||||
pdfUrl: string;
|
||||
}
|
||||
|
||||
export interface SponsorPaymentMethodSummary {
|
||||
id: string;
|
||||
type: 'card' | 'bank' | 'sepa';
|
||||
last4: string;
|
||||
brand?: string;
|
||||
isDefault: boolean;
|
||||
expiryMonth?: number;
|
||||
expiryYear?: number;
|
||||
bankName?: string;
|
||||
}
|
||||
|
||||
export interface SponsorBillingSummary {
|
||||
paymentMethods: SponsorPaymentMethodSummary[];
|
||||
invoices: SponsorInvoiceSummary[];
|
||||
stats: SponsorBillingStats;
|
||||
}
|
||||
@@ -1,31 +1,54 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"lib": ["es2022", "dom"],
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"incremental": true,
|
||||
"lib": [
|
||||
"es2022",
|
||||
"dom"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noEmit": false,
|
||||
"noEmitOnError": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
],
|
||||
"@adapters/*": [
|
||||
"../../adapters/*"
|
||||
],
|
||||
"@core/*": [
|
||||
"../../core/*"
|
||||
],
|
||||
"@testing/*": [
|
||||
"../../testing/*"
|
||||
]
|
||||
},
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["node", "express", "vitest/globals"],
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"@core/*": ["../../core/*"],
|
||||
"@adapters/*": ["../../adapters/*"],
|
||||
"@testing/*": ["../../testing/*"]
|
||||
}
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "es2017",
|
||||
"types": [
|
||||
"node",
|
||||
"express",
|
||||
"vitest/globals"
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*", "../../adapters/bootstrap/EnsureInitialData.ts"],
|
||||
"exclude": ["node_modules", "dist", "**/*.mock.ts"]
|
||||
}
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.mock.ts"
|
||||
],
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@@ -11,13 +11,14 @@ import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { Driver } from '../../domain/entities/Driver';
|
||||
import type { Penalty } from '../../domain/entities/penalty/Penalty';
|
||||
|
||||
export type GetRacePenaltiesInput = {
|
||||
raceId: string;
|
||||
};
|
||||
|
||||
export type GetRacePenaltiesResult = {
|
||||
penalties: unknown[];
|
||||
penalties: Penalty[];
|
||||
drivers: Driver[];
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user