presenter refactoring
This commit is contained in:
@@ -46,7 +46,7 @@ describe('DriverController', () => {
|
||||
describe('getDriversLeaderboard', () => {
|
||||
it('should return drivers leaderboard', async () => {
|
||||
const leaderboard: DriversLeaderboardDTO = { items: [] };
|
||||
service.getDriversLeaderboard.mockResolvedValue(leaderboard);
|
||||
service.getDriversLeaderboard.mockResolvedValue({ viewModel: leaderboard } as never);
|
||||
|
||||
const result = await controller.getDriversLeaderboard();
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('DriverController', () => {
|
||||
describe('getTotalDrivers', () => {
|
||||
it('should return total drivers stats', async () => {
|
||||
const stats: DriverStatsDTO = { totalDrivers: 100 };
|
||||
service.getTotalDrivers.mockResolvedValue(stats);
|
||||
service.getTotalDrivers.mockResolvedValue({ viewModel: stats } as never);
|
||||
|
||||
const result = await controller.getTotalDrivers();
|
||||
|
||||
@@ -70,8 +70,8 @@ describe('DriverController', () => {
|
||||
describe('getCurrentDriver', () => {
|
||||
it('should return current driver if userId exists', async () => {
|
||||
const userId = 'user-123';
|
||||
const driver: GetDriverOutputDTO = { id: 'driver-123', name: 'Driver' };
|
||||
service.getCurrentDriver.mockResolvedValue(driver);
|
||||
const driver: GetDriverOutputDTO = { id: 'driver-123', name: 'Driver' } as GetDriverOutputDTO;
|
||||
service.getCurrentDriver.mockResolvedValue({ viewModel: driver } as never);
|
||||
|
||||
const mockReq: Partial<AuthenticatedRequest> = { user: { userId } };
|
||||
|
||||
@@ -94,9 +94,9 @@ describe('DriverController', () => {
|
||||
describe('completeOnboarding', () => {
|
||||
it('should complete onboarding', async () => {
|
||||
const userId = 'user-123';
|
||||
const input: CompleteOnboardingInputDTO = { someField: 'value' };
|
||||
const input: CompleteOnboardingInputDTO = { someField: 'value' } as CompleteOnboardingInputDTO;
|
||||
const output: CompleteOnboardingOutputDTO = { success: true };
|
||||
service.completeOnboarding.mockResolvedValue(output);
|
||||
service.completeOnboarding.mockResolvedValue({ viewModel: output } as never);
|
||||
|
||||
const mockReq: Partial<AuthenticatedRequest> = { user: { userId } };
|
||||
|
||||
@@ -111,8 +111,8 @@ describe('DriverController', () => {
|
||||
it('should return registration status', async () => {
|
||||
const driverId = 'driver-123';
|
||||
const raceId = 'race-456';
|
||||
const status: DriverRegistrationStatusDTO = { registered: true };
|
||||
service.getDriverRegistrationStatus.mockResolvedValue(status);
|
||||
const status: DriverRegistrationStatusDTO = { registered: true } as DriverRegistrationStatusDTO;
|
||||
service.getDriverRegistrationStatus.mockResolvedValue({ viewModel: status } as never);
|
||||
|
||||
const result = await controller.getDriverRegistrationStatus(driverId, raceId);
|
||||
|
||||
@@ -124,8 +124,8 @@ describe('DriverController', () => {
|
||||
describe('getDriver', () => {
|
||||
it('should return driver by id', async () => {
|
||||
const driverId = 'driver-123';
|
||||
const driver: GetDriverOutputDTO = { id: driverId, name: 'Driver' };
|
||||
service.getDriver.mockResolvedValue(driver);
|
||||
const driver: GetDriverOutputDTO = { id: driverId, name: 'Driver' } as GetDriverOutputDTO;
|
||||
service.getDriver.mockResolvedValue({ viewModel: driver } as never);
|
||||
|
||||
const result = await controller.getDriver(driverId);
|
||||
|
||||
|
||||
@@ -25,14 +25,16 @@ export class DriverController {
|
||||
@ApiOperation({ summary: 'Get drivers leaderboard' })
|
||||
@ApiResponse({ status: 200, description: 'List of drivers for the leaderboard', type: DriversLeaderboardDTO })
|
||||
async getDriversLeaderboard(): Promise<DriversLeaderboardDTO> {
|
||||
return this.driverService.getDriversLeaderboard();
|
||||
const presenter = await this.driverService.getDriversLeaderboard();
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get('total-drivers')
|
||||
@ApiOperation({ summary: 'Get the total number of drivers' })
|
||||
@ApiResponse({ status: 200, description: 'Total number of drivers', type: DriverStatsDTO })
|
||||
async getTotalDrivers(): Promise<DriverStatsDTO> {
|
||||
return this.driverService.getTotalDrivers();
|
||||
const presenter = await this.driverService.getTotalDrivers();
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get('current')
|
||||
@@ -40,12 +42,13 @@ export class DriverController {
|
||||
@ApiResponse({ status: 200, description: 'Current driver data', type: GetDriverOutputDTO })
|
||||
@ApiResponse({ status: 404, description: 'Driver not found' })
|
||||
async getCurrentDriver(@Req() req: AuthenticatedRequest): Promise<GetDriverOutputDTO | null> {
|
||||
// Assuming userId is available from the request (e.g., via auth middleware)
|
||||
const userId = req.user?.userId;
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
return this.driverService.getCurrentDriver(userId);
|
||||
|
||||
const presenter = await this.driverService.getCurrentDriver(userId);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Post('complete-onboarding')
|
||||
@@ -55,9 +58,9 @@ export class DriverController {
|
||||
@Body() input: CompleteOnboardingInputDTO,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
): Promise<CompleteOnboardingOutputDTO> {
|
||||
// Assuming userId is available from the request (e.g., via auth middleware)
|
||||
const userId = req.user!.userId; // Placeholder for actual user extraction
|
||||
return this.driverService.completeOnboarding(userId, input);
|
||||
const userId = req.user!.userId;
|
||||
const presenter = await this.driverService.completeOnboarding(userId, input);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get(':driverId/races/:raceId/registration-status')
|
||||
@@ -67,7 +70,8 @@ export class DriverController {
|
||||
@Param('driverId') driverId: string,
|
||||
@Param('raceId') raceId: string,
|
||||
): Promise<DriverRegistrationStatusDTO> {
|
||||
return this.driverService.getDriverRegistrationStatus({ driverId, raceId });
|
||||
const presenter = await this.driverService.getDriverRegistrationStatus({ driverId, raceId });
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get(':driverId')
|
||||
@@ -75,7 +79,8 @@ export class DriverController {
|
||||
@ApiResponse({ status: 200, description: 'Driver data', type: GetDriverOutputDTO })
|
||||
@ApiResponse({ status: 404, description: 'Driver not found' })
|
||||
async getDriver(@Param('driverId') driverId: string): Promise<GetDriverOutputDTO | null> {
|
||||
return this.driverService.getDriver(driverId);
|
||||
const presenter = await this.driverService.getDriver(driverId);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get(':driverId/profile')
|
||||
@@ -83,7 +88,8 @@ export class DriverController {
|
||||
@ApiResponse({ status: 200, description: 'Driver profile data', type: GetDriverProfileOutputDTO })
|
||||
@ApiResponse({ status: 404, description: 'Driver not found' })
|
||||
async getDriverProfile(@Param('driverId') driverId: string): Promise<GetDriverProfileOutputDTO> {
|
||||
return this.driverService.getDriverProfile(driverId);
|
||||
const presenter = await this.driverService.getDriverProfile(driverId);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Put(':driverId/profile')
|
||||
@@ -93,7 +99,8 @@ export class DriverController {
|
||||
@Param('driverId') driverId: string,
|
||||
@Body() body: { bio?: string; country?: string },
|
||||
): Promise<GetDriverOutputDTO | null> {
|
||||
return this.driverService.updateDriverProfile(driverId, body.bio, body.country);
|
||||
const presenter = await this.driverService.updateDriverProfile(driverId, body.bio, body.country);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
// Add other Driver endpoints here based on other presenters
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { DriversLeaderboardDTO } from './dtos/DriversLeaderboardDTO';
|
||||
import { DriverStatsDTO } from './dtos/DriverStatsDTO';
|
||||
import { CompleteOnboardingInputDTO } from './dtos/CompleteOnboardingInputDTO';
|
||||
import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO';
|
||||
import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
|
||||
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
|
||||
import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
|
||||
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
|
||||
|
||||
// Use cases
|
||||
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
|
||||
@@ -14,42 +8,66 @@ import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTo
|
||||
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
|
||||
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
|
||||
import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
|
||||
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
||||
|
||||
// Presenters
|
||||
import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
|
||||
import { DriversLeaderboardPresenter } from './presenters/DriversLeaderboardPresenter';
|
||||
import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter';
|
||||
import { DriverRegistrationStatusPresenter } from './presenters/DriverRegistrationStatusPresenter';
|
||||
import { DriverPresenter } from './presenters/DriverPresenter';
|
||||
import { DriverProfilePresenter } from './presenters/DriverProfilePresenter';
|
||||
|
||||
// Tokens
|
||||
import { GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN, GET_TOTAL_DRIVERS_USE_CASE_TOKEN, COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN, IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN, UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN, GET_PROFILE_OVERVIEW_USE_CASE_TOKEN, LOGGER_TOKEN, DRIVER_REPOSITORY_TOKEN } from './DriverProviders';
|
||||
import type { ProfileOverviewOutputPort } from '@core/racing/application/ports/output/ProfileOverviewOutputPort';
|
||||
import {
|
||||
GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
|
||||
GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
|
||||
COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
|
||||
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
|
||||
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
|
||||
GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
DRIVER_REPOSITORY_TOKEN,
|
||||
} from './DriverProviders';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||
import { UpdateDriverProfileUseCase } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
||||
|
||||
@Injectable()
|
||||
export class DriverService {
|
||||
constructor(
|
||||
@Inject(GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN) private readonly getDriversLeaderboardUseCase: GetDriversLeaderboardUseCase,
|
||||
@Inject(GET_TOTAL_DRIVERS_USE_CASE_TOKEN) private readonly getTotalDriversUseCase: GetTotalDriversUseCase,
|
||||
@Inject(COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN) private readonly completeDriverOnboardingUseCase: CompleteDriverOnboardingUseCase,
|
||||
@Inject(IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN) private readonly isDriverRegisteredForRaceUseCase: IsDriverRegisteredForRaceUseCase,
|
||||
@Inject(UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN) private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase,
|
||||
@Inject(GET_PROFILE_OVERVIEW_USE_CASE_TOKEN) private readonly getProfileOverviewUseCase: GetProfileOverviewUseCase,
|
||||
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
@Inject(GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN)
|
||||
private readonly getDriversLeaderboardUseCase: GetDriversLeaderboardUseCase,
|
||||
@Inject(GET_TOTAL_DRIVERS_USE_CASE_TOKEN)
|
||||
private readonly getTotalDriversUseCase: GetTotalDriversUseCase,
|
||||
@Inject(COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN)
|
||||
private readonly completeDriverOnboardingUseCase: CompleteDriverOnboardingUseCase,
|
||||
@Inject(IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN)
|
||||
private readonly isDriverRegisteredForRaceUseCase: IsDriverRegisteredForRaceUseCase,
|
||||
@Inject(UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN)
|
||||
private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase,
|
||||
@Inject(GET_PROFILE_OVERVIEW_USE_CASE_TOKEN)
|
||||
private readonly getProfileOverviewUseCase: GetProfileOverviewUseCase,
|
||||
@Inject(DRIVER_REPOSITORY_TOKEN)
|
||||
private readonly driverRepository: IDriverRepository,
|
||||
@Inject(LOGGER_TOKEN)
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async getDriversLeaderboard(): Promise<DriversLeaderboardDTO> {
|
||||
async getDriversLeaderboard(): Promise<DriversLeaderboardPresenter> {
|
||||
this.logger.debug('[DriverService] Fetching drivers leaderboard.');
|
||||
|
||||
const result = await this.getDriversLeaderboardUseCase.execute();
|
||||
if (result.isOk()) {
|
||||
return result.value as DriversLeaderboardDTO;
|
||||
} else {
|
||||
throw new Error(`Failed to fetch drivers leaderboard: ${result.error.details.message}`);
|
||||
if (result.isErr()) {
|
||||
throw new Error(`Failed to fetch drivers leaderboard: ${result.unwrapErr().details.message}`);
|
||||
}
|
||||
|
||||
const presenter = new DriversLeaderboardPresenter();
|
||||
presenter.reset();
|
||||
presenter.present(result.unwrap());
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async getTotalDrivers(): Promise<DriverStatsDTO> {
|
||||
async getTotalDrivers(): Promise<DriverStatsPresenter> {
|
||||
this.logger.debug('[DriverService] Fetching total drivers count.');
|
||||
|
||||
const result = await this.getTotalDriversUseCase.execute();
|
||||
@@ -58,11 +76,12 @@ export class DriverService {
|
||||
}
|
||||
|
||||
const presenter = new DriverStatsPresenter();
|
||||
presenter.reset();
|
||||
presenter.present(result.unwrap());
|
||||
return presenter.viewModel;
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async completeOnboarding(userId: string, input: CompleteOnboardingInputDTO): Promise<CompleteOnboardingOutputDTO> {
|
||||
async completeOnboarding(userId: string, input: CompleteOnboardingInputDTO): Promise<CompleteOnboardingPresenter> {
|
||||
this.logger.debug('Completing onboarding for user:', userId);
|
||||
|
||||
const result = await this.completeDriverOnboardingUseCase.execute({
|
||||
@@ -75,80 +94,88 @@ export class DriverService {
|
||||
bio: input.bio,
|
||||
});
|
||||
|
||||
const presenter = new CompleteOnboardingPresenter();
|
||||
presenter.reset();
|
||||
|
||||
if (result.isOk()) {
|
||||
return {
|
||||
success: true,
|
||||
driverId: result.value.driverId,
|
||||
};
|
||||
presenter.present(result.value);
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
errorMessage: result.error.code,
|
||||
};
|
||||
presenter.presentError(result.error.code);
|
||||
}
|
||||
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async getDriverRegistrationStatus(query: GetDriverRegistrationStatusQueryDTO): Promise<DriverRegistrationStatusDTO> {
|
||||
async getDriverRegistrationStatus(
|
||||
query: GetDriverRegistrationStatusQueryDTO,
|
||||
): Promise<DriverRegistrationStatusPresenter> {
|
||||
this.logger.debug('Checking driver registration status:', query);
|
||||
|
||||
const result = await this.isDriverRegisteredForRaceUseCase.execute({ raceId: query.raceId, driverId: query.driverId });
|
||||
if (result.isOk()) {
|
||||
return result.value;
|
||||
} else {
|
||||
// For now, throw error or handle appropriately. Since it's a query, perhaps return default or throw.
|
||||
throw new Error(`Failed to check registration status: ${result.error.code}`);
|
||||
const result = await this.isDriverRegisteredForRaceUseCase.execute({
|
||||
raceId: query.raceId,
|
||||
driverId: query.driverId,
|
||||
});
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new Error(`Failed to check registration status: ${result.unwrapErr().code}`);
|
||||
}
|
||||
|
||||
const presenter = new DriverRegistrationStatusPresenter();
|
||||
presenter.reset();
|
||||
|
||||
const output = result.unwrap();
|
||||
presenter.present(output.isRegistered, output.raceId, output.driverId);
|
||||
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
|
||||
async getCurrentDriver(userId: string): Promise<DriverPresenter> {
|
||||
this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`);
|
||||
|
||||
const driver = await this.driverRepository.findById(userId);
|
||||
if (!driver) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: driver.id,
|
||||
iracingId: driver.iracingId.value,
|
||||
name: driver.name.value,
|
||||
country: driver.country.value,
|
||||
bio: driver.bio?.value,
|
||||
joinedAt: driver.joinedAt.toISOString(),
|
||||
};
|
||||
const presenter = new DriverPresenter();
|
||||
presenter.reset();
|
||||
presenter.present(driver ?? null);
|
||||
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async updateDriverProfile(driverId: string, bio?: string, country?: string): Promise<GetDriverOutputDTO | null> {
|
||||
async updateDriverProfile(
|
||||
driverId: string,
|
||||
bio?: string,
|
||||
country?: string,
|
||||
): Promise<DriverPresenter> {
|
||||
this.logger.debug(`[DriverService] Updating driver profile for driverId: ${driverId}`);
|
||||
|
||||
const result = await this.updateDriverProfileUseCase.execute({ driverId, bio, country });
|
||||
|
||||
const presenter = new DriverPresenter();
|
||||
presenter.reset();
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Failed to update driver profile: ${result.error.code}`);
|
||||
return null;
|
||||
presenter.present(null);
|
||||
return presenter;
|
||||
}
|
||||
|
||||
return result.value;
|
||||
presenter.present(result.value);
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
|
||||
async getDriver(driverId: string): Promise<DriverPresenter> {
|
||||
this.logger.debug(`[DriverService] Fetching driver for driverId: ${driverId}`);
|
||||
|
||||
const driver = await this.driverRepository.findById(driverId);
|
||||
if (!driver) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: driver.id,
|
||||
iracingId: driver.iracingId.value,
|
||||
name: driver.name.value,
|
||||
country: driver.country.value,
|
||||
bio: driver.bio?.value,
|
||||
joinedAt: driver.joinedAt.toISOString(),
|
||||
};
|
||||
const presenter = new DriverPresenter();
|
||||
presenter.reset();
|
||||
presenter.present(driver ?? null);
|
||||
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async getDriverProfile(driverId: string): Promise<GetDriverProfileOutputDTO> {
|
||||
async getDriverProfile(driverId: string): Promise<DriverProfilePresenter> {
|
||||
this.logger.debug(`[DriverService] Fetching driver profile for driverId: ${driverId}`);
|
||||
|
||||
const result = await this.getProfileOverviewUseCase.execute({ driverId });
|
||||
@@ -156,37 +183,10 @@ export class DriverService {
|
||||
throw new Error(`Failed to fetch driver profile: ${result.error.code}`);
|
||||
}
|
||||
|
||||
const outputPort = result.value;
|
||||
return this.mapProfileOverviewToDTO(outputPort);
|
||||
}
|
||||
const presenter = new DriverProfilePresenter();
|
||||
presenter.reset();
|
||||
presenter.present(result.value);
|
||||
|
||||
private mapProfileOverviewToDTO(outputPort: ProfileOverviewOutputPort): GetDriverProfileOutputDTO {
|
||||
return {
|
||||
currentDriver: outputPort.driver ? {
|
||||
id: outputPort.driver.id,
|
||||
name: outputPort.driver.name,
|
||||
country: outputPort.driver.country,
|
||||
avatarUrl: outputPort.driver.avatarUrl,
|
||||
iracingId: outputPort.driver.iracingId,
|
||||
joinedAt: outputPort.driver.joinedAt.toISOString(),
|
||||
rating: outputPort.driver.rating,
|
||||
globalRank: outputPort.driver.globalRank,
|
||||
consistency: outputPort.driver.consistency,
|
||||
bio: outputPort.driver.bio,
|
||||
totalDrivers: outputPort.driver.totalDrivers,
|
||||
} : null,
|
||||
stats: outputPort.stats,
|
||||
finishDistribution: outputPort.finishDistribution,
|
||||
teamMemberships: outputPort.teamMemberships.map(membership => ({
|
||||
teamId: membership.teamId,
|
||||
teamName: membership.teamName,
|
||||
teamTag: membership.teamTag,
|
||||
role: membership.role,
|
||||
joinedAt: membership.joinedAt.toISOString(),
|
||||
isCurrent: membership.isCurrent,
|
||||
})),
|
||||
socialSummary: outputPort.socialSummary,
|
||||
extendedProfile: outputPort.extendedProfile,
|
||||
};
|
||||
return presenter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { CompleteDriverOnboardingOutputPort } from '@core/racing/application/ports/output/CompleteDriverOnboardingOutputPort';
|
||||
import type { CompleteOnboardingOutputDTO } from '../dtos/CompleteOnboardingOutputDTO';
|
||||
|
||||
export class CompleteOnboardingPresenter {
|
||||
private result: CompleteOnboardingOutputDTO | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: CompleteDriverOnboardingOutputPort): void {
|
||||
this.result = {
|
||||
success: true,
|
||||
driverId: output.driverId,
|
||||
};
|
||||
}
|
||||
|
||||
presentError(errorCode: string): void {
|
||||
this.result = {
|
||||
success: false,
|
||||
errorMessage: errorCode,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): CompleteOnboardingOutputDTO {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
30
apps/api/src/domain/driver/presenters/DriverPresenter.ts
Normal file
30
apps/api/src/domain/driver/presenters/DriverPresenter.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { Driver } from '@core/racing/domain/entities/Driver';
|
||||
import type { GetDriverOutputDTO } from '../dtos/GetDriverOutputDTO';
|
||||
|
||||
export class DriverPresenter {
|
||||
private result: GetDriverOutputDTO | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(driver: Driver | null): void {
|
||||
if (!driver) {
|
||||
this.result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.result = {
|
||||
id: driver.id,
|
||||
iracingId: driver.iracingId.toString(),
|
||||
name: driver.name.toString(),
|
||||
country: driver.country.toString(),
|
||||
bio: driver.bio?.toString(),
|
||||
joinedAt: driver.joinedAt.toDate().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): GetDriverOutputDTO | null {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { ProfileOverviewOutputPort } from '@core/racing/application/ports/output/ProfileOverviewOutputPort';
|
||||
import type { GetDriverProfileOutputDTO } from '../dtos/GetDriverProfileOutputDTO';
|
||||
|
||||
export class DriverProfilePresenter {
|
||||
private result: GetDriverProfileOutputDTO | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: ProfileOverviewOutputPort): void {
|
||||
this.result = {
|
||||
currentDriver: output.driver
|
||||
? {
|
||||
id: output.driver.id,
|
||||
name: output.driver.name,
|
||||
country: output.driver.country,
|
||||
avatarUrl: output.driver.avatarUrl,
|
||||
iracingId: output.driver.iracingId,
|
||||
joinedAt: output.driver.joinedAt.toISOString(),
|
||||
rating: output.driver.rating,
|
||||
globalRank: output.driver.globalRank,
|
||||
consistency: output.driver.consistency,
|
||||
bio: output.driver.bio,
|
||||
totalDrivers: output.driver.totalDrivers,
|
||||
}
|
||||
: null,
|
||||
stats: output.stats,
|
||||
finishDistribution: output.finishDistribution,
|
||||
teamMemberships: output.teamMemberships.map(membership => ({
|
||||
teamId: membership.teamId,
|
||||
teamName: membership.teamName,
|
||||
teamTag: membership.teamTag,
|
||||
role: membership.role,
|
||||
joinedAt: membership.joinedAt.toISOString(),
|
||||
isCurrent: membership.isCurrent,
|
||||
})),
|
||||
socialSummary: output.socialSummary,
|
||||
extendedProfile: output.extendedProfile,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): GetDriverProfileOutputDTO {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { DriverRegistrationStatusDTO } from '../dtos/DriverRegistrationStatusDTO';
|
||||
|
||||
export class DriverRegistrationStatusPresenter {
|
||||
private result: DriverRegistrationStatusDTO | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(isRegistered: boolean, raceId: string, driverId: string): void {
|
||||
this.result = {
|
||||
isRegistered,
|
||||
raceId,
|
||||
driverId,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): DriverRegistrationStatusDTO {
|
||||
if (!this.result) {
|
||||
throw new Error('Presenter not presented');
|
||||
}
|
||||
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,31 @@
|
||||
import { DriversLeaderboardDTO } from '../dtos/DriversLeaderboardDTO';
|
||||
import { DriverLeaderboardItemDTO } from '../dtos/DriverLeaderboardItemDTO';
|
||||
import type { IDriversLeaderboardPresenter, DriversLeaderboardResultDTO } from '../../../../../core/racing/application/presenters/IDriversLeaderboardPresenter';
|
||||
import { SkillLevelService } from '../../../../../core/racing/domain/services/SkillLevelService';
|
||||
import type { DriversLeaderboardOutputPort } from '../../../../../core/racing/application/ports/output/DriversLeaderboardOutputPort';
|
||||
|
||||
export class DriversLeaderboardPresenter implements IDriversLeaderboardPresenter {
|
||||
export class DriversLeaderboardPresenter {
|
||||
private result: DriversLeaderboardDTO | null = null;
|
||||
|
||||
reset() {
|
||||
reset(): void {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: DriversLeaderboardResultDTO) {
|
||||
const drivers: DriverLeaderboardItemDTO[] = dto.drivers.map(driver => {
|
||||
const ranking = dto.rankings.find(r => r.driverId === driver.id);
|
||||
const stats = dto.stats[driver.id];
|
||||
const avatarUrl = dto.avatarUrls[driver.id];
|
||||
|
||||
const rating = ranking?.rating ?? 0;
|
||||
const racesCompleted = stats?.racesCompleted ?? 0;
|
||||
|
||||
return {
|
||||
present(output: DriversLeaderboardOutputPort): void {
|
||||
this.result = {
|
||||
drivers: output.drivers.map(driver => ({
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
rating,
|
||||
// Use core SkillLevelService to derive band from rating
|
||||
skillLevel: SkillLevelService.getSkillLevel(rating),
|
||||
nationality: driver.country,
|
||||
racesCompleted,
|
||||
wins: stats?.wins ?? 0,
|
||||
podiums: stats?.podiums ?? 0,
|
||||
// Consider a driver active if they have completed at least one race
|
||||
isActive: racesCompleted > 0,
|
||||
rank: ranking?.overallRank ?? 0,
|
||||
avatarUrl,
|
||||
};
|
||||
});
|
||||
|
||||
// Calculate totals
|
||||
const totalRaces = drivers.reduce((sum, d) => sum + (d.racesCompleted ?? 0), 0);
|
||||
const totalWins = drivers.reduce((sum, d) => sum + (d.wins ?? 0), 0);
|
||||
const activeCount = drivers.filter(d => d.isActive).length;
|
||||
|
||||
this.result = {
|
||||
drivers: drivers.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0)),
|
||||
totalRaces,
|
||||
totalWins,
|
||||
activeCount,
|
||||
rating: driver.rating,
|
||||
skillLevel: driver.skillLevel,
|
||||
nationality: driver.nationality,
|
||||
racesCompleted: driver.racesCompleted,
|
||||
wins: driver.wins,
|
||||
podiums: driver.podiums,
|
||||
isActive: driver.isActive,
|
||||
rank: driver.rank,
|
||||
avatarUrl: driver.avatarUrl,
|
||||
})),
|
||||
totalRaces: output.totalRaces,
|
||||
totalWins: output.totalWins,
|
||||
activeCount: output.activeCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user