refactor
This commit is contained in:
@@ -12,24 +12,14 @@ describe('AnalyticsController', () => {
|
||||
let controller: AnalyticsController;
|
||||
let service: ReturnType<typeof vi.mocked<AnalyticsService>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AnalyticsController],
|
||||
providers: [
|
||||
{
|
||||
provide: AnalyticsService,
|
||||
useValue: {
|
||||
recordPageView: vi.fn(),
|
||||
recordEngagement: vi.fn(),
|
||||
getDashboardData: vi.fn(),
|
||||
getAnalyticsMetrics: vi.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<AnalyticsController>(AnalyticsController);
|
||||
service = vi.mocked(module.get(AnalyticsService));
|
||||
beforeEach(() => {
|
||||
service = {
|
||||
recordPageView: vi.fn(),
|
||||
recordEngagement: vi.fn(),
|
||||
getDashboardData: vi.fn(),
|
||||
getAnalyticsMetrics: vi.fn(),
|
||||
} as any;
|
||||
controller = new AnalyticsController(service);
|
||||
});
|
||||
|
||||
describe('recordPageView', () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AnalyticsModule } from './AnalyticsModule';
|
||||
import { AnalyticsController } from './AnalyticsController';
|
||||
import { AnalyticsService } from './AnalyticsService';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
|
||||
describe('AnalyticsModule', () => {
|
||||
let module: TestingModule;
|
||||
@@ -9,7 +10,10 @@ describe('AnalyticsModule', () => {
|
||||
beforeEach(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
imports: [AnalyticsModule],
|
||||
}).compile();
|
||||
})
|
||||
.overrideProvider('Logger_TOKEN')
|
||||
.useClass(ConsoleLogger)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should compile the module', () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { vi } from 'vitest';
|
||||
import { Mock, vi } from 'vitest';
|
||||
import { AuthController } from './AuthController';
|
||||
import { AuthService } from './AuthService';
|
||||
import { SignupParams, LoginParams, AuthSessionDTO } from './dtos/AuthDto';
|
||||
import { AuthSessionDTO, LoginParams, SignupParams } from './dtos/AuthDto';
|
||||
import type { CommandResultDTO } from './presenters/CommandResultPresenter';
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController;
|
||||
@@ -36,7 +37,7 @@ describe('AuthController', () => {
|
||||
displayName: 'Test User',
|
||||
},
|
||||
};
|
||||
(service.signupWithEmail as jest.Mock).mockResolvedValue(session);
|
||||
(service.signupWithEmail as Mock).mockResolvedValue(session);
|
||||
|
||||
const result = await controller.signup(params);
|
||||
|
||||
@@ -59,7 +60,7 @@ describe('AuthController', () => {
|
||||
displayName: 'Test User',
|
||||
},
|
||||
};
|
||||
(service.loginWithEmail as jest.Mock).mockResolvedValue(session);
|
||||
(service.loginWithEmail as Mock).mockResolvedValue(session);
|
||||
|
||||
const result = await controller.login(params);
|
||||
|
||||
@@ -78,7 +79,7 @@ describe('AuthController', () => {
|
||||
displayName: 'Test User',
|
||||
},
|
||||
};
|
||||
(service.getCurrentSession as jest.Mock).mockResolvedValue(session);
|
||||
(service.getCurrentSession as Mock).mockResolvedValue(session);
|
||||
|
||||
const result = await controller.getSession();
|
||||
|
||||
@@ -87,7 +88,7 @@ describe('AuthController', () => {
|
||||
});
|
||||
|
||||
it('should return null if no session', async () => {
|
||||
(service.getCurrentSession as jest.Mock).mockResolvedValue(null);
|
||||
(service.getCurrentSession as Mock).mockResolvedValue(null);
|
||||
|
||||
const result = await controller.getSession();
|
||||
|
||||
@@ -97,8 +98,8 @@ describe('AuthController', () => {
|
||||
|
||||
describe('logout', () => {
|
||||
it('should call service.logout and return DTO', async () => {
|
||||
const dto = { success: true };
|
||||
(service.logout as jest.Mock).mockResolvedValue(dto);
|
||||
const dto: CommandResultDTO = { success: true };
|
||||
(service.logout as Mock).mockResolvedValue(dto);
|
||||
|
||||
const result = await controller.logout();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Controller, Get, Post, Body } from '@nestjs/common';
|
||||
import { AuthService } from './AuthService';
|
||||
import { LoginParams, SignupParams, AuthSessionDTO } from './dtos/AuthDto';
|
||||
import type { CommandResultDTO } from './presenters/CommandResultPresenter';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
@@ -22,7 +23,7 @@ export class AuthController {
|
||||
}
|
||||
|
||||
@Post('logout')
|
||||
async logout(): Promise<{ success: boolean }> {
|
||||
async logout(): Promise<CommandResultDTO> {
|
||||
return this.authService.logout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ export const IDENTITY_SESSION_PORT_TOKEN = 'IdentitySessionPort';
|
||||
export const LOGIN_USE_CASE_TOKEN = 'LoginUseCase';
|
||||
export const SIGNUP_USE_CASE_TOKEN = 'SignupUseCase';
|
||||
export const LOGOUT_USE_CASE_TOKEN = 'LogoutUseCase';
|
||||
export const AUTH_SESSION_PRESENTER_TOKEN = 'AuthSessionPresenter';
|
||||
export const COMMAND_RESULT_PRESENTER_TOKEN = 'CommandResultPresenter';
|
||||
|
||||
export const AuthProviders: Provider[] = [
|
||||
{
|
||||
@@ -73,20 +75,28 @@ export const AuthProviders: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: LOGIN_USE_CASE_TOKEN,
|
||||
useFactory: (authRepo: IAuthRepository, passwordHashing: IPasswordHashingService, logger: Logger) =>
|
||||
new LoginUseCase(authRepo, passwordHashing, logger),
|
||||
inject: [AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (authRepo: IAuthRepository, passwordHashing: IPasswordHashingService, logger: Logger, presenter: AuthSessionPresenter) =>
|
||||
new LoginUseCase(authRepo, passwordHashing, logger, presenter),
|
||||
inject: [AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN, AUTH_SESSION_PRESENTER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: SIGNUP_USE_CASE_TOKEN,
|
||||
useFactory: (authRepo: IAuthRepository, passwordHashing: IPasswordHashingService, logger: Logger) =>
|
||||
new SignupUseCase(authRepo, passwordHashing, logger),
|
||||
inject: [AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (authRepo: IAuthRepository, passwordHashing: IPasswordHashingService, logger: Logger, presenter: AuthSessionPresenter) =>
|
||||
new SignupUseCase(authRepo, passwordHashing, logger, presenter),
|
||||
inject: [AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN, AUTH_SESSION_PRESENTER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: LOGOUT_USE_CASE_TOKEN,
|
||||
useFactory: (sessionPort: IdentitySessionPort, logger: Logger) =>
|
||||
new LogoutUseCase(sessionPort, logger),
|
||||
inject: [IDENTITY_SESSION_PORT_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (sessionPort: IdentitySessionPort, logger: Logger, presenter: CommandResultPresenter) =>
|
||||
new LogoutUseCase(sessionPort, logger, presenter),
|
||||
inject: [IDENTITY_SESSION_PORT_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: AUTH_SESSION_PRESENTER_TOKEN,
|
||||
useClass: AuthSessionPresenter,
|
||||
},
|
||||
{
|
||||
provide: COMMAND_RESULT_PRESENTER_TOKEN,
|
||||
useClass: CommandResultPresenter,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject } from '@nestjs/common';
|
||||
|
||||
// Core Use Cases
|
||||
import { LoginUseCase, type LoginInput } from '@core/identity/application/use-cases/LoginUseCase';
|
||||
@@ -11,12 +11,10 @@ import { User } from '@core/identity/domain/entities/User';
|
||||
import type { IUserRepository } from '@core/identity/domain/repositories/IUserRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { IDENTITY_SESSION_PORT_TOKEN, LOGGER_TOKEN, LOGIN_USE_CASE_TOKEN, LOGOUT_USE_CASE_TOKEN, SIGNUP_USE_CASE_TOKEN, USER_REPOSITORY_TOKEN } from './AuthProviders';
|
||||
import { AuthenticatedUserDTO, AuthSessionDTO, LoginParams, SignupParams } from './dtos/AuthDto';
|
||||
import { AuthSessionDTO, LoginParams, SignupParams } from './dtos/AuthDto';
|
||||
import { AuthSessionPresenter } from './presenters/AuthSessionPresenter';
|
||||
import type { CommandResultDTO } from './presenters/CommandResultPresenter';
|
||||
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
|
||||
import { CommandResultPresenter, type CommandResultDTO } from './presenters/CommandResultPresenter';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
@Inject(LOGGER_TOKEN) private logger: Logger,
|
||||
@@ -25,31 +23,10 @@ export class AuthService {
|
||||
@Inject(LOGIN_USE_CASE_TOKEN) private readonly loginUseCase: LoginUseCase,
|
||||
@Inject(SIGNUP_USE_CASE_TOKEN) private readonly signupUseCase: SignupUseCase,
|
||||
@Inject(LOGOUT_USE_CASE_TOKEN) private readonly logoutUseCase: LogoutUseCase,
|
||||
private readonly authSessionPresenter: AuthSessionPresenter,
|
||||
private readonly commandResultPresenter: CommandResultPresenter,
|
||||
) {}
|
||||
|
||||
private mapUserToAuthenticatedUserDTO(user: User): AuthenticatedUserDTO {
|
||||
return {
|
||||
userId: user.getId().value,
|
||||
email: user.getEmail() ?? '',
|
||||
displayName: user.getDisplayName() ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private buildAuthSessionDTO(token: string, user: AuthenticatedUserDTO): AuthSessionDTO {
|
||||
return {
|
||||
token,
|
||||
user: {
|
||||
userId: user.userId,
|
||||
email: user.email,
|
||||
displayName: user.displayName,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async getCurrentSession(): Promise<AuthSessionDTO | null> {
|
||||
// TODO this must call a use case
|
||||
this.logger.debug('[AuthService] Attempting to get current session.');
|
||||
const coreSession = await this.identitySessionPort.getCurrentSession();
|
||||
if (!coreSession) {
|
||||
@@ -87,7 +64,8 @@ export class AuthService {
|
||||
throw new Error(error.details?.message ?? 'Signup failed');
|
||||
}
|
||||
|
||||
const userDTO = this.authSessionPresenter.getResponseModel();
|
||||
const authSessionPresenter = new AuthSessionPresenter();
|
||||
const userDTO = authSessionPresenter.getResponseModel();
|
||||
const coreUserDTO = {
|
||||
id: userDTO.userId,
|
||||
displayName: userDTO.displayName,
|
||||
@@ -116,7 +94,8 @@ export class AuthService {
|
||||
throw new Error(error.details?.message ?? 'Login failed');
|
||||
}
|
||||
|
||||
const userDTO = this.authSessionPresenter.getResponseModel();
|
||||
const authSessionPresenter = new AuthSessionPresenter();
|
||||
const userDTO = authSessionPresenter.getResponseModel();
|
||||
const coreUserDTO = {
|
||||
id: userDTO.userId,
|
||||
displayName: userDTO.displayName,
|
||||
@@ -133,6 +112,7 @@ export class AuthService {
|
||||
async logout(): Promise<CommandResultDTO> {
|
||||
this.logger.debug('[AuthService] Attempting logout.');
|
||||
|
||||
const commandResultPresenter = new CommandResultPresenter();
|
||||
const result = await this.logoutUseCase.execute();
|
||||
|
||||
if (result.isErr()) {
|
||||
@@ -140,6 +120,6 @@ export class AuthService {
|
||||
throw new Error(error.details?.message ?? 'Logout failed');
|
||||
}
|
||||
|
||||
return this.commandResultPresenter.getResponseModel();
|
||||
return commandResultPresenter.getResponseModel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,12 @@ import { UserId } from '@core/identity/domain/value-objects/UserId';
|
||||
|
||||
describe('AuthSessionPresenter', () => {
|
||||
let presenter: AuthSessionPresenter;
|
||||
let mockIdentitySessionPort: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockIdentitySessionPort = {
|
||||
createSession: vi.fn(),
|
||||
};
|
||||
presenter = new AuthSessionPresenter(mockIdentitySessionPort);
|
||||
presenter = new AuthSessionPresenter();
|
||||
});
|
||||
|
||||
it('maps successful result into response model', async () => {
|
||||
it('maps successful result into response model', () => {
|
||||
const user = User.create({
|
||||
id: UserId.fromString('user-1'),
|
||||
displayName: 'Test User',
|
||||
@@ -22,20 +18,15 @@ describe('AuthSessionPresenter', () => {
|
||||
passwordHash: { value: 'hash' } as any,
|
||||
});
|
||||
|
||||
const expectedSession = {
|
||||
token: 'token-123',
|
||||
user: {
|
||||
userId: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'Test User',
|
||||
},
|
||||
const expectedUser = {
|
||||
userId: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'Test User',
|
||||
};
|
||||
|
||||
mockIdentitySessionPort.createSession.mockResolvedValue(expectedSession);
|
||||
presenter.present({ user });
|
||||
|
||||
await presenter.present({ user });
|
||||
|
||||
expect(presenter.getResponseModel()).toEqual(expectedSession);
|
||||
expect(presenter.getResponseModel()).toEqual(expectedUser);
|
||||
});
|
||||
|
||||
it('getResponseModel throws when not presented', () => {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DashboardModule } from './DashboardModule';
|
||||
import { DashboardController } from './DashboardController';
|
||||
import { DashboardService } from './DashboardService';
|
||||
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
|
||||
import { DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN } from './DashboardProviders';
|
||||
|
||||
describe('DashboardModule', () => {
|
||||
let module: TestingModule;
|
||||
@@ -27,4 +29,10 @@ describe('DashboardModule', () => {
|
||||
expect(service).toBeDefined();
|
||||
expect(service).toBeInstanceOf(DashboardService);
|
||||
});
|
||||
|
||||
it('should bind DashboardOverviewPresenter as the output port for the use case', () => {
|
||||
const presenter = module.get<DashboardOverviewPresenter>(DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN);
|
||||
expect(presenter).toBeDefined();
|
||||
expect(presenter).toBeInstanceOf(DashboardOverviewPresenter);
|
||||
});
|
||||
});
|
||||
@@ -7,14 +7,14 @@ import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresen
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
|
||||
// Tokens
|
||||
import { LOGGER_TOKEN, DASHBOARD_OVERVIEW_USE_CASE_TOKEN } from './DashboardProviders';
|
||||
import { LOGGER_TOKEN, DASHBOARD_OVERVIEW_USE_CASE_TOKEN, DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN } from './DashboardProviders';
|
||||
|
||||
@Injectable()
|
||||
export class DashboardService {
|
||||
constructor(
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
@Inject(DASHBOARD_OVERVIEW_USE_CASE_TOKEN) private readonly dashboardOverviewUseCase: DashboardOverviewUseCase,
|
||||
private readonly dashboardOverviewPresenter: DashboardOverviewPresenter,
|
||||
@Inject(DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN) private readonly dashboardOverviewPresenter: DashboardOverviewPresenter,
|
||||
) {}
|
||||
|
||||
async getDashboardOverview(driverId: string): Promise<DashboardOverviewDTO> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { IAvatarRepository } from '@core/media/domain/repositories/IAvatarReposi
|
||||
import { FaceValidationPort } from '@core/media/application/ports/FaceValidationPort';
|
||||
import { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort';
|
||||
import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
|
||||
// Import use cases
|
||||
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
@@ -18,6 +18,22 @@ import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMedi
|
||||
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
|
||||
// Import result types
|
||||
import type { RequestAvatarGenerationResult } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
import type { UploadMediaResult } from '@core/media/application/use-cases/UploadMediaUseCase';
|
||||
import type { GetMediaResult } from '@core/media/application/use-cases/GetMediaUseCase';
|
||||
import type { DeleteMediaResult } from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||
import type { GetAvatarResult } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import type { UpdateAvatarResult } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
|
||||
// Import presenters
|
||||
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
||||
import { UploadMediaPresenter } from './presenters/UploadMediaPresenter';
|
||||
import { GetMediaPresenter } from './presenters/GetMediaPresenter';
|
||||
import { DeleteMediaPresenter } from './presenters/DeleteMediaPresenter';
|
||||
import { GetAvatarPresenter } from './presenters/GetAvatarPresenter';
|
||||
import { UpdateAvatarPresenter } from './presenters/UpdateAvatarPresenter';
|
||||
|
||||
// Define injection tokens
|
||||
export const AVATAR_GENERATION_REPOSITORY_TOKEN = 'IAvatarGenerationRepository';
|
||||
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';
|
||||
@@ -35,6 +51,14 @@ export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase';
|
||||
export const GET_AVATAR_USE_CASE_TOKEN = 'GetAvatarUseCase';
|
||||
export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase';
|
||||
|
||||
// Output port tokens
|
||||
export const REQUEST_AVATAR_GENERATION_OUTPUT_PORT_TOKEN = 'RequestAvatarGenerationOutputPort';
|
||||
export const UPLOAD_MEDIA_OUTPUT_PORT_TOKEN = 'UploadMediaOutputPort';
|
||||
export const GET_MEDIA_OUTPUT_PORT_TOKEN = 'GetMediaOutputPort';
|
||||
export const DELETE_MEDIA_OUTPUT_PORT_TOKEN = 'DeleteMediaOutputPort';
|
||||
export const GET_AVATAR_OUTPUT_PORT_TOKEN = 'GetAvatarOutputPort';
|
||||
export const UPDATE_AVATAR_OUTPUT_PORT_TOKEN = 'UpdateAvatarOutputPort';
|
||||
|
||||
import type { AvatarGenerationRequest } from '@core/media/domain/entities/AvatarGenerationRequest';
|
||||
import type { Media } from '@core/media/domain/entities/Media';
|
||||
import type { Avatar } from '@core/media/domain/entities/Avatar';
|
||||
@@ -110,6 +134,12 @@ class MockLogger implements Logger {
|
||||
|
||||
export const MediaProviders: Provider[] = [
|
||||
MediaService, // Provide the service itself
|
||||
RequestAvatarGenerationPresenter,
|
||||
UploadMediaPresenter,
|
||||
GetMediaPresenter,
|
||||
DeleteMediaPresenter,
|
||||
GetAvatarPresenter,
|
||||
UpdateAvatarPresenter,
|
||||
{
|
||||
provide: AVATAR_GENERATION_REPOSITORY_TOKEN,
|
||||
useClass: MockAvatarGenerationRepository,
|
||||
@@ -138,41 +168,66 @@ export const MediaProviders: Provider[] = [
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: MockLogger,
|
||||
},
|
||||
// Output ports
|
||||
{
|
||||
provide: REQUEST_AVATAR_GENERATION_OUTPUT_PORT_TOKEN,
|
||||
useExisting: RequestAvatarGenerationPresenter,
|
||||
},
|
||||
{
|
||||
provide: UPLOAD_MEDIA_OUTPUT_PORT_TOKEN,
|
||||
useExisting: UploadMediaPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_MEDIA_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetMediaPresenter,
|
||||
},
|
||||
{
|
||||
provide: DELETE_MEDIA_OUTPUT_PORT_TOKEN,
|
||||
useExisting: DeleteMediaPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_AVATAR_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetAvatarPresenter,
|
||||
},
|
||||
{
|
||||
provide: UPDATE_AVATAR_OUTPUT_PORT_TOKEN,
|
||||
useExisting: UpdateAvatarPresenter,
|
||||
},
|
||||
// Use cases
|
||||
{
|
||||
provide: REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN,
|
||||
useFactory: (avatarRepo: IAvatarGenerationRepository, faceValidation: FaceValidationPort, avatarGeneration: AvatarGenerationPort, logger: Logger) =>
|
||||
new RequestAvatarGenerationUseCase(avatarRepo, faceValidation, avatarGeneration, logger),
|
||||
inject: [AVATAR_GENERATION_REPOSITORY_TOKEN, FACE_VALIDATION_PORT_TOKEN, AVATAR_GENERATION_PORT_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (avatarRepo: IAvatarGenerationRepository, faceValidation: FaceValidationPort, avatarGeneration: AvatarGenerationPort, output: UseCaseOutputPort<RequestAvatarGenerationResult>, logger: Logger) =>
|
||||
new RequestAvatarGenerationUseCase(avatarRepo, faceValidation, avatarGeneration, output, logger),
|
||||
inject: [AVATAR_GENERATION_REPOSITORY_TOKEN, FACE_VALIDATION_PORT_TOKEN, AVATAR_GENERATION_PORT_TOKEN, REQUEST_AVATAR_GENERATION_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UPLOAD_MEDIA_USE_CASE_TOKEN,
|
||||
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, logger: Logger) =>
|
||||
new UploadMediaUseCase(mediaRepo, mediaStorage, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, output: UseCaseOutputPort<UploadMediaResult>, logger: Logger) =>
|
||||
new UploadMediaUseCase(mediaRepo, mediaStorage, output, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, UPLOAD_MEDIA_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_MEDIA_USE_CASE_TOKEN,
|
||||
useFactory: (mediaRepo: IMediaRepository, logger: Logger) =>
|
||||
new GetMediaUseCase(mediaRepo, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (mediaRepo: IMediaRepository, output: UseCaseOutputPort<GetMediaResult>, logger: Logger) =>
|
||||
new GetMediaUseCase(mediaRepo, output, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, GET_MEDIA_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: DELETE_MEDIA_USE_CASE_TOKEN,
|
||||
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, logger: Logger) =>
|
||||
new DeleteMediaUseCase(mediaRepo, mediaStorage, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, output: UseCaseOutputPort<DeleteMediaResult>, logger: Logger) =>
|
||||
new DeleteMediaUseCase(mediaRepo, mediaStorage, output, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, DELETE_MEDIA_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_AVATAR_USE_CASE_TOKEN,
|
||||
useFactory: (avatarRepo: IAvatarRepository, logger: Logger) =>
|
||||
new GetAvatarUseCase(avatarRepo, logger),
|
||||
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (avatarRepo: IAvatarRepository, output: UseCaseOutputPort<GetAvatarResult>, logger: Logger) =>
|
||||
new GetAvatarUseCase(avatarRepo, output, logger),
|
||||
inject: [AVATAR_REPOSITORY_TOKEN, GET_AVATAR_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UPDATE_AVATAR_USE_CASE_TOKEN,
|
||||
useFactory: (avatarRepo: IAvatarRepository, logger: Logger) =>
|
||||
new UpdateAvatarUseCase(avatarRepo, logger),
|
||||
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
useFactory: (avatarRepo: IAvatarRepository, output: UseCaseOutputPort<UpdateAvatarResult>, logger: Logger) =>
|
||||
new UpdateAvatarUseCase(avatarRepo, output, logger),
|
||||
inject: [AVATAR_REPOSITORY_TOKEN, UPDATE_AVATAR_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -59,6 +59,12 @@ export class MediaService {
|
||||
private readonly updateAvatarUseCase: UpdateAvatarUseCase,
|
||||
@Inject(LOGGER_TOKEN)
|
||||
private readonly logger: Logger,
|
||||
private readonly requestAvatarGenerationPresenter: RequestAvatarGenerationPresenter,
|
||||
private readonly uploadMediaPresenter: UploadMediaPresenter,
|
||||
private readonly getMediaPresenter: GetMediaPresenter,
|
||||
private readonly deleteMediaPresenter: DeleteMediaPresenter,
|
||||
private readonly getAvatarPresenter: GetAvatarPresenter,
|
||||
private readonly updateAvatarPresenter: UpdateAvatarPresenter,
|
||||
) {}
|
||||
|
||||
async requestAvatarGeneration(
|
||||
@@ -66,18 +72,23 @@ export class MediaService {
|
||||
): Promise<RequestAvatarGenerationOutputDTO> {
|
||||
this.logger.debug('[MediaService] Requesting avatar generation.');
|
||||
|
||||
const presenter = new RequestAvatarGenerationPresenter();
|
||||
presenter.reset();
|
||||
|
||||
const result = await this.requestAvatarGenerationUseCase.execute({
|
||||
userId: input.userId,
|
||||
facePhotoData: input.facePhotoData,
|
||||
suitColor: input.suitColor as RacingSuitColor,
|
||||
});
|
||||
|
||||
presenter.present(result);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
return {
|
||||
success: false,
|
||||
requestId: '',
|
||||
avatarUrls: [],
|
||||
errorMessage: error.details?.message ?? 'Failed to request avatar generation',
|
||||
};
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.requestAvatarGenerationPresenter.responseModel;
|
||||
}
|
||||
|
||||
async uploadMedia(
|
||||
@@ -85,69 +96,87 @@ export class MediaService {
|
||||
): Promise<UploadMediaOutputDTO> {
|
||||
this.logger.debug('[MediaService] Uploading media.');
|
||||
|
||||
const presenter = new UploadMediaPresenter();
|
||||
presenter.reset();
|
||||
|
||||
const result = await this.uploadMediaUseCase.execute({
|
||||
file: input.file,
|
||||
uploadedBy: input.userId ?? '',
|
||||
metadata: input.metadata,
|
||||
metadata: input.metadata || {},
|
||||
});
|
||||
|
||||
presenter.present(result);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
return {
|
||||
success: false,
|
||||
error: error.details?.message ?? 'Upload failed',
|
||||
};
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.uploadMediaPresenter.responseModel;
|
||||
}
|
||||
|
||||
async getMedia(mediaId: string): Promise<GetMediaOutputDTO | null> {
|
||||
this.logger.debug(`[MediaService] Getting media: ${mediaId}`);
|
||||
|
||||
const presenter = new GetMediaPresenter();
|
||||
presenter.reset();
|
||||
|
||||
const result = await this.getMediaUseCase.execute({ mediaId });
|
||||
presenter.present(result);
|
||||
|
||||
return presenter.responseModel;
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
if (error.code === 'MEDIA_NOT_FOUND') {
|
||||
return null;
|
||||
}
|
||||
throw new Error(error.details?.message ?? 'Failed to get media');
|
||||
}
|
||||
|
||||
return this.getMediaPresenter.responseModel;
|
||||
}
|
||||
|
||||
async deleteMedia(mediaId: string): Promise<DeleteMediaOutputDTO> {
|
||||
this.logger.debug(`[MediaService] Deleting media: ${mediaId}`);
|
||||
|
||||
const presenter = new DeleteMediaPresenter();
|
||||
presenter.reset();
|
||||
|
||||
const result = await this.deleteMediaUseCase.execute({ mediaId });
|
||||
presenter.present(result);
|
||||
|
||||
return presenter.responseModel;
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
return {
|
||||
success: false,
|
||||
error: error.details?.message ?? 'Failed to delete media',
|
||||
};
|
||||
}
|
||||
|
||||
return this.deleteMediaPresenter.responseModel;
|
||||
}
|
||||
|
||||
async getAvatar(driverId: string): Promise<GetAvatarOutputDTO | null> {
|
||||
this.logger.debug(`[MediaService] Getting avatar for driver: ${driverId}`);
|
||||
|
||||
const presenter = new GetAvatarPresenter();
|
||||
presenter.reset();
|
||||
|
||||
const result = await this.getAvatarUseCase.execute({ driverId });
|
||||
presenter.present(result);
|
||||
|
||||
return presenter.responseModel;
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
if (error.code === 'AVATAR_NOT_FOUND') {
|
||||
return null;
|
||||
}
|
||||
throw new Error(error.details?.message ?? 'Failed to get avatar');
|
||||
}
|
||||
|
||||
return this.getAvatarPresenter.responseModel;
|
||||
}
|
||||
|
||||
async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise<UpdateAvatarOutputDTO> {
|
||||
this.logger.debug(`[MediaService] Updating avatar for driver: ${driverId}`);
|
||||
|
||||
const presenter = new UpdateAvatarPresenter();
|
||||
presenter.reset();
|
||||
|
||||
const result = await this.updateAvatarUseCase.execute({
|
||||
driverId,
|
||||
mediaUrl: input.mediaUrl,
|
||||
mediaUrl: input.avatarUrl,
|
||||
});
|
||||
|
||||
presenter.present(result);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
return {
|
||||
success: false,
|
||||
error: error.details?.message ?? 'Failed to update avatar',
|
||||
};
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.updateAvatarPresenter.responseModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,19 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
DeleteMediaResult,
|
||||
DeleteMediaErrorCode,
|
||||
} from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { DeleteMediaResult } from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||
import type { DeleteMediaOutputDTO } from '../dtos/DeleteMediaOutputDTO';
|
||||
|
||||
type DeleteMediaResponseModel = DeleteMediaOutputDTO;
|
||||
|
||||
export type DeleteMediaApplicationError = ApplicationErrorCode<
|
||||
DeleteMediaErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class DeleteMediaPresenter {
|
||||
export class DeleteMediaPresenter implements UseCaseOutputPort<DeleteMediaResult> {
|
||||
private model: DeleteMediaResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<DeleteMediaResult, DeleteMediaApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
this.model = {
|
||||
success: false,
|
||||
error: error.details?.message ?? 'Failed to delete media',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
present(result: DeleteMediaResult): void {
|
||||
this.model = {
|
||||
success: output.deleted,
|
||||
error: undefined,
|
||||
success: result.deleted,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +1,19 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetAvatarResult,
|
||||
GetAvatarErrorCode,
|
||||
} from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { GetAvatarResult } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import type { GetAvatarOutputDTO } from '../dtos/GetAvatarOutputDTO';
|
||||
|
||||
export type GetAvatarResponseModel = GetAvatarOutputDTO | null;
|
||||
|
||||
export type GetAvatarApplicationError = ApplicationErrorCode<
|
||||
GetAvatarErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class GetAvatarPresenter {
|
||||
export class GetAvatarPresenter implements UseCaseOutputPort<GetAvatarResult> {
|
||||
private model: GetAvatarResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<GetAvatarResult, GetAvatarApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
if (error.code === 'AVATAR_NOT_FOUND') {
|
||||
this.model = null;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(error.details?.message ?? 'Failed to get avatar');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
present(result: GetAvatarResult): void {
|
||||
this.model = {
|
||||
avatarUrl: output.avatar.mediaUrl,
|
||||
avatarUrl: result.avatar.mediaUrl,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,18 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { GetMediaResult, GetMediaErrorCode } from '@core/media/application/use-cases/GetMediaUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { GetMediaResult } from '@core/media/application/use-cases/GetMediaUseCase';
|
||||
import type { GetMediaOutputDTO } from '../dtos/GetMediaOutputDTO';
|
||||
|
||||
export type GetMediaResponseModel = GetMediaOutputDTO | null;
|
||||
|
||||
export type GetMediaApplicationError = ApplicationErrorCode<
|
||||
GetMediaErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class GetMediaPresenter {
|
||||
export class GetMediaPresenter implements UseCaseOutputPort<GetMediaResult> {
|
||||
private model: GetMediaResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<GetMediaResult, GetMediaApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
if (error.code === 'MEDIA_NOT_FOUND') {
|
||||
this.model = null;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(error.details?.message ?? 'Failed to get media');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
const media = output.media;
|
||||
present(result: GetMediaResult): void {
|
||||
const media = result.media;
|
||||
|
||||
this.model = {
|
||||
id: media.id,
|
||||
|
||||
@@ -1,50 +1,21 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
RequestAvatarGenerationResult,
|
||||
RequestAvatarGenerationErrorCode,
|
||||
} from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { RequestAvatarGenerationResult } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
import type { RequestAvatarGenerationOutputDTO } from '../dtos/RequestAvatarGenerationOutputDTO';
|
||||
|
||||
type RequestAvatarGenerationResponseModel = RequestAvatarGenerationOutputDTO;
|
||||
|
||||
export type RequestAvatarGenerationApplicationError = ApplicationErrorCode<
|
||||
RequestAvatarGenerationErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class RequestAvatarGenerationPresenter {
|
||||
export class RequestAvatarGenerationPresenter implements UseCaseOutputPort<RequestAvatarGenerationResult> {
|
||||
private model: RequestAvatarGenerationResponseModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(
|
||||
result: Result<
|
||||
RequestAvatarGenerationResult,
|
||||
RequestAvatarGenerationApplicationError
|
||||
>,
|
||||
): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
this.model = {
|
||||
success: false,
|
||||
requestId: '',
|
||||
avatarUrls: [],
|
||||
errorMessage: error.details?.message ?? 'Failed to request avatar generation',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
present(result: RequestAvatarGenerationResult): void {
|
||||
this.model = {
|
||||
success: output.status === 'completed',
|
||||
requestId: output.requestId,
|
||||
avatarUrls: output.avatarUrls,
|
||||
errorMessage: undefined,
|
||||
success: result.status === 'completed',
|
||||
requestId: result.requestId,
|
||||
avatarUrls: result.avatarUrls || [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,19 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
UpdateAvatarResult,
|
||||
UpdateAvatarErrorCode,
|
||||
} from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { UpdateAvatarResult } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
import type { UpdateAvatarOutputDTO } from '../dtos/UpdateAvatarOutputDTO';
|
||||
|
||||
type UpdateAvatarResponseModel = UpdateAvatarOutputDTO;
|
||||
|
||||
export type UpdateAvatarApplicationError = ApplicationErrorCode<
|
||||
UpdateAvatarErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class UpdateAvatarPresenter {
|
||||
export class UpdateAvatarPresenter implements UseCaseOutputPort<UpdateAvatarResult> {
|
||||
private model: UpdateAvatarResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<UpdateAvatarResult, UpdateAvatarApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to update avatar');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
present(result: UpdateAvatarResult): void {
|
||||
this.model = {
|
||||
success: true,
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +1,21 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
UploadMediaResult,
|
||||
UploadMediaErrorCode,
|
||||
} from '@core/media/application/use-cases/UploadMediaUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { UploadMediaResult } from '@core/media/application/use-cases/UploadMediaUseCase';
|
||||
import type { UploadMediaOutputDTO } from '../dtos/UploadMediaOutputDTO';
|
||||
|
||||
type UploadMediaResponseModel = UploadMediaOutputDTO;
|
||||
|
||||
export type UploadMediaApplicationError = ApplicationErrorCode<
|
||||
UploadMediaErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class UploadMediaPresenter {
|
||||
export class UploadMediaPresenter implements UseCaseOutputPort<UploadMediaResult> {
|
||||
private model: UploadMediaResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<UploadMediaResult, UploadMediaApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
this.model = {
|
||||
success: false,
|
||||
error: error.details?.message ?? 'Upload failed',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
present(result: UploadMediaResult): void {
|
||||
this.model = {
|
||||
success: true,
|
||||
mediaId: output.mediaId,
|
||||
url: output.url,
|
||||
error: undefined,
|
||||
mediaId: result.mediaId,
|
||||
url: result.url,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ export class PaymentsController {
|
||||
@ApiOperation({ summary: 'Get payments based on filters' })
|
||||
@ApiResponse({ status: 200, description: 'List of payments', type: GetPaymentsOutput })
|
||||
async getPayments(@Query() query: GetPaymentsQuery): Promise<GetPaymentsOutput> {
|
||||
const presenter = await this.paymentsService.getPayments(query);
|
||||
return presenter.viewModel;
|
||||
return this.paymentsService.getPayments(query);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@@ -21,16 +20,14 @@ export class PaymentsController {
|
||||
@ApiOperation({ summary: 'Create a new payment' })
|
||||
@ApiResponse({ status: 201, description: 'Payment created', type: CreatePaymentOutput })
|
||||
async createPayment(@Body() input: CreatePaymentInput): Promise<CreatePaymentOutput> {
|
||||
const presenter = await this.paymentsService.createPayment(input);
|
||||
return presenter.viewModel;
|
||||
return this.paymentsService.createPayment(input);
|
||||
}
|
||||
|
||||
@Patch('status')
|
||||
@ApiOperation({ summary: 'Update the status of a payment' })
|
||||
@ApiResponse({ status: 200, description: 'Payment status updated', type: UpdatePaymentStatusOutput })
|
||||
async updatePaymentStatus(@Body() input: UpdatePaymentStatusInput): Promise<UpdatePaymentStatusOutput> {
|
||||
const presenter = await this.paymentsService.updatePaymentStatus(input);
|
||||
return presenter.viewModel;
|
||||
return this.paymentsService.updatePaymentStatus(input);
|
||||
}
|
||||
|
||||
@Get('membership-fees')
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { IPaymentRepository } from '@core/payments/domain/repositories/IPay
|
||||
import type { IMembershipFeeRepository, IMemberPaymentRepository } from '@core/payments/domain/repositories/IMembershipFeeRepository';
|
||||
import type { IPrizeRepository } from '@core/payments/domain/repositories/IPrizeRepository';
|
||||
import type { IWalletRepository, ITransactionRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
|
||||
// Import use cases
|
||||
import { GetPaymentsUseCase } from '@core/payments/application/use-cases/GetPaymentsUseCase';
|
||||
@@ -29,6 +29,20 @@ import { InMemoryPrizeRepository } from '@adapters/payments/persistence/inmemory
|
||||
import { InMemoryWalletRepository, InMemoryTransactionRepository } from '@adapters/payments/persistence/inmemory/InMemoryWalletRepository';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
|
||||
// Presenters
|
||||
import { GetPaymentsPresenter } from './presenters/GetPaymentsPresenter';
|
||||
import { CreatePaymentPresenter } from './presenters/CreatePaymentPresenter';
|
||||
import { UpdatePaymentStatusPresenter } from './presenters/UpdatePaymentStatusPresenter';
|
||||
import { GetMembershipFeesPresenter } from './presenters/GetMembershipFeesPresenter';
|
||||
import { UpsertMembershipFeePresenter } from './presenters/UpsertMembershipFeePresenter';
|
||||
import { UpdateMemberPaymentPresenter } from './presenters/UpdateMemberPaymentPresenter';
|
||||
import { GetPrizesPresenter } from './presenters/GetPrizesPresenter';
|
||||
import { CreatePrizePresenter } from './presenters/CreatePrizePresenter';
|
||||
import { AwardPrizePresenter } from './presenters/AwardPrizePresenter';
|
||||
import { DeletePrizePresenter } from './presenters/DeletePrizePresenter';
|
||||
import { GetWalletPresenter } from './presenters/GetWalletPresenter';
|
||||
import { ProcessWalletTransactionPresenter } from './presenters/ProcessWalletTransactionPresenter';
|
||||
|
||||
// Repository injection tokens
|
||||
export const PAYMENT_REPOSITORY_TOKEN = 'IPaymentRepository';
|
||||
export const MEMBERSHIP_FEE_REPOSITORY_TOKEN = 'IMembershipFeeRepository';
|
||||
@@ -52,9 +66,87 @@ export const DELETE_PRIZE_USE_CASE_TOKEN = 'DeletePrizeUseCase';
|
||||
export const GET_WALLET_USE_CASE_TOKEN = 'GetWalletUseCase';
|
||||
export const PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN = 'ProcessWalletTransactionUseCase';
|
||||
|
||||
// Output port tokens
|
||||
export const GET_PAYMENTS_OUTPUT_PORT_TOKEN = 'GetPaymentsOutputPort_TOKEN';
|
||||
export const CREATE_PAYMENT_OUTPUT_PORT_TOKEN = 'CreatePaymentOutputPort_TOKEN';
|
||||
export const UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN = 'UpdatePaymentStatusOutputPort_TOKEN';
|
||||
export const GET_MEMBERSHIP_FEES_OUTPUT_PORT_TOKEN = 'GetMembershipFeesOutputPort_TOKEN';
|
||||
export const UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN = 'UpsertMembershipFeeOutputPort_TOKEN';
|
||||
export const UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_TOKEN = 'UpdateMemberPaymentOutputPort_TOKEN';
|
||||
export const GET_PRIZES_OUTPUT_PORT_TOKEN = 'GetPrizesOutputPort_TOKEN';
|
||||
export const CREATE_PRIZE_OUTPUT_PORT_TOKEN = 'CreatePrizeOutputPort_TOKEN';
|
||||
export const AWARD_PRIZE_OUTPUT_PORT_TOKEN = 'AwardPrizeOutputPort_TOKEN';
|
||||
export const DELETE_PRIZE_OUTPUT_PORT_TOKEN = 'DeletePrizeOutputPort_TOKEN';
|
||||
export const GET_WALLET_OUTPUT_PORT_TOKEN = 'GetWalletOutputPort_TOKEN';
|
||||
export const PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN = 'ProcessWalletTransactionOutputPort_TOKEN';
|
||||
|
||||
export const PaymentsProviders: Provider[] = [
|
||||
PaymentsService,
|
||||
|
||||
// Presenters
|
||||
GetPaymentsPresenter,
|
||||
CreatePaymentPresenter,
|
||||
UpdatePaymentStatusPresenter,
|
||||
GetMembershipFeesPresenter,
|
||||
UpsertMembershipFeePresenter,
|
||||
UpdateMemberPaymentPresenter,
|
||||
GetPrizesPresenter,
|
||||
CreatePrizePresenter,
|
||||
AwardPrizePresenter,
|
||||
DeletePrizePresenter,
|
||||
GetWalletPresenter,
|
||||
ProcessWalletTransactionPresenter,
|
||||
|
||||
// Output ports
|
||||
{
|
||||
provide: GET_PAYMENTS_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetPaymentsPresenter,
|
||||
},
|
||||
{
|
||||
provide: CREATE_PAYMENT_OUTPUT_PORT_TOKEN,
|
||||
useExisting: CreatePaymentPresenter,
|
||||
},
|
||||
{
|
||||
provide: UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN,
|
||||
useExisting: UpdatePaymentStatusPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_MEMBERSHIP_FEES_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetMembershipFeesPresenter,
|
||||
},
|
||||
{
|
||||
provide: UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN,
|
||||
useExisting: UpsertMembershipFeePresenter,
|
||||
},
|
||||
{
|
||||
provide: UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_TOKEN,
|
||||
useExisting: UpdateMemberPaymentPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_PRIZES_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetPrizesPresenter,
|
||||
},
|
||||
{
|
||||
provide: CREATE_PRIZE_OUTPUT_PORT_TOKEN,
|
||||
useExisting: CreatePrizePresenter,
|
||||
},
|
||||
{
|
||||
provide: AWARD_PRIZE_OUTPUT_PORT_TOKEN,
|
||||
useExisting: AwardPrizePresenter,
|
||||
},
|
||||
{
|
||||
provide: DELETE_PRIZE_OUTPUT_PORT_TOKEN,
|
||||
useExisting: DeletePrizePresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_WALLET_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetWalletPresenter,
|
||||
},
|
||||
{
|
||||
provide: PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN,
|
||||
useExisting: ProcessWalletTransactionPresenter,
|
||||
},
|
||||
|
||||
// Logger
|
||||
{
|
||||
provide: LOGGER_TOKEN,
|
||||
@@ -96,66 +188,66 @@ export const PaymentsProviders: Provider[] = [
|
||||
// Use cases (use cases receive repositories, services receive use cases)
|
||||
{
|
||||
provide: GET_PAYMENTS_USE_CASE_TOKEN,
|
||||
useFactory: (paymentRepo: IPaymentRepository) => new GetPaymentsUseCase(paymentRepo),
|
||||
inject: [PAYMENT_REPOSITORY_TOKEN],
|
||||
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<any>) => new GetPaymentsUseCase(paymentRepo, output),
|
||||
inject: [PAYMENT_REPOSITORY_TOKEN, GET_PAYMENTS_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: CREATE_PAYMENT_USE_CASE_TOKEN,
|
||||
useFactory: (paymentRepo: IPaymentRepository) => new CreatePaymentUseCase(paymentRepo),
|
||||
inject: [PAYMENT_REPOSITORY_TOKEN],
|
||||
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<any>) => new CreatePaymentUseCase(paymentRepo, output),
|
||||
inject: [PAYMENT_REPOSITORY_TOKEN, CREATE_PAYMENT_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UPDATE_PAYMENT_STATUS_USE_CASE_TOKEN,
|
||||
useFactory: (paymentRepo: IPaymentRepository) => new UpdatePaymentStatusUseCase(paymentRepo),
|
||||
inject: [PAYMENT_REPOSITORY_TOKEN],
|
||||
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<any>) => new UpdatePaymentStatusUseCase(paymentRepo, output),
|
||||
inject: [PAYMENT_REPOSITORY_TOKEN, UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_MEMBERSHIP_FEES_USE_CASE_TOKEN,
|
||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository) =>
|
||||
new GetMembershipFeesUseCase(membershipFeeRepo, memberPaymentRepo),
|
||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN],
|
||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository, output: UseCaseOutputPort<any>) =>
|
||||
new GetMembershipFeesUseCase(membershipFeeRepo, memberPaymentRepo, output),
|
||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN, GET_MEMBERSHIP_FEES_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UPSERT_MEMBERSHIP_FEE_USE_CASE_TOKEN,
|
||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository) => new UpsertMembershipFeeUseCase(membershipFeeRepo),
|
||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN],
|
||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository, output: UseCaseOutputPort<any>) => new UpsertMembershipFeeUseCase(membershipFeeRepo, output),
|
||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UPDATE_MEMBER_PAYMENT_USE_CASE_TOKEN,
|
||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository) =>
|
||||
new UpdateMemberPaymentUseCase(membershipFeeRepo, memberPaymentRepo),
|
||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN],
|
||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository, output: UseCaseOutputPort<any>) =>
|
||||
new UpdateMemberPaymentUseCase(membershipFeeRepo, memberPaymentRepo, output),
|
||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN, UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_PRIZES_USE_CASE_TOKEN,
|
||||
useFactory: (prizeRepo: IPrizeRepository) => new GetPrizesUseCase(prizeRepo),
|
||||
inject: [PRIZE_REPOSITORY_TOKEN],
|
||||
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<any>) => new GetPrizesUseCase(prizeRepo, output),
|
||||
inject: [PRIZE_REPOSITORY_TOKEN, GET_PRIZES_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: CREATE_PRIZE_USE_CASE_TOKEN,
|
||||
useFactory: (prizeRepo: IPrizeRepository) => new CreatePrizeUseCase(prizeRepo),
|
||||
inject: [PRIZE_REPOSITORY_TOKEN],
|
||||
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<any>) => new CreatePrizeUseCase(prizeRepo, output),
|
||||
inject: [PRIZE_REPOSITORY_TOKEN, CREATE_PRIZE_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: AWARD_PRIZE_USE_CASE_TOKEN,
|
||||
useFactory: (prizeRepo: IPrizeRepository) => new AwardPrizeUseCase(prizeRepo),
|
||||
inject: [PRIZE_REPOSITORY_TOKEN],
|
||||
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<any>) => new AwardPrizeUseCase(prizeRepo, output),
|
||||
inject: [PRIZE_REPOSITORY_TOKEN, AWARD_PRIZE_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: DELETE_PRIZE_USE_CASE_TOKEN,
|
||||
useFactory: (prizeRepo: IPrizeRepository) => new DeletePrizeUseCase(prizeRepo),
|
||||
inject: [PRIZE_REPOSITORY_TOKEN],
|
||||
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<any>) => new DeletePrizeUseCase(prizeRepo, output),
|
||||
inject: [PRIZE_REPOSITORY_TOKEN, DELETE_PRIZE_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_WALLET_USE_CASE_TOKEN,
|
||||
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository) =>
|
||||
new GetWalletUseCase(walletRepo, transactionRepo),
|
||||
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN],
|
||||
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository, output: UseCaseOutputPort<any>) =>
|
||||
new GetWalletUseCase(walletRepo, transactionRepo, output),
|
||||
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN, GET_WALLET_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN,
|
||||
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository) =>
|
||||
new ProcessWalletTransactionUseCase(walletRepo, transactionRepo),
|
||||
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN],
|
||||
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository, output: UseCaseOutputPort<any>) =>
|
||||
new ProcessWalletTransactionUseCase(walletRepo, transactionRepo, output),
|
||||
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN, PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -90,54 +90,78 @@ export class PaymentsService {
|
||||
@Inject(GET_WALLET_USE_CASE_TOKEN) private readonly getWalletUseCase: GetWalletUseCase,
|
||||
@Inject(PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN) private readonly processWalletTransactionUseCase: ProcessWalletTransactionUseCase,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
private readonly getPaymentsPresenter: GetPaymentsPresenter,
|
||||
private readonly createPaymentPresenter: CreatePaymentPresenter,
|
||||
private readonly updatePaymentStatusPresenter: UpdatePaymentStatusPresenter,
|
||||
private readonly getMembershipFeesPresenter: GetMembershipFeesPresenter,
|
||||
private readonly upsertMembershipFeePresenter: UpsertMembershipFeePresenter,
|
||||
private readonly updateMemberPaymentPresenter: UpdateMemberPaymentPresenter,
|
||||
private readonly getPrizesPresenter: GetPrizesPresenter,
|
||||
private readonly createPrizePresenter: CreatePrizePresenter,
|
||||
private readonly awardPrizePresenter: AwardPrizePresenter,
|
||||
private readonly deletePrizePresenter: DeletePrizePresenter,
|
||||
private readonly getWalletPresenter: GetWalletPresenter,
|
||||
private readonly processWalletTransactionPresenter: ProcessWalletTransactionPresenter,
|
||||
) {}
|
||||
|
||||
async getPayments(query: GetPaymentsQuery): Promise<GetPaymentsPresenter> {
|
||||
async getPayments(query: GetPaymentsQuery): Promise<GetPaymentsOutput> {
|
||||
this.logger.debug('[PaymentsService] Getting payments', { query });
|
||||
|
||||
const presenter = new GetPaymentsPresenter();
|
||||
await this.getPaymentsUseCase.execute(query, presenter);
|
||||
return presenter;
|
||||
const result = await this.getPaymentsUseCase.execute(query);
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message ?? 'Failed to get payments');
|
||||
}
|
||||
return this.getPaymentsPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async createPayment(input: CreatePaymentInput): Promise<CreatePaymentPresenter> {
|
||||
async createPayment(input: CreatePaymentInput): Promise<CreatePaymentOutput> {
|
||||
this.logger.debug('[PaymentsService] Creating payment', { input });
|
||||
|
||||
const presenter = new CreatePaymentPresenter();
|
||||
await this.createPaymentUseCase.execute(input, presenter);
|
||||
return presenter;
|
||||
const result = await this.createPaymentUseCase.execute(input);
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message ?? 'Failed to create payment');
|
||||
}
|
||||
return this.createPaymentPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async updatePaymentStatus(input: UpdatePaymentStatusInput): Promise<UpdatePaymentStatusPresenter> {
|
||||
async updatePaymentStatus(input: UpdatePaymentStatusInput): Promise<UpdatePaymentStatusOutput> {
|
||||
this.logger.debug('[PaymentsService] Updating payment status', { input });
|
||||
|
||||
const presenter = new UpdatePaymentStatusPresenter();
|
||||
await this.updatePaymentStatusUseCase.execute(input, presenter);
|
||||
return presenter;
|
||||
const result = await this.updatePaymentStatusUseCase.execute(input);
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message ?? 'Failed to update payment status');
|
||||
}
|
||||
return this.updatePaymentStatusPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getMembershipFees(query: GetMembershipFeesQuery): Promise<GetMembershipFeesPresenter> {
|
||||
async getMembershipFees(query: GetMembershipFeesQuery): Promise<GetMembershipFeesOutput> {
|
||||
this.logger.debug('[PaymentsService] Getting membership fees', { query });
|
||||
|
||||
const presenter = new GetMembershipFeesPresenter();
|
||||
await this.getMembershipFeesUseCase.execute(query, presenter);
|
||||
return presenter;
|
||||
const result = await this.getMembershipFeesUseCase.execute(query);
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message ?? 'Failed to get membership fees');
|
||||
}
|
||||
return this.getMembershipFeesPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async upsertMembershipFee(input: UpsertMembershipFeeInput): Promise<UpsertMembershipFeePresenter> {
|
||||
async upsertMembershipFee(input: UpsertMembershipFeeInput): Promise<UpsertMembershipFeeOutput> {
|
||||
this.logger.debug('[PaymentsService] Upserting membership fee', { input });
|
||||
|
||||
const presenter = new UpsertMembershipFeePresenter();
|
||||
await this.upsertMembershipFeeUseCase.execute(input, presenter);
|
||||
return presenter;
|
||||
const result = await this.upsertMembershipFeeUseCase.execute(input);
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message ?? 'Failed to upsert membership fee');
|
||||
}
|
||||
return this.upsertMembershipFeePresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async updateMemberPayment(input: UpdateMemberPaymentInput): Promise<UpdateMemberPaymentPresenter> {
|
||||
async updateMemberPayment(input: UpdateMemberPaymentInput): Promise<UpdateMemberPaymentOutput> {
|
||||
this.logger.debug('[PaymentsService] Updating member payment', { input });
|
||||
|
||||
const presenter = new UpdateMemberPaymentPresenter();
|
||||
await this.updateMemberPaymentUseCase.execute(input, presenter);
|
||||
return presenter;
|
||||
const result = await this.updateMemberPaymentUseCase.execute(input);
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message ?? 'Failed to update member payment');
|
||||
}
|
||||
return this.updateMemberPaymentPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getPrizes(query: GetPrizesQuery): Promise<GetPrizesPresenter> {
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
import type {
|
||||
ICreatePaymentPresenter,
|
||||
CreatePaymentResultDTO,
|
||||
CreatePaymentViewModel,
|
||||
} from '@core/payments/application/presenters/ICreatePaymentPresenter';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { CreatePaymentResult } from '@core/payments/application/use-cases/CreatePaymentUseCase';
|
||||
import type { CreatePaymentOutput } from '../dtos/PaymentsDto';
|
||||
|
||||
export class CreatePaymentPresenter implements ICreatePaymentPresenter {
|
||||
private responseModel: CreatePaymentViewModel | null = null;
|
||||
export class CreatePaymentPresenter implements UseCaseOutputPort<CreatePaymentResult> {
|
||||
private responseModel: CreatePaymentOutput | null = null;
|
||||
|
||||
reset() {
|
||||
this.responseModel = null;
|
||||
}
|
||||
|
||||
present(dto: CreatePaymentResultDTO) {
|
||||
this.responseModel = dto;
|
||||
present(result: CreatePaymentResult): void {
|
||||
this.responseModel = {
|
||||
payment: {
|
||||
id: result.payment.id,
|
||||
type: result.payment.type,
|
||||
amount: result.payment.amount,
|
||||
platformFee: result.payment.platformFee,
|
||||
netAmount: result.payment.netAmount,
|
||||
payerId: result.payment.payerId,
|
||||
payerType: result.payment.payerType,
|
||||
leagueId: result.payment.leagueId,
|
||||
...(result.payment.seasonId !== undefined ? { seasonId: result.payment.seasonId } : {}),
|
||||
status: result.payment.status,
|
||||
createdAt: result.payment.createdAt,
|
||||
...(result.payment.completedAt !== undefined ? { completedAt: result.payment.completedAt } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getResponseModel(): CreatePaymentViewModel | null {
|
||||
return this.responseModel;
|
||||
}
|
||||
|
||||
get responseModel(): CreatePaymentViewModel {
|
||||
getResponseModel(): CreatePaymentOutput {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
import type {
|
||||
IGetPaymentsPresenter,
|
||||
GetPaymentsResultDTO,
|
||||
GetPaymentsViewModel,
|
||||
} from '@core/payments/application/presenters/IGetPaymentsPresenter';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { GetPaymentsResult } from '@core/payments/application/use-cases/GetPaymentsUseCase';
|
||||
import type { GetPaymentsOutput } from '../dtos/PaymentsDto';
|
||||
|
||||
export class GetPaymentsPresenter implements IGetPaymentsPresenter {
|
||||
private result: GetPaymentsViewModel | null = null;
|
||||
export class GetPaymentsPresenter implements UseCaseOutputPort<GetPaymentsResult> {
|
||||
private responseModel: GetPaymentsOutput | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
this.responseModel = null;
|
||||
}
|
||||
|
||||
present(dto: GetPaymentsResultDTO) {
|
||||
this.result = dto;
|
||||
present(result: GetPaymentsResult): void {
|
||||
this.responseModel = {
|
||||
payments: result.payments.map(payment => ({
|
||||
id: payment.id,
|
||||
type: payment.type,
|
||||
amount: payment.amount,
|
||||
platformFee: payment.platformFee,
|
||||
netAmount: payment.netAmount,
|
||||
payerId: payment.payerId,
|
||||
payerType: payment.payerType,
|
||||
leagueId: payment.leagueId,
|
||||
...(payment.seasonId !== undefined ? { seasonId: payment.seasonId } : {}),
|
||||
status: payment.status,
|
||||
createdAt: payment.createdAt,
|
||||
...(payment.completedAt !== undefined ? { completedAt: payment.completedAt } : {}),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): GetPaymentsViewModel | null {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
get viewModel(): GetPaymentsViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
getResponseModel(): GetPaymentsOutput {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,35 @@
|
||||
import type {
|
||||
IUpdatePaymentStatusPresenter,
|
||||
UpdatePaymentStatusResultDTO,
|
||||
UpdatePaymentStatusViewModel,
|
||||
} from '@core/payments/application/presenters/IUpdatePaymentStatusPresenter';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { UpdatePaymentStatusResult } from '@core/payments/application/use-cases/UpdatePaymentStatusUseCase';
|
||||
import type { UpdatePaymentStatusOutput } from '../dtos/PaymentsDto';
|
||||
|
||||
export class UpdatePaymentStatusPresenter implements IUpdatePaymentStatusPresenter {
|
||||
private result: UpdatePaymentStatusViewModel | null = null;
|
||||
export class UpdatePaymentStatusPresenter implements UseCaseOutputPort<UpdatePaymentStatusResult> {
|
||||
private responseModel: UpdatePaymentStatusOutput | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
this.responseModel = null;
|
||||
}
|
||||
|
||||
present(dto: UpdatePaymentStatusResultDTO) {
|
||||
this.result = dto;
|
||||
present(result: UpdatePaymentStatusResult): void {
|
||||
this.responseModel = {
|
||||
payment: {
|
||||
id: result.payment.id,
|
||||
type: result.payment.type,
|
||||
amount: result.payment.amount,
|
||||
platformFee: result.payment.platformFee,
|
||||
netAmount: result.payment.netAmount,
|
||||
payerId: result.payment.payerId,
|
||||
payerType: result.payment.payerType,
|
||||
leagueId: result.payment.leagueId,
|
||||
...(result.payment.seasonId !== undefined ? { seasonId: result.payment.seasonId } : {}),
|
||||
status: result.payment.status,
|
||||
createdAt: result.payment.createdAt,
|
||||
...(result.payment.completedAt !== undefined ? { completedAt: result.payment.completedAt } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): UpdatePaymentStatusViewModel | null {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
get viewModel(): UpdatePaymentStatusViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
getResponseModel(): UpdatePaymentStatusOutput {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type {
|
||||
ReviewProtestResult,
|
||||
ReviewProtestApplicationError,
|
||||
} from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { ReviewProtestResult } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||
|
||||
export interface ReviewProtestResponseDTO {
|
||||
success: boolean;
|
||||
@@ -13,34 +10,18 @@ export interface ReviewProtestResponseDTO {
|
||||
decision?: 'uphold' | 'dismiss';
|
||||
}
|
||||
|
||||
export class ReviewProtestPresenter {
|
||||
export class ReviewProtestPresenter implements UseCaseOutputPort<ReviewProtestResult> {
|
||||
private model: ReviewProtestResponseDTO | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(
|
||||
result: Result<ReviewProtestResult, ReviewProtestApplicationError>,
|
||||
): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
this.model = {
|
||||
success: false,
|
||||
errorCode: error.code,
|
||||
message: error.details?.message,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const value = result.unwrap();
|
||||
|
||||
present(result: ReviewProtestResult): void {
|
||||
this.model = {
|
||||
success: true,
|
||||
protestId: value.protestId,
|
||||
stewardId: value.stewardId,
|
||||
decision: value.decision,
|
||||
protestId: result.protestId,
|
||||
decision: result.status === 'upheld' ? 'uphold' : 'dismiss',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -96,11 +96,14 @@ export class RaceService {
|
||||
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({});
|
||||
|
||||
const presenter = new GetAllRacesPresenter();
|
||||
presenter.reset();
|
||||
presenter.present(result);
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().code);
|
||||
}
|
||||
|
||||
return presenter;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,17 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetAllRacesResult,
|
||||
GetAllRacesErrorCode,
|
||||
} from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
||||
|
||||
export type GetAllRacesResponseModel = AllRacesPageDTO;
|
||||
|
||||
export type GetAllRacesApplicationError = ApplicationErrorCode<
|
||||
GetAllRacesErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class GetAllRacesPresenter {
|
||||
export class GetAllRacesPresenter implements UseCaseOutputPort<GetAllRacesResult> {
|
||||
private model: GetAllRacesResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<GetAllRacesResult, GetAllRacesApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get all races');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
present(result: GetAllRacesResult): void {
|
||||
const leagueMap = new Map<string, string>();
|
||||
const uniqueLeagues = new Map<string, { id: string; name: string }>();
|
||||
|
||||
for (const league of output.leagues) {
|
||||
for (const league of result.leagues) {
|
||||
const id = league.id.toString();
|
||||
const name = league.name.toString();
|
||||
leagueMap.set(id, name);
|
||||
@@ -39,7 +19,7 @@ export class GetAllRacesPresenter {
|
||||
}
|
||||
|
||||
this.model = {
|
||||
races: output.races.map(race => ({
|
||||
races: result.races.map(race => ({
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
|
||||
Reference in New Issue
Block a user