refactor
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Post, Body, Req, Param } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Put, Body, Req, Param } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
|
||||
|
||||
@@ -25,16 +25,14 @@ export class DriverController {
|
||||
@ApiOperation({ summary: 'Get drivers leaderboard' })
|
||||
@ApiResponse({ status: 200, description: 'List of drivers for the leaderboard', type: DriversLeaderboardDTO })
|
||||
async getDriversLeaderboard(): Promise<DriversLeaderboardDTO> {
|
||||
const presenter = await this.driverService.getDriversLeaderboard();
|
||||
return presenter.viewModel;
|
||||
return await this.driverService.getDriversLeaderboard();
|
||||
}
|
||||
|
||||
@Get('total-drivers')
|
||||
@ApiOperation({ summary: 'Get the total number of drivers' })
|
||||
@ApiResponse({ status: 200, description: 'Total number of drivers', type: DriverStatsDTO })
|
||||
async getTotalDrivers(): Promise<DriverStatsDTO> {
|
||||
const presenter = await this.driverService.getTotalDrivers();
|
||||
return presenter.viewModel;
|
||||
return await this.driverService.getTotalDrivers();
|
||||
}
|
||||
|
||||
@Get('current')
|
||||
@@ -47,8 +45,7 @@ export class DriverController {
|
||||
return null;
|
||||
}
|
||||
|
||||
const presenter = await this.driverService.getCurrentDriver(userId);
|
||||
return presenter.viewModel;
|
||||
return await this.driverService.getCurrentDriver(userId);
|
||||
}
|
||||
|
||||
@Post('complete-onboarding')
|
||||
@@ -59,8 +56,7 @@ export class DriverController {
|
||||
@Req() req: AuthenticatedRequest,
|
||||
): Promise<CompleteOnboardingOutputDTO> {
|
||||
const userId = req.user!.userId;
|
||||
const presenter = await this.driverService.completeOnboarding(userId, input);
|
||||
return presenter.viewModel;
|
||||
return await this.driverService.completeOnboarding(userId, input);
|
||||
}
|
||||
|
||||
@Get(':driverId/races/:raceId/registration-status')
|
||||
@@ -70,8 +66,7 @@ export class DriverController {
|
||||
@Param('driverId') driverId: string,
|
||||
@Param('raceId') raceId: string,
|
||||
): Promise<DriverRegistrationStatusDTO> {
|
||||
const presenter = await this.driverService.getDriverRegistrationStatus({ driverId, raceId });
|
||||
return presenter.viewModel;
|
||||
return await this.driverService.getDriverRegistrationStatus({ driverId, raceId });
|
||||
}
|
||||
|
||||
@Get(':driverId')
|
||||
@@ -79,8 +74,7 @@ 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> {
|
||||
const presenter = await this.driverService.getDriver(driverId);
|
||||
return presenter.viewModel;
|
||||
return await this.driverService.getDriver(driverId);
|
||||
}
|
||||
|
||||
@Get(':driverId/profile')
|
||||
@@ -88,8 +82,7 @@ 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> {
|
||||
const presenter = await this.driverService.getDriverProfile(driverId);
|
||||
return presenter.viewModel;
|
||||
return await this.driverService.getDriverProfile(driverId);
|
||||
}
|
||||
|
||||
@Put(':driverId/profile')
|
||||
@@ -99,8 +92,7 @@ export class DriverController {
|
||||
@Param('driverId') driverId: string,
|
||||
@Body() body: { bio?: string; country?: string },
|
||||
): Promise<GetDriverOutputDTO | null> {
|
||||
const presenter = await this.driverService.updateDriverProfile(driverId, body.bio, body.country);
|
||||
return presenter.viewModel;
|
||||
return await this.driverService.updateDriverProfile(driverId, body.bio, body.country);
|
||||
}
|
||||
|
||||
// Add other Driver endpoints here based on other presenters
|
||||
|
||||
@@ -69,6 +69,13 @@ export const GET_PROFILE_OVERVIEW_USE_CASE_TOKEN = 'GetProfileOverviewUseCase';
|
||||
|
||||
export const DriverProviders: Provider[] = [
|
||||
DriverService, // Provide the service itself
|
||||
// Presenters
|
||||
DriversLeaderboardPresenter,
|
||||
DriverStatsPresenter,
|
||||
CompleteOnboardingPresenter,
|
||||
DriverRegistrationStatusPresenter,
|
||||
DriverPresenter,
|
||||
DriverProfilePresenter,
|
||||
{
|
||||
provide: DRIVER_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryDriverRepository(logger), // Factory for InMemoryDriverRepository
|
||||
@@ -138,8 +145,9 @@ export const DriverProviders: Provider[] = [
|
||||
driverStatsService: IDriverStatsService,
|
||||
imageService: IImageServicePort,
|
||||
logger: Logger,
|
||||
) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, imageService, logger),
|
||||
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, IMAGE_SERVICE_PORT_TOKEN, LOGGER_TOKEN],
|
||||
presenter: DriversLeaderboardPresenter,
|
||||
) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, imageService, logger, presenter),
|
||||
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, IMAGE_SERVICE_PORT_TOKEN, LOGGER_TOKEN, DriversLeaderboardPresenter.name],
|
||||
},
|
||||
{
|
||||
provide: GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
|
||||
@@ -148,19 +156,19 @@ export const DriverProviders: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
|
||||
useFactory: (driverRepo: IDriverRepository) => new CompleteDriverOnboardingUseCase(driverRepo),
|
||||
inject: [DRIVER_REPOSITORY_TOKEN],
|
||||
useFactory: (driverRepo: IDriverRepository, logger: Logger, presenter: CompleteOnboardingPresenter) => new CompleteDriverOnboardingUseCase(driverRepo, logger, presenter),
|
||||
inject: [DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, CompleteOnboardingPresenter.name],
|
||||
},
|
||||
{
|
||||
provide: IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
|
||||
useFactory: (registrationRepo: IRaceRegistrationRepository, logger: Logger) =>
|
||||
new IsDriverRegisteredForRaceUseCase(registrationRepo, logger),
|
||||
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (registrationRepo: IRaceRegistrationRepository, logger: Logger, presenter: DriverRegistrationStatusPresenter) =>
|
||||
new IsDriverRegisteredForRaceUseCase(registrationRepo, logger, presenter),
|
||||
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN, DriverRegistrationStatusPresenter.name],
|
||||
},
|
||||
{
|
||||
provide: UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
|
||||
useFactory: (driverRepo: IDriverRepository) => new UpdateDriverProfileUseCase(driverRepo),
|
||||
inject: [DRIVER_REPOSITORY_TOKEN],
|
||||
useFactory: (driverRepo: IDriverRepository, presenter: DriverPresenter, logger: Logger) => new UpdateDriverProfileUseCase(driverRepo, presenter, logger),
|
||||
inject: [DRIVER_REPOSITORY_TOKEN, DriverPresenter.name, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
|
||||
@@ -173,6 +181,7 @@ export const DriverProviders: Provider[] = [
|
||||
driverExtendedProfileProvider: DriverExtendedProfileProvider,
|
||||
driverStatsService: IDriverStatsService,
|
||||
rankingService: IRankingService,
|
||||
presenter: DriverProfilePresenter,
|
||||
) =>
|
||||
new GetProfileOverviewUseCase(
|
||||
driverRepo,
|
||||
@@ -207,6 +216,7 @@ export const DriverProviders: Provider[] = [
|
||||
rating: ranking.rating,
|
||||
overallRank: ranking.overallRank,
|
||||
})),
|
||||
presenter,
|
||||
),
|
||||
inject: [
|
||||
DRIVER_REPOSITORY_TOKEN,
|
||||
@@ -217,6 +227,7 @@ export const DriverProviders: Provider[] = [
|
||||
DRIVER_EXTENDED_PROFILE_PROVIDER_TOKEN,
|
||||
DRIVER_STATS_SERVICE_TOKEN,
|
||||
RANKING_SERVICE_TOKEN,
|
||||
DriverProfilePresenter.name,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -14,7 +14,7 @@ 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';
|
||||
import { UpdateDriverProfileUseCase, type UpdateDriverProfileInput } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
||||
|
||||
// Presenters
|
||||
import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
|
||||
@@ -70,10 +70,12 @@ export class DriverService {
|
||||
|
||||
const result = await this.getDriversLeaderboardUseCase.execute({});
|
||||
|
||||
const presenter = new DriversLeaderboardPresenter();
|
||||
presenter.present(result);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to load drivers leaderboard');
|
||||
}
|
||||
|
||||
return presenter.getResponseModel();
|
||||
return this.driversLeaderboardPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getTotalDrivers(): Promise<DriverStatsDTO> {
|
||||
@@ -101,14 +103,15 @@ export class DriverService {
|
||||
lastName: input.lastName,
|
||||
displayName: input.displayName,
|
||||
country: input.country,
|
||||
timezone: input.timezone,
|
||||
bio: input.bio,
|
||||
...(input.bio !== undefined ? { bio: input.bio } : {}),
|
||||
});
|
||||
|
||||
const presenter = new CompleteOnboardingPresenter();
|
||||
presenter.present(result);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to complete onboarding');
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.completeOnboardingPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getDriverRegistrationStatus(
|
||||
@@ -121,10 +124,12 @@ export class DriverService {
|
||||
driverId: query.driverId,
|
||||
});
|
||||
|
||||
const presenter = new DriverRegistrationStatusPresenter();
|
||||
presenter.present(result);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to check registration status');
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.driverRegistrationStatusPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
|
||||
@@ -132,10 +137,9 @@ export class DriverService {
|
||||
|
||||
const driver = await this.driverRepository.findById(userId);
|
||||
|
||||
const presenter = new DriverPresenter();
|
||||
presenter.present(driver ?? null);
|
||||
this.driverPresenter.present(driver ?? null);
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.driverPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async updateDriverProfile(
|
||||
@@ -145,19 +149,21 @@ export class DriverService {
|
||||
): Promise<GetDriverOutputDTO | null> {
|
||||
this.logger.debug(`[DriverService] Updating driver profile for driverId: ${driverId}`);
|
||||
|
||||
const result = await this.updateDriverProfileUseCase.execute({ driverId, bio, country });
|
||||
const input: UpdateDriverProfileInput = { driverId };
|
||||
if (bio !== undefined) input.bio = bio;
|
||||
if (country !== undefined) input.country = country;
|
||||
|
||||
const presenter = new DriverPresenter();
|
||||
const result = await this.updateDriverProfileUseCase.execute(input);
|
||||
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Failed to update driver profile: ${result.error.code}`);
|
||||
presenter.present(null);
|
||||
return presenter.responseModel;
|
||||
this.logger.error(`Failed to update driver profile: ${result.unwrapErr().code}`);
|
||||
this.driverPresenter.present(null);
|
||||
return this.driverPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
const updatedDriver = await this.driverRepository.findById(driverId);
|
||||
presenter.present(updatedDriver ?? null);
|
||||
return presenter.responseModel;
|
||||
this.driverPresenter.present(updatedDriver ?? null);
|
||||
return this.driverPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
|
||||
@@ -165,10 +171,9 @@ export class DriverService {
|
||||
|
||||
const driver = await this.driverRepository.findById(driverId);
|
||||
|
||||
const presenter = new DriverPresenter();
|
||||
presenter.present(driver ?? null);
|
||||
this.driverPresenter.present(driver ?? null);
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.driverPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getDriverProfile(driverId: string): Promise<GetDriverProfileOutputDTO> {
|
||||
@@ -176,9 +181,11 @@ export class DriverService {
|
||||
|
||||
const result = await this.getProfileOverviewUseCase.execute({ driverId });
|
||||
|
||||
const presenter = new DriverProfilePresenter();
|
||||
presenter.present(result);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to load driver profile');
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.driverProfilePresenter.getResponseModel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ describe('DriverRegistrationStatusPresenter', () => {
|
||||
});
|
||||
|
||||
describe('present', () => {
|
||||
it('should map parameters to view model for registered driver', () => {
|
||||
presenter.present(true, 'race-123', 'driver-456');
|
||||
it('should map parameters to response model for registered driver', () => {
|
||||
presenter.present({ isRegistered: true, raceId: 'race-123', driverId: 'driver-456' });
|
||||
|
||||
const result = presenter.viewModel;
|
||||
const result = presenter.getResponseModel();
|
||||
|
||||
expect(result).toEqual({
|
||||
isRegistered: true,
|
||||
@@ -21,10 +21,10 @@ describe('DriverRegistrationStatusPresenter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should map parameters to view model for unregistered driver', () => {
|
||||
presenter.present(false, 'race-789', 'driver-101');
|
||||
it('should map parameters to response model for unregistered driver', () => {
|
||||
presenter.present({ isRegistered: false, raceId: 'race-789', driverId: 'driver-101' });
|
||||
|
||||
const result = presenter.viewModel;
|
||||
const result = presenter.getResponseModel();
|
||||
|
||||
expect(result).toEqual({
|
||||
isRegistered: false,
|
||||
@@ -36,11 +36,11 @@ describe('DriverRegistrationStatusPresenter', () => {
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the result', () => {
|
||||
presenter.present(true, 'race-123', 'driver-456');
|
||||
expect(presenter.viewModel).toBeDefined();
|
||||
presenter.present({ isRegistered: true, raceId: 'race-123', driverId: 'driver-456' });
|
||||
expect(presenter.getResponseModel()).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,10 @@ export class DriverRegistrationStatusPresenter
|
||||
{
|
||||
private responseModel: DriverRegistrationStatusDTO | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.responseModel = null;
|
||||
}
|
||||
|
||||
present(result: IsDriverRegisteredForRaceResult): void {
|
||||
this.responseModel = {
|
||||
isRegistered: result.isRegistered,
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import { DriversLeaderboardDTO } from '../dtos/DriversLeaderboardDTO';
|
||||
import type {
|
||||
GetDriversLeaderboardResult,
|
||||
GetDriversLeaderboardErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
export type DriversLeaderboardApplicationError = ApplicationErrorCode<
|
||||
GetDriversLeaderboardErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
export class DriversLeaderboardPresenter implements UseCaseOutputPort<GetDriversLeaderboardResult> {
|
||||
private responseModel: DriversLeaderboardDTO | null = null;
|
||||
|
||||
export class DriversLeaderboardPresenter {
|
||||
present(
|
||||
result: Result<GetDriversLeaderboardResult, DriversLeaderboardApplicationError>,
|
||||
): DriversLeaderboardDTO {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to load drivers leaderboard');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
return {
|
||||
drivers: output.items.map(item => ({
|
||||
present(result: GetDriversLeaderboardResult): void {
|
||||
this.responseModel = {
|
||||
drivers: result.items.map(item => ({
|
||||
id: item.driver.id,
|
||||
name: item.driver.name.toString(),
|
||||
rating: item.rating,
|
||||
@@ -36,9 +22,14 @@ export class DriversLeaderboardPresenter {
|
||||
rank: item.rank,
|
||||
avatarUrl: item.avatarUrl,
|
||||
})),
|
||||
totalRaces: output.items.reduce((sum, d) => sum + d.racesCompleted, 0),
|
||||
totalWins: output.items.reduce((sum, d) => sum + d.wins, 0),
|
||||
activeCount: output.items.filter(d => d.isActive).length,
|
||||
totalRaces: result.totalRaces,
|
||||
totalWins: result.totalWins,
|
||||
activeCount: result.activeCount,
|
||||
};
|
||||
}
|
||||
|
||||
getResponseModel(): DriversLeaderboardDTO {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user