website refactor

This commit is contained in:
2026-01-14 17:25:30 +01:00
parent 4b7d82ab43
commit e3c6146c1d
10 changed files with 243 additions and 1 deletions

View File

@@ -175,6 +175,22 @@
}
}
},
"/drivers/{driverId}/liveries": {
"get": {
"responses": {
"200": {
"description": "List of driver liveries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetDriverLiveriesOutputDTO"
}
}
}
}
}
}
},
"/drivers/{driverId}/races/{raceId}/registration-status": {
"get": {
"responses": {

View File

@@ -20,6 +20,7 @@ import { CompleteOnboardingInputDTO } from './dtos/CompleteOnboardingInputDTO';
import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO';
import { DriversLeaderboardDTO } from './dtos/DriversLeaderboardDTO';
import { DriverStatsDTO } from './dtos/DriverStatsDTO';
import { GetDriverLiveriesOutputDTO } from './dtos/GetDriverLiveriesOutputDTO';
import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
@@ -43,6 +44,7 @@ describe('DriverController', () => {
getDriver: vi.fn(),
getDriverProfile: vi.fn(),
updateDriverProfile: vi.fn(),
getDriverLiveries: vi.fn(),
},
},
],
@@ -170,6 +172,19 @@ describe('DriverController', () => {
});
});
describe('getDriverLiveries', () => {
it('should return driver liveries', async () => {
const driverId = 'driver-123';
const liveries: GetDriverLiveriesOutputDTO = { liveries: [] };
service.getDriverLiveries.mockResolvedValue(liveries);
const result = await controller.getDriverLiveries(driverId);
expect(service.getDriverLiveries).toHaveBeenCalledWith(driverId);
expect(result).toEqual(liveries);
});
});
describe('auth guards (HTTP)', () => {
let app: any;

View File

@@ -12,6 +12,7 @@ import { CompleteOnboardingOutputDTO } from './dtos/CompleteOnboardingOutputDTO'
import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO';
import { DriversLeaderboardDTO } from './dtos/DriversLeaderboardDTO';
import { DriverStatsDTO } from './dtos/DriverStatsDTO';
import { GetDriverLiveriesOutputDTO } from './dtos/GetDriverLiveriesOutputDTO';
import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
@@ -111,5 +112,13 @@ export class DriverController {
return await this.driverService.updateDriverProfile(driverId, body.bio, body.country);
}
@Public()
@Get(':driverId/liveries')
@ApiOperation({ summary: 'Get driver liveries' })
@ApiResponse({ status: 200, description: 'List of driver liveries', type: GetDriverLiveriesOutputDTO })
async getDriverLiveries(@Param('driverId') driverId: string): Promise<GetDriverLiveriesOutputDTO> {
return await this.driverService.getDriverLiveries(driverId);
}
// Add other Driver endpoints here based on other presenters
}

View File

@@ -3,6 +3,7 @@ import { Provider } from '@nestjs/common';
// Import core interfaces
import { DriverExtendedProfileProvider } from '@core/racing/application/ports/DriverExtendedProfileProvider';
import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
import { ILiveryRepository } from '@core/racing/domain/repositories/ILiveryRepository';
import { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
@@ -15,6 +16,7 @@ import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
// Import use cases
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
import { GetDriverLiveriesUseCase } from '@core/racing/application/use-cases/GetDriverLiveriesUseCase';
import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
@@ -26,6 +28,7 @@ import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImage
import { InMemoryNotificationPreferenceRepository } from '@adapters/notifications/persistence/inmemory/InMemoryNotificationPreferenceRepository';
import { InMemoryDriverExtendedProfileProvider } from '@adapters/racing/ports/InMemoryDriverExtendedProfileProvider';
import { InMemoryDriverRatingProvider } from '@adapters/racing/ports/InMemoryDriverRatingProvider';
import { InMemoryLiveryRepository } from '@adapters/racing/persistence/inmemory/InMemoryLiveryRepository';
// Import new use cases
import { RankingUseCase } from '@core/racing/application/use-cases/RankingUseCase';
import { DriverStatsUseCase } from '@core/racing/application/use-cases/DriverStatsUseCase';
@@ -47,6 +50,7 @@ import { DriverProfilePresenter } from './presenters/DriverProfilePresenter';
import { DriverRegistrationStatusPresenter } from './presenters/DriverRegistrationStatusPresenter';
import { DriversLeaderboardPresenter } from './presenters/DriversLeaderboardPresenter';
import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
import { GetDriverLiveriesPresenter } from './presenters/GetDriverLiveriesPresenter';
import {
DRIVER_REPOSITORY_TOKEN,
@@ -58,9 +62,11 @@ import {
TEAM_REPOSITORY_TOKEN,
TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
SOCIAL_GRAPH_REPOSITORY_TOKEN,
LIVERY_REPOSITORY_TOKEN,
LOGGER_TOKEN,
GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
GET_DRIVER_LIVERIES_USE_CASE_TOKEN,
COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
@@ -112,6 +118,7 @@ export const DriverProviders: Provider[] = createLoggedProviders([
},
inject: [MEDIA_RESOLVER_TOKEN],
},
GetDriverLiveriesPresenter,
// Logger
{
@@ -189,6 +196,11 @@ export const DriverProviders: Provider[] = createLoggedProviders([
useFactory: (logger: Logger) => new InMemoryNotificationPreferenceRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: LIVERY_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryLiveryRepository(logger),
inject: [LOGGER_TOKEN],
},
// Use cases
{
@@ -258,4 +270,10 @@ export const DriverProviders: Provider[] = createLoggedProviders([
RANKING_SERVICE_TOKEN,
],
},
{
provide: GET_DRIVER_LIVERIES_USE_CASE_TOKEN,
useFactory: (liveryRepository: ILiveryRepository, logger: Logger) =>
new GetDriverLiveriesUseCase(liveryRepository, logger),
inject: [LIVERY_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
], initLogger);

View File

@@ -317,4 +317,41 @@ describe('DriverService', () => {
expect(getProfileOverviewUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1' });
expect(driverProfilePresenter.getResponseModel).toHaveBeenCalled();
});
it('getDriverLiveries executes use case and returns presenter model', async () => {
const getDriverLiveriesUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
const getDriverLiveriesPresenter = {
present: vi.fn(),
getResponseModel: vi.fn(() => ({ liveries: [] }))
};
const driverPresenter = {
setMediaResolver: vi.fn(),
setBaseUrl: vi.fn(),
present: vi.fn(),
getResponseModel: vi.fn(() => null)
};
const service = new DriverService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
getDriverLiveriesUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ findById: vi.fn() } as any,
logger as any,
{ getResponseModel: vi.fn(() => ({ items: [] })) } as any,
{ getResponseModel: vi.fn(() => ({ totalDrivers: 0 })) } as any,
{ getResponseModel: vi.fn(() => ({ success: true })) } as any,
{ getResponseModel: vi.fn(() => ({ isRegistered: false })) } as any,
driverPresenter as any,
{ getResponseModel: vi.fn(() => ({ profile: {} })) } as any,
getDriverLiveriesPresenter as any,
);
await expect(service.getDriverLiveries('d1')).resolves.toEqual({ liveries: [] });
expect(getDriverLiveriesUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1' });
expect(getDriverLiveriesPresenter.present).toHaveBeenCalled();
expect(getDriverLiveriesPresenter.getResponseModel).toHaveBeenCalled();
});
});

View File

@@ -6,11 +6,13 @@ import { DriverRegistrationStatusDTO } from './dtos/DriverRegistrationStatusDTO'
import { DriversLeaderboardDTO } from './dtos/DriversLeaderboardDTO';
import { DriverStatsDTO } from './dtos/DriverStatsDTO';
import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
import { GetDriverLiveriesOutputDTO } from './dtos/GetDriverLiveriesOutputDTO';
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
import { GetDriverRegistrationStatusQueryDTO } from './dtos/GetDriverRegistrationStatusQueryDTO';
// Use cases
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
import { GetDriverLiveriesUseCase } from '@core/racing/application/use-cases/GetDriverLiveriesUseCase';
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
@@ -24,6 +26,7 @@ import { DriverProfilePresenter } from './presenters/DriverProfilePresenter';
import { DriverRegistrationStatusPresenter } from './presenters/DriverRegistrationStatusPresenter';
import { DriversLeaderboardPresenter } from './presenters/DriversLeaderboardPresenter';
import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
import { GetDriverLiveriesPresenter } from './presenters/GetDriverLiveriesPresenter';
// Tokens
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
@@ -31,6 +34,7 @@ import type { Logger } from '@core/shared/application';
import {
COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
DRIVER_REPOSITORY_TOKEN,
GET_DRIVER_LIVERIES_USE_CASE_TOKEN,
GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
@@ -46,6 +50,8 @@ export class DriverService {
private readonly getDriversLeaderboardUseCase: GetDriversLeaderboardUseCase,
@Inject(GET_TOTAL_DRIVERS_USE_CASE_TOKEN)
private readonly getTotalDriversUseCase: GetTotalDriversUseCase,
@Inject(GET_DRIVER_LIVERIES_USE_CASE_TOKEN)
private readonly getDriverLiveriesUseCase: GetDriverLiveriesUseCase,
@Inject(COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN)
private readonly completeDriverOnboardingUseCase: CompleteDriverOnboardingUseCase,
@Inject(IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN)
@@ -65,6 +71,7 @@ export class DriverService {
private readonly driverRegistrationStatusPresenter?: DriverRegistrationStatusPresenter,
private readonly driverPresenter?: DriverPresenter,
private readonly driverProfilePresenter?: DriverProfilePresenter,
private readonly getDriverLiveriesPresenter?: GetDriverLiveriesPresenter,
) {
// Presenters are configured by providers, no need to configure here
}
@@ -175,4 +182,15 @@ export class DriverService {
}
return this.driverProfilePresenter!.getResponseModel();
}
async getDriverLiveries(driverId: string): Promise<GetDriverLiveriesOutputDTO> {
this.logger.debug(`[DriverService] Fetching driver liveries for driverId: ${driverId}`);
const result = await this.getDriverLiveriesUseCase.execute({ driverId });
if (result.isErr()) {
throw new Error(result.unwrapErr().details.message);
}
await this.getDriverLiveriesPresenter!.present(result);
return this.getDriverLiveriesPresenter!.getResponseModel()!;
}
}

View File

@@ -11,6 +11,7 @@ import { SOCIAL_GRAPH_REPOSITORY_TOKEN } from '../../persistence/social/SocialPe
export const TEAM_MEMBERSHIP_REPOSITORY_TOKEN = 'ITeamMembershipRepository';
export { SOCIAL_GRAPH_REPOSITORY_TOKEN };
export const LIVERY_REPOSITORY_TOKEN = 'ILiveryRepository';
export const LOGGER_TOKEN = 'Logger';
// New tokens for clean architecture
@@ -24,10 +25,12 @@ export const COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN = 'CompleteDriverOnboardi
export const IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN = 'IsDriverRegisteredForRaceUseCase';
export const UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN = 'UpdateDriverProfileUseCase';
export const GET_PROFILE_OVERVIEW_USE_CASE_TOKEN = 'GetProfileOverviewUseCase';
export const GET_DRIVER_LIVERIES_USE_CASE_TOKEN = 'GetDriverLiveriesUseCase';
export const GET_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN = 'GetDriversLeaderboardOutputPort_TOKEN';
export const GET_TOTAL_DRIVERS_OUTPUT_PORT_TOKEN = 'GetTotalDriversOutputPort_TOKEN';
export const COMPLETE_DRIVER_ONBOARDING_OUTPUT_PORT_TOKEN = 'CompleteDriverOnboardingOutputPort_TOKEN';
export const IS_DRIVER_REGISTERED_FOR_RACE_OUTPUT_PORT_TOKEN = 'IsDriverRegisteredForRaceOutputPort_TOKEN';
export const UPDATE_DRIVER_PROFILE_OUTPUT_PORT_TOKEN = 'UpdateDriverProfileOutputPort_TOKEN';
export const GET_PROFILE_OVERVIEW_OUTPUT_PORT_TOKEN = 'GetProfileOverviewOutputPort_TOKEN';
export const GET_PROFILE_OVERVIEW_OUTPUT_PORT_TOKEN = 'GetProfileOverviewOutputPort_TOKEN';
export const GET_DRIVER_LIVERIES_OUTPUT_PORT_TOKEN = 'GetDriverLiveriesOutputPort_TOKEN';

View File

@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
class LiveryDTO {
@ApiProperty()
id!: string;
@ApiProperty()
name!: string;
@ApiProperty()
imageUrl!: string;
@ApiProperty()
createdAt!: string;
@ApiProperty()
isActive!: boolean;
}
export class GetDriverLiveriesOutputDTO {
@ApiProperty({ type: [LiveryDTO] })
liveries!: LiveryDTO[];
}

View File

@@ -0,0 +1,70 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { Result } from '@core/shared/application/Result';
import { GetDriverLiveriesPresenter } from './GetDriverLiveriesPresenter';
import { DriverLivery } from '@core/racing/domain/entities/DriverLivery';
describe('GetDriverLiveriesPresenter', () => {
let presenter: GetDriverLiveriesPresenter;
beforeEach(() => {
presenter = new GetDriverLiveriesPresenter();
});
describe('present', () => {
it('should map core result to API response model correctly', () => {
const mockLiveries: DriverLivery[] = [
DriverLivery.create({
id: 'livery1',
driverId: 'driver1',
gameId: 'game1',
carId: 'car1',
uploadedImageUrl: 'http://example.com/livery1.png',
createdAt: new Date('2023-01-01'),
}),
DriverLivery.create({
id: 'livery2',
driverId: 'driver1',
gameId: 'game1',
carId: 'car2',
uploadedImageUrl: 'http://example.com/livery2.png',
createdAt: new Date('2023-01-02'),
}),
];
const result = Result.ok(mockLiveries);
presenter.present(result);
const response = presenter.getResponseModel();
expect(response).toEqual({
liveries: [
{
id: 'livery1',
name: 'Livery for car1',
imageUrl: 'http://example.com/livery1.png',
createdAt: '2023-01-01T00:00:00.000Z',
isActive: true,
},
{
id: 'livery2',
name: 'Livery for car2',
imageUrl: 'http://example.com/livery2.png',
createdAt: '2023-01-02T00:00:00.000Z',
isActive: false,
},
],
});
});
it('should handle empty liveries array', () => {
const result = Result.ok([]);
presenter.present(result);
const response = presenter.getResponseModel();
expect(response).toEqual({
liveries: [],
});
});
});
});

View File

@@ -0,0 +1,33 @@
import { Result } from '@core/shared/application/Result';
import type { DriverLivery } from '@core/racing/domain/entities/DriverLivery';
import type { GetDriverLiveriesOutputDTO } from '../dtos/GetDriverLiveriesOutputDTO';
export class GetDriverLiveriesPresenter {
private responseModel: GetDriverLiveriesOutputDTO | null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async present(result: Result<DriverLivery[], any>): Promise<void> {
if (result.isErr()) {
const error = result.unwrapErr();
throw new Error(error.details?.message ?? 'Failed to get driver liveries');
}
const liveries = result.unwrap();
const dto: GetDriverLiveriesOutputDTO = {
liveries: liveries.map((livery, index) => ({
id: livery.id,
name: `Livery for ${livery.carId.toString()}`, // Simple name generation
imageUrl: livery.uploadedImageUrl.toString(),
createdAt: livery.createdAt.toISOString(),
isActive: index === 0, // First livery is active by default
})),
};
this.responseModel = dto;
}
getResponseModel(): GetDriverLiveriesOutputDTO | null {
return this.responseModel;
}
}