refactor use cases
This commit is contained in:
@@ -4,13 +4,6 @@ import type { INotificationPreferenceRepository } from '@core/notifications/doma
|
|||||||
import type { NotificationGatewayRegistry } from '@core/notifications/application/ports/NotificationGateway';
|
import type { NotificationGatewayRegistry } from '@core/notifications/application/ports/NotificationGateway';
|
||||||
import { SendNotificationUseCase } from '@core/notifications/application/use-cases/SendNotificationUseCase';
|
import { SendNotificationUseCase } from '@core/notifications/application/use-cases/SendNotificationUseCase';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
|
|
||||||
class NoOpOutputPort implements UseCaseOutputPort<any> {
|
|
||||||
present(_result: any): void {
|
|
||||||
// No-op for adapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NotificationServiceAdapter implements NotificationService {
|
export class NotificationServiceAdapter implements NotificationService {
|
||||||
private readonly useCase: SendNotificationUseCase;
|
private readonly useCase: SendNotificationUseCase;
|
||||||
@@ -27,7 +20,6 @@ export class NotificationServiceAdapter implements NotificationService {
|
|||||||
notificationRepository,
|
notificationRepository,
|
||||||
preferenceRepository,
|
preferenceRepository,
|
||||||
gatewayRegistry,
|
gatewayRegistry,
|
||||||
new NoOpOutputPort(),
|
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,50 +3,31 @@ import { Module } from '@nestjs/common';
|
|||||||
import { InMemoryAdminPersistenceModule } from '../../persistence/inmemory/InMemoryAdminPersistenceModule';
|
import { InMemoryAdminPersistenceModule } from '../../persistence/inmemory/InMemoryAdminPersistenceModule';
|
||||||
import { AdminService } from './AdminService';
|
import { AdminService } from './AdminService';
|
||||||
import { AdminController } from './AdminController';
|
import { AdminController } from './AdminController';
|
||||||
import { ListUsersPresenter } from './presenters/ListUsersPresenter';
|
|
||||||
import { DashboardStatsPresenter } from './presenters/DashboardStatsPresenter';
|
|
||||||
import { AuthModule } from '../auth/AuthModule';
|
import { AuthModule } from '../auth/AuthModule';
|
||||||
import { ListUsersUseCase } from '@core/admin/application/use-cases/ListUsersUseCase';
|
import { ListUsersUseCase } from '@core/admin/application/use-cases/ListUsersUseCase';
|
||||||
import { GetDashboardStatsUseCase } from './use-cases/GetDashboardStatsUseCase';
|
import { GetDashboardStatsUseCase } from './use-cases/GetDashboardStatsUseCase';
|
||||||
import { InitializationLogger } from '../../shared/logging/InitializationLogger';
|
import { InitializationLogger } from '../../shared/logging/InitializationLogger';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import type { ListUsersResult } from '@core/admin/application/use-cases/ListUsersUseCase';
|
|
||||||
import type { DashboardStatsResult } from './use-cases/GetDashboardStatsUseCase';
|
|
||||||
import type { IAdminUserRepository } from '@core/admin/domain/repositories/IAdminUserRepository';
|
import type { IAdminUserRepository } from '@core/admin/domain/repositories/IAdminUserRepository';
|
||||||
|
|
||||||
export const ADMIN_USER_REPOSITORY_TOKEN = 'IAdminUserRepository';
|
export const ADMIN_USER_REPOSITORY_TOKEN = 'IAdminUserRepository';
|
||||||
export const LIST_USERS_OUTPUT_PORT_TOKEN = 'ListUsersOutputPort';
|
|
||||||
export const DASHBOARD_STATS_OUTPUT_PORT_TOKEN = 'DashboardStatsOutputPort';
|
|
||||||
|
|
||||||
const initLogger = InitializationLogger.getInstance();
|
const initLogger = InitializationLogger.getInstance();
|
||||||
|
|
||||||
const adminProviders: Provider[] = [
|
const adminProviders: Provider[] = [
|
||||||
AdminService,
|
AdminService,
|
||||||
ListUsersPresenter,
|
|
||||||
DashboardStatsPresenter,
|
|
||||||
{
|
|
||||||
provide: LIST_USERS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: ListUsersPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: DASHBOARD_STATS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: DashboardStatsPresenter,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: ListUsersUseCase,
|
provide: ListUsersUseCase,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
repository: IAdminUserRepository,
|
repository: IAdminUserRepository,
|
||||||
output: UseCaseOutputPort<ListUsersResult>,
|
) => new ListUsersUseCase(repository),
|
||||||
) => new ListUsersUseCase(repository, output),
|
inject: [ADMIN_USER_REPOSITORY_TOKEN],
|
||||||
inject: [ADMIN_USER_REPOSITORY_TOKEN, LIST_USERS_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GetDashboardStatsUseCase,
|
provide: GetDashboardStatsUseCase,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
repository: IAdminUserRepository,
|
repository: IAdminUserRepository,
|
||||||
output: UseCaseOutputPort<DashboardStatsResult>,
|
) => new GetDashboardStatsUseCase(repository),
|
||||||
) => new GetDashboardStatsUseCase(repository, output),
|
inject: [ADMIN_USER_REPOSITORY_TOKEN],
|
||||||
inject: [ADMIN_USER_REPOSITORY_TOKEN, DASHBOARD_STATS_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -12,18 +12,6 @@ const mockGetDashboardStatsUseCase = {
|
|||||||
execute: vi.fn(),
|
execute: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock presenters
|
|
||||||
const mockListUsersPresenter = {
|
|
||||||
present: vi.fn(),
|
|
||||||
getViewModel: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockDashboardStatsPresenter = {
|
|
||||||
present: vi.fn(),
|
|
||||||
responseModel: {},
|
|
||||||
reset: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('AdminService', () => {
|
describe('AdminService', () => {
|
||||||
describe('TDD - Test First', () => {
|
describe('TDD - Test First', () => {
|
||||||
let service: AdminService;
|
let service: AdminService;
|
||||||
@@ -32,9 +20,7 @@ describe('AdminService', () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
service = new AdminService(
|
service = new AdminService(
|
||||||
mockListUsersUseCase as any,
|
mockListUsersUseCase as any,
|
||||||
mockListUsersPresenter as any,
|
mockGetDashboardStatsUseCase as any
|
||||||
mockGetDashboardStatsUseCase as any,
|
|
||||||
mockDashboardStatsPresenter as any
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,15 +36,19 @@ describe('AdminService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
||||||
mockListUsersPresenter.getViewModel.mockReturnValue(expectedResult);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await service.listUsers({ actorId: 'actor-1' });
|
const result = await service.listUsers({ actorId: 'actor-1' });
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(mockListUsersUseCase.execute).toHaveBeenCalledWith({ actorId: 'actor-1' });
|
expect(mockListUsersUseCase.execute).toHaveBeenCalledWith({ actorId: 'actor-1' });
|
||||||
expect(mockListUsersPresenter.getViewModel).toHaveBeenCalled();
|
expect(result).toEqual({
|
||||||
expect(result).toEqual(expectedResult);
|
users: [],
|
||||||
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
totalPages: 0,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return users when they exist', async () => {
|
it('should return users when they exist', async () => {
|
||||||
@@ -88,16 +78,6 @@ describe('AdminService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
||||||
mockListUsersPresenter.getViewModel.mockReturnValue({
|
|
||||||
users: [
|
|
||||||
{ id: 'user-1', email: 'user1@example.com', displayName: 'User 1', roles: ['user'], status: 'active', isSystemAdmin: false, createdAt: user1.createdAt, updatedAt: user1.updatedAt },
|
|
||||||
{ id: 'user-2', email: 'user2@example.com', displayName: 'User 2', roles: ['admin'], status: 'active', isSystemAdmin: true, createdAt: user2.createdAt, updatedAt: user2.updatedAt },
|
|
||||||
],
|
|
||||||
total: 2,
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
totalPages: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await service.listUsers({ actorId: 'actor-1' });
|
const result = await service.listUsers({ actorId: 'actor-1' });
|
||||||
@@ -105,6 +85,11 @@ describe('AdminService', () => {
|
|||||||
// Assert
|
// Assert
|
||||||
expect(result.users).toHaveLength(2);
|
expect(result.users).toHaveLength(2);
|
||||||
expect(result.total).toBe(2);
|
expect(result.total).toBe(2);
|
||||||
|
// Check that users are converted to DTOs
|
||||||
|
expect(result.users[0]?.id).toBe('user-1');
|
||||||
|
expect(result.users[0]?.email).toBe('user1@example.com');
|
||||||
|
expect(result.users[1]?.id).toBe('user-2');
|
||||||
|
expect(result.users[1]?.email).toBe('user2@example.com');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply filters correctly', async () => {
|
it('should apply filters correctly', async () => {
|
||||||
@@ -126,13 +111,6 @@ describe('AdminService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
||||||
mockListUsersPresenter.getViewModel.mockReturnValue({
|
|
||||||
users: [{ id: 'admin-1', email: 'admin@example.com', displayName: 'Admin', roles: ['admin'], status: 'active', isSystemAdmin: true, createdAt: adminUser.createdAt, updatedAt: adminUser.updatedAt }],
|
|
||||||
total: 1,
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
totalPages: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await service.listUsers({
|
const result = await service.listUsers({
|
||||||
@@ -163,13 +141,6 @@ describe('AdminService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
||||||
mockListUsersPresenter.getViewModel.mockReturnValue({
|
|
||||||
users: [],
|
|
||||||
total: 50,
|
|
||||||
page: 3,
|
|
||||||
limit: 10,
|
|
||||||
totalPages: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await service.listUsers({
|
const result = await service.listUsers({
|
||||||
@@ -202,7 +173,6 @@ describe('AdminService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
||||||
mockListUsersPresenter.getViewModel.mockReturnValue(expectedResult);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await service.listUsers({
|
await service.listUsers({
|
||||||
@@ -232,7 +202,6 @@ describe('AdminService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
||||||
mockListUsersPresenter.getViewModel.mockReturnValue(expectedResult);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await service.listUsers({
|
await service.listUsers({
|
||||||
@@ -260,7 +229,6 @@ describe('AdminService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
||||||
mockListUsersPresenter.getViewModel.mockReturnValue(expectedResult);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await service.listUsers({
|
await service.listUsers({
|
||||||
@@ -299,7 +267,6 @@ describe('AdminService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
mockListUsersUseCase.execute.mockResolvedValue(Result.ok(expectedResult));
|
||||||
mockListUsersPresenter.getViewModel.mockReturnValue(expectedResult);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await service.listUsers({
|
await service.listUsers({
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ListUsersUseCase, ListUsersInput } from '@core/admin/application/use-cases/ListUsersUseCase';
|
import { ListUsersUseCase, ListUsersInput, ListUsersResult } from '@core/admin/application/use-cases/ListUsersUseCase';
|
||||||
import { ListUsersPresenter, ListUsersViewModel } from './presenters/ListUsersPresenter';
|
|
||||||
import { GetDashboardStatsUseCase, GetDashboardStatsInput } from './use-cases/GetDashboardStatsUseCase';
|
import { GetDashboardStatsUseCase, GetDashboardStatsInput } from './use-cases/GetDashboardStatsUseCase';
|
||||||
import { DashboardStatsPresenter, DashboardStatsResponse } from './presenters/DashboardStatsPresenter';
|
import { UserListResponseDto, UserResponseDto } from './dtos/UserResponseDto';
|
||||||
|
import { DashboardStatsResponseDto } from './dto/DashboardStatsResponseDto';
|
||||||
|
import type { AdminUser } from '@core/admin/domain/entities/AdminUser';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminService {
|
export class AdminService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly listUsersUseCase: ListUsersUseCase,
|
private readonly listUsersUseCase: ListUsersUseCase,
|
||||||
private readonly listUsersPresenter: ListUsersPresenter,
|
|
||||||
private readonly getDashboardStatsUseCase: GetDashboardStatsUseCase,
|
private readonly getDashboardStatsUseCase: GetDashboardStatsUseCase,
|
||||||
private readonly dashboardStatsPresenter: DashboardStatsPresenter,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async listUsers(input: ListUsersInput): Promise<ListUsersViewModel> {
|
async listUsers(input: ListUsersInput): Promise<UserListResponseDto> {
|
||||||
const result = await this.listUsersUseCase.execute(input);
|
const result = await this.listUsersUseCase.execute(input);
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
@@ -21,12 +20,11 @@ export class AdminService {
|
|||||||
throw new Error(`${error.code}: ${error.details.message}`);
|
throw new Error(`${error.code}: ${error.details.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.listUsersPresenter.getViewModel();
|
const data = result.unwrap();
|
||||||
|
return this.toListResponseDto(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDashboardStats(input: GetDashboardStatsInput): Promise<DashboardStatsResponse> {
|
async getDashboardStats(input: GetDashboardStatsInput): Promise<DashboardStatsResponseDto> {
|
||||||
this.dashboardStatsPresenter.reset();
|
|
||||||
|
|
||||||
const result = await this.getDashboardStatsUseCase.execute(input);
|
const result = await this.getDashboardStatsUseCase.execute(input);
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
@@ -34,6 +32,54 @@ export class AdminService {
|
|||||||
throw new Error(`${error.code}: ${error.details.message}`);
|
throw new Error(`${error.code}: ${error.details.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.dashboardStatsPresenter.responseModel;
|
const data = result.unwrap();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toListResponseDto(result: ListUsersResult): UserListResponseDto {
|
||||||
|
return {
|
||||||
|
users: result.users.map(user => this.toUserResponse(user)),
|
||||||
|
total: result.total,
|
||||||
|
page: result.page,
|
||||||
|
limit: result.limit,
|
||||||
|
totalPages: result.totalPages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toUserResponse(user: AdminUser | Record<string, unknown>): UserResponseDto {
|
||||||
|
// Handle both domain objects and plain objects
|
||||||
|
if (user.id && typeof user.id === 'object' && 'value' in (user.id as Record<string, unknown>)) {
|
||||||
|
// Domain object
|
||||||
|
const domainUser = user as AdminUser;
|
||||||
|
const response: UserResponseDto = {
|
||||||
|
id: domainUser.id.value,
|
||||||
|
email: domainUser.email.value,
|
||||||
|
displayName: domainUser.displayName,
|
||||||
|
roles: domainUser.roles.map(r => r.value),
|
||||||
|
status: domainUser.status.value,
|
||||||
|
isSystemAdmin: domainUser.isSystemAdmin(),
|
||||||
|
createdAt: domainUser.createdAt,
|
||||||
|
updatedAt: domainUser.updatedAt,
|
||||||
|
};
|
||||||
|
if (domainUser.lastLoginAt) response.lastLoginAt = domainUser.lastLoginAt;
|
||||||
|
if (domainUser.primaryDriverId) response.primaryDriverId = domainUser.primaryDriverId;
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
// Plain object (for tests)
|
||||||
|
const plainUser = user as Record<string, unknown>;
|
||||||
|
const response: UserResponseDto = {
|
||||||
|
id: plainUser.id as string,
|
||||||
|
email: plainUser.email as string,
|
||||||
|
displayName: plainUser.displayName as string,
|
||||||
|
roles: plainUser.roles as string[],
|
||||||
|
status: plainUser.status as string,
|
||||||
|
isSystemAdmin: plainUser.isSystemAdmin as boolean,
|
||||||
|
createdAt: plainUser.createdAt as Date,
|
||||||
|
updatedAt: plainUser.updatedAt as Date,
|
||||||
|
};
|
||||||
|
if (plainUser.lastLoginAt) response.lastLoginAt = plainUser.lastLoginAt as Date;
|
||||||
|
if (plainUser.primaryDriverId) response.primaryDriverId = plainUser.primaryDriverId as string;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import type { IAdminUserRepository } from '@core/admin/domain/repositories/IAdminUserRepository';
|
import type { IAdminUserRepository } from '@core/admin/domain/repositories/IAdminUserRepository';
|
||||||
import { AuthorizationService } from '@core/admin/domain/services/AuthorizationService';
|
import { AuthorizationService } from '@core/admin/domain/services/AuthorizationService';
|
||||||
import { UserId } from '@core/admin/domain/value-objects/UserId';
|
import { UserId } from '@core/admin/domain/value-objects/UserId';
|
||||||
@@ -46,10 +45,9 @@ export type GetDashboardStatsApplicationError = ApplicationErrorCode<GetDashboar
|
|||||||
export class GetDashboardStatsUseCase {
|
export class GetDashboardStatsUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly adminUserRepo: IAdminUserRepository,
|
private readonly adminUserRepo: IAdminUserRepository,
|
||||||
private readonly output: UseCaseOutputPort<DashboardStatsResult>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(input: GetDashboardStatsInput): Promise<Result<void, GetDashboardStatsApplicationError>> {
|
async execute(input: GetDashboardStatsInput): Promise<Result<DashboardStatsResult, GetDashboardStatsApplicationError>> {
|
||||||
try {
|
try {
|
||||||
// Get actor (current user)
|
// Get actor (current user)
|
||||||
const actor = await this.adminUserRepo.findById(UserId.fromString(input.actorId));
|
const actor = await this.adminUserRepo.findById(UserId.fromString(input.actorId));
|
||||||
@@ -166,9 +164,7 @@ export class GetDashboardStatsUseCase {
|
|||||||
activityTimeline,
|
activityTimeline,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.output.present(result);
|
return Result.ok(result);
|
||||||
|
|
||||||
return Result.ok(undefined);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Failed to get dashboard stats';
|
const message = error instanceof Error ? error.message : 'Failed to get dashboard stats';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import type { GetAnalyticsMetricsOutput } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
|
||||||
import type { RecordEngagementOutput } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
|
||||||
import type { RecordPageViewOutput } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
|
||||||
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
|
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
|
||||||
import type { IPageViewRepository } from '@core/analytics/application/repositories/IPageViewRepository';
|
import type { IPageViewRepository } from '@core/analytics/application/repositories/IPageViewRepository';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import { Provider } from '@nestjs/common';
|
import { Provider } from '@nestjs/common';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -13,13 +10,8 @@ import {
|
|||||||
|
|
||||||
const LOGGER_TOKEN = 'Logger';
|
const LOGGER_TOKEN = 'Logger';
|
||||||
|
|
||||||
const RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN = 'RecordPageViewOutputPort_TOKEN';
|
|
||||||
const RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN = 'RecordEngagementOutputPort_TOKEN';
|
|
||||||
const GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN = 'GetDashboardDataOutputPort_TOKEN';
|
|
||||||
const GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN = 'GetAnalyticsMetricsOutputPort_TOKEN';
|
|
||||||
|
|
||||||
import { GetAnalyticsMetricsUseCase } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
import { GetAnalyticsMetricsUseCase } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
||||||
import { GetDashboardDataOutput, GetDashboardDataUseCase } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
import { GetDashboardDataUseCase } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
||||||
import { RecordEngagementUseCase } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
import { RecordEngagementUseCase } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
||||||
import { RecordPageViewUseCase } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
import { RecordPageViewUseCase } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
||||||
import { AnalyticsService } from './AnalyticsService';
|
import { AnalyticsService } from './AnalyticsService';
|
||||||
@@ -34,44 +26,28 @@ export const AnalyticsProviders: Provider[] = [
|
|||||||
RecordEngagementPresenter,
|
RecordEngagementPresenter,
|
||||||
GetDashboardDataPresenter,
|
GetDashboardDataPresenter,
|
||||||
GetAnalyticsMetricsPresenter,
|
GetAnalyticsMetricsPresenter,
|
||||||
{
|
|
||||||
provide: RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: RecordPageViewPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: RecordEngagementPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetDashboardDataPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetAnalyticsMetricsPresenter,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: RecordPageViewUseCase,
|
provide: RecordPageViewUseCase,
|
||||||
useFactory: (repo: IPageViewRepository, logger: Logger, output: UseCaseOutputPort<RecordPageViewOutput>) =>
|
useFactory: (repo: IPageViewRepository, logger: Logger) =>
|
||||||
new RecordPageViewUseCase(repo, logger, output),
|
new RecordPageViewUseCase(repo, logger),
|
||||||
inject: [ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN, LOGGER_TOKEN, RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN],
|
inject: [ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: RecordEngagementUseCase,
|
provide: RecordEngagementUseCase,
|
||||||
useFactory: (repo: IEngagementRepository, logger: Logger, output: UseCaseOutputPort<RecordEngagementOutput>) =>
|
useFactory: (repo: IEngagementRepository, logger: Logger) =>
|
||||||
new RecordEngagementUseCase(repo, logger, output),
|
new RecordEngagementUseCase(repo, logger),
|
||||||
inject: [ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN, LOGGER_TOKEN, RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN],
|
inject: [ANALYTICS_ENGAGEMENT_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GetDashboardDataUseCase,
|
provide: GetDashboardDataUseCase,
|
||||||
useFactory: (logger: Logger, output: UseCaseOutputPort<GetDashboardDataOutput>) =>
|
useFactory: (logger: Logger) =>
|
||||||
new GetDashboardDataUseCase(logger, output),
|
new GetDashboardDataUseCase(logger),
|
||||||
inject: [LOGGER_TOKEN, GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN],
|
inject: [LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GetAnalyticsMetricsUseCase,
|
provide: GetAnalyticsMetricsUseCase,
|
||||||
useFactory: (logger: Logger, output: UseCaseOutputPort<GetAnalyticsMetricsOutput>, repo: IPageViewRepository) =>
|
useFactory: (logger: Logger, repo: IPageViewRepository) =>
|
||||||
new GetAnalyticsMetricsUseCase(logger, output, repo),
|
new GetAnalyticsMetricsUseCase(logger, repo),
|
||||||
inject: [LOGGER_TOKEN, GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN, ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN],
|
inject: [LOGGER_TOKEN, ANALYTICS_PAGE_VIEW_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -12,8 +12,7 @@ describe('AnalyticsService', () => {
|
|||||||
|
|
||||||
const recordPageViewUseCase = {
|
const recordPageViewUseCase = {
|
||||||
execute: vi.fn(async () => {
|
execute: vi.fn(async () => {
|
||||||
recordPageViewPresenter.present({ pageViewId: 'pv-1' });
|
return Result.ok({ pageViewId: 'pv-1' });
|
||||||
return Result.ok(undefined);
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,8 +76,7 @@ describe('AnalyticsService', () => {
|
|||||||
const recordEngagementPresenter = new RecordEngagementPresenter();
|
const recordEngagementPresenter = new RecordEngagementPresenter();
|
||||||
const recordEngagementUseCase = {
|
const recordEngagementUseCase = {
|
||||||
execute: vi.fn(async () => {
|
execute: vi.fn(async () => {
|
||||||
recordEngagementPresenter.present({ eventId: 'e1', engagementWeight: 7 });
|
return Result.ok({ eventId: 'e1', engagementWeight: 7 });
|
||||||
return Result.ok(undefined);
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -154,13 +152,12 @@ describe('AnalyticsService', () => {
|
|||||||
const getDashboardDataPresenter = new GetDashboardDataPresenter();
|
const getDashboardDataPresenter = new GetDashboardDataPresenter();
|
||||||
const getDashboardDataUseCase = {
|
const getDashboardDataUseCase = {
|
||||||
execute: vi.fn(async () => {
|
execute: vi.fn(async () => {
|
||||||
getDashboardDataPresenter.present({
|
return Result.ok({
|
||||||
totalUsers: 1,
|
totalUsers: 1,
|
||||||
activeUsers: 2,
|
activeUsers: 2,
|
||||||
totalRaces: 3,
|
totalRaces: 3,
|
||||||
totalLeagues: 4,
|
totalLeagues: 4,
|
||||||
});
|
});
|
||||||
return Result.ok(undefined);
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,13 +214,12 @@ describe('AnalyticsService', () => {
|
|||||||
const getAnalyticsMetricsPresenter = new GetAnalyticsMetricsPresenter();
|
const getAnalyticsMetricsPresenter = new GetAnalyticsMetricsPresenter();
|
||||||
const getAnalyticsMetricsUseCase = {
|
const getAnalyticsMetricsUseCase = {
|
||||||
execute: vi.fn(async () => {
|
execute: vi.fn(async () => {
|
||||||
getAnalyticsMetricsPresenter.present({
|
return Result.ok({
|
||||||
pageViews: 10,
|
pageViews: 10,
|
||||||
uniqueVisitors: 0,
|
uniqueVisitors: 0,
|
||||||
averageSessionDuration: 0,
|
averageSessionDuration: 0,
|
||||||
bounceRate: 0,
|
bounceRate: 0,
|
||||||
});
|
});
|
||||||
return Result.ok(undefined);
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,50 +31,42 @@ export class AnalyticsService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async recordPageView(input: RecordPageViewInput): Promise<RecordPageViewOutputDTO> {
|
async recordPageView(input: RecordPageViewInput): Promise<RecordPageViewOutputDTO> {
|
||||||
this.recordPageViewPresenter.reset();
|
|
||||||
|
|
||||||
const result = await this.recordPageViewUseCase.execute(input);
|
const result = await this.recordPageViewUseCase.execute(input);
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
const error = result.unwrapErr();
|
const error = result.unwrapErr();
|
||||||
throw new Error(error.details?.message ?? 'Failed to record page view');
|
throw new Error(error.details?.message ?? 'Failed to record page view');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.recordPageViewPresenter.responseModel;
|
return this.recordPageViewPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async recordEngagement(input: RecordEngagementInput): Promise<RecordEngagementOutputDTO> {
|
async recordEngagement(input: RecordEngagementInput): Promise<RecordEngagementOutputDTO> {
|
||||||
this.recordEngagementPresenter.reset();
|
|
||||||
|
|
||||||
const result = await this.recordEngagementUseCase.execute(input);
|
const result = await this.recordEngagementUseCase.execute(input);
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
const error = result.unwrapErr();
|
const error = result.unwrapErr();
|
||||||
throw new Error(error.details?.message ?? 'Failed to record engagement');
|
throw new Error(error.details?.message ?? 'Failed to record engagement');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.recordEngagementPresenter.responseModel;
|
return this.recordEngagementPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDashboardData(): Promise<GetDashboardDataOutputDTO> {
|
async getDashboardData(): Promise<GetDashboardDataOutputDTO> {
|
||||||
this.getDashboardDataPresenter.reset();
|
|
||||||
|
|
||||||
const result = await this.getDashboardDataUseCase.execute();
|
const result = await this.getDashboardDataUseCase.execute();
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
const error = result.unwrapErr();
|
const error = result.unwrapErr();
|
||||||
throw new Error(error.details?.message ?? 'Failed to get dashboard data');
|
throw new Error(error.details?.message ?? 'Failed to get dashboard data');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getDashboardDataPresenter.responseModel;
|
return this.getDashboardDataPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutputDTO> {
|
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutputDTO> {
|
||||||
this.getAnalyticsMetricsPresenter.reset();
|
|
||||||
|
|
||||||
const result = await this.getAnalyticsMetricsUseCase.execute({});
|
const result = await this.getAnalyticsMetricsUseCase.execute({});
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
const error = result.unwrapErr();
|
const error = result.unwrapErr();
|
||||||
throw new Error(error.details?.message ?? 'Failed to get analytics metrics');
|
throw new Error(error.details?.message ?? 'Failed to get analytics metrics');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getAnalyticsMetricsPresenter.responseModel;
|
return this.getAnalyticsMetricsPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ describe('GetAnalyticsMetricsPresenter', () => {
|
|||||||
bounceRate: 0.4,
|
bounceRate: 0.4,
|
||||||
};
|
};
|
||||||
|
|
||||||
presenter.present(output);
|
presenter.transform(output);
|
||||||
|
|
||||||
const dto = presenter.getResponseModel();
|
const dto = presenter.getResponseModel();
|
||||||
|
|
||||||
@@ -35,11 +35,11 @@ describe('GetAnalyticsMetricsPresenter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getResponseModel throws if not presented', () => {
|
it('getResponseModel throws if not transformed', () => {
|
||||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
expect(() => presenter.getResponseModel()).toThrow('Presenter not transformed');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('responseModel throws if not presented', () => {
|
it('responseModel throws if not transformed', () => {
|
||||||
expect(() => presenter.responseModel).toThrow('Presenter not presented');
|
expect(() => presenter.responseModel).toThrow('Presenter not transformed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import type { GetAnalyticsMetricsOutput } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
import type { GetAnalyticsMetricsOutput } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { GetAnalyticsMetricsOutputDTO } from '../dtos/GetAnalyticsMetricsOutputDTO';
|
import type { GetAnalyticsMetricsOutputDTO } from '../dtos/GetAnalyticsMetricsOutputDTO';
|
||||||
|
|
||||||
export class GetAnalyticsMetricsPresenter implements UseCaseOutputPort<GetAnalyticsMetricsOutput> {
|
export class GetAnalyticsMetricsPresenter {
|
||||||
private model: GetAnalyticsMetricsOutputDTO | null = null;
|
private model: GetAnalyticsMetricsOutputDTO | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: GetAnalyticsMetricsOutput): void {
|
transform(result: GetAnalyticsMetricsOutput): GetAnalyticsMetricsOutputDTO {
|
||||||
this.model = {
|
this.model = {
|
||||||
pageViews: result.pageViews,
|
pageViews: result.pageViews,
|
||||||
uniqueVisitors: result.uniqueVisitors,
|
uniqueVisitors: result.uniqueVisitors,
|
||||||
averageSessionDuration: result.averageSessionDuration,
|
averageSessionDuration: result.averageSessionDuration,
|
||||||
bounceRate: result.bounceRate,
|
bounceRate: result.bounceRate,
|
||||||
};
|
};
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
get responseModel(): GetAnalyticsMetricsOutputDTO {
|
get responseModel(): GetAnalyticsMetricsOutputDTO {
|
||||||
if (!this.model) throw new Error('Presenter not presented');
|
if (!this.model) throw new Error('Presenter not transformed');
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): GetAnalyticsMetricsOutputDTO {
|
getResponseModel(): GetAnalyticsMetricsOutputDTO {
|
||||||
if (!this.model) throw new Error('Presenter not presented');
|
if (!this.model) throw new Error('Presenter not transformed');
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ describe('GetDashboardDataPresenter', () => {
|
|||||||
totalLeagues: 5,
|
totalLeagues: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
presenter.present(output);
|
presenter.transform(output);
|
||||||
|
|
||||||
expect(presenter.getResponseModel()).toEqual({
|
expect(presenter.getResponseModel()).toEqual({
|
||||||
totalUsers: 100,
|
totalUsers: 100,
|
||||||
@@ -33,11 +33,11 @@ describe('GetDashboardDataPresenter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getResponseModel throws if not presented', () => {
|
it('getResponseModel throws if not transformed', () => {
|
||||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
expect(() => presenter.getResponseModel()).toThrow('Presenter not transformed');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('responseModel throws if not presented', () => {
|
it('responseModel throws if not transformed', () => {
|
||||||
expect(() => presenter.responseModel).toThrow('Presenter not presented');
|
expect(() => presenter.responseModel).toThrow('Presenter not transformed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import type { GetDashboardDataOutput } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
import type { GetDashboardDataOutput } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { GetDashboardDataOutputDTO } from '../dtos/GetDashboardDataOutputDTO';
|
import type { GetDashboardDataOutputDTO } from '../dtos/GetDashboardDataOutputDTO';
|
||||||
|
|
||||||
export class GetDashboardDataPresenter implements UseCaseOutputPort<GetDashboardDataOutput> {
|
export class GetDashboardDataPresenter {
|
||||||
private model: GetDashboardDataOutputDTO | null = null;
|
private model: GetDashboardDataOutputDTO | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: GetDashboardDataOutput): void {
|
transform(result: GetDashboardDataOutput): GetDashboardDataOutputDTO {
|
||||||
this.model = {
|
this.model = {
|
||||||
totalUsers: result.totalUsers,
|
totalUsers: result.totalUsers,
|
||||||
activeUsers: result.activeUsers,
|
activeUsers: result.activeUsers,
|
||||||
totalRaces: result.totalRaces,
|
totalRaces: result.totalRaces,
|
||||||
totalLeagues: result.totalLeagues,
|
totalLeagues: result.totalLeagues,
|
||||||
};
|
};
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
get responseModel(): GetDashboardDataOutputDTO {
|
get responseModel(): GetDashboardDataOutputDTO {
|
||||||
if (!this.model) throw new Error('Presenter not presented');
|
if (!this.model) throw new Error('Presenter not transformed');
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): GetDashboardDataOutputDTO {
|
getResponseModel(): GetDashboardDataOutputDTO {
|
||||||
if (!this.model) throw new Error('Presenter not presented');
|
if (!this.model) throw new Error('Presenter not transformed');
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,23 +15,15 @@ describe('RecordEngagementPresenter', () => {
|
|||||||
engagementWeight: 10,
|
engagementWeight: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
presenter.present(output);
|
presenter.transform(output);
|
||||||
|
|
||||||
expect(presenter.getResponseModel()).toEqual({
|
|
||||||
eventId: 'event-123',
|
|
||||||
engagementWeight: 10,
|
|
||||||
});
|
|
||||||
expect(presenter.responseModel).toEqual({
|
expect(presenter.responseModel).toEqual({
|
||||||
eventId: 'event-123',
|
eventId: 'event-123',
|
||||||
engagementWeight: 10,
|
engagementWeight: 10,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getResponseModel throws if not presented', () => {
|
it('responseModel throws if not transformed', () => {
|
||||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
expect(() => presenter.responseModel).toThrow('Presenter not transformed');
|
||||||
});
|
|
||||||
|
|
||||||
it('responseModel throws if not presented', () => {
|
|
||||||
expect(() => presenter.responseModel).toThrow('Presenter not presented');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,28 +1,19 @@
|
|||||||
import type { RecordEngagementOutput } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
import type { RecordEngagementOutput } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { RecordEngagementOutputDTO } from '../dtos/RecordEngagementOutputDTO';
|
import type { RecordEngagementOutputDTO } from '../dtos/RecordEngagementOutputDTO';
|
||||||
|
|
||||||
export class RecordEngagementPresenter implements UseCaseOutputPort<RecordEngagementOutput> {
|
export class RecordEngagementPresenter {
|
||||||
private model: RecordEngagementOutputDTO | null = null;
|
private model: RecordEngagementOutputDTO | null = null;
|
||||||
|
|
||||||
reset(): void {
|
transform(output: RecordEngagementOutput): RecordEngagementOutputDTO {
|
||||||
this.model = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(result: RecordEngagementOutput): void {
|
|
||||||
this.model = {
|
this.model = {
|
||||||
eventId: result.eventId,
|
eventId: output.eventId,
|
||||||
engagementWeight: result.engagementWeight,
|
engagementWeight: output.engagementWeight,
|
||||||
};
|
};
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
get responseModel(): RecordEngagementOutputDTO {
|
get responseModel(): RecordEngagementOutputDTO {
|
||||||
if (!this.model) throw new Error('Presenter not presented');
|
if (!this.model) throw new Error('Presenter not transformed');
|
||||||
return this.model;
|
|
||||||
}
|
|
||||||
|
|
||||||
getResponseModel(): RecordEngagementOutputDTO {
|
|
||||||
if (!this.model) throw new Error('Presenter not presented');
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,21 +14,14 @@ describe('RecordPageViewPresenter', () => {
|
|||||||
pageViewId: 'pv-123',
|
pageViewId: 'pv-123',
|
||||||
};
|
};
|
||||||
|
|
||||||
presenter.present(output);
|
presenter.transform(output);
|
||||||
|
|
||||||
expect(presenter.getResponseModel()).toEqual({
|
|
||||||
pageViewId: 'pv-123',
|
|
||||||
});
|
|
||||||
expect(presenter.responseModel).toEqual({
|
expect(presenter.responseModel).toEqual({
|
||||||
pageViewId: 'pv-123',
|
pageViewId: 'pv-123',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getResponseModel throws if not presented', () => {
|
it('responseModel throws if not transformed', () => {
|
||||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
expect(() => presenter.responseModel).toThrow('Presenter not transformed');
|
||||||
});
|
|
||||||
|
|
||||||
it('responseModel throws if not presented', () => {
|
|
||||||
expect(() => presenter.responseModel).toThrow('Presenter not presented');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
import type { RecordPageViewOutput } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
import type { RecordPageViewOutput } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { RecordPageViewOutputDTO } from '../dtos/RecordPageViewOutputDTO';
|
import type { RecordPageViewOutputDTO } from '../dtos/RecordPageViewOutputDTO';
|
||||||
|
|
||||||
export class RecordPageViewPresenter implements UseCaseOutputPort<RecordPageViewOutput> {
|
export class RecordPageViewPresenter {
|
||||||
private model: RecordPageViewOutputDTO | null = null;
|
private model: RecordPageViewOutputDTO | null = null;
|
||||||
|
|
||||||
reset(): void {
|
transform(output: RecordPageViewOutput): RecordPageViewOutputDTO {
|
||||||
this.model = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(result: RecordPageViewOutput): void {
|
|
||||||
this.model = {
|
this.model = {
|
||||||
pageViewId: result.pageViewId,
|
pageViewId: output.pageViewId,
|
||||||
};
|
};
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
get responseModel(): RecordPageViewOutputDTO {
|
get responseModel(): RecordPageViewOutputDTO {
|
||||||
if (!this.model) throw new Error('Presenter not presented');
|
if (!this.model) throw new Error('Presenter not transformed');
|
||||||
return this.model;
|
|
||||||
}
|
|
||||||
|
|
||||||
getResponseModel(): RecordPageViewOutputDTO {
|
|
||||||
if (!this.model) throw new Error('Presenter not presented');
|
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Provider } from '@nestjs/common';
|
import { Provider } from '@nestjs/common';
|
||||||
|
|
||||||
|
import type { Logger } from '@core/shared/application';
|
||||||
import { CookieIdentitySessionAdapter } from '@adapters/identity/session/CookieIdentitySessionAdapter';
|
import { CookieIdentitySessionAdapter } from '@adapters/identity/session/CookieIdentitySessionAdapter';
|
||||||
import { LoginUseCase } from '@core/identity/application/use-cases/LoginUseCase';
|
import { LoginUseCase } from '@core/identity/application/use-cases/LoginUseCase';
|
||||||
import { LogoutUseCase } from '@core/identity/application/use-cases/LogoutUseCase';
|
import { LogoutUseCase } from '@core/identity/application/use-cases/LogoutUseCase';
|
||||||
@@ -13,13 +14,6 @@ import type { ICompanyRepository } from '@core/identity/domain/repositories/ICom
|
|||||||
import type { IMagicLinkRepository } from '@core/identity/domain/repositories/IMagicLinkRepository';
|
import type { IMagicLinkRepository } from '@core/identity/domain/repositories/IMagicLinkRepository';
|
||||||
import type { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
|
import type { IPasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
|
||||||
import type { IMagicLinkNotificationPort } from '@core/identity/domain/ports/IMagicLinkNotificationPort';
|
import type { IMagicLinkNotificationPort } from '@core/identity/domain/ports/IMagicLinkNotificationPort';
|
||||||
import type { LoginResult } from '@core/identity/application/use-cases/LoginUseCase';
|
|
||||||
import type { LogoutResult } from '@core/identity/application/use-cases/LogoutUseCase';
|
|
||||||
import type { SignupResult } from '@core/identity/application/use-cases/SignupUseCase';
|
|
||||||
import type { SignupSponsorResult } from '@core/identity/application/use-cases/SignupSponsorUseCase';
|
|
||||||
import type { ForgotPasswordResult } from '@core/identity/application/use-cases/ForgotPasswordUseCase';
|
|
||||||
import type { ResetPasswordResult } from '@core/identity/application/use-cases/ResetPasswordUseCase';
|
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AUTH_REPOSITORY_TOKEN,
|
AUTH_REPOSITORY_TOKEN,
|
||||||
@@ -75,9 +69,8 @@ export const AuthProviders: Provider[] = [
|
|||||||
authRepo: IAuthRepository,
|
authRepo: IAuthRepository,
|
||||||
passwordHashing: IPasswordHashingService,
|
passwordHashing: IPasswordHashingService,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<LoginResult>,
|
) => new LoginUseCase(authRepo, passwordHashing, logger),
|
||||||
) => new LoginUseCase(authRepo, passwordHashing, logger, output),
|
inject: [AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||||
inject: [AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN, AUTH_SESSION_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SIGNUP_USE_CASE_TOKEN,
|
provide: SIGNUP_USE_CASE_TOKEN,
|
||||||
@@ -85,9 +78,8 @@ export const AuthProviders: Provider[] = [
|
|||||||
authRepo: IAuthRepository,
|
authRepo: IAuthRepository,
|
||||||
passwordHashing: IPasswordHashingService,
|
passwordHashing: IPasswordHashingService,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<SignupResult>,
|
) => new SignupUseCase(authRepo, passwordHashing, logger),
|
||||||
) => new SignupUseCase(authRepo, passwordHashing, logger, output),
|
inject: [AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||||
inject: [AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN, AUTH_SESSION_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SIGNUP_SPONSOR_USE_CASE_TOKEN,
|
provide: SIGNUP_SPONSOR_USE_CASE_TOKEN,
|
||||||
@@ -96,15 +88,14 @@ export const AuthProviders: Provider[] = [
|
|||||||
companyRepo: ICompanyRepository,
|
companyRepo: ICompanyRepository,
|
||||||
passwordHashing: IPasswordHashingService,
|
passwordHashing: IPasswordHashingService,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<SignupSponsorResult>,
|
) => new SignupSponsorUseCase(authRepo, companyRepo, passwordHashing, logger),
|
||||||
) => new SignupSponsorUseCase(authRepo, companyRepo, passwordHashing, logger, output),
|
inject: [AUTH_REPOSITORY_TOKEN, COMPANY_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||||
inject: [AUTH_REPOSITORY_TOKEN, COMPANY_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN, SIGNUP_SPONSOR_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: LOGOUT_USE_CASE_TOKEN,
|
provide: LOGOUT_USE_CASE_TOKEN,
|
||||||
useFactory: (sessionPort: IdentitySessionPort, logger: Logger, output: UseCaseOutputPort<LogoutResult>) =>
|
useFactory: (sessionPort: IdentitySessionPort, logger: Logger) =>
|
||||||
new LogoutUseCase(sessionPort, logger, output),
|
new LogoutUseCase(sessionPort, logger),
|
||||||
inject: [IDENTITY_SESSION_PORT_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_OUTPUT_PORT_TOKEN],
|
inject: [IDENTITY_SESSION_PORT_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
ForgotPasswordPresenter,
|
ForgotPasswordPresenter,
|
||||||
ResetPasswordPresenter,
|
ResetPasswordPresenter,
|
||||||
@@ -132,9 +123,8 @@ export const AuthProviders: Provider[] = [
|
|||||||
magicLinkRepo: IMagicLinkRepository,
|
magicLinkRepo: IMagicLinkRepository,
|
||||||
notificationPort: IMagicLinkNotificationPort,
|
notificationPort: IMagicLinkNotificationPort,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<ForgotPasswordResult>,
|
) => new ForgotPasswordUseCase(authRepo, magicLinkRepo, notificationPort, logger),
|
||||||
) => new ForgotPasswordUseCase(authRepo, magicLinkRepo, notificationPort, logger, output),
|
inject: [AUTH_REPOSITORY_TOKEN, MAGIC_LINK_REPOSITORY_TOKEN, MAGIC_LINK_NOTIFICATION_PORT_TOKEN, LOGGER_TOKEN],
|
||||||
inject: [AUTH_REPOSITORY_TOKEN, MAGIC_LINK_REPOSITORY_TOKEN, MAGIC_LINK_NOTIFICATION_PORT_TOKEN, LOGGER_TOKEN, FORGOT_PASSWORD_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: RESET_PASSWORD_USE_CASE_TOKEN,
|
provide: RESET_PASSWORD_USE_CASE_TOKEN,
|
||||||
@@ -143,8 +133,7 @@ export const AuthProviders: Provider[] = [
|
|||||||
magicLinkRepo: IMagicLinkRepository,
|
magicLinkRepo: IMagicLinkRepository,
|
||||||
passwordHashing: IPasswordHashingService,
|
passwordHashing: IPasswordHashingService,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<ResetPasswordResult>,
|
) => new ResetPasswordUseCase(authRepo, magicLinkRepo, passwordHashing, logger),
|
||||||
) => new ResetPasswordUseCase(authRepo, magicLinkRepo, passwordHashing, logger, output),
|
inject: [AUTH_REPOSITORY_TOKEN, MAGIC_LINK_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||||
inject: [AUTH_REPOSITORY_TOKEN, MAGIC_LINK_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, LOGGER_TOKEN, RESET_PASSWORD_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -87,8 +87,7 @@ describe('AuthService', () => {
|
|||||||
|
|
||||||
const signupUseCase = {
|
const signupUseCase = {
|
||||||
execute: vi.fn(async () => {
|
execute: vi.fn(async () => {
|
||||||
authSessionPresenter.present({ userId: 'u2', email: 'e2', displayName: 'Jane Smith' });
|
return Result.ok({ userId: 'u2', email: 'e2', displayName: 'Jane Smith' });
|
||||||
return Result.ok(undefined);
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,8 +155,7 @@ describe('AuthService', () => {
|
|||||||
|
|
||||||
const loginUseCase = {
|
const loginUseCase = {
|
||||||
execute: vi.fn(async () => {
|
execute: vi.fn(async () => {
|
||||||
authSessionPresenter.present({ userId: 'u3', email: 'e3', displayName: 'Bob Wilson' });
|
return Result.ok({ userId: 'u3', email: 'e3', displayName: 'Bob Wilson' });
|
||||||
return Result.ok(undefined);
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -234,8 +232,7 @@ describe('AuthService', () => {
|
|||||||
const commandResultPresenter = new FakeCommandResultPresenter();
|
const commandResultPresenter = new FakeCommandResultPresenter();
|
||||||
const logoutUseCase = {
|
const logoutUseCase = {
|
||||||
execute: vi.fn(async () => {
|
execute: vi.fn(async () => {
|
||||||
commandResultPresenter.present({ success: true });
|
return Result.ok({ success: true });
|
||||||
return Result.ok(undefined);
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -116,8 +116,6 @@ export class AuthService {
|
|||||||
async signupWithEmail(params: SignupParamsDTO): Promise<AuthSessionDTO> {
|
async signupWithEmail(params: SignupParamsDTO): Promise<AuthSessionDTO> {
|
||||||
this.logger.debug(`[AuthService] Attempting signup for email: ${params.email}`);
|
this.logger.debug(`[AuthService] Attempting signup for email: ${params.email}`);
|
||||||
|
|
||||||
this.authSessionPresenter.reset();
|
|
||||||
|
|
||||||
const input: SignupInput = {
|
const input: SignupInput = {
|
||||||
email: params.email,
|
email: params.email,
|
||||||
password: params.password,
|
password: params.password,
|
||||||
@@ -131,6 +129,9 @@ export class AuthService {
|
|||||||
throw new Error(mapApplicationErrorToMessage(error, 'Signup failed'));
|
throw new Error(mapApplicationErrorToMessage(error, 'Signup failed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const signupResult = result.unwrap();
|
||||||
|
this.authSessionPresenter.present(signupResult);
|
||||||
|
|
||||||
const userDTO = this.authSessionPresenter.responseModel;
|
const userDTO = this.authSessionPresenter.responseModel;
|
||||||
const inferredRole = inferDemoRoleFromEmail(userDTO.email);
|
const inferredRole = inferDemoRoleFromEmail(userDTO.email);
|
||||||
const session = await this.identitySessionPort.createSession({
|
const session = await this.identitySessionPort.createSession({
|
||||||
@@ -149,8 +150,6 @@ export class AuthService {
|
|||||||
async signupSponsor(params: SignupSponsorParamsDTO): Promise<AuthSessionDTO> {
|
async signupSponsor(params: SignupSponsorParamsDTO): Promise<AuthSessionDTO> {
|
||||||
this.logger.debug(`[AuthService] Attempting sponsor signup for email: ${params.email}`);
|
this.logger.debug(`[AuthService] Attempting sponsor signup for email: ${params.email}`);
|
||||||
|
|
||||||
this.authSessionPresenter.reset();
|
|
||||||
|
|
||||||
const input: SignupSponsorInput = {
|
const input: SignupSponsorInput = {
|
||||||
email: params.email,
|
email: params.email,
|
||||||
password: params.password,
|
password: params.password,
|
||||||
@@ -165,6 +164,9 @@ export class AuthService {
|
|||||||
throw new Error(mapApplicationErrorToMessage(error, 'Sponsor signup failed'));
|
throw new Error(mapApplicationErrorToMessage(error, 'Sponsor signup failed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const signupResult = result.unwrap();
|
||||||
|
this.authSessionPresenter.present(signupResult);
|
||||||
|
|
||||||
const userDTO = this.authSessionPresenter.responseModel;
|
const userDTO = this.authSessionPresenter.responseModel;
|
||||||
const inferredRole = inferDemoRoleFromEmail(userDTO.email);
|
const inferredRole = inferDemoRoleFromEmail(userDTO.email);
|
||||||
const session = await this.identitySessionPort.createSession({
|
const session = await this.identitySessionPort.createSession({
|
||||||
@@ -183,8 +185,6 @@ export class AuthService {
|
|||||||
async loginWithEmail(params: LoginParamsDTO): Promise<AuthSessionDTO> {
|
async loginWithEmail(params: LoginParamsDTO): Promise<AuthSessionDTO> {
|
||||||
this.logger.debug(`[AuthService] Attempting login for email: ${params.email}`);
|
this.logger.debug(`[AuthService] Attempting login for email: ${params.email}`);
|
||||||
|
|
||||||
this.authSessionPresenter.reset();
|
|
||||||
|
|
||||||
const input: LoginInput = {
|
const input: LoginInput = {
|
||||||
email: params.email,
|
email: params.email,
|
||||||
password: params.password,
|
password: params.password,
|
||||||
@@ -197,6 +197,9 @@ export class AuthService {
|
|||||||
throw new Error(mapApplicationErrorToMessage(error, 'Login failed'));
|
throw new Error(mapApplicationErrorToMessage(error, 'Login failed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loginResult = result.unwrap();
|
||||||
|
this.authSessionPresenter.present(loginResult);
|
||||||
|
|
||||||
const userDTO = this.authSessionPresenter.responseModel;
|
const userDTO = this.authSessionPresenter.responseModel;
|
||||||
const sessionOptions = params.rememberMe !== undefined
|
const sessionOptions = params.rememberMe !== undefined
|
||||||
? { rememberMe: params.rememberMe }
|
? { rememberMe: params.rememberMe }
|
||||||
@@ -223,8 +226,6 @@ export class AuthService {
|
|||||||
async logout(): Promise<CommandResultDTO> {
|
async logout(): Promise<CommandResultDTO> {
|
||||||
this.logger.debug('[AuthService] Attempting logout.');
|
this.logger.debug('[AuthService] Attempting logout.');
|
||||||
|
|
||||||
this.commandResultPresenter.reset();
|
|
||||||
|
|
||||||
const result = await this.logoutUseCase.execute();
|
const result = await this.logoutUseCase.execute();
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
@@ -232,6 +233,9 @@ export class AuthService {
|
|||||||
throw new Error(mapApplicationErrorToMessage(error, 'Logout failed'));
|
throw new Error(mapApplicationErrorToMessage(error, 'Logout failed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const logoutResult = result.unwrap();
|
||||||
|
this.commandResultPresenter.present(logoutResult);
|
||||||
|
|
||||||
return this.commandResultPresenter.responseModel;
|
return this.commandResultPresenter.responseModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,8 +289,6 @@ export class AuthService {
|
|||||||
async forgotPassword(params: { email: string }): Promise<{ message: string; magicLink?: string }> {
|
async forgotPassword(params: { email: string }): Promise<{ message: string; magicLink?: string }> {
|
||||||
this.logger.debug(`[AuthService] Attempting forgot password for email: ${params.email}`);
|
this.logger.debug(`[AuthService] Attempting forgot password for email: ${params.email}`);
|
||||||
|
|
||||||
this.forgotPasswordPresenter.reset();
|
|
||||||
|
|
||||||
const input: ForgotPasswordInput = {
|
const input: ForgotPasswordInput = {
|
||||||
email: params.email,
|
email: params.email,
|
||||||
};
|
};
|
||||||
@@ -298,6 +300,9 @@ export class AuthService {
|
|||||||
throw new Error(mapApplicationErrorToMessage(error, 'Forgot password failed'));
|
throw new Error(mapApplicationErrorToMessage(error, 'Forgot password failed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const forgotPasswordResult = executeResult.unwrap();
|
||||||
|
this.forgotPasswordPresenter.present(forgotPasswordResult);
|
||||||
|
|
||||||
const response = this.forgotPasswordPresenter.responseModel;
|
const response = this.forgotPasswordPresenter.responseModel;
|
||||||
const result: { message: string; magicLink?: string } = {
|
const result: { message: string; magicLink?: string } = {
|
||||||
message: response.message,
|
message: response.message,
|
||||||
@@ -311,8 +316,6 @@ export class AuthService {
|
|||||||
async resetPassword(params: { token: string; newPassword: string }): Promise<{ message: string }> {
|
async resetPassword(params: { token: string; newPassword: string }): Promise<{ message: string }> {
|
||||||
this.logger.debug('[AuthService] Attempting reset password');
|
this.logger.debug('[AuthService] Attempting reset password');
|
||||||
|
|
||||||
this.resetPasswordPresenter.reset();
|
|
||||||
|
|
||||||
const input: ResetPasswordInput = {
|
const input: ResetPasswordInput = {
|
||||||
token: params.token,
|
token: params.token,
|
||||||
newPassword: params.newPassword,
|
newPassword: params.newPassword,
|
||||||
@@ -325,6 +328,9 @@ export class AuthService {
|
|||||||
throw new Error(mapApplicationErrorToMessage(error, 'Reset password failed'));
|
throw new Error(mapApplicationErrorToMessage(error, 'Reset password failed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetResult = result.unwrap();
|
||||||
|
this.resetPasswordPresenter.present(resetResult);
|
||||||
|
|
||||||
return this.resetPasswordPresenter.responseModel;
|
return this.resetPasswordPresenter.responseModel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,16 +4,14 @@ import { ACHIEVEMENT_REPOSITORY_TOKEN } from '../../persistence/achievement/Achi
|
|||||||
import { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData';
|
import { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData';
|
||||||
import type { RacingSeedDependencies } from '../../../../../adapters/bootstrap/SeedRacingData';
|
import type { RacingSeedDependencies } from '../../../../../adapters/bootstrap/SeedRacingData';
|
||||||
import { SeedDemoUsers } from '../../../../../adapters/bootstrap/SeedDemoUsers';
|
import { SeedDemoUsers } from '../../../../../adapters/bootstrap/SeedDemoUsers';
|
||||||
import { SignupWithEmailUseCase, type SignupWithEmailResult } from '@core/identity/application/use-cases/SignupWithEmailUseCase';
|
import { SignupWithEmailUseCase } from '@core/identity/application/use-cases/SignupWithEmailUseCase';
|
||||||
import {
|
import {
|
||||||
CreateAchievementUseCase,
|
CreateAchievementUseCase,
|
||||||
type CreateAchievementResult,
|
|
||||||
type IAchievementRepository,
|
type IAchievementRepository,
|
||||||
} from '@core/identity/application/use-cases/achievement/CreateAchievementUseCase';
|
} from '@core/identity/application/use-cases/achievement/CreateAchievementUseCase';
|
||||||
import type { IUserRepository } from '@core/identity/domain/repositories/IUserRepository';
|
import type { IUserRepository } from '@core/identity/domain/repositories/IUserRepository';
|
||||||
import type { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
|
import type { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import { CookieIdentitySessionAdapter } from '../../../../../adapters/identity/session/CookieIdentitySessionAdapter';
|
import { CookieIdentitySessionAdapter } from '../../../../../adapters/identity/session/CookieIdentitySessionAdapter';
|
||||||
import { USER_REPOSITORY_TOKEN as IDENTITY_USER_REPOSITORY_TOKEN, AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN } from '../../persistence/identity/IdentityPersistenceTokens';
|
import { USER_REPOSITORY_TOKEN as IDENTITY_USER_REPOSITORY_TOKEN, AUTH_REPOSITORY_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN } from '../../persistence/identity/IdentityPersistenceTokens';
|
||||||
import { ADMIN_USER_REPOSITORY_TOKEN } from '../../persistence/admin/AdminPersistenceTokens';
|
import { ADMIN_USER_REPOSITORY_TOKEN } from '../../persistence/admin/AdminPersistenceTokens';
|
||||||
@@ -29,21 +27,6 @@ export const RACING_SEED_DEPENDENCIES_TOKEN = 'RacingSeedDependencies';
|
|||||||
export const ENSURE_INITIAL_DATA_TOKEN = 'EnsureInitialData_Bootstrap';
|
export const ENSURE_INITIAL_DATA_TOKEN = 'EnsureInitialData_Bootstrap';
|
||||||
export const SEED_DEMO_USERS_TOKEN = 'SeedDemoUsers';
|
export const SEED_DEMO_USERS_TOKEN = 'SeedDemoUsers';
|
||||||
|
|
||||||
// Adapter classes for output ports
|
|
||||||
class SignupWithEmailOutputAdapter implements UseCaseOutputPort<SignupWithEmailResult> {
|
|
||||||
present(result: SignupWithEmailResult): void {
|
|
||||||
// Bootstrap doesn't need to handle output, just log success
|
|
||||||
console.log('[Bootstrap] Signup completed', result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CreateAchievementOutputAdapter implements UseCaseOutputPort<CreateAchievementResult> {
|
|
||||||
present(result: CreateAchievementResult): void {
|
|
||||||
// Bootstrap doesn't need to handle output, just log success
|
|
||||||
console.log('[Bootstrap] Achievement created', result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BootstrapProviders: Provider[] = [
|
export const BootstrapProviders: Provider[] = [
|
||||||
{
|
{
|
||||||
provide: RACING_SEED_DEPENDENCIES_TOKEN,
|
provide: RACING_SEED_DEPENDENCIES_TOKEN,
|
||||||
@@ -152,8 +135,7 @@ export const BootstrapProviders: Provider[] = [
|
|||||||
return new SignupWithEmailUseCase(
|
return new SignupWithEmailUseCase(
|
||||||
userRepository,
|
userRepository,
|
||||||
sessionPort,
|
sessionPort,
|
||||||
logger,
|
logger
|
||||||
new SignupWithEmailOutputAdapter()
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [USER_REPOSITORY_TOKEN, IDENTITY_SESSION_PORT_TOKEN, 'Logger'],
|
inject: [USER_REPOSITORY_TOKEN, IDENTITY_SESSION_PORT_TOKEN, 'Logger'],
|
||||||
@@ -166,8 +148,7 @@ export const BootstrapProviders: Provider[] = [
|
|||||||
) => {
|
) => {
|
||||||
return new CreateAchievementUseCase(
|
return new CreateAchievementUseCase(
|
||||||
achievementRepository,
|
achievementRepository,
|
||||||
logger,
|
logger
|
||||||
new CreateAchievementOutputAdapter()
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [ACHIEVEMENT_REPOSITORY_TOKEN, 'Logger'],
|
inject: [ACHIEVEMENT_REPOSITORY_TOKEN, 'Logger'],
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { DashboardModule } from './DashboardModule';
|
|||||||
import { DashboardController } from './DashboardController';
|
import { DashboardController } from './DashboardController';
|
||||||
import { DashboardService } from './DashboardService';
|
import { DashboardService } from './DashboardService';
|
||||||
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
|
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
|
||||||
import { DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN } from './DashboardProviders';
|
|
||||||
|
|
||||||
describe('DashboardModule', () => {
|
describe('DashboardModule', () => {
|
||||||
let module: TestingModule;
|
let module: TestingModule;
|
||||||
@@ -30,8 +29,8 @@ describe('DashboardModule', () => {
|
|||||||
expect(service).toBeInstanceOf(DashboardService);
|
expect(service).toBeInstanceOf(DashboardService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should bind DashboardOverviewPresenter as the output port for the use case', () => {
|
it('should provide DashboardOverviewPresenter', () => {
|
||||||
const presenter = module.get<DashboardOverviewPresenter>(DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN);
|
const presenter = module.get<DashboardOverviewPresenter>(DashboardOverviewPresenter);
|
||||||
expect(presenter).toBeDefined();
|
expect(presenter).toBeDefined();
|
||||||
expect(presenter).toBeInstanceOf(DashboardOverviewPresenter);
|
expect(presenter).toBeInstanceOf(DashboardOverviewPresenter);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
|||||||
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
|
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
|
||||||
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
|
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
|
||||||
import {
|
import {
|
||||||
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
|
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
IMAGE_SERVICE_TOKEN,
|
IMAGE_SERVICE_TOKEN,
|
||||||
@@ -36,7 +35,6 @@ import {
|
|||||||
|
|
||||||
// Re-export tokens for convenience (legacy imports)
|
// Re-export tokens for convenience (legacy imports)
|
||||||
export {
|
export {
|
||||||
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
|
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
IMAGE_SERVICE_TOKEN,
|
IMAGE_SERVICE_TOKEN,
|
||||||
@@ -60,10 +58,6 @@ export const DashboardProviders: Provider[] = [
|
|||||||
useFactory: (logger: Logger) => new InMemoryImageServiceAdapter(logger),
|
useFactory: (logger: Logger) => new InMemoryImageServiceAdapter(logger),
|
||||||
inject: [LOGGER_TOKEN],
|
inject: [LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: DashboardOverviewPresenter,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
|
provide: DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
@@ -77,7 +71,6 @@ export const DashboardProviders: Provider[] = [
|
|||||||
feedRepo: IFeedRepository,
|
feedRepo: IFeedRepository,
|
||||||
socialRepo: ISocialGraphRepository,
|
socialRepo: ISocialGraphRepository,
|
||||||
imageService: ImageServicePort,
|
imageService: ImageServicePort,
|
||||||
output: DashboardOverviewPresenter,
|
|
||||||
) =>
|
) =>
|
||||||
new DashboardOverviewUseCase(
|
new DashboardOverviewUseCase(
|
||||||
driverRepo,
|
driverRepo,
|
||||||
@@ -91,7 +84,6 @@ export const DashboardProviders: Provider[] = [
|
|||||||
socialRepo,
|
socialRepo,
|
||||||
async (driverId: string) => imageService.getDriverAvatar(driverId),
|
async (driverId: string) => imageService.getDriverAvatar(driverId),
|
||||||
() => null,
|
() => null,
|
||||||
output,
|
|
||||||
),
|
),
|
||||||
inject: [
|
inject: [
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
@@ -104,7 +96,6 @@ export const DashboardProviders: Provider[] = [
|
|||||||
SOCIAL_FEED_REPOSITORY_TOKEN,
|
SOCIAL_FEED_REPOSITORY_TOKEN,
|
||||||
SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
||||||
IMAGE_SERVICE_TOKEN,
|
IMAGE_SERVICE_TOKEN,
|
||||||
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import { DashboardService } from './DashboardService';
|
|||||||
|
|
||||||
describe('DashboardService', () => {
|
describe('DashboardService', () => {
|
||||||
it('getDashboardOverview returns presenter model on success', async () => {
|
it('getDashboardOverview returns presenter model on success', async () => {
|
||||||
const presenter = { getResponseModel: vi.fn(() => ({ feed: [] })) };
|
const mockResult = { currentDriver: null, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 0, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [] }, friends: [] };
|
||||||
const useCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const presenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ feed: [] })) };
|
||||||
|
const useCase = { execute: vi.fn(async () => Result.ok(mockResult)) };
|
||||||
|
|
||||||
const service = new DashboardService(
|
const service = new DashboardService(
|
||||||
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
|
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
|
||||||
@@ -15,13 +16,14 @@ describe('DashboardService', () => {
|
|||||||
|
|
||||||
await expect(service.getDashboardOverview('d1')).resolves.toEqual({ feed: [] });
|
await expect(service.getDashboardOverview('d1')).resolves.toEqual({ feed: [] });
|
||||||
expect(useCase.execute).toHaveBeenCalledWith({ driverId: 'd1' });
|
expect(useCase.execute).toHaveBeenCalledWith({ driverId: 'd1' });
|
||||||
|
expect(presenter.present).toHaveBeenCalledWith(mockResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getDashboardOverview throws with details message on error', async () => {
|
it('getDashboardOverview throws with details message on error', async () => {
|
||||||
const service = new DashboardService(
|
const service = new DashboardService(
|
||||||
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
|
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
|
||||||
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any,
|
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any,
|
||||||
{ getResponseModel: vi.fn() } as any,
|
{ present: vi.fn(), getResponseModel: vi.fn() } as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: boom');
|
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: boom');
|
||||||
@@ -31,7 +33,7 @@ describe('DashboardService', () => {
|
|||||||
const service = new DashboardService(
|
const service = new DashboardService(
|
||||||
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
|
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
|
||||||
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
|
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
|
||||||
{ getResponseModel: vi.fn() } as any,
|
{ present: vi.fn(), getResponseModel: vi.fn() } as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: Unknown error');
|
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: Unknown error');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO';
|
import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO';
|
||||||
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
|
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ import type { Logger } from '@core/shared/application/Logger';
|
|||||||
|
|
||||||
// Tokens (standalone to avoid circular imports)
|
// Tokens (standalone to avoid circular imports)
|
||||||
import {
|
import {
|
||||||
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
|
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
} from './DashboardTokens';
|
} from './DashboardTokens';
|
||||||
@@ -18,7 +17,7 @@ export class DashboardService {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||||
@Inject(DASHBOARD_OVERVIEW_USE_CASE_TOKEN) private readonly dashboardOverviewUseCase: DashboardOverviewUseCase,
|
@Inject(DASHBOARD_OVERVIEW_USE_CASE_TOKEN) private readonly dashboardOverviewUseCase: DashboardOverviewUseCase,
|
||||||
@Inject(DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN) private readonly presenter: DashboardOverviewPresenter,
|
private readonly presenter: DashboardOverviewPresenter,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getDashboardOverview(driverId: string): Promise<DashboardOverviewDTO> {
|
async getDashboardOverview(driverId: string): Promise<DashboardOverviewDTO> {
|
||||||
@@ -29,9 +28,11 @@ export class DashboardService {
|
|||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
const error = result.error;
|
const error = result.error;
|
||||||
const message = error?.details?.message || 'Unknown error';
|
const message = error?.details?.message || 'Unknown error';
|
||||||
throw new Error(`Failed to get dashboard overview: ${message}`);
|
throw new NotFoundException(`Failed to get dashboard overview: ${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Present the result
|
||||||
|
this.presenter.present(result.unwrap());
|
||||||
return this.presenter.getResponseModel();
|
return this.presenter.getResponseModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,4 @@ export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
|
|||||||
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
|
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
|
||||||
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
|
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
|
||||||
export const DASHBOARD_OVERVIEW_USE_CASE_TOKEN = 'DashboardOverviewUseCase';
|
export const DASHBOARD_OVERVIEW_USE_CASE_TOKEN = 'DashboardOverviewUseCase';
|
||||||
export const DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN = 'DashboardOverviewOutputPort';
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import type {
|
import type {
|
||||||
DashboardOverviewResult,
|
DashboardOverviewResult,
|
||||||
} from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
} from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
||||||
@@ -13,7 +12,7 @@ import {
|
|||||||
DashboardFriendSummaryDTO,
|
DashboardFriendSummaryDTO,
|
||||||
} from '../dtos/DashboardOverviewDTO';
|
} from '../dtos/DashboardOverviewDTO';
|
||||||
|
|
||||||
export class DashboardOverviewPresenter implements UseCaseOutputPort<DashboardOverviewResult> {
|
export class DashboardOverviewPresenter {
|
||||||
private responseModel: DashboardOverviewDTO | null = null;
|
private responseModel: DashboardOverviewDTO | null = null;
|
||||||
|
|
||||||
present(data: DashboardOverviewResult): void {
|
present(data: DashboardOverviewResult): void {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepos
|
|||||||
import { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
|
import { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
|
||||||
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
|
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
|
||||||
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
|
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
||||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
||||||
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
|
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
|
||||||
@@ -65,12 +65,6 @@ import {
|
|||||||
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
|
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
|
||||||
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
|
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
|
||||||
GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
|
GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
|
||||||
GET_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_TOTAL_DRIVERS_OUTPUT_PORT_TOKEN,
|
|
||||||
COMPLETE_DRIVER_ONBOARDING_OUTPUT_PORT_TOKEN,
|
|
||||||
IS_DRIVER_REGISTERED_FOR_RACE_OUTPUT_PORT_TOKEN,
|
|
||||||
UPDATE_DRIVER_PROFILE_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_PROFILE_OVERVIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
DRIVER_STATS_REPOSITORY_TOKEN,
|
DRIVER_STATS_REPOSITORY_TOKEN,
|
||||||
MEDIA_REPOSITORY_TOKEN,
|
MEDIA_REPOSITORY_TOKEN,
|
||||||
RANKING_SERVICE_TOKEN,
|
RANKING_SERVICE_TOKEN,
|
||||||
@@ -119,32 +113,6 @@ export const DriverProviders: Provider[] = createLoggedProviders([
|
|||||||
inject: [MEDIA_RESOLVER_TOKEN],
|
inject: [MEDIA_RESOLVER_TOKEN],
|
||||||
},
|
},
|
||||||
|
|
||||||
// Output ports (point to presenters)
|
|
||||||
{
|
|
||||||
provide: GET_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: DriversLeaderboardPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_TOTAL_DRIVERS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: DriverStatsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: COMPLETE_DRIVER_ONBOARDING_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: CompleteOnboardingPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: IS_DRIVER_REGISTERED_FOR_RACE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: DriverRegistrationStatusPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: UPDATE_DRIVER_PROFILE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: DriverPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_PROFILE_OVERVIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: DriverProfilePresenter,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Logger
|
// Logger
|
||||||
{
|
{
|
||||||
provide: LOGGER_TOKEN,
|
provide: LOGGER_TOKEN,
|
||||||
@@ -230,37 +198,35 @@ export const DriverProviders: Provider[] = createLoggedProviders([
|
|||||||
rankingUseCase: IRankingUseCase,
|
rankingUseCase: IRankingUseCase,
|
||||||
driverStatsUseCase: IDriverStatsUseCase,
|
driverStatsUseCase: IDriverStatsUseCase,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<unknown>,
|
|
||||||
) => new GetDriversLeaderboardUseCase(
|
) => new GetDriversLeaderboardUseCase(
|
||||||
driverRepo,
|
driverRepo,
|
||||||
rankingUseCase,
|
rankingUseCase,
|
||||||
driverStatsUseCase,
|
driverStatsUseCase,
|
||||||
logger,
|
logger
|
||||||
output
|
|
||||||
),
|
),
|
||||||
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, LOGGER_TOKEN, GET_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN],
|
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
|
provide: GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
|
||||||
useFactory: (driverRepo: IDriverRepository, output: UseCaseOutputPort<unknown>) => new GetTotalDriversUseCase(driverRepo, output),
|
useFactory: (driverRepo: IDriverRepository) => new GetTotalDriversUseCase(driverRepo),
|
||||||
inject: [DRIVER_REPOSITORY_TOKEN, GET_TOTAL_DRIVERS_OUTPUT_PORT_TOKEN],
|
inject: [DRIVER_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
|
provide: COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
|
||||||
useFactory: (driverRepo: IDriverRepository, logger: Logger, output: UseCaseOutputPort<unknown>) => new CompleteDriverOnboardingUseCase(driverRepo, logger, output),
|
useFactory: (driverRepo: IDriverRepository, logger: Logger) => new CompleteDriverOnboardingUseCase(driverRepo, logger),
|
||||||
inject: [DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, COMPLETE_DRIVER_ONBOARDING_OUTPUT_PORT_TOKEN],
|
inject: [DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
|
provide: IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
|
||||||
useFactory: (registrationRepo: IRaceRegistrationRepository, logger: Logger, output: UseCaseOutputPort<unknown>) =>
|
useFactory: (registrationRepo: IRaceRegistrationRepository, logger: Logger) =>
|
||||||
new IsDriverRegisteredForRaceUseCase(registrationRepo, logger, output),
|
new IsDriverRegisteredForRaceUseCase(registrationRepo, logger),
|
||||||
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN, IS_DRIVER_REGISTERED_FOR_RACE_OUTPUT_PORT_TOKEN],
|
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
|
provide: UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
|
||||||
useFactory: (driverRepo: IDriverRepository, logger: Logger, output: UseCaseOutputPort<unknown>) =>
|
useFactory: (driverRepo: IDriverRepository, logger: Logger) =>
|
||||||
new UpdateDriverProfileUseCase(driverRepo, logger, output),
|
new UpdateDriverProfileUseCase(driverRepo, logger),
|
||||||
inject: [DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, UPDATE_DRIVER_PROFILE_OUTPUT_PORT_TOKEN],
|
inject: [DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
|
provide: GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
|
||||||
@@ -272,7 +238,6 @@ export const DriverProviders: Provider[] = createLoggedProviders([
|
|||||||
driverExtendedProfileProvider: DriverExtendedProfileProvider,
|
driverExtendedProfileProvider: DriverExtendedProfileProvider,
|
||||||
driverStatsUseCase: IDriverStatsUseCase,
|
driverStatsUseCase: IDriverStatsUseCase,
|
||||||
rankingUseCase: IRankingUseCase,
|
rankingUseCase: IRankingUseCase,
|
||||||
output: UseCaseOutputPort<unknown>,
|
|
||||||
) =>
|
) =>
|
||||||
new GetProfileOverviewUseCase(
|
new GetProfileOverviewUseCase(
|
||||||
driverRepo,
|
driverRepo,
|
||||||
@@ -282,7 +247,6 @@ export const DriverProviders: Provider[] = createLoggedProviders([
|
|||||||
driverExtendedProfileProvider,
|
driverExtendedProfileProvider,
|
||||||
driverStatsUseCase,
|
driverStatsUseCase,
|
||||||
rankingUseCase,
|
rankingUseCase,
|
||||||
output,
|
|
||||||
),
|
),
|
||||||
inject: [
|
inject: [
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
@@ -292,7 +256,6 @@ export const DriverProviders: Provider[] = createLoggedProviders([
|
|||||||
DRIVER_EXTENDED_PROFILE_PROVIDER_TOKEN,
|
DRIVER_EXTENDED_PROFILE_PROVIDER_TOKEN,
|
||||||
DRIVER_STATS_SERVICE_TOKEN,
|
DRIVER_STATS_SERVICE_TOKEN,
|
||||||
RANKING_SERVICE_TOKEN,
|
RANKING_SERVICE_TOKEN,
|
||||||
GET_PROFILE_OVERVIEW_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
], initLogger);
|
], initLogger);
|
||||||
@@ -143,32 +143,6 @@ export const GET_LEAGUE_WALLET_USE_CASE = 'GetLeagueWalletUseCase';
|
|||||||
export const WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE = 'WithdrawFromLeagueWalletUseCase';
|
export const WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE = 'WithdrawFromLeagueWalletUseCase';
|
||||||
export const GET_SEASON_SPONSORSHIPS_USE_CASE = 'GetSeasonSponsorshipsUseCase';
|
export const GET_SEASON_SPONSORSHIPS_USE_CASE = 'GetSeasonSponsorshipsUseCase';
|
||||||
|
|
||||||
export const GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN = 'GetAllLeaguesWithCapacityOutputPort_TOKEN';
|
|
||||||
export const GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN = 'GetAllLeaguesWithCapacityAndScoringOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN = 'GetLeagueStandingsOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN = 'GetLeagueProtestsOutputPort_TOKEN';
|
|
||||||
export const GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN = 'GetSeasonSponsorshipsOutputPort_TOKEN';
|
|
||||||
export const LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN = 'ListLeagueScoringPresetsOutputPort_TOKEN';
|
|
||||||
export const APPROVE_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN = 'ApproveLeagueJoinRequestOutputPort_TOKEN';
|
|
||||||
export const CREATE_LEAGUE_OUTPUT_PORT_TOKEN = 'CreateLeagueOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN = 'GetLeagueAdminPermissionsOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_MEMBERSHIPS_OUTPUT_PORT_TOKEN = 'GetLeagueMembershipsOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_ROSTER_MEMBERS_OUTPUT_PORT_TOKEN = 'GetLeagueRosterMembersOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_ROSTER_JOIN_REQUESTS_OUTPUT_PORT_TOKEN = 'GetLeagueRosterJoinRequestsOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_OWNER_SUMMARY_OUTPUT_PORT_TOKEN = 'GetLeagueOwnerSummaryOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN = 'GetLeagueSeasonsOutputPort_TOKEN';
|
|
||||||
export const JOIN_LEAGUE_OUTPUT_PORT_TOKEN = 'JoinLeagueOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN = 'GetLeagueScheduleOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_STATS_OUTPUT_PORT_TOKEN = 'GetLeagueStatsOutputPort_TOKEN';
|
|
||||||
export const REJECT_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN = 'RejectLeagueJoinRequestOutputPort_TOKEN';
|
|
||||||
export const REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN = 'RemoveLeagueMemberOutputPort_TOKEN';
|
|
||||||
export const TOTAL_LEAGUES_OUTPUT_PORT_TOKEN = 'TotalLeaguesOutputPort_TOKEN';
|
|
||||||
export const TRANSFER_LEAGUE_OWNERSHIP_OUTPUT_PORT_TOKEN = 'TransferLeagueOwnershipOutputPort_TOKEN';
|
|
||||||
export const UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN = 'UpdateLeagueMemberRoleOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN = 'GetLeagueFullConfigOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN = 'GetLeagueScoringConfigOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN = 'GetLeagueWalletOutputPort_TOKEN';
|
|
||||||
export const WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN = 'WithdrawFromLeagueWalletOutputPort_TOKEN';
|
|
||||||
|
|
||||||
export const LeagueProviders: Provider[] = [
|
export const LeagueProviders: Provider[] = [
|
||||||
LeagueService,
|
LeagueService,
|
||||||
@@ -227,133 +201,6 @@ export const LeagueProviders: Provider[] = [
|
|||||||
DeleteLeagueSeasonScheduleRacePresenter,
|
DeleteLeagueSeasonScheduleRacePresenter,
|
||||||
PublishLeagueSeasonSchedulePresenter,
|
PublishLeagueSeasonSchedulePresenter,
|
||||||
UnpublishLeagueSeasonSchedulePresenter,
|
UnpublishLeagueSeasonSchedulePresenter,
|
||||||
// Output ports
|
|
||||||
{
|
|
||||||
provide: GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: AllLeaguesWithCapacityPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: AllLeaguesWithCapacityAndScoringPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: LeagueStandingsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetLeagueProtestsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetSeasonSponsorshipsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: LeagueScoringPresetsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: APPROVE_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: ApproveLeagueJoinRequestPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: CREATE_LEAGUE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: CreateLeaguePresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetLeagueAdminPermissionsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_MEMBERSHIPS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetLeagueMembershipsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_ROSTER_MEMBERS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetLeagueRosterMembersPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_ROSTER_JOIN_REQUESTS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetLeagueRosterJoinRequestsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_OWNER_SUMMARY_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetLeagueOwnerSummaryPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetLeagueSeasonsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: JOIN_LEAGUE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: JoinLeaguePresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: LeagueSchedulePresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_STATS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: LeagueStatsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: REJECT_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: RejectLeagueJoinRequestPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: RemoveLeagueMemberPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: TOTAL_LEAGUES_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: TotalLeaguesPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: TRANSFER_LEAGUE_OWNERSHIP_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: TransferLeagueOwnershipPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: UpdateLeagueMemberRolePresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: LeagueConfigPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: LeagueScoringConfigPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetLeagueWalletPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: WithdrawFromLeagueWalletPresenter,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Schedule mutation output ports
|
|
||||||
{
|
|
||||||
provide: LeagueTokens.CREATE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: CreateLeagueSeasonScheduleRacePresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: LeagueTokens.UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: UpdateLeagueSeasonScheduleRacePresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: LeagueTokens.DELETE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: DeleteLeagueSeasonScheduleRacePresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: LeagueTokens.PUBLISH_LEAGUE_SEASON_SCHEDULE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: PublishLeagueSeasonSchedulePresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: LeagueTokens.UNPUBLISH_LEAGUE_SEASON_SCHEDULE_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: UnpublishLeagueSeasonSchedulePresenter,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Use cases
|
// Use cases
|
||||||
{
|
{
|
||||||
@@ -370,7 +217,6 @@ export const LeagueProviders: Provider[] = [
|
|||||||
seasonRepo: ISeasonRepository,
|
seasonRepo: ISeasonRepository,
|
||||||
scoringRepo: import('@core/racing/domain/repositories/ILeagueScoringConfigRepository').ILeagueScoringConfigRepository,
|
scoringRepo: import('@core/racing/domain/repositories/ILeagueScoringConfigRepository').ILeagueScoringConfigRepository,
|
||||||
gameRepo: import('@core/racing/domain/repositories/IGameRepository').IGameRepository,
|
gameRepo: import('@core/racing/domain/repositories/IGameRepository').IGameRepository,
|
||||||
output: AllLeaguesWithCapacityAndScoringPresenter,
|
|
||||||
) =>
|
) =>
|
||||||
new GetAllLeaguesWithCapacityAndScoringUseCase(
|
new GetAllLeaguesWithCapacityAndScoringUseCase(
|
||||||
leagueRepo,
|
leagueRepo,
|
||||||
@@ -379,7 +225,6 @@ export const LeagueProviders: Provider[] = [
|
|||||||
scoringRepo,
|
scoringRepo,
|
||||||
gameRepo,
|
gameRepo,
|
||||||
{ getPresetById: getLeagueScoringPresetById },
|
{ getPresetById: getLeagueScoringPresetById },
|
||||||
output,
|
|
||||||
),
|
),
|
||||||
inject: [
|
inject: [
|
||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
@@ -387,7 +232,6 @@ export const LeagueProviders: Provider[] = [
|
|||||||
SEASON_REPOSITORY_TOKEN,
|
SEASON_REPOSITORY_TOKEN,
|
||||||
LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN,
|
LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN,
|
||||||
GAME_REPOSITORY_TOKEN,
|
GAME_REPOSITORY_TOKEN,
|
||||||
GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -395,12 +239,10 @@ export const LeagueProviders: Provider[] = [
|
|||||||
useFactory: (
|
useFactory: (
|
||||||
standingRepo: IStandingRepository,
|
standingRepo: IStandingRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
output: LeagueStandingsPresenter,
|
) => new GetLeagueStandingsUseCase(standingRepo, driverRepo),
|
||||||
) => new GetLeagueStandingsUseCase(standingRepo, driverRepo, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
STANDING_REPOSITORY_TOKEN,
|
STANDING_REPOSITORY_TOKEN,
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -421,9 +263,9 @@ export const LeagueProviders: Provider[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_TOTAL_LEAGUES_USE_CASE,
|
provide: GET_TOTAL_LEAGUES_USE_CASE,
|
||||||
useFactory: (leagueRepo: ILeagueRepository, output: TotalLeaguesPresenter) =>
|
useFactory: (leagueRepo: ILeagueRepository) =>
|
||||||
new GetTotalLeaguesUseCase(leagueRepo, output),
|
new GetTotalLeaguesUseCase(leagueRepo),
|
||||||
inject: [LEAGUE_REPOSITORY_TOKEN, TOTAL_LEAGUES_OUTPUT_PORT_TOKEN],
|
inject: [LEAGUE_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_LEAGUE_JOIN_REQUESTS_USE_CASE,
|
provide: GET_LEAGUE_JOIN_REQUESTS_USE_CASE,
|
||||||
@@ -431,9 +273,8 @@ export const LeagueProviders: Provider[] = [
|
|||||||
membershipRepo: ILeagueMembershipRepository,
|
membershipRepo: ILeagueMembershipRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
output: LeagueJoinRequestsPresenter,
|
) => new GetLeagueJoinRequestsUseCase(membershipRepo, driverRepo, leagueRepo),
|
||||||
) => new GetLeagueJoinRequestsUseCase(membershipRepo, driverRepo, leagueRepo, output),
|
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN],
|
||||||
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LeagueJoinRequestsPresenter],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE,
|
provide: APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE,
|
||||||
@@ -453,21 +294,30 @@ export const LeagueProviders: Provider[] = [
|
|||||||
provide: REMOVE_LEAGUE_MEMBER_USE_CASE,
|
provide: REMOVE_LEAGUE_MEMBER_USE_CASE,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
membershipRepo: ILeagueMembershipRepository,
|
membershipRepo: ILeagueMembershipRepository,
|
||||||
output: RemoveLeagueMemberPresenter,
|
) => new RemoveLeagueMemberUseCase(membershipRepo),
|
||||||
) => new RemoveLeagueMemberUseCase(membershipRepo, output),
|
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||||
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE,
|
provide: UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
membershipRepo: ILeagueMembershipRepository,
|
membershipRepo: ILeagueMembershipRepository,
|
||||||
output: UpdateLeagueMemberRolePresenter,
|
) => new UpdateLeagueMemberRoleUseCase(membershipRepo),
|
||||||
) => new UpdateLeagueMemberRoleUseCase(membershipRepo, output),
|
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||||
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_LEAGUE_OWNER_SUMMARY_USE_CASE,
|
provide: GET_LEAGUE_OWNER_SUMMARY_USE_CASE,
|
||||||
useClass: GetLeagueOwnerSummaryUseCase,
|
useFactory: (
|
||||||
|
leagueRepo: ILeagueRepository,
|
||||||
|
driverRepo: IDriverRepository,
|
||||||
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
|
standingRepo: IStandingRepository,
|
||||||
|
) => new GetLeagueOwnerSummaryUseCase(leagueRepo, driverRepo, leagueMembershipRepo, standingRepo),
|
||||||
|
inject: [
|
||||||
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
|
STANDING_REPOSITORY_TOKEN,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_LEAGUE_PROTESTS_USE_CASE,
|
provide: GET_LEAGUE_PROTESTS_USE_CASE,
|
||||||
@@ -476,14 +326,12 @@ export const LeagueProviders: Provider[] = [
|
|||||||
protestRepo: IProtestRepository,
|
protestRepo: IProtestRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
output: GetLeagueProtestsPresenter,
|
) => new GetLeagueProtestsUseCase(raceRepo, protestRepo, driverRepo, leagueRepo),
|
||||||
) => new GetLeagueProtestsUseCase(raceRepo, protestRepo, driverRepo, leagueRepo, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
PROTEST_REPOSITORY_TOKEN,
|
PROTEST_REPOSITORY_TOKEN,
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -496,9 +344,8 @@ export const LeagueProviders: Provider[] = [
|
|||||||
membershipRepo: ILeagueMembershipRepository,
|
membershipRepo: ILeagueMembershipRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
output: GetLeagueMembershipsPresenter,
|
) => new GetLeagueMembershipsUseCase(membershipRepo, driverRepo, leagueRepo),
|
||||||
) => new GetLeagueMembershipsUseCase(membershipRepo, driverRepo, leagueRepo, output),
|
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN],
|
||||||
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, GetLeagueMembershipsPresenter],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_LEAGUE_ROSTER_MEMBERS_USE_CASE,
|
provide: GET_LEAGUE_ROSTER_MEMBERS_USE_CASE,
|
||||||
@@ -506,13 +353,11 @@ export const LeagueProviders: Provider[] = [
|
|||||||
membershipRepo: ILeagueMembershipRepository,
|
membershipRepo: ILeagueMembershipRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
output: GetLeagueRosterMembersPresenter,
|
) => new GetLeagueRosterMembersUseCase(membershipRepo, driverRepo, leagueRepo),
|
||||||
) => new GetLeagueRosterMembersUseCase(membershipRepo, driverRepo, leagueRepo, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
GET_LEAGUE_ROSTER_MEMBERS_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -521,13 +366,11 @@ export const LeagueProviders: Provider[] = [
|
|||||||
membershipRepo: ILeagueMembershipRepository,
|
membershipRepo: ILeagueMembershipRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
output: GetLeagueRosterJoinRequestsPresenter,
|
) => new GetLeagueRosterJoinRequestsUseCase(membershipRepo, driverRepo, leagueRepo),
|
||||||
) => new GetLeagueRosterJoinRequestsUseCase(membershipRepo, driverRepo, leagueRepo, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
GET_LEAGUE_ROSTER_JOIN_REQUESTS_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -537,14 +380,12 @@ export const LeagueProviders: Provider[] = [
|
|||||||
seasonRepo: ISeasonRepository,
|
seasonRepo: ISeasonRepository,
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: LeagueSchedulePresenter,
|
) => new GetLeagueScheduleUseCase(leagueRepo, seasonRepo, raceRepo, logger),
|
||||||
) => new GetLeagueScheduleUseCase(leagueRepo, seasonRepo, raceRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
SEASON_REPOSITORY_TOKEN,
|
SEASON_REPOSITORY_TOKEN,
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -553,13 +394,11 @@ export const LeagueProviders: Provider[] = [
|
|||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: GetLeagueAdminPermissionsPresenter,
|
) => new GetLeagueAdminPermissionsUseCase(leagueRepo, leagueMembershipRepo, logger),
|
||||||
) => new GetLeagueAdminPermissionsUseCase(leagueRepo, leagueMembershipRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -568,13 +407,11 @@ export const LeagueProviders: Provider[] = [
|
|||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
walletRepo: ILeagueWalletRepository,
|
walletRepo: ILeagueWalletRepository,
|
||||||
transactionRepo: ITransactionRepository,
|
transactionRepo: ITransactionRepository,
|
||||||
output: GetLeagueWalletPresenter,
|
) => new GetLeagueWalletUseCase(leagueRepo, walletRepo, transactionRepo),
|
||||||
) => new GetLeagueWalletUseCase(leagueRepo, walletRepo, transactionRepo, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
LEAGUE_WALLET_REPOSITORY_TOKEN,
|
LEAGUE_WALLET_REPOSITORY_TOKEN,
|
||||||
TRANSACTION_REPOSITORY_TOKEN,
|
TRANSACTION_REPOSITORY_TOKEN,
|
||||||
GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -584,14 +421,12 @@ export const LeagueProviders: Provider[] = [
|
|||||||
walletRepo: ILeagueWalletRepository,
|
walletRepo: ILeagueWalletRepository,
|
||||||
transactionRepo: ITransactionRepository,
|
transactionRepo: ITransactionRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: WithdrawFromLeagueWalletPresenter,
|
) => new WithdrawFromLeagueWalletUseCase(leagueRepo, walletRepo, transactionRepo, logger),
|
||||||
) => new WithdrawFromLeagueWalletUseCase(leagueRepo, walletRepo, transactionRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
LEAGUE_WALLET_REPOSITORY_TOKEN,
|
LEAGUE_WALLET_REPOSITORY_TOKEN,
|
||||||
TRANSACTION_REPOSITORY_TOKEN,
|
TRANSACTION_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -602,7 +437,6 @@ export const LeagueProviders: Provider[] = [
|
|||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
output: GetSeasonSponsorshipsPresenter,
|
|
||||||
) =>
|
) =>
|
||||||
new GetSeasonSponsorshipsUseCase(
|
new GetSeasonSponsorshipsUseCase(
|
||||||
seasonSponsorshipRepo,
|
seasonSponsorshipRepo,
|
||||||
@@ -610,7 +444,6 @@ export const LeagueProviders: Provider[] = [
|
|||||||
leagueRepo,
|
leagueRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
raceRepo,
|
raceRepo,
|
||||||
output,
|
|
||||||
),
|
),
|
||||||
inject: [
|
inject: [
|
||||||
'ISeasonSponsorshipRepository',
|
'ISeasonSponsorshipRepository',
|
||||||
@@ -618,23 +451,21 @@ export const LeagueProviders: Provider[] = [
|
|||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ // TODO wtf is this here? doesn't look like it adhers to our concepts
|
{ // TODO wtf is this here? doesn't look like it adhers to our concepts
|
||||||
provide: LIST_LEAGUE_SCORING_PRESETS_USE_CASE,
|
provide: LIST_LEAGUE_SCORING_PRESETS_USE_CASE,
|
||||||
useFactory: (output: LeagueScoringPresetsPresenter) =>
|
useFactory: () =>
|
||||||
new ListLeagueScoringPresetsUseCase(listLeagueScoringPresets(), output),
|
new ListLeagueScoringPresetsUseCase(listLeagueScoringPresets()),
|
||||||
inject: [LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN],
|
inject: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: JOIN_LEAGUE_USE_CASE,
|
provide: JOIN_LEAGUE_USE_CASE,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
membershipRepo: ILeagueMembershipRepository,
|
membershipRepo: ILeagueMembershipRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: JoinLeaguePresenter,
|
) => new JoinLeagueUseCase(membershipRepo, logger),
|
||||||
) => new JoinLeagueUseCase(membershipRepo, logger, output),
|
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
inject: [LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, JoinLeaguePresenter],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: TRANSFER_LEAGUE_OWNERSHIP_USE_CASE,
|
provide: TRANSFER_LEAGUE_OWNERSHIP_USE_CASE,
|
||||||
@@ -652,16 +483,14 @@ export const LeagueProviders: Provider[] = [
|
|||||||
seasonRepo: ISeasonRepository,
|
seasonRepo: ISeasonRepository,
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: CreateLeagueSeasonScheduleRacePresenter,
|
|
||||||
) =>
|
) =>
|
||||||
new CreateLeagueSeasonScheduleRaceUseCase(seasonRepo, raceRepo, logger, output, {
|
new CreateLeagueSeasonScheduleRaceUseCase(seasonRepo, raceRepo, logger, {
|
||||||
generateRaceId: () => `race-${randomUUID()}`,
|
generateRaceId: () => `race-${randomUUID()}`,
|
||||||
}),
|
}),
|
||||||
inject: [
|
inject: [
|
||||||
SEASON_REPOSITORY_TOKEN,
|
SEASON_REPOSITORY_TOKEN,
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
LeagueTokens.CREATE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -670,13 +499,11 @@ export const LeagueProviders: Provider[] = [
|
|||||||
seasonRepo: ISeasonRepository,
|
seasonRepo: ISeasonRepository,
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UpdateLeagueSeasonScheduleRacePresenter,
|
) => new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepo, raceRepo, logger),
|
||||||
) => new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepo, raceRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
SEASON_REPOSITORY_TOKEN,
|
SEASON_REPOSITORY_TOKEN,
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
LeagueTokens.UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -685,13 +512,11 @@ export const LeagueProviders: Provider[] = [
|
|||||||
seasonRepo: ISeasonRepository,
|
seasonRepo: ISeasonRepository,
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: DeleteLeagueSeasonScheduleRacePresenter,
|
) => new DeleteLeagueSeasonScheduleRaceUseCase(seasonRepo, raceRepo, logger),
|
||||||
) => new DeleteLeagueSeasonScheduleRaceUseCase(seasonRepo, raceRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
SEASON_REPOSITORY_TOKEN,
|
SEASON_REPOSITORY_TOKEN,
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
LeagueTokens.DELETE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -699,12 +524,10 @@ export const LeagueProviders: Provider[] = [
|
|||||||
useFactory: (
|
useFactory: (
|
||||||
seasonRepo: ISeasonRepository,
|
seasonRepo: ISeasonRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: PublishLeagueSeasonSchedulePresenter,
|
) => new PublishLeagueSeasonScheduleUseCase(seasonRepo, logger),
|
||||||
) => new PublishLeagueSeasonScheduleUseCase(seasonRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
SEASON_REPOSITORY_TOKEN,
|
SEASON_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
LeagueTokens.PUBLISH_LEAGUE_SEASON_SCHEDULE_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -712,12 +535,10 @@ export const LeagueProviders: Provider[] = [
|
|||||||
useFactory: (
|
useFactory: (
|
||||||
seasonRepo: ISeasonRepository,
|
seasonRepo: ISeasonRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UnpublishLeagueSeasonSchedulePresenter,
|
) => new UnpublishLeagueSeasonScheduleUseCase(seasonRepo, logger),
|
||||||
) => new UnpublishLeagueSeasonScheduleUseCase(seasonRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
SEASON_REPOSITORY_TOKEN,
|
SEASON_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
LeagueTokens.UNPUBLISH_LEAGUE_SEASON_SCHEDULE_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -62,15 +62,19 @@ describe('LeagueService', () => {
|
|||||||
present: vi.fn(),
|
present: vi.fn(),
|
||||||
getViewModel: vi.fn(() => ({ leagues: [], totalCount: 0 })),
|
getViewModel: vi.fn(() => ({ leagues: [], totalCount: 0 })),
|
||||||
};
|
};
|
||||||
const leagueStandingsPresenter = { getResponseModel: vi.fn(() => ({ standings: [] })) };
|
const leagueStandingsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ standings: [] })) };
|
||||||
const leagueProtestsPresenter = { getResponseModel: vi.fn(() => ({ protests: [] })) };
|
const leagueProtestsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ protests: [] })) };
|
||||||
const seasonSponsorshipsPresenter = { getViewModel: vi.fn(() => ({ sponsorships: [] })) };
|
const seasonSponsorshipsPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ sponsorships: [] })) };
|
||||||
const leagueScoringPresetsPresenter = { getViewModel: vi.fn(() => ({ presets: [] })) };
|
const leagueScoringPresetsPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ presets: [] })) };
|
||||||
const approveLeagueJoinRequestPresenter = { getViewModel: vi.fn(() => ({ success: true })) };
|
const approveLeagueJoinRequestPresenter = {
|
||||||
const createLeaguePresenter = { getViewModel: vi.fn(() => ({ id: 'l1' })) };
|
present: vi.fn(),
|
||||||
const getLeagueAdminPermissionsPresenter = { getResponseModel: vi.fn(() => ({ canManage: true })) };
|
getViewModel: vi.fn(() => ({ success: true }))
|
||||||
|
};
|
||||||
|
const createLeaguePresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ id: 'l1' })) };
|
||||||
|
const getLeagueAdminPermissionsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ canManage: true })) };
|
||||||
const getLeagueMembershipsPresenter = {
|
const getLeagueMembershipsPresenter = {
|
||||||
reset: vi.fn(),
|
reset: vi.fn(),
|
||||||
|
present: vi.fn(),
|
||||||
getViewModel: vi.fn(() => ({ memberships: { memberships: [] } })),
|
getViewModel: vi.fn(() => ({ memberships: { memberships: [] } })),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,27 +89,30 @@ describe('LeagueService', () => {
|
|||||||
present: vi.fn(),
|
present: vi.fn(),
|
||||||
getViewModel: vi.fn(() => ([])),
|
getViewModel: vi.fn(() => ([])),
|
||||||
};
|
};
|
||||||
const getLeagueOwnerSummaryPresenter = { getViewModel: vi.fn(() => ({ ownerId: 'o1' })) };
|
const getLeagueOwnerSummaryPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ ownerId: 'o1' })) };
|
||||||
const getLeagueSeasonsPresenter = { getResponseModel: vi.fn(() => ([])) };
|
const getLeagueSeasonsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ([])) };
|
||||||
const joinLeaguePresenter = { getViewModel: vi.fn(() => ({ success: true })) };
|
const joinLeaguePresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ success: true })) };
|
||||||
const leagueSchedulePresenter = { getViewModel: vi.fn(() => ({ seasonId: 'season-1', published: false, races: [] })) };
|
const leagueSchedulePresenter = { reset: vi.fn(), present: vi.fn(), getViewModel: vi.fn(() => ({ seasonId: 'season-1', published: false, races: [] })) };
|
||||||
const leagueStatsPresenter = { getResponseModel: vi.fn(() => ({ stats: {} })) };
|
const leagueStatsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ stats: {} })) };
|
||||||
const rejectLeagueJoinRequestPresenter = { getViewModel: vi.fn(() => ({ success: true })) };
|
const rejectLeagueJoinRequestPresenter = {
|
||||||
const removeLeagueMemberPresenter = { getViewModel: vi.fn(() => ({ success: true })) };
|
present: vi.fn(),
|
||||||
const totalLeaguesPresenter = { getResponseModel: vi.fn(() => ({ total: 1 })) };
|
getViewModel: vi.fn(() => ({ success: true }))
|
||||||
const transferLeagueOwnershipPresenter = { getViewModel: vi.fn(() => ({ success: true })) };
|
};
|
||||||
const updateLeagueMemberRolePresenter = { getViewModel: vi.fn(() => ({ success: true })) };
|
const removeLeagueMemberPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ success: true })) };
|
||||||
|
const totalLeaguesPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ total: 1 })) };
|
||||||
|
const transferLeagueOwnershipPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ success: true })) };
|
||||||
|
const updateLeagueMemberRolePresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ success: true })) };
|
||||||
const leagueConfigPresenter = { getViewModel: vi.fn(() => ({ form: {} })) };
|
const leagueConfigPresenter = { getViewModel: vi.fn(() => ({ form: {} })) };
|
||||||
const leagueScoringConfigPresenter = { getViewModel: vi.fn(() => ({ config: {} })) };
|
const leagueScoringConfigPresenter = { getViewModel: vi.fn(() => ({ config: {} })) };
|
||||||
const getLeagueWalletPresenter = { getResponseModel: vi.fn(() => ({ balance: 0 })) };
|
const getLeagueWalletPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ balance: 0 })) };
|
||||||
const withdrawFromLeagueWalletPresenter = { getResponseModel: vi.fn(() => ({ success: true })) };
|
const withdrawFromLeagueWalletPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true })) };
|
||||||
const leagueJoinRequestsPresenter = { getViewModel: vi.fn(() => ({ joinRequests: [] })) };
|
const leagueJoinRequestsPresenter = { reset: vi.fn(), present: vi.fn(), getViewModel: vi.fn(() => ({ joinRequests: [] })) };
|
||||||
|
|
||||||
const createLeagueSeasonScheduleRacePresenter = { getResponseModel: vi.fn(() => ({ raceId: 'race-1' })) };
|
const createLeagueSeasonScheduleRacePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ raceId: 'race-1' })) };
|
||||||
const updateLeagueSeasonScheduleRacePresenter = { getResponseModel: vi.fn(() => ({ success: true })) };
|
const updateLeagueSeasonScheduleRacePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true })) };
|
||||||
const deleteLeagueSeasonScheduleRacePresenter = { getResponseModel: vi.fn(() => ({ success: true })) };
|
const deleteLeagueSeasonScheduleRacePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true })) };
|
||||||
const publishLeagueSeasonSchedulePresenter = { getResponseModel: vi.fn(() => ({ success: true, published: true })) };
|
const publishLeagueSeasonSchedulePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true, published: true })) };
|
||||||
const unpublishLeagueSeasonSchedulePresenter = { getResponseModel: vi.fn(() => ({ success: true, published: false })) };
|
const unpublishLeagueSeasonSchedulePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true, published: false })) };
|
||||||
|
|
||||||
const service = new (LeagueService as any)(
|
const service = new (LeagueService as any)(
|
||||||
getAllLeaguesWithCapacityUseCase as any,
|
getAllLeaguesWithCapacityUseCase as any,
|
||||||
@@ -195,8 +202,7 @@ describe('LeagueService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(rejectLeagueJoinRequestUseCase.execute).toHaveBeenCalledWith(
|
expect(rejectLeagueJoinRequestUseCase.execute).toHaveBeenCalledWith(
|
||||||
{ leagueId: 'l1', joinRequestId: 'r1' },
|
{ leagueId: 'l1', joinRequestId: 'r1' }
|
||||||
rejectLeagueJoinRequestPresenter,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await withUserId('user-1', async () => {
|
await withUserId('user-1', async () => {
|
||||||
|
|||||||
@@ -135,65 +135,39 @@ import {
|
|||||||
} from './presenters/LeagueSeasonScheduleMutationPresenters';
|
} from './presenters/LeagueSeasonScheduleMutationPresenters';
|
||||||
// Tokens
|
// Tokens
|
||||||
import {
|
import {
|
||||||
APPROVE_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN,
|
|
||||||
APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE,
|
APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE,
|
||||||
CREATE_LEAGUE_OUTPUT_PORT_TOKEN,
|
|
||||||
CREATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
|
CREATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
|
||||||
CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE,
|
CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE,
|
||||||
GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_USE_CASE,
|
GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_USE_CASE,
|
||||||
GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE,
|
GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE,
|
||||||
GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE,
|
GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE,
|
||||||
GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_FULL_CONFIG_USE_CASE,
|
GET_LEAGUE_FULL_CONFIG_USE_CASE,
|
||||||
GET_LEAGUE_JOIN_REQUESTS_USE_CASE,
|
GET_LEAGUE_JOIN_REQUESTS_USE_CASE,
|
||||||
GET_LEAGUE_MEMBERSHIPS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_MEMBERSHIPS_USE_CASE,
|
GET_LEAGUE_MEMBERSHIPS_USE_CASE,
|
||||||
GET_LEAGUE_ROSTER_JOIN_REQUESTS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_ROSTER_JOIN_REQUESTS_USE_CASE,
|
|
||||||
GET_LEAGUE_ROSTER_MEMBERS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_ROSTER_MEMBERS_USE_CASE,
|
|
||||||
GET_LEAGUE_OWNER_SUMMARY_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_OWNER_SUMMARY_USE_CASE,
|
GET_LEAGUE_OWNER_SUMMARY_USE_CASE,
|
||||||
GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_PROTESTS_USE_CASE,
|
GET_LEAGUE_PROTESTS_USE_CASE,
|
||||||
GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_SCHEDULE_USE_CASE,
|
GET_LEAGUE_SCHEDULE_USE_CASE,
|
||||||
GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_SCORING_CONFIG_USE_CASE,
|
GET_LEAGUE_SCORING_CONFIG_USE_CASE,
|
||||||
GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_SEASONS_USE_CASE,
|
GET_LEAGUE_SEASONS_USE_CASE,
|
||||||
GET_LEAGUE_STATS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_STATS_USE_CASE,
|
GET_LEAGUE_STATS_USE_CASE,
|
||||||
GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_STANDINGS_USE_CASE,
|
GET_LEAGUE_STANDINGS_USE_CASE,
|
||||||
GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_LEAGUE_WALLET_USE_CASE,
|
GET_LEAGUE_WALLET_USE_CASE,
|
||||||
GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_SEASON_SPONSORSHIPS_USE_CASE,
|
GET_SEASON_SPONSORSHIPS_USE_CASE,
|
||||||
GET_TOTAL_LEAGUES_USE_CASE,
|
GET_TOTAL_LEAGUES_USE_CASE,
|
||||||
JOIN_LEAGUE_OUTPUT_PORT_TOKEN,
|
|
||||||
JOIN_LEAGUE_USE_CASE,
|
JOIN_LEAGUE_USE_CASE,
|
||||||
LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN,
|
|
||||||
LIST_LEAGUE_SCORING_PRESETS_USE_CASE,
|
LIST_LEAGUE_SCORING_PRESETS_USE_CASE,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
PUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE,
|
PUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE,
|
||||||
REJECT_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN,
|
|
||||||
REJECT_LEAGUE_JOIN_REQUEST_USE_CASE,
|
REJECT_LEAGUE_JOIN_REQUEST_USE_CASE,
|
||||||
REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN,
|
|
||||||
REMOVE_LEAGUE_MEMBER_USE_CASE,
|
REMOVE_LEAGUE_MEMBER_USE_CASE,
|
||||||
TOTAL_LEAGUES_OUTPUT_PORT_TOKEN,
|
|
||||||
TRANSFER_LEAGUE_OWNERSHIP_OUTPUT_PORT_TOKEN,
|
|
||||||
TRANSFER_LEAGUE_OWNERSHIP_USE_CASE,
|
TRANSFER_LEAGUE_OWNERSHIP_USE_CASE,
|
||||||
UNPUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE,
|
UNPUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE,
|
||||||
UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN,
|
|
||||||
UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE,
|
UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE,
|
||||||
UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
|
UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
|
||||||
DELETE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
|
DELETE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
|
||||||
WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
|
|
||||||
WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE,
|
WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE,
|
||||||
|
GET_LEAGUE_ROSTER_MEMBERS_USE_CASE,
|
||||||
|
GET_LEAGUE_ROSTER_JOIN_REQUESTS_USE_CASE,
|
||||||
} from './LeagueTokens';
|
} from './LeagueTokens';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -240,52 +214,47 @@ export class LeagueService {
|
|||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||||
|
|
||||||
// Injected presenters
|
// Injected presenters
|
||||||
@Inject(GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN) private readonly allLeaguesWithCapacityPresenter: AllLeaguesWithCapacityPresenter,
|
@Inject(AllLeaguesWithCapacityPresenter) private readonly allLeaguesWithCapacityPresenter: AllLeaguesWithCapacityPresenter,
|
||||||
@Inject(GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN) private readonly allLeaguesWithCapacityAndScoringPresenter: AllLeaguesWithCapacityAndScoringPresenter,
|
@Inject(AllLeaguesWithCapacityAndScoringPresenter) private readonly allLeaguesWithCapacityAndScoringPresenter: AllLeaguesWithCapacityAndScoringPresenter,
|
||||||
@Inject(GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN) private readonly leagueStandingsPresenter: LeagueStandingsPresenter,
|
@Inject(LeagueStandingsPresenter) private readonly leagueStandingsPresenter: LeagueStandingsPresenter,
|
||||||
@Inject(GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN) private readonly leagueProtestsPresenter: GetLeagueProtestsPresenter,
|
@Inject(GetLeagueProtestsPresenter) private readonly leagueProtestsPresenter: GetLeagueProtestsPresenter,
|
||||||
@Inject(GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN) private readonly seasonSponsorshipsPresenter: GetSeasonSponsorshipsPresenter,
|
@Inject(GetSeasonSponsorshipsPresenter) private readonly seasonSponsorshipsPresenter: GetSeasonSponsorshipsPresenter,
|
||||||
@Inject(LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN) private readonly leagueScoringPresetsPresenter: LeagueScoringPresetsPresenter,
|
@Inject(LeagueScoringPresetsPresenter) private readonly leagueScoringPresetsPresenter: LeagueScoringPresetsPresenter,
|
||||||
@Inject(APPROVE_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN) private readonly approveLeagueJoinRequestPresenter: ApproveLeagueJoinRequestPresenter,
|
@Inject(ApproveLeagueJoinRequestPresenter) private readonly approveLeagueJoinRequestPresenter: ApproveLeagueJoinRequestPresenter,
|
||||||
@Inject(CREATE_LEAGUE_OUTPUT_PORT_TOKEN) private readonly createLeaguePresenter: CreateLeaguePresenter,
|
@Inject(CreateLeaguePresenter) private readonly createLeaguePresenter: CreateLeaguePresenter,
|
||||||
@Inject(GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN) private readonly getLeagueAdminPermissionsPresenter: GetLeagueAdminPermissionsPresenter,
|
@Inject(GetLeagueAdminPermissionsPresenter) private readonly getLeagueAdminPermissionsPresenter: GetLeagueAdminPermissionsPresenter,
|
||||||
@Inject(GET_LEAGUE_MEMBERSHIPS_OUTPUT_PORT_TOKEN) private readonly getLeagueMembershipsPresenter: GetLeagueMembershipsPresenter,
|
@Inject(GetLeagueMembershipsPresenter) private readonly getLeagueMembershipsPresenter: GetLeagueMembershipsPresenter,
|
||||||
@Inject(GET_LEAGUE_OWNER_SUMMARY_OUTPUT_PORT_TOKEN) private readonly getLeagueOwnerSummaryPresenter: GetLeagueOwnerSummaryPresenter,
|
@Inject(GetLeagueOwnerSummaryPresenter) private readonly getLeagueOwnerSummaryPresenter: GetLeagueOwnerSummaryPresenter,
|
||||||
@Inject(GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN) private readonly getLeagueSeasonsPresenter: GetLeagueSeasonsPresenter,
|
@Inject(GetLeagueSeasonsPresenter) private readonly getLeagueSeasonsPresenter: GetLeagueSeasonsPresenter,
|
||||||
@Inject(JOIN_LEAGUE_OUTPUT_PORT_TOKEN) private readonly joinLeaguePresenter: JoinLeaguePresenter,
|
@Inject(JoinLeaguePresenter) private readonly joinLeaguePresenter: JoinLeaguePresenter,
|
||||||
@Inject(GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN) private readonly leagueSchedulePresenter: LeagueSchedulePresenter,
|
@Inject(LeagueSchedulePresenter) private readonly leagueSchedulePresenter: LeagueSchedulePresenter,
|
||||||
@Inject(GET_LEAGUE_STATS_OUTPUT_PORT_TOKEN) private readonly leagueStatsPresenter: LeagueStatsPresenter,
|
@Inject(LeagueStatsPresenter) private readonly leagueStatsPresenter: LeagueStatsPresenter,
|
||||||
@Inject(REJECT_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN) private readonly rejectLeagueJoinRequestPresenter: RejectLeagueJoinRequestPresenter,
|
@Inject(RejectLeagueJoinRequestPresenter) private readonly rejectLeagueJoinRequestPresenter: RejectLeagueJoinRequestPresenter,
|
||||||
@Inject(REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN) private readonly removeLeagueMemberPresenter: RemoveLeagueMemberPresenter,
|
@Inject(RemoveLeagueMemberPresenter) private readonly removeLeagueMemberPresenter: RemoveLeagueMemberPresenter,
|
||||||
@Inject(TOTAL_LEAGUES_OUTPUT_PORT_TOKEN) private readonly totalLeaguesPresenter: TotalLeaguesPresenter,
|
@Inject(TotalLeaguesPresenter) private readonly totalLeaguesPresenter: TotalLeaguesPresenter,
|
||||||
@Inject(TRANSFER_LEAGUE_OWNERSHIP_OUTPUT_PORT_TOKEN) private readonly transferLeagueOwnershipPresenter: TransferLeagueOwnershipPresenter,
|
@Inject(TransferLeagueOwnershipPresenter) private readonly transferLeagueOwnershipPresenter: TransferLeagueOwnershipPresenter,
|
||||||
@Inject(UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN) private readonly updateLeagueMemberRolePresenter: UpdateLeagueMemberRolePresenter,
|
@Inject(UpdateLeagueMemberRolePresenter) private readonly updateLeagueMemberRolePresenter: UpdateLeagueMemberRolePresenter,
|
||||||
@Inject(GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN) private readonly leagueConfigPresenter: LeagueConfigPresenter,
|
@Inject(LeagueConfigPresenter) private readonly leagueConfigPresenter: LeagueConfigPresenter,
|
||||||
@Inject(GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN) private readonly leagueScoringConfigPresenter: LeagueScoringConfigPresenter,
|
@Inject(LeagueScoringConfigPresenter) private readonly leagueScoringConfigPresenter: LeagueScoringConfigPresenter,
|
||||||
@Inject(GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN) private readonly getLeagueWalletPresenter: GetLeagueWalletPresenter,
|
@Inject(GetLeagueWalletPresenter) private readonly getLeagueWalletPresenter: GetLeagueWalletPresenter,
|
||||||
@Inject(WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN) private readonly withdrawFromLeagueWalletPresenter: WithdrawFromLeagueWalletPresenter,
|
@Inject(WithdrawFromLeagueWalletPresenter) private readonly withdrawFromLeagueWalletPresenter: WithdrawFromLeagueWalletPresenter,
|
||||||
@Inject(LeagueJoinRequestsPresenter) private readonly leagueJoinRequestsPresenter: LeagueJoinRequestsPresenter,
|
@Inject(LeagueJoinRequestsPresenter) private readonly leagueJoinRequestsPresenter: LeagueJoinRequestsPresenter,
|
||||||
|
|
||||||
// Schedule mutation presenters
|
// Schedule mutation presenters
|
||||||
@Inject(CreateLeagueSeasonScheduleRacePresenter)
|
@Inject(CreateLeagueSeasonScheduleRacePresenter) private readonly createLeagueSeasonScheduleRacePresenter: CreateLeagueSeasonScheduleRacePresenter,
|
||||||
private readonly createLeagueSeasonScheduleRacePresenter: CreateLeagueSeasonScheduleRacePresenter,
|
@Inject(UpdateLeagueSeasonScheduleRacePresenter) private readonly updateLeagueSeasonScheduleRacePresenter: UpdateLeagueSeasonScheduleRacePresenter,
|
||||||
@Inject(UpdateLeagueSeasonScheduleRacePresenter)
|
@Inject(DeleteLeagueSeasonScheduleRacePresenter) private readonly deleteLeagueSeasonScheduleRacePresenter: DeleteLeagueSeasonScheduleRacePresenter,
|
||||||
private readonly updateLeagueSeasonScheduleRacePresenter: UpdateLeagueSeasonScheduleRacePresenter,
|
@Inject(PublishLeagueSeasonSchedulePresenter) private readonly publishLeagueSeasonSchedulePresenter: PublishLeagueSeasonSchedulePresenter,
|
||||||
@Inject(DeleteLeagueSeasonScheduleRacePresenter)
|
@Inject(UnpublishLeagueSeasonSchedulePresenter) private readonly unpublishLeagueSeasonSchedulePresenter: UnpublishLeagueSeasonSchedulePresenter,
|
||||||
private readonly deleteLeagueSeasonScheduleRacePresenter: DeleteLeagueSeasonScheduleRacePresenter,
|
|
||||||
@Inject(PublishLeagueSeasonSchedulePresenter)
|
|
||||||
private readonly publishLeagueSeasonSchedulePresenter: PublishLeagueSeasonSchedulePresenter,
|
|
||||||
@Inject(UnpublishLeagueSeasonSchedulePresenter)
|
|
||||||
private readonly unpublishLeagueSeasonSchedulePresenter: UnpublishLeagueSeasonSchedulePresenter,
|
|
||||||
|
|
||||||
// Roster admin read delegation
|
// Roster admin read delegation
|
||||||
@Inject(GET_LEAGUE_ROSTER_MEMBERS_USE_CASE)
|
@Inject(GET_LEAGUE_ROSTER_MEMBERS_USE_CASE)
|
||||||
private readonly getLeagueRosterMembersUseCase: GetLeagueRosterMembersUseCase,
|
private readonly getLeagueRosterMembersUseCase: GetLeagueRosterMembersUseCase,
|
||||||
@Inject(GET_LEAGUE_ROSTER_JOIN_REQUESTS_USE_CASE)
|
@Inject(GET_LEAGUE_ROSTER_JOIN_REQUESTS_USE_CASE)
|
||||||
private readonly getLeagueRosterJoinRequestsUseCase: GetLeagueRosterJoinRequestsUseCase,
|
private readonly getLeagueRosterJoinRequestsUseCase: GetLeagueRosterJoinRequestsUseCase,
|
||||||
@Inject(GET_LEAGUE_ROSTER_MEMBERS_OUTPUT_PORT_TOKEN)
|
@Inject(GetLeagueRosterMembersPresenter)
|
||||||
private readonly getLeagueRosterMembersPresenter: GetLeagueRosterMembersPresenter,
|
private readonly getLeagueRosterMembersPresenter: GetLeagueRosterMembersPresenter,
|
||||||
@Inject(GET_LEAGUE_ROSTER_JOIN_REQUESTS_OUTPUT_PORT_TOKEN)
|
@Inject(GetLeagueRosterJoinRequestsPresenter)
|
||||||
private readonly getLeagueRosterJoinRequestsPresenter: GetLeagueRosterJoinRequestsPresenter,
|
private readonly getLeagueRosterJoinRequestsPresenter: GetLeagueRosterJoinRequestsPresenter,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -327,7 +296,11 @@ export class LeagueService {
|
|||||||
|
|
||||||
async getTotalLeagues(): Promise<TotalLeaguesDTO> {
|
async getTotalLeagues(): Promise<TotalLeaguesDTO> {
|
||||||
this.logger.debug('[LeagueService] Fetching total leagues count.');
|
this.logger.debug('[LeagueService] Fetching total leagues count.');
|
||||||
await this.getTotalLeaguesUseCase.execute({});
|
const result = await this.getTotalLeaguesUseCase.execute({});
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.totalLeaguesPresenter.present(result.unwrap());
|
||||||
return this.totalLeaguesPresenter.getResponseModel()!;
|
return this.totalLeaguesPresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,8 +318,13 @@ export class LeagueService {
|
|||||||
await this.requireLeagueAdminPermissions(leagueId);
|
await this.requireLeagueAdminPermissions(leagueId);
|
||||||
|
|
||||||
this.leagueJoinRequestsPresenter.reset?.();
|
this.leagueJoinRequestsPresenter.reset?.();
|
||||||
await this.getLeagueJoinRequestsUseCase.execute({ leagueId });
|
const result = await this.getLeagueJoinRequestsUseCase.execute({ leagueId });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.leagueJoinRequestsPresenter.present(result.unwrap());
|
||||||
return this.leagueJoinRequestsPresenter.getViewModel()!.joinRequests;
|
return this.leagueJoinRequestsPresenter.getViewModel()!.joinRequests;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,10 +333,8 @@ export class LeagueService {
|
|||||||
|
|
||||||
await this.requireLeagueAdminPermissions(input.leagueId);
|
await this.requireLeagueAdminPermissions(input.leagueId);
|
||||||
|
|
||||||
this.approveLeagueJoinRequestPresenter.reset?.();
|
|
||||||
const result = await this.approveLeagueJoinRequestUseCase.execute(
|
const result = await this.approveLeagueJoinRequestUseCase.execute(
|
||||||
{ leagueId: input.leagueId, joinRequestId: input.requestId },
|
{ leagueId: input.leagueId, joinRequestId: input.requestId },
|
||||||
this.approveLeagueJoinRequestPresenter,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
@@ -379,6 +355,7 @@ export class LeagueService {
|
|||||||
throw new Error(err.code);
|
throw new Error(err.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.approveLeagueJoinRequestPresenter.present(result.unwrap());
|
||||||
return this.approveLeagueJoinRequestPresenter.getViewModel()!;
|
return this.approveLeagueJoinRequestPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,10 +364,8 @@ export class LeagueService {
|
|||||||
|
|
||||||
await this.requireLeagueAdminPermissions(input.leagueId);
|
await this.requireLeagueAdminPermissions(input.leagueId);
|
||||||
|
|
||||||
this.rejectLeagueJoinRequestPresenter.reset?.();
|
|
||||||
const result = await this.rejectLeagueJoinRequestUseCase.execute(
|
const result = await this.rejectLeagueJoinRequestUseCase.execute(
|
||||||
{ leagueId: input.leagueId, joinRequestId: input.requestId },
|
{ leagueId: input.leagueId, joinRequestId: input.requestId },
|
||||||
this.rejectLeagueJoinRequestPresenter,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
@@ -411,6 +386,7 @@ export class LeagueService {
|
|||||||
throw new Error(err.code);
|
throw new Error(err.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.rejectLeagueJoinRequestPresenter.present(result.unwrap());
|
||||||
return this.rejectLeagueJoinRequestPresenter.getViewModel()!;
|
return this.rejectLeagueJoinRequestPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,10 +395,8 @@ export class LeagueService {
|
|||||||
|
|
||||||
await this.requireLeagueAdminPermissions(leagueId);
|
await this.requireLeagueAdminPermissions(leagueId);
|
||||||
|
|
||||||
this.approveLeagueJoinRequestPresenter.reset?.();
|
|
||||||
const result = await this.approveLeagueJoinRequestUseCase.execute(
|
const result = await this.approveLeagueJoinRequestUseCase.execute(
|
||||||
{ leagueId, joinRequestId },
|
{ leagueId, joinRequestId },
|
||||||
this.approveLeagueJoinRequestPresenter,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
@@ -443,6 +417,7 @@ export class LeagueService {
|
|||||||
throw new Error(err.code);
|
throw new Error(err.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.approveLeagueJoinRequestPresenter.present(result.unwrap());
|
||||||
return this.approveLeagueJoinRequestPresenter.getViewModel()!;
|
return this.approveLeagueJoinRequestPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,16 +426,15 @@ export class LeagueService {
|
|||||||
|
|
||||||
await this.requireLeagueAdminPermissions(leagueId);
|
await this.requireLeagueAdminPermissions(leagueId);
|
||||||
|
|
||||||
this.rejectLeagueJoinRequestPresenter.reset?.();
|
|
||||||
const result = await this.rejectLeagueJoinRequestUseCase.execute(
|
const result = await this.rejectLeagueJoinRequestUseCase.execute(
|
||||||
{ leagueId, joinRequestId },
|
{ leagueId, joinRequestId },
|
||||||
this.rejectLeagueJoinRequestPresenter,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new NotFoundException('Join request not found');
|
throw new NotFoundException('Join request not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.rejectLeagueJoinRequestPresenter.present(result.unwrap());
|
||||||
return this.rejectLeagueJoinRequestPresenter.getViewModel()!;
|
return this.rejectLeagueJoinRequestPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,11 +443,16 @@ export class LeagueService {
|
|||||||
|
|
||||||
this.logger.debug('Getting league admin permissions', { leagueId: query.leagueId, performerDriverId: actor.driverId });
|
this.logger.debug('Getting league admin permissions', { leagueId: query.leagueId, performerDriverId: actor.driverId });
|
||||||
|
|
||||||
await this.getLeagueAdminPermissionsUseCase.execute({
|
const result = await this.getLeagueAdminPermissionsUseCase.execute({
|
||||||
leagueId: query.leagueId,
|
leagueId: query.leagueId,
|
||||||
performerDriverId: actor.driverId,
|
performerDriverId: actor.driverId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getLeagueAdminPermissionsPresenter.present(result.unwrap());
|
||||||
return this.getLeagueAdminPermissionsPresenter.getResponseModel()!;
|
return this.getLeagueAdminPermissionsPresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +523,11 @@ export class LeagueService {
|
|||||||
|
|
||||||
async getLeagueOwnerSummary(query: GetLeagueOwnerSummaryQueryDTO): Promise<LeagueOwnerSummaryDTO> {
|
async getLeagueOwnerSummary(query: GetLeagueOwnerSummaryQueryDTO): Promise<LeagueOwnerSummaryDTO> {
|
||||||
this.logger.debug('Getting league owner summary:', query);
|
this.logger.debug('Getting league owner summary:', query);
|
||||||
await this.getLeagueOwnerSummaryUseCase.execute(query);
|
const result = await this.getLeagueOwnerSummaryUseCase.execute(query);
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.getLeagueOwnerSummaryPresenter.present(result.unwrap());
|
||||||
return this.getLeagueOwnerSummaryPresenter.getViewModel()!;
|
return this.getLeagueOwnerSummaryPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,19 +545,31 @@ export class LeagueService {
|
|||||||
|
|
||||||
async getLeagueProtests(query: GetLeagueProtestsQueryDTO): Promise<LeagueAdminProtestsDTO> {
|
async getLeagueProtests(query: GetLeagueProtestsQueryDTO): Promise<LeagueAdminProtestsDTO> {
|
||||||
this.logger.debug('Getting league protests:', query);
|
this.logger.debug('Getting league protests:', query);
|
||||||
await this.getLeagueProtestsUseCase.execute(query);
|
const result = await this.getLeagueProtestsUseCase.execute(query);
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.leagueProtestsPresenter.present(result.unwrap());
|
||||||
return this.leagueProtestsPresenter.getResponseModel()!;
|
return this.leagueProtestsPresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueSeasons(query: GetLeagueSeasonsQueryDTO): Promise<LeagueSeasonSummaryDTO[]> {
|
async getLeagueSeasons(query: GetLeagueSeasonsQueryDTO): Promise<LeagueSeasonSummaryDTO[]> {
|
||||||
this.logger.debug('Getting league seasons:', query);
|
this.logger.debug('Getting league seasons:', query);
|
||||||
await this.getLeagueSeasonsUseCase.execute(query);
|
const result = await this.getLeagueSeasonsUseCase.execute(query);
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.getLeagueSeasonsPresenter.present(result.unwrap());
|
||||||
return this.getLeagueSeasonsPresenter.getResponseModel()!;
|
return this.getLeagueSeasonsPresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
|
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
|
||||||
this.logger.debug('Getting league memberships', { leagueId });
|
this.logger.debug('Getting league memberships', { leagueId });
|
||||||
await this.getLeagueMembershipsUseCase.execute({ leagueId });
|
const result = await this.getLeagueMembershipsUseCase.execute({ leagueId });
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.getLeagueMembershipsPresenter.present(result.unwrap());
|
||||||
return this.getLeagueMembershipsPresenter.getViewModel()!.memberships;
|
return this.getLeagueMembershipsPresenter.getViewModel()!.memberships;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,6 +585,7 @@ export class LeagueService {
|
|||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.getLeagueRosterMembersPresenter.present(result.unwrap());
|
||||||
return this.getLeagueRosterMembersPresenter.getViewModel()!;
|
return this.getLeagueRosterMembersPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,12 +601,17 @@ export class LeagueService {
|
|||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.getLeagueRosterJoinRequestsPresenter.present(result.unwrap());
|
||||||
return this.getLeagueRosterJoinRequestsPresenter.getViewModel()!;
|
return this.getLeagueRosterJoinRequestsPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueStandings(leagueId: string): Promise<LeagueStandingsDTO> {
|
async getLeagueStandings(leagueId: string): Promise<LeagueStandingsDTO> {
|
||||||
this.logger.debug('Getting league standings', { leagueId });
|
this.logger.debug('Getting league standings', { leagueId });
|
||||||
await this.getLeagueStandingsUseCase.execute({ leagueId });
|
const result = await this.getLeagueStandingsUseCase.execute({ leagueId });
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.leagueStandingsPresenter.present(result.unwrap());
|
||||||
return this.leagueStandingsPresenter.getResponseModel()!;
|
return this.leagueStandingsPresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,8 +619,13 @@ export class LeagueService {
|
|||||||
this.logger.debug('Getting league schedule', { leagueId, query });
|
this.logger.debug('Getting league schedule', { leagueId, query });
|
||||||
|
|
||||||
const input: GetLeagueScheduleInput = query?.seasonId ? { leagueId, seasonId: query.seasonId } : { leagueId };
|
const input: GetLeagueScheduleInput = query?.seasonId ? { leagueId, seasonId: query.seasonId } : { leagueId };
|
||||||
await this.getLeagueScheduleUseCase.execute(input);
|
const result = await this.getLeagueScheduleUseCase.execute(input);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.leagueSchedulePresenter.present(result.unwrap());
|
||||||
return this.leagueSchedulePresenter.getViewModel()!;
|
return this.leagueSchedulePresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,6 +645,7 @@ export class LeagueService {
|
|||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.publishLeagueSeasonSchedulePresenter.present(result.unwrap());
|
||||||
return this.publishLeagueSeasonSchedulePresenter.getResponseModel()!;
|
return this.publishLeagueSeasonSchedulePresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,6 +665,7 @@ export class LeagueService {
|
|||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.unpublishLeagueSeasonSchedulePresenter.present(result.unwrap());
|
||||||
return this.unpublishLeagueSeasonSchedulePresenter.getResponseModel()!;
|
return this.unpublishLeagueSeasonSchedulePresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,6 +694,7 @@ export class LeagueService {
|
|||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.createLeagueSeasonScheduleRacePresenter.present(result.unwrap());
|
||||||
return this.createLeagueSeasonScheduleRacePresenter.getResponseModel()!;
|
return this.createLeagueSeasonScheduleRacePresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,6 +727,7 @@ export class LeagueService {
|
|||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateLeagueSeasonScheduleRacePresenter.present(result.unwrap());
|
||||||
return this.updateLeagueSeasonScheduleRacePresenter.getResponseModel()!;
|
return this.updateLeagueSeasonScheduleRacePresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,12 +745,17 @@ export class LeagueService {
|
|||||||
throw new Error(result.unwrapErr().code);
|
throw new Error(result.unwrapErr().code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.deleteLeagueSeasonScheduleRacePresenter.present(result.unwrap());
|
||||||
return this.deleteLeagueSeasonScheduleRacePresenter.getResponseModel()!;
|
return this.deleteLeagueSeasonScheduleRacePresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLeagueStats(leagueId: string): Promise<LeagueStatsDTO> {
|
async getLeagueStats(leagueId: string): Promise<LeagueStatsDTO> {
|
||||||
this.logger.debug('Getting league stats', { leagueId });
|
this.logger.debug('Getting league stats', { leagueId });
|
||||||
await this.getLeagueStatsUseCase.execute({ leagueId });
|
const result = await this.getLeagueStatsUseCase.execute({ leagueId });
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.leagueStatsPresenter.present(result.unwrap());
|
||||||
return this.leagueStatsPresenter.getResponseModel()!;
|
return this.leagueStatsPresenter.getResponseModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,7 +802,11 @@ export class LeagueService {
|
|||||||
enableNationsChampionship: false,
|
enableNationsChampionship: false,
|
||||||
enableTrophyChampionship: false,
|
enableTrophyChampionship: false,
|
||||||
};
|
};
|
||||||
await this.createLeagueWithSeasonAndScoringUseCase.execute(command);
|
const result = await this.createLeagueWithSeasonAndScoringUseCase.execute(command);
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.createLeaguePresenter.present(result.unwrap());
|
||||||
return this.createLeaguePresenter.getViewModel()!;
|
return this.createLeaguePresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,7 +825,11 @@ export class LeagueService {
|
|||||||
async listLeagueScoringPresets(): Promise<LeagueScoringPresetsViewModel> {
|
async listLeagueScoringPresets(): Promise<LeagueScoringPresetsViewModel> {
|
||||||
this.logger.debug('Listing league scoring presets');
|
this.logger.debug('Listing league scoring presets');
|
||||||
|
|
||||||
await this.listLeagueScoringPresetsUseCase.execute({});
|
const result = await this.listLeagueScoringPresetsUseCase.execute({});
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.leagueScoringPresetsPresenter.present(result.unwrap());
|
||||||
return this.leagueScoringPresetsPresenter.getViewModel()!;
|
return this.leagueScoringPresetsPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,7 +837,11 @@ export class LeagueService {
|
|||||||
const actor = this.getActor();
|
const actor = this.getActor();
|
||||||
this.logger.debug('Joining league', { leagueId, actorDriverId: actor.driverId });
|
this.logger.debug('Joining league', { leagueId, actorDriverId: actor.driverId });
|
||||||
|
|
||||||
await this.joinLeagueUseCase.execute({ leagueId, driverId: actor.driverId });
|
const result = await this.joinLeagueUseCase.execute({ leagueId, driverId: actor.driverId });
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.joinLeaguePresenter.present(result.unwrap());
|
||||||
return this.joinLeaguePresenter.getViewModel()!;
|
return this.joinLeaguePresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,19 +852,28 @@ export class LeagueService {
|
|||||||
|
|
||||||
const actor = this.getActor();
|
const actor = this.getActor();
|
||||||
|
|
||||||
await this.transferLeagueOwnershipUseCase.execute({
|
const result = await this.transferLeagueOwnershipUseCase.execute({
|
||||||
leagueId,
|
leagueId,
|
||||||
currentOwnerId: actor.driverId,
|
currentOwnerId: actor.driverId,
|
||||||
newOwnerId: input.newOwnerId,
|
newOwnerId: input.newOwnerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transferLeagueOwnershipPresenter.present(result.unwrap());
|
||||||
return this.transferLeagueOwnershipPresenter.getViewModel()!;
|
return this.transferLeagueOwnershipPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSeasonSponsorships(seasonId: string): Promise<GetSeasonSponsorshipsOutputDTO> {
|
async getSeasonSponsorships(seasonId: string): Promise<GetSeasonSponsorshipsOutputDTO> {
|
||||||
this.logger.debug('Getting season sponsorships', { seasonId });
|
this.logger.debug('Getting season sponsorships', { seasonId });
|
||||||
|
|
||||||
await this.getSeasonSponsorshipsUseCase.execute({ seasonId });
|
const result = await this.getSeasonSponsorshipsUseCase.execute({ seasonId });
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.seasonSponsorshipsPresenter.present(result.unwrap());
|
||||||
return this.seasonSponsorshipsPresenter.getViewModel()!;
|
return this.seasonSponsorshipsPresenter.getViewModel()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -847,8 +883,13 @@ export class LeagueService {
|
|||||||
// `GetLeagueScheduleUseCase` is wired to `LeagueSchedulePresenter` (not `LeagueRacesPresenter`),
|
// `GetLeagueScheduleUseCase` is wired to `LeagueSchedulePresenter` (not `LeagueRacesPresenter`),
|
||||||
// so `LeagueRacesPresenter.getViewModel()` can be null at runtime.
|
// so `LeagueRacesPresenter.getViewModel()` can be null at runtime.
|
||||||
this.leagueSchedulePresenter.reset?.();
|
this.leagueSchedulePresenter.reset?.();
|
||||||
await this.getLeagueScheduleUseCase.execute({ leagueId });
|
const result = await this.getLeagueScheduleUseCase.execute({ leagueId });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.leagueSchedulePresenter.present(result.unwrap());
|
||||||
return {
|
return {
|
||||||
races: this.leagueSchedulePresenter.getViewModel()?.races ?? [],
|
races: this.leagueSchedulePresenter.getViewModel()?.races ?? [],
|
||||||
};
|
};
|
||||||
@@ -856,7 +897,11 @@ export class LeagueService {
|
|||||||
|
|
||||||
async getLeagueWallet(leagueId: string): Promise<GetLeagueWalletOutputDTO> {
|
async getLeagueWallet(leagueId: string): Promise<GetLeagueWalletOutputDTO> {
|
||||||
this.logger.debug('Getting league wallet', { leagueId });
|
this.logger.debug('Getting league wallet', { leagueId });
|
||||||
await this.getLeagueWalletUseCase.execute({ leagueId });
|
const result = await this.getLeagueWalletUseCase.execute({ leagueId });
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
this.getLeagueWalletPresenter.present(result.unwrap());
|
||||||
return this.getLeagueWalletPresenter.getResponseModel();
|
return this.getLeagueWalletPresenter.getResponseModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -868,7 +913,7 @@ export class LeagueService {
|
|||||||
|
|
||||||
const actor = this.getActor();
|
const actor = this.getActor();
|
||||||
|
|
||||||
await this.withdrawFromLeagueWalletUseCase.execute({
|
const result = await this.withdrawFromLeagueWalletUseCase.execute({
|
||||||
leagueId,
|
leagueId,
|
||||||
requestedById: actor.driverId,
|
requestedById: actor.driverId,
|
||||||
amount: input.amount,
|
amount: input.amount,
|
||||||
@@ -876,6 +921,11 @@ export class LeagueService {
|
|||||||
reason: input.destinationAccount,
|
reason: input.destinationAccount,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
throw new Error(result.unwrapErr().code);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.withdrawFromLeagueWalletPresenter.present(result.unwrap());
|
||||||
return this.withdrawFromLeagueWalletPresenter.getResponseModel();
|
return this.withdrawFromLeagueWalletPresenter.getResponseModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,37 +48,3 @@ export const UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE = 'UpdateLeagueSeasonSc
|
|||||||
export const DELETE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE = 'DeleteLeagueSeasonScheduleRaceUseCase';
|
export const DELETE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE = 'DeleteLeagueSeasonScheduleRaceUseCase';
|
||||||
export const PUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE = 'PublishLeagueSeasonScheduleUseCase';
|
export const PUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE = 'PublishLeagueSeasonScheduleUseCase';
|
||||||
export const UNPUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE = 'UnpublishLeagueSeasonScheduleUseCase';
|
export const UNPUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE = 'UnpublishLeagueSeasonScheduleUseCase';
|
||||||
|
|
||||||
export const GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN = 'GetAllLeaguesWithCapacityOutputPort_TOKEN';
|
|
||||||
export const GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN = 'GetAllLeaguesWithCapacityAndScoringOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN = 'GetLeagueStandingsOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN = 'GetLeagueProtestsOutputPort_TOKEN';
|
|
||||||
export const GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN = 'GetSeasonSponsorshipsOutputPort_TOKEN';
|
|
||||||
export const LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN = 'ListLeagueScoringPresetsOutputPort_TOKEN';
|
|
||||||
export const APPROVE_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN = 'ApproveLeagueJoinRequestOutputPort_TOKEN';
|
|
||||||
export const CREATE_LEAGUE_OUTPUT_PORT_TOKEN = 'CreateLeagueOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN = 'GetLeagueAdminPermissionsOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_MEMBERSHIPS_OUTPUT_PORT_TOKEN = 'GetLeagueMembershipsOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_ROSTER_MEMBERS_OUTPUT_PORT_TOKEN = 'GetLeagueRosterMembersOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_ROSTER_JOIN_REQUESTS_OUTPUT_PORT_TOKEN = 'GetLeagueRosterJoinRequestsOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_OWNER_SUMMARY_OUTPUT_PORT_TOKEN = 'GetLeagueOwnerSummaryOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN = 'GetLeagueSeasonsOutputPort_TOKEN';
|
|
||||||
export const JOIN_LEAGUE_OUTPUT_PORT_TOKEN = 'JoinLeagueOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN = 'GetLeagueScheduleOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_STATS_OUTPUT_PORT_TOKEN = 'GetLeagueStatsOutputPort_TOKEN';
|
|
||||||
export const REJECT_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN = 'RejectLeagueJoinRequestOutputPort_TOKEN';
|
|
||||||
export const REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN = 'RemoveLeagueMemberOutputPort_TOKEN';
|
|
||||||
export const TOTAL_LEAGUES_OUTPUT_PORT_TOKEN = 'TotalLeaguesOutputPort_TOKEN';
|
|
||||||
export const TRANSFER_LEAGUE_OWNERSHIP_OUTPUT_PORT_TOKEN = 'TransferLeagueOwnershipOutputPort_TOKEN';
|
|
||||||
export const UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN = 'UpdateLeagueMemberRoleOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN = 'GetLeagueFullConfigOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN = 'GetLeagueScoringConfigOutputPort_TOKEN';
|
|
||||||
export const GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN = 'GetLeagueWalletOutputPort_TOKEN';
|
|
||||||
export const WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN = 'WithdrawFromLeagueWalletOutputPort_TOKEN';
|
|
||||||
|
|
||||||
// Schedule mutation output ports
|
|
||||||
export const CREATE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN = 'CreateLeagueSeasonScheduleRaceOutputPort_TOKEN';
|
|
||||||
export const UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN = 'UpdateLeagueSeasonScheduleRaceOutputPort_TOKEN';
|
|
||||||
export const DELETE_LEAGUE_SEASON_SCHEDULE_RACE_OUTPUT_PORT_TOKEN = 'DeleteLeagueSeasonScheduleRaceOutputPort_TOKEN';
|
|
||||||
export const PUBLISH_LEAGUE_SEASON_SCHEDULE_OUTPUT_PORT_TOKEN = 'PublishLeagueSeasonScheduleOutputPort_TOKEN';
|
|
||||||
export const UNPUBLISH_LEAGUE_SEASON_SCHEDULE_OUTPUT_PORT_TOKEN = 'UnpublishLeagueSeasonScheduleOutputPort_TOKEN';
|
|
||||||
@@ -17,6 +17,8 @@ describe('LeagueOwnerSummaryPresenter', () => {
|
|||||||
joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') } as any,
|
joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') } as any,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} as any,
|
} as any,
|
||||||
|
totalMembers: 50,
|
||||||
|
activeMembers: 45,
|
||||||
rating: 1500,
|
rating: 1500,
|
||||||
rank: 100,
|
rank: 100,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||||
import type { GetLeagueRosterMembersResult } from '@core/racing/application/use-cases/GetLeagueRosterMembersUseCase';
|
import type { GetLeagueRosterMembersResult } from '@core/racing/application/use-cases/GetLeagueRosterMembersUseCase';
|
||||||
import type { GetLeagueRosterJoinRequestsResult } from '@core/racing/application/use-cases/GetLeagueRosterJoinRequestsUseCase';
|
import type { GetLeagueRosterJoinRequestsResult } from '@core/racing/application/use-cases/GetLeagueRosterJoinRequestsUseCase';
|
||||||
@@ -5,6 +6,7 @@ import type { LeagueRosterMemberDTO } from '../dtos/LeagueRosterMemberDTO';
|
|||||||
import type { LeagueRosterJoinRequestDTO } from '../dtos/LeagueRosterJoinRequestDTO';
|
import type { LeagueRosterJoinRequestDTO } from '../dtos/LeagueRosterJoinRequestDTO';
|
||||||
import type { DriverDTO } from '../../driver/dtos/DriverDTO';
|
import type { DriverDTO } from '../../driver/dtos/DriverDTO';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class GetLeagueRosterMembersPresenter implements UseCaseOutputPort<GetLeagueRosterMembersResult> {
|
export class GetLeagueRosterMembersPresenter implements UseCaseOutputPort<GetLeagueRosterMembersResult> {
|
||||||
private viewModel: LeagueRosterMemberDTO[] | null = null;
|
private viewModel: LeagueRosterMemberDTO[] | null = null;
|
||||||
|
|
||||||
@@ -37,6 +39,7 @@ export class GetLeagueRosterMembersPresenter implements UseCaseOutputPort<GetLea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class GetLeagueRosterJoinRequestsPresenter implements UseCaseOutputPort<GetLeagueRosterJoinRequestsResult> {
|
export class GetLeagueRosterJoinRequestsPresenter implements UseCaseOutputPort<GetLeagueRosterJoinRequestsResult> {
|
||||||
private viewModel: LeagueRosterJoinRequestDTO[] | null = null;
|
private viewModel: LeagueRosterJoinRequestDTO[] | null = null;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { IAvatarRepository } from '@core/media/domain/repositories/IAvatarReposi
|
|||||||
import { FaceValidationPort } from '@core/media/application/ports/FaceValidationPort';
|
import { FaceValidationPort } from '@core/media/application/ports/FaceValidationPort';
|
||||||
import { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort';
|
import { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort';
|
||||||
import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
|
import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
// Import use cases
|
// Import use cases
|
||||||
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||||
@@ -17,14 +17,6 @@ import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMedi
|
|||||||
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||||
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
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 presenters
|
||||||
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
||||||
import { UploadMediaPresenter } from './presenters/UploadMediaPresenter';
|
import { UploadMediaPresenter } from './presenters/UploadMediaPresenter';
|
||||||
@@ -47,12 +39,6 @@ import {
|
|||||||
DELETE_MEDIA_USE_CASE_TOKEN,
|
DELETE_MEDIA_USE_CASE_TOKEN,
|
||||||
GET_AVATAR_USE_CASE_TOKEN,
|
GET_AVATAR_USE_CASE_TOKEN,
|
||||||
UPDATE_AVATAR_USE_CASE_TOKEN,
|
UPDATE_AVATAR_USE_CASE_TOKEN,
|
||||||
REQUEST_AVATAR_GENERATION_OUTPUT_PORT_TOKEN,
|
|
||||||
UPLOAD_MEDIA_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_MEDIA_OUTPUT_PORT_TOKEN,
|
|
||||||
DELETE_MEDIA_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_AVATAR_OUTPUT_PORT_TOKEN,
|
|
||||||
UPDATE_AVATAR_OUTPUT_PORT_TOKEN,
|
|
||||||
} from './MediaTokens';
|
} from './MediaTokens';
|
||||||
|
|
||||||
export * from './MediaTokens';
|
export * from './MediaTokens';
|
||||||
@@ -133,66 +119,41 @@ export const MediaProviders: Provider[] = createLoggedProviders([
|
|||||||
provide: LOGGER_TOKEN,
|
provide: LOGGER_TOKEN,
|
||||||
useClass: MockLogger,
|
useClass: MockLogger,
|
||||||
},
|
},
|
||||||
// Output ports
|
// Use cases - simplified without 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,
|
provide: REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN,
|
||||||
useFactory: (avatarRepo: IAvatarGenerationRepository, faceValidation: FaceValidationPort, avatarGeneration: AvatarGenerationPort, output: UseCaseOutputPort<RequestAvatarGenerationResult>, logger: Logger) =>
|
useFactory: (avatarRepo: IAvatarGenerationRepository, faceValidation: FaceValidationPort, avatarGeneration: AvatarGenerationPort, logger: Logger) =>
|
||||||
new RequestAvatarGenerationUseCase(avatarRepo, faceValidation, avatarGeneration, output, logger),
|
new RequestAvatarGenerationUseCase(avatarRepo, faceValidation, avatarGeneration, logger),
|
||||||
inject: [AVATAR_GENERATION_REPOSITORY_TOKEN, FACE_VALIDATION_PORT_TOKEN, AVATAR_GENERATION_PORT_TOKEN, REQUEST_AVATAR_GENERATION_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
inject: [AVATAR_GENERATION_REPOSITORY_TOKEN, FACE_VALIDATION_PORT_TOKEN, AVATAR_GENERATION_PORT_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UPLOAD_MEDIA_USE_CASE_TOKEN,
|
provide: UPLOAD_MEDIA_USE_CASE_TOKEN,
|
||||||
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, output: UseCaseOutputPort<UploadMediaResult>, logger: Logger) =>
|
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, logger: Logger) =>
|
||||||
new UploadMediaUseCase(mediaRepo, mediaStorage, output, logger),
|
new UploadMediaUseCase(mediaRepo, mediaStorage, logger),
|
||||||
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, UPLOAD_MEDIA_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_MEDIA_USE_CASE_TOKEN,
|
provide: GET_MEDIA_USE_CASE_TOKEN,
|
||||||
useFactory: (mediaRepo: IMediaRepository, output: UseCaseOutputPort<GetMediaResult>, logger: Logger) =>
|
useFactory: (mediaRepo: IMediaRepository, logger: Logger) =>
|
||||||
new GetMediaUseCase(mediaRepo, output, logger),
|
new GetMediaUseCase(mediaRepo, logger),
|
||||||
inject: [MEDIA_REPOSITORY_TOKEN, GET_MEDIA_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
inject: [MEDIA_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: DELETE_MEDIA_USE_CASE_TOKEN,
|
provide: DELETE_MEDIA_USE_CASE_TOKEN,
|
||||||
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, output: UseCaseOutputPort<DeleteMediaResult>, logger: Logger) =>
|
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, logger: Logger) =>
|
||||||
new DeleteMediaUseCase(mediaRepo, mediaStorage, output, logger),
|
new DeleteMediaUseCase(mediaRepo, mediaStorage, logger),
|
||||||
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, DELETE_MEDIA_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_AVATAR_USE_CASE_TOKEN,
|
provide: GET_AVATAR_USE_CASE_TOKEN,
|
||||||
useFactory: (avatarRepo: IAvatarRepository, output: UseCaseOutputPort<GetAvatarResult>, logger: Logger) =>
|
useFactory: (avatarRepo: IAvatarRepository, logger: Logger) =>
|
||||||
new GetAvatarUseCase(avatarRepo, output, logger),
|
new GetAvatarUseCase(avatarRepo, logger),
|
||||||
inject: [AVATAR_REPOSITORY_TOKEN, GET_AVATAR_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UPDATE_AVATAR_USE_CASE_TOKEN,
|
provide: UPDATE_AVATAR_USE_CASE_TOKEN,
|
||||||
useFactory: (avatarRepo: IAvatarRepository, output: UseCaseOutputPort<UpdateAvatarResult>, logger: Logger) =>
|
useFactory: (avatarRepo: IAvatarRepository, logger: Logger) =>
|
||||||
new UpdateAvatarUseCase(avatarRepo, output, logger),
|
new UpdateAvatarUseCase(avatarRepo, logger),
|
||||||
inject: [AVATAR_REPOSITORY_TOKEN, UPDATE_AVATAR_OUTPUT_PORT_TOKEN, LOGGER_TOKEN],
|
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
], initLogger);
|
], initLogger);
|
||||||
@@ -7,11 +7,12 @@ describe('MediaService', () => {
|
|||||||
|
|
||||||
it('requestAvatarGeneration returns presenter response on success', async () => {
|
it('requestAvatarGeneration returns presenter response on success', async () => {
|
||||||
const requestAvatarGenerationPresenter = {
|
const requestAvatarGenerationPresenter = {
|
||||||
|
transform: vi.fn((result) => ({ success: true, requestId: result.requestId, avatarUrls: result.avatarUrls, errorMessage: '' })),
|
||||||
responseModel: { success: true, requestId: 'r1', avatarUrls: ['u1'], errorMessage: '' },
|
responseModel: { success: true, requestId: 'r1', avatarUrls: ['u1'], errorMessage: '' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestAvatarGenerationUseCase = {
|
const requestAvatarGenerationUseCase = {
|
||||||
execute: vi.fn(async () => Result.ok(undefined)),
|
execute: vi.fn(async () => Result.ok({ requestId: 'r1', status: 'completed', avatarUrls: ['u1'] })),
|
||||||
};
|
};
|
||||||
|
|
||||||
const service = new MediaService(
|
const service = new MediaService(
|
||||||
@@ -39,6 +40,7 @@ describe('MediaService', () => {
|
|||||||
facePhotoData: {} as any,
|
facePhotoData: {} as any,
|
||||||
suitColor: 'red',
|
suitColor: 'red',
|
||||||
});
|
});
|
||||||
|
expect(requestAvatarGenerationPresenter.transform).toHaveBeenCalledWith({ requestId: 'r1', status: 'completed', avatarUrls: ['u1'] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('requestAvatarGeneration returns failure DTO on error', async () => {
|
it('requestAvatarGeneration returns failure DTO on error', async () => {
|
||||||
@@ -69,8 +71,11 @@ describe('MediaService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('uploadMedia returns presenter response on success', async () => {
|
it('uploadMedia returns presenter response on success', async () => {
|
||||||
const uploadMediaPresenter = { responseModel: { success: true, mediaId: 'm1' } };
|
const uploadMediaPresenter = {
|
||||||
const uploadMediaUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
transform: vi.fn((result) => ({ success: true, mediaId: result.mediaId })),
|
||||||
|
responseModel: { success: true, mediaId: 'm1' }
|
||||||
|
};
|
||||||
|
const uploadMediaUseCase = { execute: vi.fn(async () => Result.ok({ mediaId: 'm1', url: 'https://example.com/m1.png' })) };
|
||||||
|
|
||||||
const service = new MediaService(
|
const service = new MediaService(
|
||||||
{ execute: vi.fn() } as any,
|
{ execute: vi.fn() } as any,
|
||||||
@@ -100,10 +105,15 @@ describe('MediaService', () => {
|
|||||||
uploadedBy: 'u1',
|
uploadedBy: 'u1',
|
||||||
metadata: { a: 1 },
|
metadata: { a: 1 },
|
||||||
});
|
});
|
||||||
|
expect(uploadMediaPresenter.transform).toHaveBeenCalledWith({ mediaId: 'm1', url: 'https://example.com/m1.png' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uploadMedia uses empty uploadedBy when userId missing', async () => {
|
it('uploadMedia uses empty uploadedBy when userId missing', async () => {
|
||||||
const uploadMediaUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const uploadMediaUseCase = { execute: vi.fn(async () => Result.ok({ mediaId: 'm1', url: 'https://example.com/m1.png' })) };
|
||||||
|
const uploadMediaPresenter = {
|
||||||
|
transform: vi.fn((result) => ({ success: true, mediaId: result.mediaId })),
|
||||||
|
responseModel: { success: true, mediaId: 'm1' }
|
||||||
|
};
|
||||||
|
|
||||||
const service = new MediaService(
|
const service = new MediaService(
|
||||||
{ execute: vi.fn() } as any,
|
{ execute: vi.fn() } as any,
|
||||||
@@ -114,7 +124,7 @@ describe('MediaService', () => {
|
|||||||
{ execute: vi.fn() } as any,
|
{ execute: vi.fn() } as any,
|
||||||
logger as any,
|
logger as any,
|
||||||
{ responseModel: {} } as any,
|
{ responseModel: {} } as any,
|
||||||
{ responseModel: { success: true, mediaId: 'm1' } } as any,
|
uploadMediaPresenter as any,
|
||||||
{ responseModel: {} } as any,
|
{ responseModel: {} } as any,
|
||||||
{ responseModel: {} } as any,
|
{ responseModel: {} } as any,
|
||||||
{ responseModel: {} } as any,
|
{ responseModel: {} } as any,
|
||||||
@@ -123,6 +133,7 @@ describe('MediaService', () => {
|
|||||||
|
|
||||||
await expect(service.uploadMedia({ file: {} as any } as any)).resolves.toEqual({ success: true, mediaId: 'm1' });
|
await expect(service.uploadMedia({ file: {} as any } as any)).resolves.toEqual({ success: true, mediaId: 'm1' });
|
||||||
expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ file: {} as any, uploadedBy: '', metadata: {} });
|
expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ file: {} as any, uploadedBy: '', metadata: {} });
|
||||||
|
expect(uploadMediaPresenter.transform).toHaveBeenCalledWith({ mediaId: 'm1', url: 'https://example.com/m1.png' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uploadMedia returns failure DTO on error', async () => {
|
it('uploadMedia returns failure DTO on error', async () => {
|
||||||
@@ -153,8 +164,12 @@ describe('MediaService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('getMedia returns presenter response on success', async () => {
|
it('getMedia returns presenter response on success', async () => {
|
||||||
const getMediaUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const uploadedAt = new Date();
|
||||||
const getMediaPresenter = { responseModel: { mediaId: 'm1' } };
|
const getMediaUseCase = { execute: vi.fn(async () => Result.ok({ media: { id: 'm1', filename: 'test.png', originalName: 'test.png', mimeType: 'image/png', size: 100, url: 'https://example.com/m1.png', type: 'image', uploadedBy: 'u1', uploadedAt } })) };
|
||||||
|
const getMediaPresenter = {
|
||||||
|
transform: vi.fn((result) => ({ id: result.media.id, url: result.media.url, type: result.media.type, uploadedAt: result.media.uploadedAt, size: result.media.size })),
|
||||||
|
responseModel: { id: 'm1', url: 'https://example.com/m1.png', type: 'image', uploadedAt, size: 100 }
|
||||||
|
};
|
||||||
|
|
||||||
const service = new MediaService(
|
const service = new MediaService(
|
||||||
{ execute: vi.fn() } as any,
|
{ execute: vi.fn() } as any,
|
||||||
@@ -172,8 +187,10 @@ describe('MediaService', () => {
|
|||||||
{ responseModel: {} } as any,
|
{ responseModel: {} } as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(service.getMedia('m1')).resolves.toEqual({ mediaId: 'm1' });
|
const result = await service.getMedia('m1');
|
||||||
|
expect(result).toEqual({ id: 'm1', url: 'https://example.com/m1.png', type: 'image', uploadedAt, size: 100 });
|
||||||
expect(getMediaUseCase.execute).toHaveBeenCalledWith({ mediaId: 'm1' });
|
expect(getMediaUseCase.execute).toHaveBeenCalledWith({ mediaId: 'm1' });
|
||||||
|
expect(getMediaPresenter.transform).toHaveBeenCalledWith({ media: { id: 'm1', filename: 'test.png', originalName: 'test.png', mimeType: 'image/png', size: 100, url: 'https://example.com/m1.png', type: 'image', uploadedBy: 'u1', uploadedAt } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getMedia returns null on MEDIA_NOT_FOUND', async () => {
|
it('getMedia returns null on MEDIA_NOT_FOUND', async () => {
|
||||||
@@ -217,8 +234,11 @@ describe('MediaService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('deleteMedia returns presenter response on success', async () => {
|
it('deleteMedia returns presenter response on success', async () => {
|
||||||
const deleteMediaUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const deleteMediaUseCase = { execute: vi.fn(async () => Result.ok({ mediaId: 'm1', deleted: true })) };
|
||||||
const deleteMediaPresenter = { responseModel: { success: true } };
|
const deleteMediaPresenter = {
|
||||||
|
transform: vi.fn((result) => ({ success: result.deleted })),
|
||||||
|
responseModel: { success: true }
|
||||||
|
};
|
||||||
|
|
||||||
const service = new MediaService(
|
const service = new MediaService(
|
||||||
{ execute: vi.fn() } as any,
|
{ execute: vi.fn() } as any,
|
||||||
@@ -238,6 +258,7 @@ describe('MediaService', () => {
|
|||||||
|
|
||||||
await expect(service.deleteMedia('m1')).resolves.toEqual({ success: true });
|
await expect(service.deleteMedia('m1')).resolves.toEqual({ success: true });
|
||||||
expect(deleteMediaUseCase.execute).toHaveBeenCalledWith({ mediaId: 'm1' });
|
expect(deleteMediaUseCase.execute).toHaveBeenCalledWith({ mediaId: 'm1' });
|
||||||
|
expect(deleteMediaPresenter.transform).toHaveBeenCalledWith({ mediaId: 'm1', deleted: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deleteMedia returns failure DTO on error', async () => {
|
it('deleteMedia returns failure DTO on error', async () => {
|
||||||
@@ -261,8 +282,12 @@ describe('MediaService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('getAvatar returns presenter response on success', async () => {
|
it('getAvatar returns presenter response on success', async () => {
|
||||||
const getAvatarUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const selectedAt = new Date();
|
||||||
const getAvatarPresenter = { responseModel: { avatarUrl: 'u1' } };
|
const getAvatarUseCase = { execute: vi.fn(async () => Result.ok({ avatar: { id: 'a1', driverId: 'd1', mediaUrl: 'https://example.com/avatar.png', selectedAt } })) };
|
||||||
|
const getAvatarPresenter = {
|
||||||
|
transform: vi.fn((result) => ({ avatarUrl: result.avatar.mediaUrl })),
|
||||||
|
responseModel: { avatarUrl: 'https://example.com/avatar.png' }
|
||||||
|
};
|
||||||
|
|
||||||
const service = new MediaService(
|
const service = new MediaService(
|
||||||
{ execute: vi.fn() } as any,
|
{ execute: vi.fn() } as any,
|
||||||
@@ -280,8 +305,9 @@ describe('MediaService', () => {
|
|||||||
{ responseModel: {} } as any,
|
{ responseModel: {} } as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(service.getAvatar('d1')).resolves.toEqual({ avatarUrl: 'u1' });
|
await expect(service.getAvatar('d1')).resolves.toEqual({ avatarUrl: 'https://example.com/avatar.png' });
|
||||||
expect(getAvatarUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1' });
|
expect(getAvatarUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1' });
|
||||||
|
expect(getAvatarPresenter.transform).toHaveBeenCalledWith({ avatar: { id: 'a1', driverId: 'd1', mediaUrl: 'https://example.com/avatar.png', selectedAt } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getAvatar returns null on AVATAR_NOT_FOUND', async () => {
|
it('getAvatar returns null on AVATAR_NOT_FOUND', async () => {
|
||||||
@@ -325,8 +351,11 @@ describe('MediaService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updateAvatar returns presenter response on success', async () => {
|
it('updateAvatar returns presenter response on success', async () => {
|
||||||
const updateAvatarUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const updateAvatarUseCase = { execute: vi.fn(async () => Result.ok({ avatarId: 'a1', driverId: 'd1' })) };
|
||||||
const updateAvatarPresenter = { responseModel: { success: true } };
|
const updateAvatarPresenter = {
|
||||||
|
transform: vi.fn(() => ({ success: true })),
|
||||||
|
responseModel: { success: true }
|
||||||
|
};
|
||||||
|
|
||||||
const service = new MediaService(
|
const service = new MediaService(
|
||||||
{ execute: vi.fn() } as any,
|
{ execute: vi.fn() } as any,
|
||||||
@@ -346,6 +375,7 @@ describe('MediaService', () => {
|
|||||||
|
|
||||||
await expect(service.updateAvatar('d1', { avatarUrl: 'u1' } as any)).resolves.toEqual({ success: true });
|
await expect(service.updateAvatar('d1', { avatarUrl: 'u1' } as any)).resolves.toEqual({ success: true });
|
||||||
expect(updateAvatarUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1', mediaUrl: 'u1' });
|
expect(updateAvatarUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1', mediaUrl: 'u1' });
|
||||||
|
expect(updateAvatarPresenter.transform).toHaveBeenCalledWith({ avatarId: 'a1', driverId: 'd1' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updateAvatar returns failure DTO on error', async () => {
|
it('updateAvatar returns failure DTO on error', async () => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMedi
|
|||||||
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||||
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||||
|
|
||||||
// Presenters
|
// Presenters (now transformers)
|
||||||
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
||||||
import { UploadMediaPresenter } from './presenters/UploadMediaPresenter';
|
import { UploadMediaPresenter } from './presenters/UploadMediaPresenter';
|
||||||
import { GetMediaPresenter } from './presenters/GetMediaPresenter';
|
import { GetMediaPresenter } from './presenters/GetMediaPresenter';
|
||||||
@@ -90,7 +90,7 @@ export class MediaService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.requestAvatarGenerationPresenter.responseModel;
|
return this.requestAvatarGenerationPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadMedia(
|
async uploadMedia(
|
||||||
@@ -112,7 +112,7 @@ export class MediaService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.uploadMediaPresenter.responseModel;
|
return this.uploadMediaPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMedia(mediaId: string): Promise<GetMediaOutputDTO | null> {
|
async getMedia(mediaId: string): Promise<GetMediaOutputDTO | null> {
|
||||||
@@ -128,7 +128,7 @@ export class MediaService {
|
|||||||
throw new Error(error.details?.message ?? 'Failed to get media');
|
throw new Error(error.details?.message ?? 'Failed to get media');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getMediaPresenter.responseModel;
|
return this.getMediaPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMedia(mediaId: string): Promise<DeleteMediaOutputDTO> {
|
async deleteMedia(mediaId: string): Promise<DeleteMediaOutputDTO> {
|
||||||
@@ -144,7 +144,7 @@ export class MediaService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.deleteMediaPresenter.responseModel;
|
return this.deleteMediaPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAvatar(driverId: string): Promise<GetAvatarOutputDTO | null> {
|
async getAvatar(driverId: string): Promise<GetAvatarOutputDTO | null> {
|
||||||
@@ -160,7 +160,7 @@ export class MediaService {
|
|||||||
throw new Error(error.details?.message ?? 'Failed to get avatar');
|
throw new Error(error.details?.message ?? 'Failed to get avatar');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getAvatarPresenter.responseModel;
|
return this.getAvatarPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise<UpdateAvatarOutputDTO> {
|
async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise<UpdateAvatarOutputDTO> {
|
||||||
@@ -189,7 +189,7 @@ export class MediaService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.updateAvatarPresenter.responseModel;
|
return this.updateAvatarPresenter.transform(result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateFacePhoto(input: ValidateFaceInputDTO): Promise<ValidateFaceOutputDTO> {
|
async validateFacePhoto(input: ValidateFaceInputDTO): Promise<ValidateFaceOutputDTO> {
|
||||||
|
|||||||
@@ -12,10 +12,3 @@ export const GET_MEDIA_USE_CASE_TOKEN = 'GetMediaUseCase';
|
|||||||
export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase';
|
export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase';
|
||||||
export const GET_AVATAR_USE_CASE_TOKEN = 'GetAvatarUseCase';
|
export const GET_AVATAR_USE_CASE_TOKEN = 'GetAvatarUseCase';
|
||||||
export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase';
|
export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase';
|
||||||
|
|
||||||
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';
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { DeleteMediaResult } from '@core/media/application/use-cases/DeleteMediaUseCase';
|
import type { DeleteMediaResult } from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||||
import type { DeleteMediaOutputDTO } from '../dtos/DeleteMediaOutputDTO';
|
import type { DeleteMediaOutputDTO } from '../dtos/DeleteMediaOutputDTO';
|
||||||
|
|
||||||
type DeleteMediaResponseModel = DeleteMediaOutputDTO;
|
type DeleteMediaResponseModel = DeleteMediaOutputDTO;
|
||||||
|
|
||||||
export class DeleteMediaPresenter implements UseCaseOutputPort<DeleteMediaResult> {
|
export class DeleteMediaPresenter {
|
||||||
private model: DeleteMediaResponseModel | null = null;
|
private model: DeleteMediaResponseModel | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: DeleteMediaResult): void {
|
transform(result: DeleteMediaResult): DeleteMediaResponseModel {
|
||||||
this.model = {
|
this.model = {
|
||||||
success: result.deleted,
|
success: result.deleted,
|
||||||
};
|
};
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): DeleteMediaResponseModel | null {
|
getResponseModel(): DeleteMediaResponseModel | null {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { GetAvatarResult } from '@core/media/application/use-cases/GetAvatarUseCase';
|
import type { GetAvatarResult } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||||
import type { GetAvatarOutputDTO } from '../dtos/GetAvatarOutputDTO';
|
import type { GetAvatarOutputDTO } from '../dtos/GetAvatarOutputDTO';
|
||||||
|
|
||||||
export type GetAvatarResponseModel = GetAvatarOutputDTO | null;
|
export type GetAvatarResponseModel = GetAvatarOutputDTO | null;
|
||||||
|
|
||||||
export class GetAvatarPresenter implements UseCaseOutputPort<GetAvatarResult> {
|
export class GetAvatarPresenter {
|
||||||
private model: GetAvatarResponseModel | null = null;
|
private model: GetAvatarResponseModel | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: GetAvatarResult): void {
|
transform(result: GetAvatarResult): GetAvatarResponseModel {
|
||||||
this.model = {
|
this.model = {
|
||||||
avatarUrl: result.avatar.mediaUrl,
|
avatarUrl: result.avatar.mediaUrl,
|
||||||
};
|
};
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): GetAvatarResponseModel | null {
|
getResponseModel(): GetAvatarResponseModel | null {
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { GetMediaResult } from '@core/media/application/use-cases/GetMediaUseCase';
|
import type { GetMediaResult } from '@core/media/application/use-cases/GetMediaUseCase';
|
||||||
import type { GetMediaOutputDTO } from '../dtos/GetMediaOutputDTO';
|
import type { GetMediaOutputDTO } from '../dtos/GetMediaOutputDTO';
|
||||||
|
|
||||||
export type GetMediaResponseModel = GetMediaOutputDTO | null;
|
export type GetMediaResponseModel = GetMediaOutputDTO | null;
|
||||||
|
|
||||||
export class GetMediaPresenter implements UseCaseOutputPort<GetMediaResult> {
|
export class GetMediaPresenter {
|
||||||
private model: GetMediaResponseModel | null = null;
|
private model: GetMediaResponseModel | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: GetMediaResult): void {
|
transform(result: GetMediaResult): GetMediaResponseModel {
|
||||||
const media = result.media;
|
const media = result.media;
|
||||||
|
|
||||||
const model: GetMediaResponseModel = {
|
const model: GetMediaResponseModel = {
|
||||||
@@ -29,6 +28,7 @@ export class GetMediaPresenter implements UseCaseOutputPort<GetMediaResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.model = model;
|
this.model = model;
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): GetMediaResponseModel | null {
|
getResponseModel(): GetMediaResponseModel | null {
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { RequestAvatarGenerationResult } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
import type { RequestAvatarGenerationResult } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||||
import type { RequestAvatarGenerationOutputDTO } from '../dtos/RequestAvatarGenerationOutputDTO';
|
import type { RequestAvatarGenerationOutputDTO } from '../dtos/RequestAvatarGenerationOutputDTO';
|
||||||
|
|
||||||
type RequestAvatarGenerationResponseModel = RequestAvatarGenerationOutputDTO;
|
type RequestAvatarGenerationResponseModel = RequestAvatarGenerationOutputDTO;
|
||||||
|
|
||||||
export class RequestAvatarGenerationPresenter implements UseCaseOutputPort<RequestAvatarGenerationResult> {
|
export class RequestAvatarGenerationPresenter {
|
||||||
private model: RequestAvatarGenerationResponseModel | null = null;
|
private model: RequestAvatarGenerationResponseModel | null = null;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: RequestAvatarGenerationResult): void {
|
transform(result: RequestAvatarGenerationResult): RequestAvatarGenerationResponseModel {
|
||||||
this.model = {
|
this.model = {
|
||||||
success: result.status === 'completed',
|
success: result.status === 'completed',
|
||||||
requestId: result.requestId,
|
requestId: result.requestId,
|
||||||
avatarUrls: result.avatarUrls || [],
|
avatarUrls: result.avatarUrls || [],
|
||||||
};
|
};
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): RequestAvatarGenerationResponseModel | null {
|
getResponseModel(): RequestAvatarGenerationResponseModel | null {
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { UpdateAvatarResult } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
import type { UpdateAvatarResult } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||||
import type { UpdateAvatarOutputDTO } from '../dtos/UpdateAvatarOutputDTO';
|
import type { UpdateAvatarOutputDTO } from '../dtos/UpdateAvatarOutputDTO';
|
||||||
|
|
||||||
type UpdateAvatarResponseModel = UpdateAvatarOutputDTO;
|
type UpdateAvatarResponseModel = UpdateAvatarOutputDTO;
|
||||||
|
|
||||||
export class UpdateAvatarPresenter implements UseCaseOutputPort<UpdateAvatarResult> {
|
export class UpdateAvatarPresenter {
|
||||||
private model: UpdateAvatarResponseModel | null = null;
|
private model: UpdateAvatarResponseModel | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: UpdateAvatarResult): void {
|
transform(result: UpdateAvatarResult): UpdateAvatarResponseModel {
|
||||||
void result;
|
void result;
|
||||||
|
|
||||||
this.model = {
|
this.model = {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): UpdateAvatarResponseModel | null {
|
getResponseModel(): UpdateAvatarResponseModel | null {
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import type { UploadMediaResult } from '@core/media/application/use-cases/UploadMediaUseCase';
|
import type { UploadMediaResult } from '@core/media/application/use-cases/UploadMediaUseCase';
|
||||||
import type { UploadMediaOutputDTO } from '../dtos/UploadMediaOutputDTO';
|
import type { UploadMediaOutputDTO } from '../dtos/UploadMediaOutputDTO';
|
||||||
|
|
||||||
type UploadMediaResponseModel = UploadMediaOutputDTO;
|
type UploadMediaResponseModel = UploadMediaOutputDTO;
|
||||||
|
|
||||||
export class UploadMediaPresenter implements UseCaseOutputPort<UploadMediaResult> {
|
export class UploadMediaPresenter {
|
||||||
private model: UploadMediaResponseModel | null = null;
|
private model: UploadMediaResponseModel | null = null;
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: UploadMediaResult): void {
|
transform(result: UploadMediaResult): UploadMediaResponseModel {
|
||||||
const model: UploadMediaResponseModel = {
|
const model: UploadMediaResponseModel = {
|
||||||
success: true,
|
success: true,
|
||||||
mediaId: result.mediaId,
|
mediaId: result.mediaId,
|
||||||
@@ -22,6 +21,7 @@ export class UploadMediaPresenter implements UseCaseOutputPort<UploadMediaResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.model = model;
|
this.model = model;
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseModel(): UploadMediaResponseModel | null {
|
getResponseModel(): UploadMediaResponseModel | null {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type { IPaymentRepository } from '@core/payments/domain/repositories/IPay
|
|||||||
import type { IMembershipFeeRepository, IMemberPaymentRepository } from '@core/payments/domain/repositories/IMembershipFeeRepository';
|
import type { IMembershipFeeRepository, IMemberPaymentRepository } from '@core/payments/domain/repositories/IMembershipFeeRepository';
|
||||||
import type { IPrizeRepository } from '@core/payments/domain/repositories/IPrizeRepository';
|
import type { IPrizeRepository } from '@core/payments/domain/repositories/IPrizeRepository';
|
||||||
import type { IWalletRepository, ITransactionRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
import type { IWalletRepository, ITransactionRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
|
|
||||||
// Import use cases
|
// Import use cases
|
||||||
import { GetPaymentsUseCase } from '@core/payments/application/use-cases/GetPaymentsUseCase';
|
import { GetPaymentsUseCase } from '@core/payments/application/use-cases/GetPaymentsUseCase';
|
||||||
@@ -24,20 +23,6 @@ import { ProcessWalletTransactionUseCase } from '@core/payments/application/use-
|
|||||||
// Import concrete in-memory implementations
|
// Import concrete in-memory implementations
|
||||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
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';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PAYMENT_REPOSITORY_TOKEN,
|
PAYMENT_REPOSITORY_TOKEN,
|
||||||
MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
MEMBERSHIP_FEE_REPOSITORY_TOKEN,
|
||||||
@@ -58,88 +43,12 @@ import {
|
|||||||
DELETE_PRIZE_USE_CASE_TOKEN,
|
DELETE_PRIZE_USE_CASE_TOKEN,
|
||||||
GET_WALLET_USE_CASE_TOKEN,
|
GET_WALLET_USE_CASE_TOKEN,
|
||||||
PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN,
|
PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN,
|
||||||
GET_PAYMENTS_OUTPUT_PORT_TOKEN,
|
|
||||||
CREATE_PAYMENT_OUTPUT_PORT_TOKEN,
|
|
||||||
UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_MEMBERSHIP_FEES_OUTPUT_PORT_TOKEN,
|
|
||||||
UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN,
|
|
||||||
UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_PRIZES_OUTPUT_PORT_TOKEN,
|
|
||||||
CREATE_PRIZE_OUTPUT_PORT_TOKEN,
|
|
||||||
AWARD_PRIZE_OUTPUT_PORT_TOKEN,
|
|
||||||
DELETE_PRIZE_OUTPUT_PORT_TOKEN,
|
|
||||||
GET_WALLET_OUTPUT_PORT_TOKEN,
|
|
||||||
PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN,
|
|
||||||
} from './PaymentsTokens';
|
} from './PaymentsTokens';
|
||||||
|
|
||||||
export * from './PaymentsTokens';
|
export * from './PaymentsTokens';
|
||||||
|
|
||||||
export const PaymentsProviders: Provider[] = [
|
export const PaymentsProviders: Provider[] = [
|
||||||
|
|
||||||
// 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
|
// Logger
|
||||||
{
|
{
|
||||||
provide: LOGGER_TOKEN,
|
provide: LOGGER_TOKEN,
|
||||||
@@ -151,66 +60,66 @@ export const PaymentsProviders: Provider[] = [
|
|||||||
// Use cases (use cases receive repositories, services receive use cases)
|
// Use cases (use cases receive repositories, services receive use cases)
|
||||||
{
|
{
|
||||||
provide: GET_PAYMENTS_USE_CASE_TOKEN,
|
provide: GET_PAYMENTS_USE_CASE_TOKEN,
|
||||||
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<unknown>) => new GetPaymentsUseCase(paymentRepo, output),
|
useFactory: (paymentRepo: IPaymentRepository) => new GetPaymentsUseCase(paymentRepo),
|
||||||
inject: [PAYMENT_REPOSITORY_TOKEN, GET_PAYMENTS_OUTPUT_PORT_TOKEN],
|
inject: [PAYMENT_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CREATE_PAYMENT_USE_CASE_TOKEN,
|
provide: CREATE_PAYMENT_USE_CASE_TOKEN,
|
||||||
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<unknown>) => new CreatePaymentUseCase(paymentRepo, output),
|
useFactory: (paymentRepo: IPaymentRepository) => new CreatePaymentUseCase(paymentRepo),
|
||||||
inject: [PAYMENT_REPOSITORY_TOKEN, CREATE_PAYMENT_OUTPUT_PORT_TOKEN],
|
inject: [PAYMENT_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UPDATE_PAYMENT_STATUS_USE_CASE_TOKEN,
|
provide: UPDATE_PAYMENT_STATUS_USE_CASE_TOKEN,
|
||||||
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<unknown>) => new UpdatePaymentStatusUseCase(paymentRepo, output),
|
useFactory: (paymentRepo: IPaymentRepository) => new UpdatePaymentStatusUseCase(paymentRepo),
|
||||||
inject: [PAYMENT_REPOSITORY_TOKEN, UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN],
|
inject: [PAYMENT_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_MEMBERSHIP_FEES_USE_CASE_TOKEN,
|
provide: GET_MEMBERSHIP_FEES_USE_CASE_TOKEN,
|
||||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository, output: UseCaseOutputPort<unknown>) =>
|
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository) =>
|
||||||
new GetMembershipFeesUseCase(membershipFeeRepo, memberPaymentRepo, output),
|
new GetMembershipFeesUseCase(membershipFeeRepo, memberPaymentRepo),
|
||||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN, GET_MEMBERSHIP_FEES_OUTPUT_PORT_TOKEN],
|
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UPSERT_MEMBERSHIP_FEE_USE_CASE_TOKEN,
|
provide: UPSERT_MEMBERSHIP_FEE_USE_CASE_TOKEN,
|
||||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository, output: UseCaseOutputPort<unknown>) => new UpsertMembershipFeeUseCase(membershipFeeRepo, output),
|
useFactory: (membershipFeeRepo: IMembershipFeeRepository) => new UpsertMembershipFeeUseCase(membershipFeeRepo),
|
||||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN],
|
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UPDATE_MEMBER_PAYMENT_USE_CASE_TOKEN,
|
provide: UPDATE_MEMBER_PAYMENT_USE_CASE_TOKEN,
|
||||||
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository, output: UseCaseOutputPort<unknown>) =>
|
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository) =>
|
||||||
new UpdateMemberPaymentUseCase(membershipFeeRepo, memberPaymentRepo, output),
|
new UpdateMemberPaymentUseCase(membershipFeeRepo, memberPaymentRepo),
|
||||||
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN, UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_TOKEN],
|
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_PRIZES_USE_CASE_TOKEN,
|
provide: GET_PRIZES_USE_CASE_TOKEN,
|
||||||
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<unknown>) => new GetPrizesUseCase(prizeRepo, output),
|
useFactory: (prizeRepo: IPrizeRepository) => new GetPrizesUseCase(prizeRepo),
|
||||||
inject: [PRIZE_REPOSITORY_TOKEN, GET_PRIZES_OUTPUT_PORT_TOKEN],
|
inject: [PRIZE_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CREATE_PRIZE_USE_CASE_TOKEN,
|
provide: CREATE_PRIZE_USE_CASE_TOKEN,
|
||||||
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<unknown>) => new CreatePrizeUseCase(prizeRepo, output),
|
useFactory: (prizeRepo: IPrizeRepository) => new CreatePrizeUseCase(prizeRepo),
|
||||||
inject: [PRIZE_REPOSITORY_TOKEN, CREATE_PRIZE_OUTPUT_PORT_TOKEN],
|
inject: [PRIZE_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: AWARD_PRIZE_USE_CASE_TOKEN,
|
provide: AWARD_PRIZE_USE_CASE_TOKEN,
|
||||||
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<unknown>) => new AwardPrizeUseCase(prizeRepo, output),
|
useFactory: (prizeRepo: IPrizeRepository) => new AwardPrizeUseCase(prizeRepo),
|
||||||
inject: [PRIZE_REPOSITORY_TOKEN, AWARD_PRIZE_OUTPUT_PORT_TOKEN],
|
inject: [PRIZE_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: DELETE_PRIZE_USE_CASE_TOKEN,
|
provide: DELETE_PRIZE_USE_CASE_TOKEN,
|
||||||
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<unknown>) => new DeletePrizeUseCase(prizeRepo, output),
|
useFactory: (prizeRepo: IPrizeRepository) => new DeletePrizeUseCase(prizeRepo),
|
||||||
inject: [PRIZE_REPOSITORY_TOKEN, DELETE_PRIZE_OUTPUT_PORT_TOKEN],
|
inject: [PRIZE_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_WALLET_USE_CASE_TOKEN,
|
provide: GET_WALLET_USE_CASE_TOKEN,
|
||||||
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository, output: UseCaseOutputPort<unknown>) =>
|
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository) =>
|
||||||
new GetWalletUseCase(walletRepo, transactionRepo, output),
|
new GetWalletUseCase(walletRepo, transactionRepo),
|
||||||
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN, GET_WALLET_OUTPUT_PORT_TOKEN],
|
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN,
|
provide: PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN,
|
||||||
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository, output: UseCaseOutputPort<unknown>) =>
|
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository) =>
|
||||||
new ProcessWalletTransactionUseCase(walletRepo, transactionRepo, output),
|
new ProcessWalletTransactionUseCase(walletRepo, transactionRepo),
|
||||||
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN, PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN],
|
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -6,42 +6,23 @@ describe('PaymentsService', () => {
|
|||||||
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
||||||
|
|
||||||
function makeService(overrides?: Partial<Record<string, any>>) {
|
function makeService(overrides?: Partial<Record<string, any>>) {
|
||||||
const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok({ payments: [] })) };
|
||||||
const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ paymentId: 'p1' })) };
|
||||||
const updatePaymentStatusUseCase =
|
const updatePaymentStatusUseCase =
|
||||||
overrides?.updatePaymentStatusUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
overrides?.updatePaymentStatusUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const getMembershipFeesUseCase =
|
const getMembershipFeesUseCase =
|
||||||
overrides?.getMembershipFeesUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
overrides?.getMembershipFeesUseCase ?? { execute: vi.fn(async () => Result.ok({ fee: null, payments: [] })) };
|
||||||
const upsertMembershipFeeUseCase =
|
const upsertMembershipFeeUseCase =
|
||||||
overrides?.upsertMembershipFeeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
overrides?.upsertMembershipFeeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const updateMemberPaymentUseCase =
|
const updateMemberPaymentUseCase =
|
||||||
overrides?.updateMemberPaymentUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
overrides?.updateMemberPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const getPrizesUseCase = overrides?.getPrizesUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
const getPrizesUseCase = overrides?.getPrizesUseCase ?? { execute: vi.fn(async () => Result.ok({ prizes: [] })) };
|
||||||
const createPrizeUseCase = overrides?.createPrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
const createPrizeUseCase = overrides?.createPrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const awardPrizeUseCase = overrides?.awardPrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
const awardPrizeUseCase = overrides?.awardPrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const deletePrizeUseCase = overrides?.deletePrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
const deletePrizeUseCase = overrides?.deletePrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const getWalletUseCase = overrides?.getWalletUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
const getWalletUseCase = overrides?.getWalletUseCase ?? { execute: vi.fn(async () => Result.ok({ balance: 0 })) };
|
||||||
const processWalletTransactionUseCase =
|
const processWalletTransactionUseCase =
|
||||||
overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
|
overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
|
|
||||||
const getPaymentsPresenter = overrides?.getPaymentsPresenter ?? { getResponseModel: vi.fn(() => ({ payments: [] })) };
|
|
||||||
const createPaymentPresenter =
|
|
||||||
overrides?.createPaymentPresenter ?? { getResponseModel: vi.fn(() => ({ paymentId: 'p1' })) };
|
|
||||||
const updatePaymentStatusPresenter =
|
|
||||||
overrides?.updatePaymentStatusPresenter ?? { getResponseModel: vi.fn(() => ({ success: true })) };
|
|
||||||
|
|
||||||
const getMembershipFeesPresenter = overrides?.getMembershipFeesPresenter ?? { viewModel: { fee: null, payments: [] } };
|
|
||||||
const upsertMembershipFeePresenter = overrides?.upsertMembershipFeePresenter ?? { viewModel: { success: true } };
|
|
||||||
const updateMemberPaymentPresenter = overrides?.updateMemberPaymentPresenter ?? { viewModel: { success: true } };
|
|
||||||
|
|
||||||
const getPrizesPresenter = overrides?.getPrizesPresenter ?? { viewModel: { prizes: [] } };
|
|
||||||
const createPrizePresenter = overrides?.createPrizePresenter ?? { viewModel: { success: true } };
|
|
||||||
const awardPrizePresenter = overrides?.awardPrizePresenter ?? { viewModel: { success: true } };
|
|
||||||
const deletePrizePresenter = overrides?.deletePrizePresenter ?? { viewModel: { success: true } };
|
|
||||||
|
|
||||||
const getWalletPresenter = overrides?.getWalletPresenter ?? { viewModel: { balance: 0 } };
|
|
||||||
const processWalletTransactionPresenter =
|
|
||||||
overrides?.processWalletTransactionPresenter ?? { viewModel: { success: true } };
|
|
||||||
|
|
||||||
const service = new PaymentsService(
|
const service = new PaymentsService(
|
||||||
getPaymentsUseCase as any,
|
getPaymentsUseCase as any,
|
||||||
@@ -57,18 +38,6 @@ describe('PaymentsService', () => {
|
|||||||
getWalletUseCase as any,
|
getWalletUseCase as any,
|
||||||
processWalletTransactionUseCase as any,
|
processWalletTransactionUseCase as any,
|
||||||
logger as any,
|
logger as any,
|
||||||
getPaymentsPresenter as any,
|
|
||||||
createPaymentPresenter as any,
|
|
||||||
updatePaymentStatusPresenter as any,
|
|
||||||
getMembershipFeesPresenter as any,
|
|
||||||
upsertMembershipFeePresenter as any,
|
|
||||||
updateMemberPaymentPresenter as any,
|
|
||||||
getPrizesPresenter as any,
|
|
||||||
createPrizePresenter as any,
|
|
||||||
awardPrizePresenter as any,
|
|
||||||
deletePrizePresenter as any,
|
|
||||||
getWalletPresenter as any,
|
|
||||||
processWalletTransactionPresenter as any,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -85,26 +54,13 @@ describe('PaymentsService', () => {
|
|||||||
deletePrizeUseCase,
|
deletePrizeUseCase,
|
||||||
getWalletUseCase,
|
getWalletUseCase,
|
||||||
processWalletTransactionUseCase,
|
processWalletTransactionUseCase,
|
||||||
getPaymentsPresenter,
|
|
||||||
createPaymentPresenter,
|
|
||||||
updatePaymentStatusPresenter,
|
|
||||||
getMembershipFeesPresenter,
|
|
||||||
upsertMembershipFeePresenter,
|
|
||||||
updateMemberPaymentPresenter,
|
|
||||||
getPrizesPresenter,
|
|
||||||
createPrizePresenter,
|
|
||||||
awardPrizePresenter,
|
|
||||||
deletePrizePresenter,
|
|
||||||
getWalletPresenter,
|
|
||||||
processWalletTransactionPresenter,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
it('getPayments returns presenter model on success', async () => {
|
it('getPayments returns presenter model on success', async () => {
|
||||||
const { service, getPaymentsUseCase, getPaymentsPresenter } = makeService();
|
const { service, getPaymentsUseCase } = makeService();
|
||||||
await expect(service.getPayments({ leagueId: 'l1' } as any)).resolves.toEqual({ payments: [] });
|
await expect(service.getPayments({ leagueId: 'l1' } as any)).resolves.toEqual({ payments: [] });
|
||||||
expect(getPaymentsUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
expect(getPaymentsUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||||
expect(getPaymentsPresenter.getResponseModel).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getPayments throws when use case returns error (code message)', async () => {
|
it('getPayments throws when use case returns error (code message)', async () => {
|
||||||
@@ -115,12 +71,9 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('createPayment returns presenter model on success', async () => {
|
it('createPayment returns presenter model on success', async () => {
|
||||||
const { service, createPaymentUseCase, createPaymentPresenter } = makeService({
|
const { service, createPaymentUseCase } = makeService();
|
||||||
createPaymentPresenter: { getResponseModel: vi.fn(() => ({ paymentId: 'p1' })) },
|
|
||||||
});
|
|
||||||
await expect(service.createPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ paymentId: 'p1' });
|
await expect(service.createPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ paymentId: 'p1' });
|
||||||
expect(createPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
expect(createPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||||
expect(createPaymentPresenter.getResponseModel).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createPayment throws when use case returns error', async () => {
|
it('createPayment throws when use case returns error', async () => {
|
||||||
@@ -131,12 +84,9 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updatePaymentStatus returns presenter model on success', async () => {
|
it('updatePaymentStatus returns presenter model on success', async () => {
|
||||||
const { service, updatePaymentStatusUseCase, updatePaymentStatusPresenter } = makeService({
|
const { service, updatePaymentStatusUseCase } = makeService();
|
||||||
updatePaymentStatusPresenter: { getResponseModel: vi.fn(() => ({ success: true })) },
|
|
||||||
});
|
|
||||||
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).resolves.toEqual({ success: true });
|
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).resolves.toEqual({ success: true });
|
||||||
expect(updatePaymentStatusUseCase.execute).toHaveBeenCalledWith({ paymentId: 'p1' });
|
expect(updatePaymentStatusUseCase.execute).toHaveBeenCalledWith({ paymentId: 'p1' });
|
||||||
expect(updatePaymentStatusPresenter.getResponseModel).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updatePaymentStatus throws when use case returns error', async () => {
|
it('updatePaymentStatus throws when use case returns error', async () => {
|
||||||
@@ -147,8 +97,8 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('getMembershipFees returns viewModel on success', async () => {
|
it('getMembershipFees returns viewModel on success', async () => {
|
||||||
const { service, getMembershipFeesUseCase, getMembershipFeesPresenter } = makeService({
|
const { service, getMembershipFeesUseCase } = makeService({
|
||||||
getMembershipFeesPresenter: { viewModel: { fee: { amount: 1 }, payments: [] } },
|
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.ok({ fee: { amount: 1 }, payments: [] })) }
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).resolves.toEqual({
|
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).resolves.toEqual({
|
||||||
@@ -156,7 +106,6 @@ describe('PaymentsService', () => {
|
|||||||
payments: [],
|
payments: [],
|
||||||
});
|
});
|
||||||
expect(getMembershipFeesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', driverId: 'd1' });
|
expect(getMembershipFeesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', driverId: 'd1' });
|
||||||
expect(getMembershipFeesPresenter.viewModel).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getMembershipFees throws when use case returns error', async () => {
|
it('getMembershipFees throws when use case returns error', async () => {
|
||||||
@@ -167,9 +116,7 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('upsertMembershipFee returns viewModel on success', async () => {
|
it('upsertMembershipFee returns viewModel on success', async () => {
|
||||||
const { service, upsertMembershipFeeUseCase } = makeService({
|
const { service, upsertMembershipFeeUseCase } = makeService();
|
||||||
upsertMembershipFeePresenter: { viewModel: { success: true } },
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
||||||
expect(upsertMembershipFeeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
expect(upsertMembershipFeeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||||
@@ -186,9 +133,7 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updateMemberPayment returns viewModel on success', async () => {
|
it('updateMemberPayment returns viewModel on success', async () => {
|
||||||
const { service, updateMemberPaymentUseCase } = makeService({
|
const { service, updateMemberPaymentUseCase } = makeService();
|
||||||
updateMemberPaymentPresenter: { viewModel: { success: true } },
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
||||||
expect(updateMemberPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
expect(updateMemberPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||||
@@ -203,10 +148,9 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('getPrizes maps seasonId optional', async () => {
|
it('getPrizes maps seasonId optional', async () => {
|
||||||
const getPrizesUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const getPrizesUseCase = { execute: vi.fn(async () => Result.ok({ prizes: [] })) };
|
||||||
const { service } = makeService({
|
const { service } = makeService({
|
||||||
getPrizesUseCase,
|
getPrizesUseCase,
|
||||||
getPrizesPresenter: { viewModel: { prizes: [] } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(service.getPrizes({ leagueId: 'l1' } as any)).resolves.toEqual({ prizes: [] });
|
await expect(service.getPrizes({ leagueId: 'l1' } as any)).resolves.toEqual({ prizes: [] });
|
||||||
@@ -217,10 +161,9 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('createPrize calls use case and returns viewModel', async () => {
|
it('createPrize calls use case and returns viewModel', async () => {
|
||||||
const createPrizeUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const createPrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const { service } = makeService({
|
const { service } = makeService({
|
||||||
createPrizeUseCase,
|
createPrizeUseCase,
|
||||||
createPrizePresenter: { viewModel: { success: true } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(service.createPrize({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
await expect(service.createPrize({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
||||||
@@ -228,10 +171,9 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('awardPrize calls use case and returns viewModel', async () => {
|
it('awardPrize calls use case and returns viewModel', async () => {
|
||||||
const awardPrizeUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const awardPrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const { service } = makeService({
|
const { service } = makeService({
|
||||||
awardPrizeUseCase,
|
awardPrizeUseCase,
|
||||||
awardPrizePresenter: { viewModel: { success: true } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(service.awardPrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
|
await expect(service.awardPrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
|
||||||
@@ -239,10 +181,9 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('deletePrize calls use case and returns viewModel', async () => {
|
it('deletePrize calls use case and returns viewModel', async () => {
|
||||||
const deletePrizeUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const deletePrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const { service } = makeService({
|
const { service } = makeService({
|
||||||
deletePrizeUseCase,
|
deletePrizeUseCase,
|
||||||
deletePrizePresenter: { viewModel: { success: true } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(service.deletePrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
|
await expect(service.deletePrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
|
||||||
@@ -250,10 +191,9 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('getWallet calls use case and returns viewModel', async () => {
|
it('getWallet calls use case and returns viewModel', async () => {
|
||||||
const getWalletUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const getWalletUseCase = { execute: vi.fn(async () => Result.ok({ balance: 10 })) };
|
||||||
const { service } = makeService({
|
const { service } = makeService({
|
||||||
getWalletUseCase,
|
getWalletUseCase,
|
||||||
getWalletPresenter: { viewModel: { balance: 10 } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(service.getWallet({ leagueId: 'l1' } as any)).resolves.toEqual({ balance: 10 });
|
await expect(service.getWallet({ leagueId: 'l1' } as any)).resolves.toEqual({ balance: 10 });
|
||||||
@@ -261,10 +201,9 @@ describe('PaymentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('processWalletTransaction calls use case and returns viewModel', async () => {
|
it('processWalletTransaction calls use case and returns viewModel', async () => {
|
||||||
const processWalletTransactionUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
|
const processWalletTransactionUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||||
const { service } = makeService({
|
const { service } = makeService({
|
||||||
processWalletTransactionUseCase,
|
processWalletTransactionUseCase,
|
||||||
processWalletTransactionPresenter: { viewModel: { success: true } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(service.processWalletTransaction({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
await expect(service.processWalletTransaction({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
||||||
|
|||||||
@@ -15,20 +15,6 @@ import type { UpdateMemberPaymentUseCase } from '@core/payments/application/use-
|
|||||||
import type { UpdatePaymentStatusUseCase } from '@core/payments/application/use-cases/UpdatePaymentStatusUseCase';
|
import type { UpdatePaymentStatusUseCase } from '@core/payments/application/use-cases/UpdatePaymentStatusUseCase';
|
||||||
import type { UpsertMembershipFeeUseCase } from '@core/payments/application/use-cases/UpsertMembershipFeeUseCase';
|
import type { UpsertMembershipFeeUseCase } from '@core/payments/application/use-cases/UpsertMembershipFeeUseCase';
|
||||||
|
|
||||||
// Presenters
|
|
||||||
import { AwardPrizePresenter } from './presenters/AwardPrizePresenter';
|
|
||||||
import { CreatePaymentPresenter } from './presenters/CreatePaymentPresenter';
|
|
||||||
import { CreatePrizePresenter } from './presenters/CreatePrizePresenter';
|
|
||||||
import { DeletePrizePresenter } from './presenters/DeletePrizePresenter';
|
|
||||||
import { GetMembershipFeesPresenter } from './presenters/GetMembershipFeesPresenter';
|
|
||||||
import { GetPaymentsPresenter } from './presenters/GetPaymentsPresenter';
|
|
||||||
import { GetPrizesPresenter } from './presenters/GetPrizesPresenter';
|
|
||||||
import { GetWalletPresenter } from './presenters/GetWalletPresenter';
|
|
||||||
import { ProcessWalletTransactionPresenter } from './presenters/ProcessWalletTransactionPresenter';
|
|
||||||
import { UpdateMemberPaymentPresenter } from './presenters/UpdateMemberPaymentPresenter';
|
|
||||||
import { UpdatePaymentStatusPresenter } from './presenters/UpdatePaymentStatusPresenter';
|
|
||||||
import { UpsertMembershipFeePresenter } from './presenters/UpsertMembershipFeePresenter';
|
|
||||||
|
|
||||||
// DTOs
|
// DTOs
|
||||||
import type {
|
import type {
|
||||||
AwardPrizeInput,
|
AwardPrizeInput,
|
||||||
@@ -90,18 +76,6 @@ export class PaymentsService {
|
|||||||
@Inject(GET_WALLET_USE_CASE_TOKEN) private readonly getWalletUseCase: GetWalletUseCase,
|
@Inject(GET_WALLET_USE_CASE_TOKEN) private readonly getWalletUseCase: GetWalletUseCase,
|
||||||
@Inject(PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN) private readonly processWalletTransactionUseCase: ProcessWalletTransactionUseCase,
|
@Inject(PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN) private readonly processWalletTransactionUseCase: ProcessWalletTransactionUseCase,
|
||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@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<GetPaymentsOutput> {
|
async getPayments(query: GetPaymentsQuery): Promise<GetPaymentsOutput> {
|
||||||
@@ -111,7 +85,11 @@ export class PaymentsService {
|
|||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code ?? 'Failed to get payments');
|
throw new Error(result.unwrapErr().code ?? 'Failed to get payments');
|
||||||
}
|
}
|
||||||
return this.getPaymentsPresenter.getResponseModel();
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to get payments: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPayment(input: CreatePaymentInput): Promise<CreatePaymentOutput> {
|
async createPayment(input: CreatePaymentInput): Promise<CreatePaymentOutput> {
|
||||||
@@ -121,7 +99,11 @@ export class PaymentsService {
|
|||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code ?? 'Failed to create payment');
|
throw new Error(result.unwrapErr().code ?? 'Failed to create payment');
|
||||||
}
|
}
|
||||||
return this.createPaymentPresenter.getResponseModel();
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to create payment: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePaymentStatus(input: UpdatePaymentStatusInput): Promise<UpdatePaymentStatusOutput> {
|
async updatePaymentStatus(input: UpdatePaymentStatusInput): Promise<UpdatePaymentStatusOutput> {
|
||||||
@@ -131,7 +113,11 @@ export class PaymentsService {
|
|||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code ?? 'Failed to update payment status');
|
throw new Error(result.unwrapErr().code ?? 'Failed to update payment status');
|
||||||
}
|
}
|
||||||
return this.updatePaymentStatusPresenter.getResponseModel();
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to update payment status: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMembershipFees(query: GetMembershipFeesQuery): Promise<GetMembershipFeesOutput> {
|
async getMembershipFees(query: GetMembershipFeesQuery): Promise<GetMembershipFeesOutput> {
|
||||||
@@ -141,7 +127,11 @@ export class PaymentsService {
|
|||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code ?? 'Failed to get membership fees');
|
throw new Error(result.unwrapErr().code ?? 'Failed to get membership fees');
|
||||||
}
|
}
|
||||||
return this.getMembershipFeesPresenter.viewModel;
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to get membership fees: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async upsertMembershipFee(input: UpsertMembershipFeeInput): Promise<UpsertMembershipFeeOutput> {
|
async upsertMembershipFee(input: UpsertMembershipFeeInput): Promise<UpsertMembershipFeeOutput> {
|
||||||
@@ -153,7 +143,11 @@ export class PaymentsService {
|
|||||||
// but we keep the check for consistency
|
// but we keep the check for consistency
|
||||||
throw new Error('Failed to upsert membership fee');
|
throw new Error('Failed to upsert membership fee');
|
||||||
}
|
}
|
||||||
return this.upsertMembershipFeePresenter.viewModel;
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to upsert membership fee: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMemberPayment(input: UpdateMemberPaymentInput): Promise<UpdateMemberPaymentOutput> {
|
async updateMemberPayment(input: UpdateMemberPaymentInput): Promise<UpdateMemberPaymentOutput> {
|
||||||
@@ -163,7 +157,11 @@ export class PaymentsService {
|
|||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
throw new Error(result.unwrapErr().code ?? 'Failed to update member payment');
|
throw new Error(result.unwrapErr().code ?? 'Failed to update member payment');
|
||||||
}
|
}
|
||||||
return this.updateMemberPaymentPresenter.viewModel;
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to update member payment: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPrizes(query: GetPrizesQuery): Promise<GetPrizesOutput> {
|
async getPrizes(query: GetPrizesQuery): Promise<GetPrizesOutput> {
|
||||||
@@ -175,42 +173,89 @@ export class PaymentsService {
|
|||||||
if (query.seasonId !== undefined) {
|
if (query.seasonId !== undefined) {
|
||||||
input.seasonId = query.seasonId;
|
input.seasonId = query.seasonId;
|
||||||
}
|
}
|
||||||
await this.getPrizesUseCase.execute(input);
|
const result = await this.getPrizesUseCase.execute(input);
|
||||||
return this.getPrizesPresenter.viewModel;
|
if (result.isErr()) {
|
||||||
|
throw new Error('Failed to get prizes');
|
||||||
|
}
|
||||||
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to get prizes: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPrize(input: CreatePrizeInput): Promise<CreatePrizeOutput> {
|
async createPrize(input: CreatePrizeInput): Promise<CreatePrizeOutput> {
|
||||||
this.logger.debug('[PaymentsService] Creating prize', { input });
|
this.logger.debug('[PaymentsService] Creating prize', { input });
|
||||||
|
|
||||||
await this.createPrizeUseCase.execute(input);
|
const result = await this.createPrizeUseCase.execute(input);
|
||||||
return this.createPrizePresenter.viewModel;
|
if (result.isErr()) {
|
||||||
|
const err = result.unwrapErr();
|
||||||
|
throw new Error(err.code ?? 'Failed to create prize');
|
||||||
|
}
|
||||||
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to create prize: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async awardPrize(input: AwardPrizeInput): Promise<AwardPrizeOutput> {
|
async awardPrize(input: AwardPrizeInput): Promise<AwardPrizeOutput> {
|
||||||
this.logger.debug('[PaymentsService] Awarding prize', { input });
|
this.logger.debug('[PaymentsService] Awarding prize', { input });
|
||||||
|
|
||||||
await this.awardPrizeUseCase.execute(input);
|
const result = await this.awardPrizeUseCase.execute(input);
|
||||||
return this.awardPrizePresenter.viewModel;
|
if (result.isErr()) {
|
||||||
|
const err = result.unwrapErr();
|
||||||
|
throw new Error(err.code ?? 'Failed to award prize');
|
||||||
|
}
|
||||||
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to award prize: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deletePrize(input: DeletePrizeInput): Promise<DeletePrizeOutput> {
|
async deletePrize(input: DeletePrizeInput): Promise<DeletePrizeOutput> {
|
||||||
this.logger.debug('[PaymentsService] Deleting prize', { input });
|
this.logger.debug('[PaymentsService] Deleting prize', { input });
|
||||||
|
|
||||||
await this.deletePrizeUseCase.execute(input);
|
const result = await this.deletePrizeUseCase.execute(input);
|
||||||
return this.deletePrizePresenter.viewModel;
|
if (result.isErr()) {
|
||||||
|
const err = result.unwrapErr();
|
||||||
|
throw new Error(err.code ?? 'Failed to delete prize');
|
||||||
|
}
|
||||||
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to delete prize: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWallet(query: GetWalletQuery): Promise<GetWalletOutput> {
|
async getWallet(query: GetWalletQuery): Promise<GetWalletOutput> {
|
||||||
this.logger.debug('[PaymentsService] Getting wallet', { query });
|
this.logger.debug('[PaymentsService] Getting wallet', { query });
|
||||||
|
|
||||||
await this.getWalletUseCase.execute({ leagueId: query.leagueId! });
|
const result = await this.getWalletUseCase.execute({ leagueId: query.leagueId! });
|
||||||
return this.getWalletPresenter.viewModel;
|
if (result.isErr()) {
|
||||||
|
const err = result.unwrapErr();
|
||||||
|
throw new Error(err.code ?? 'Failed to get wallet');
|
||||||
|
}
|
||||||
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to get wallet: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async processWalletTransaction(input: ProcessWalletTransactionInput): Promise<ProcessWalletTransactionOutput> {
|
async processWalletTransaction(input: ProcessWalletTransactionInput): Promise<ProcessWalletTransactionOutput> {
|
||||||
this.logger.debug('[PaymentsService] Processing wallet transaction', { input });
|
this.logger.debug('[PaymentsService] Processing wallet transaction', { input });
|
||||||
|
|
||||||
await this.processWalletTransactionUseCase.execute(input);
|
const result = await this.processWalletTransactionUseCase.execute(input);
|
||||||
return this.processWalletTransactionPresenter.viewModel;
|
if (result.isErr()) {
|
||||||
|
const err = result.unwrapErr();
|
||||||
|
throw new Error(err.code ?? 'Failed to process wallet transaction');
|
||||||
|
}
|
||||||
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Failed to process wallet transaction: no value returned');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,16 +28,3 @@ export const AWARD_PRIZE_USE_CASE_TOKEN = 'AwardPrizeUseCase';
|
|||||||
export const DELETE_PRIZE_USE_CASE_TOKEN = 'DeletePrizeUseCase';
|
export const DELETE_PRIZE_USE_CASE_TOKEN = 'DeletePrizeUseCase';
|
||||||
export const GET_WALLET_USE_CASE_TOKEN = 'GetWalletUseCase';
|
export const GET_WALLET_USE_CASE_TOKEN = 'GetWalletUseCase';
|
||||||
export const PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN = 'ProcessWalletTransactionUseCase';
|
export const PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN = 'ProcessWalletTransactionUseCase';
|
||||||
|
|
||||||
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';
|
|
||||||
@@ -20,17 +20,13 @@ export const PROTEST_REPOSITORY_TOKEN = 'IProtestRepository';
|
|||||||
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
||||||
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
|
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
|
||||||
export const LOGGER_TOKEN = 'Logger';
|
export const LOGGER_TOKEN = 'Logger';
|
||||||
export const REVIEW_PROTEST_PRESENTER_TOKEN = 'ReviewProtestPresenter';
|
|
||||||
|
|
||||||
export const ProtestsProviders: Provider[] = [
|
export const ProtestsProviders: Provider[] = [
|
||||||
{
|
{
|
||||||
provide: LOGGER_TOKEN,
|
provide: LOGGER_TOKEN,
|
||||||
useClass: ConsoleLogger,
|
useClass: ConsoleLogger,
|
||||||
},
|
},
|
||||||
{
|
ReviewProtestPresenter,
|
||||||
provide: REVIEW_PROTEST_PRESENTER_TOKEN,
|
|
||||||
useClass: ReviewProtestPresenter,
|
|
||||||
},
|
|
||||||
// Use cases
|
// Use cases
|
||||||
{
|
{
|
||||||
provide: ReviewProtestUseCase,
|
provide: ReviewProtestUseCase,
|
||||||
@@ -39,14 +35,12 @@ export const ProtestsProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: ReviewProtestPresenter,
|
) => new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger),
|
||||||
) => new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
PROTEST_REPOSITORY_TOKEN,
|
PROTEST_REPOSITORY_TOKEN,
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
REVIEW_PROTEST_PRESENTER_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -40,13 +40,15 @@ describe('ProtestsService', () => {
|
|||||||
|
|
||||||
it('returns DTO with success model on success', async () => {
|
it('returns DTO with success model on success', async () => {
|
||||||
executeMock.mockImplementation(async (command) => {
|
executeMock.mockImplementation(async (command) => {
|
||||||
presenter.present({ protestId: command.protestId } as ReviewProtestResult);
|
return Result.ok({
|
||||||
return Result.ok(undefined);
|
leagueId: 'league-1',
|
||||||
|
protestId: command.protestId,
|
||||||
|
status: 'upheld',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const dto = await service.reviewProtest(baseCommand);
|
const dto = await service.reviewProtest(baseCommand);
|
||||||
|
|
||||||
expect(presenter.getResponseModel()).not.toBeNull();
|
|
||||||
expect(executeMock).toHaveBeenCalledWith(baseCommand);
|
expect(executeMock).toHaveBeenCalledWith(baseCommand);
|
||||||
expect(dto).toEqual<ReviewProtestResponseDTO>({
|
expect(dto).toEqual<ReviewProtestResponseDTO>({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -63,8 +65,7 @@ describe('ProtestsService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
executeMock.mockImplementation(async () => {
|
executeMock.mockImplementation(async () => {
|
||||||
presenter.presentError(error);
|
return Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error);
|
||||||
return Result.err<void, ReviewProtestApplicationError>(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dto = await service.reviewProtest(baseCommand);
|
const dto = await service.reviewProtest(baseCommand);
|
||||||
@@ -83,8 +84,7 @@ describe('ProtestsService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
executeMock.mockImplementation(async () => {
|
executeMock.mockImplementation(async () => {
|
||||||
presenter.presentError(error);
|
return Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error);
|
||||||
return Result.err<void, ReviewProtestApplicationError>(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dto = await service.reviewProtest(baseCommand);
|
const dto = await service.reviewProtest(baseCommand);
|
||||||
@@ -103,8 +103,7 @@ describe('ProtestsService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
executeMock.mockImplementation(async () => {
|
executeMock.mockImplementation(async () => {
|
||||||
presenter.presentError(error);
|
return Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error);
|
||||||
return Result.err<void, ReviewProtestApplicationError>(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dto = await service.reviewProtest(baseCommand);
|
const dto = await service.reviewProtest(baseCommand);
|
||||||
@@ -124,8 +123,7 @@ describe('ProtestsService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
executeMock.mockImplementation(async () => {
|
executeMock.mockImplementation(async () => {
|
||||||
presenter.presentError(error);
|
return Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error);
|
||||||
return Result.err<void, ReviewProtestApplicationError>(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dto = await service.reviewProtest(baseCommand);
|
const dto = await service.reviewProtest(baseCommand);
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewP
|
|||||||
import { ReviewProtestPresenter, type ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';
|
import { ReviewProtestPresenter, type ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';
|
||||||
|
|
||||||
// Tokens
|
// Tokens
|
||||||
import { LOGGER_TOKEN, REVIEW_PROTEST_PRESENTER_TOKEN } from './ProtestsProviders';
|
import { LOGGER_TOKEN } from './ProtestsProviders';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProtestsService {
|
export class ProtestsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly reviewProtestUseCase: ReviewProtestUseCase,
|
private readonly reviewProtestUseCase: ReviewProtestUseCase,
|
||||||
@Inject(REVIEW_PROTEST_PRESENTER_TOKEN) private readonly reviewProtestPresenter: ReviewProtestPresenter,
|
private readonly reviewProtestPresenter: ReviewProtestPresenter,
|
||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -27,14 +27,20 @@ export class ProtestsService {
|
|||||||
}): Promise<ReviewProtestResponseDTO> {
|
}): Promise<ReviewProtestResponseDTO> {
|
||||||
this.logger.debug('[ProtestsService] Reviewing protest:', command);
|
this.logger.debug('[ProtestsService] Reviewing protest:', command);
|
||||||
|
|
||||||
// Set the command on the presenter so it can include stewardId and decision in the response
|
const result = await this.reviewProtestUseCase.execute(command);
|
||||||
this.reviewProtestPresenter.setCommand({
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const err = result.unwrapErr();
|
||||||
|
this.reviewProtestPresenter.presentError(err);
|
||||||
|
return this.reviewProtestPresenter.responseModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present the result with the additional context
|
||||||
|
this.reviewProtestPresenter.present(result.unwrap(), {
|
||||||
stewardId: command.stewardId,
|
stewardId: command.stewardId,
|
||||||
decision: command.decision,
|
decision: command.decision,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.reviewProtestUseCase.execute(command);
|
|
||||||
|
|
||||||
return this.reviewProtestPresenter.responseModel;
|
return this.reviewProtestPresenter.responseModel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,27 +12,21 @@ export interface ReviewProtestResponseDTO {
|
|||||||
|
|
||||||
export class ReviewProtestPresenter implements UseCaseOutputPort<ReviewProtestResult> {
|
export class ReviewProtestPresenter implements UseCaseOutputPort<ReviewProtestResult> {
|
||||||
private model: ReviewProtestResponseDTO | null = null;
|
private model: ReviewProtestResponseDTO | null = null;
|
||||||
private command: { stewardId: string; decision: 'uphold' | 'dismiss' } | null = null;
|
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.model = null;
|
this.model = null;
|
||||||
this.command = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCommand(command: { stewardId: string; decision: 'uphold' | 'dismiss' }): void {
|
present(result: ReviewProtestResult, context?: { stewardId: string; decision: 'uphold' | 'dismiss' }): void {
|
||||||
this.command = command;
|
if (!context) {
|
||||||
}
|
throw new Error('Context must be provided when presenting result');
|
||||||
|
|
||||||
present(result: ReviewProtestResult): void {
|
|
||||||
if (!this.command) {
|
|
||||||
throw new Error('Command must be set before presenting result');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.model = {
|
this.model = {
|
||||||
success: true,
|
success: true,
|
||||||
protestId: result.protestId,
|
protestId: result.protestId,
|
||||||
stewardId: this.command.stewardId,
|
stewardId: context.stewardId,
|
||||||
decision: this.command.decision,
|
decision: context.decision,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepo
|
|||||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
||||||
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
|
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
import type { Logger } from '@core/shared/application/Logger';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import { Result } from '@core/shared/application/Result';
|
|
||||||
|
|
||||||
// Import concrete in-memory implementations
|
// Import concrete in-memory implementations
|
||||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||||
@@ -43,29 +41,6 @@ import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases
|
|||||||
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||||
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
|
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
|
||||||
|
|
||||||
// Import use case result types
|
|
||||||
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
|
||||||
import type { GetTotalRacesResult } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
|
|
||||||
import type { ImportRaceResultsApiResult } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
|
|
||||||
import type { GetRaceDetailResult } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
|
|
||||||
import type { GetRacesPageDataResult } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
|
|
||||||
import type { GetAllRacesPageDataResult } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
|
|
||||||
import type { GetRaceResultsDetailResult } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
|
|
||||||
import type { GetRaceWithSOFResult } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
|
|
||||||
import type { GetRaceProtestsResult } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
|
||||||
import type { GetRacePenaltiesResult } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
|
|
||||||
import type { RegisterForRaceResult } from '@core/racing/application/use-cases/RegisterForRaceUseCase';
|
|
||||||
import type { WithdrawFromRaceResult } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
|
|
||||||
import type { CancelRaceResult } from '@core/racing/application/use-cases/CancelRaceUseCase';
|
|
||||||
import type { CompleteRaceResult } from '@core/racing/application/use-cases/CompleteRaceUseCase';
|
|
||||||
import type { ReopenRaceResult } from '@core/racing/application/use-cases/ReopenRaceUseCase';
|
|
||||||
import type { ImportRaceResultsResult } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
|
|
||||||
import type { FileProtestResult } from '@core/racing/application/use-cases/FileProtestUseCase';
|
|
||||||
import type { QuickPenaltyResult } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
|
|
||||||
import type { ApplyPenaltyResult } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
|
|
||||||
import type { RequestProtestDefenseResult } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
|
|
||||||
import type { ReviewProtestResult } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
|
||||||
|
|
||||||
// Import presenters
|
// Import presenters
|
||||||
import { AllRacesPageDataPresenter } from './presenters/AllRacesPageDataPresenter';
|
import { AllRacesPageDataPresenter } from './presenters/AllRacesPageDataPresenter';
|
||||||
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
|
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
|
||||||
@@ -107,183 +82,6 @@ import {
|
|||||||
|
|
||||||
export * from './RaceTokens';
|
export * from './RaceTokens';
|
||||||
|
|
||||||
// Adapter classes to bridge presenters with UseCaseOutputPort interface
|
|
||||||
class GetAllRacesOutputAdapter implements UseCaseOutputPort<GetAllRacesResult> {
|
|
||||||
constructor(private presenter: GetAllRacesPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetAllRacesResult): void {
|
|
||||||
this.presenter.present(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GetTotalRacesOutputAdapter implements UseCaseOutputPort<GetTotalRacesResult> {
|
|
||||||
constructor(private presenter: GetTotalRacesPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetTotalRacesResult): void {
|
|
||||||
// Wrap the result in a Result.ok() to match presenter expectations
|
|
||||||
const resultWrapper = Result.ok<GetTotalRacesResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
|
|
||||||
this.presenter.present(resultWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImportRaceResultsApiOutputAdapter implements UseCaseOutputPort<ImportRaceResultsApiResult> {
|
|
||||||
constructor(private presenter: ImportRaceResultsApiPresenter) {}
|
|
||||||
|
|
||||||
present(result: ImportRaceResultsApiResult): void {
|
|
||||||
const resultWrapper = Result.ok<ImportRaceResultsApiResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
|
|
||||||
this.presenter.present(resultWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RaceDetailOutputAdapter implements UseCaseOutputPort<GetRaceDetailResult> {
|
|
||||||
constructor(private presenter: RaceDetailPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetRaceDetailResult): void {
|
|
||||||
this.presenter.present(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RacesPageDataOutputAdapter implements UseCaseOutputPort<GetRacesPageDataResult> {
|
|
||||||
constructor(private presenter: RacesPageDataPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetRacesPageDataResult): void {
|
|
||||||
const resultWrapper = Result.ok<GetRacesPageDataResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
|
|
||||||
this.presenter.present(resultWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AllRacesPageDataOutputAdapter implements UseCaseOutputPort<GetAllRacesPageDataResult> {
|
|
||||||
constructor(private presenter: AllRacesPageDataPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetAllRacesPageDataResult): void {
|
|
||||||
const resultWrapper = Result.ok<GetAllRacesPageDataResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
|
|
||||||
this.presenter.present(resultWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RaceResultsDetailOutputAdapter implements UseCaseOutputPort<GetRaceResultsDetailResult> {
|
|
||||||
constructor(private presenter: RaceResultsDetailPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetRaceResultsDetailResult): void {
|
|
||||||
this.presenter.present(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RaceWithSOFOutputAdapter implements UseCaseOutputPort<GetRaceWithSOFResult> {
|
|
||||||
constructor(private presenter: RaceWithSOFPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetRaceWithSOFResult): void {
|
|
||||||
const resultWrapper = Result.ok<GetRaceWithSOFResult, { code: 'RACE_NOT_FOUND' | 'REPOSITORY_ERROR'; details: { message: string } }>(result);
|
|
||||||
this.presenter.present(resultWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RaceProtestsOutputAdapter implements UseCaseOutputPort<GetRaceProtestsResult> {
|
|
||||||
constructor(private presenter: RaceProtestsPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetRaceProtestsResult): void {
|
|
||||||
const resultWrapper = Result.ok<GetRaceProtestsResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
|
|
||||||
this.presenter.present(resultWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RacePenaltiesOutputAdapter implements UseCaseOutputPort<GetRacePenaltiesResult> {
|
|
||||||
constructor(private presenter: RacePenaltiesPresenter) {}
|
|
||||||
|
|
||||||
present(result: GetRacePenaltiesResult): void {
|
|
||||||
const resultWrapper = Result.ok<GetRacePenaltiesResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
|
|
||||||
this.presenter.present(resultWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RegisterForRaceOutputAdapter implements UseCaseOutputPort<RegisterForRaceResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Race registered successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WithdrawFromRaceOutputAdapter implements UseCaseOutputPort<WithdrawFromRaceResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Race withdrawal successful');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CancelRaceOutputAdapter implements UseCaseOutputPort<CancelRaceResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Race cancelled successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CompleteRaceOutputAdapter implements UseCaseOutputPort<CompleteRaceResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Race completed successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReopenRaceOutputAdapter implements UseCaseOutputPort<ReopenRaceResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Race reopened successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImportRaceResultsOutputAdapter implements UseCaseOutputPort<ImportRaceResultsResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Race results imported successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FileProtestOutputAdapter implements UseCaseOutputPort<FileProtestResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Protest filed successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuickPenaltyOutputAdapter implements UseCaseOutputPort<QuickPenaltyResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Penalty applied successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApplyPenaltyOutputAdapter implements UseCaseOutputPort<ApplyPenaltyResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Penalty applied successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RequestProtestDefenseOutputAdapter implements UseCaseOutputPort<RequestProtestDefenseResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Defense request sent successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReviewProtestOutputAdapter implements UseCaseOutputPort<ReviewProtestResult> {
|
|
||||||
constructor(private presenter: CommandResultPresenter) {}
|
|
||||||
|
|
||||||
present(): void {
|
|
||||||
this.presenter.presentSuccess('Protest reviewed successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RaceProviders: Provider[] = [
|
export const RaceProviders: Provider[] = [
|
||||||
{
|
{
|
||||||
provide: DRIVER_RATING_PROVIDER_TOKEN,
|
provide: DRIVER_RATING_PROVIDER_TOKEN,
|
||||||
@@ -354,24 +152,19 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: GetAllRacesPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
const useCase = new GetAllRacesUseCase(raceRepo, leagueRepo, logger);
|
return new GetAllRacesUseCase(raceRepo, leagueRepo, logger);
|
||||||
useCase.setOutput(new GetAllRacesOutputAdapter(presenter));
|
|
||||||
return useCase;
|
|
||||||
},
|
},
|
||||||
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, GET_ALL_RACES_PRESENTER_TOKEN],
|
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GetTotalRacesUseCase,
|
provide: GetTotalRacesUseCase,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
logger: Logger,
|
|
||||||
presenter: GetTotalRacesPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new GetTotalRacesUseCase(raceRepo, logger, new GetTotalRacesOutputAdapter(presenter));
|
return new GetTotalRacesUseCase(raceRepo);
|
||||||
},
|
},
|
||||||
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, GET_TOTAL_RACES_PRESENTER_TOKEN],
|
inject: [RACE_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: ImportRaceResultsApiUseCase,
|
provide: ImportRaceResultsApiUseCase,
|
||||||
@@ -382,7 +175,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
standingRepo: IStandingRepository,
|
standingRepo: IStandingRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: ImportRaceResultsApiPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new ImportRaceResultsApiUseCase(
|
return new ImportRaceResultsApiUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
@@ -391,7 +183,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
driverRepo,
|
driverRepo,
|
||||||
standingRepo,
|
standingRepo,
|
||||||
logger,
|
logger,
|
||||||
new ImportRaceResultsApiOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
@@ -401,7 +192,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
STANDING_REPOSITORY_TOKEN,
|
STANDING_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -413,7 +203,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRegRepo: IRaceRegistrationRepository,
|
raceRegRepo: IRaceRegistrationRepository,
|
||||||
resultRepo: IResultRepository,
|
resultRepo: IResultRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
presenter: RaceDetailPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new GetRaceDetailUseCase(
|
return new GetRaceDetailUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
@@ -422,7 +211,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRegRepo,
|
raceRegRepo,
|
||||||
resultRepo,
|
resultRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
new RaceDetailOutputAdapter(presenter),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
@@ -432,7 +220,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||||
RESULT_REPOSITORY_TOKEN,
|
RESULT_REPOSITORY_TOKEN,
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
RACE_DETAIL_PRESENTER_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -441,16 +228,14 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: RacesPageDataPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new GetRacesPageDataUseCase(
|
return new GetRacesPageDataUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
leagueRepo,
|
leagueRepo,
|
||||||
logger,
|
logger,
|
||||||
new RacesPageDataOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, RACES_PAGE_DATA_PRESENTER_TOKEN],
|
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GetAllRacesPageDataUseCase,
|
provide: GetAllRacesPageDataUseCase,
|
||||||
@@ -458,16 +243,14 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: AllRacesPageDataPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new GetAllRacesPageDataUseCase(
|
return new GetAllRacesPageDataUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
leagueRepo,
|
leagueRepo,
|
||||||
logger,
|
logger,
|
||||||
new AllRacesPageDataOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, ALL_RACES_PAGE_DATA_PRESENTER_TOKEN],
|
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GetRaceResultsDetailUseCase,
|
provide: GetRaceResultsDetailUseCase,
|
||||||
@@ -477,7 +260,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
resultRepo: IResultRepository,
|
resultRepo: IResultRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
penaltyRepo: IPenaltyRepository,
|
penaltyRepo: IPenaltyRepository,
|
||||||
presenter: RaceResultsDetailPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new GetRaceResultsDetailUseCase(
|
return new GetRaceResultsDetailUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
@@ -485,7 +267,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
resultRepo,
|
resultRepo,
|
||||||
driverRepo,
|
driverRepo,
|
||||||
penaltyRepo,
|
penaltyRepo,
|
||||||
new RaceResultsDetailOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
@@ -494,7 +275,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
RESULT_REPOSITORY_TOKEN,
|
RESULT_REPOSITORY_TOKEN,
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
PENALTY_REPOSITORY_TOKEN,
|
PENALTY_REPOSITORY_TOKEN,
|
||||||
RACE_RESULTS_DETAIL_PRESENTER_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -504,7 +284,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRegRepo: IRaceRegistrationRepository,
|
raceRegRepo: IRaceRegistrationRepository,
|
||||||
resultRepo: IResultRepository,
|
resultRepo: IResultRepository,
|
||||||
driverRatingProvider: DriverRatingProvider,
|
driverRatingProvider: DriverRatingProvider,
|
||||||
presenter: RaceWithSOFPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new GetRaceWithSOFUseCase(
|
return new GetRaceWithSOFUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
@@ -514,7 +293,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
const rating = driverRatingProvider.getRating(input.driverId);
|
const rating = driverRatingProvider.getRating(input.driverId);
|
||||||
return { rating };
|
return { rating };
|
||||||
},
|
},
|
||||||
new RaceWithSOFOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
@@ -522,7 +300,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
RACE_REGISTRATION_REPOSITORY_TOKEN,
|
||||||
RESULT_REPOSITORY_TOKEN,
|
RESULT_REPOSITORY_TOKEN,
|
||||||
DRIVER_RATING_PROVIDER_TOKEN,
|
DRIVER_RATING_PROVIDER_TOKEN,
|
||||||
RACE_WITH_SOF_PRESENTER_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -530,30 +307,26 @@ export const RaceProviders: Provider[] = [
|
|||||||
useFactory: (
|
useFactory: (
|
||||||
protestRepo: IProtestRepository,
|
protestRepo: IProtestRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
presenter: RaceProtestsPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new GetRaceProtestsUseCase(
|
return new GetRaceProtestsUseCase(
|
||||||
protestRepo,
|
protestRepo,
|
||||||
driverRepo,
|
driverRepo,
|
||||||
new RaceProtestsOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, RACE_PROTESTS_PRESENTER_TOKEN],
|
inject: [PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GetRacePenaltiesUseCase,
|
provide: GetRacePenaltiesUseCase,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
penaltyRepo: IPenaltyRepository,
|
penaltyRepo: IPenaltyRepository,
|
||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
presenter: RacePenaltiesPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new GetRacePenaltiesUseCase(
|
return new GetRacePenaltiesUseCase(
|
||||||
penaltyRepo,
|
penaltyRepo,
|
||||||
driverRepo,
|
driverRepo,
|
||||||
new RacePenaltiesOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [PENALTY_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, RACE_PENALTIES_PRESENTER_TOKEN],
|
inject: [PENALTY_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: RegisterForRaceUseCase,
|
provide: RegisterForRaceUseCase,
|
||||||
@@ -561,16 +334,14 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRegRepo: IRaceRegistrationRepository,
|
raceRegRepo: IRaceRegistrationRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new RegisterForRaceUseCase(
|
return new RegisterForRaceUseCase(
|
||||||
raceRegRepo,
|
raceRegRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
logger,
|
logger,
|
||||||
new RegisterForRaceOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: WithdrawFromRaceUseCase,
|
provide: WithdrawFromRaceUseCase,
|
||||||
@@ -578,31 +349,27 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
raceRegRepo: IRaceRegistrationRepository,
|
raceRegRepo: IRaceRegistrationRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new WithdrawFromRaceUseCase(
|
return new WithdrawFromRaceUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
raceRegRepo,
|
raceRegRepo,
|
||||||
logger,
|
logger,
|
||||||
new WithdrawFromRaceOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [RACE_REPOSITORY_TOKEN, RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [RACE_REPOSITORY_TOKEN, RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CancelRaceUseCase,
|
provide: CancelRaceUseCase,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new CancelRaceUseCase(
|
return new CancelRaceUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
logger,
|
logger,
|
||||||
new CancelRaceOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CompleteRaceUseCase,
|
provide: CompleteRaceUseCase,
|
||||||
@@ -612,7 +379,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
resultRepo: IResultRepository,
|
resultRepo: IResultRepository,
|
||||||
standingRepo: IStandingRepository,
|
standingRepo: IStandingRepository,
|
||||||
driverRatingProvider: DriverRatingProvider,
|
driverRatingProvider: DriverRatingProvider,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new CompleteRaceUseCase(
|
return new CompleteRaceUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
@@ -623,7 +389,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
const rating = driverRatingProvider.getRating(input.driverId);
|
const rating = driverRatingProvider.getRating(input.driverId);
|
||||||
return { rating, ratingChange: null };
|
return { rating, ratingChange: null };
|
||||||
},
|
},
|
||||||
new CompleteRaceOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
@@ -632,7 +397,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
RESULT_REPOSITORY_TOKEN,
|
RESULT_REPOSITORY_TOKEN,
|
||||||
STANDING_REPOSITORY_TOKEN,
|
STANDING_REPOSITORY_TOKEN,
|
||||||
DRIVER_RATING_PROVIDER_TOKEN,
|
DRIVER_RATING_PROVIDER_TOKEN,
|
||||||
COMMAND_RESULT_PRESENTER_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -640,15 +404,13 @@ export const RaceProviders: Provider[] = [
|
|||||||
useFactory: (
|
useFactory: (
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new ReopenRaceUseCase(
|
return new ReopenRaceUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
logger,
|
logger,
|
||||||
new ReopenRaceOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: ImportRaceResultsUseCase,
|
provide: ImportRaceResultsUseCase,
|
||||||
@@ -659,7 +421,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
driverRepo: IDriverRepository,
|
driverRepo: IDriverRepository,
|
||||||
standingRepo: IStandingRepository,
|
standingRepo: IStandingRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new ImportRaceResultsUseCase(
|
return new ImportRaceResultsUseCase(
|
||||||
raceRepo,
|
raceRepo,
|
||||||
@@ -668,7 +429,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
driverRepo,
|
driverRepo,
|
||||||
standingRepo,
|
standingRepo,
|
||||||
logger,
|
logger,
|
||||||
new ImportRaceResultsOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
@@ -678,7 +438,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
STANDING_REPOSITORY_TOKEN,
|
STANDING_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
COMMAND_RESULT_PRESENTER_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -687,16 +446,14 @@ export const RaceProviders: Provider[] = [
|
|||||||
protestRepo: IProtestRepository,
|
protestRepo: IProtestRepository,
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new FileProtestUseCase(
|
return new FileProtestUseCase(
|
||||||
protestRepo,
|
protestRepo,
|
||||||
raceRepo,
|
raceRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
new FileProtestOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: QuickPenaltyUseCase,
|
provide: QuickPenaltyUseCase,
|
||||||
@@ -705,17 +462,15 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new QuickPenaltyUseCase(
|
return new QuickPenaltyUseCase(
|
||||||
penaltyRepo,
|
penaltyRepo,
|
||||||
raceRepo,
|
raceRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
logger,
|
logger,
|
||||||
new QuickPenaltyOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [PENALTY_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [PENALTY_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: ApplyPenaltyUseCase,
|
provide: ApplyPenaltyUseCase,
|
||||||
@@ -725,7 +480,6 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new ApplyPenaltyUseCase(
|
return new ApplyPenaltyUseCase(
|
||||||
penaltyRepo,
|
penaltyRepo,
|
||||||
@@ -733,10 +487,9 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo,
|
raceRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
logger,
|
logger,
|
||||||
new ApplyPenaltyOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [PENALTY_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [PENALTY_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: RequestProtestDefenseUseCase,
|
provide: RequestProtestDefenseUseCase,
|
||||||
@@ -745,17 +498,15 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new RequestProtestDefenseUseCase(
|
return new RequestProtestDefenseUseCase(
|
||||||
protestRepo,
|
protestRepo,
|
||||||
raceRepo,
|
raceRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
logger,
|
logger,
|
||||||
new RequestProtestDefenseOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: ReviewProtestUseCase,
|
provide: ReviewProtestUseCase,
|
||||||
@@ -764,16 +515,14 @@ export const RaceProviders: Provider[] = [
|
|||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
presenter: CommandResultPresenter,
|
|
||||||
) => {
|
) => {
|
||||||
return new ReviewProtestUseCase(
|
return new ReviewProtestUseCase(
|
||||||
protestRepo,
|
protestRepo,
|
||||||
raceRepo,
|
raceRepo,
|
||||||
leagueMembershipRepo,
|
leagueMembershipRepo,
|
||||||
logger,
|
logger,
|
||||||
new ReviewProtestOutputAdapter(presenter)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
|
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { RaceService } from './RaceService';
|
import { RaceService } from './RaceService';
|
||||||
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
|
||||||
describe('RaceService', () => {
|
describe('RaceService', () => {
|
||||||
it('invokes each use case and returns the corresponding presenter', async () => {
|
it('invokes each use case and returns the corresponding presenter', async () => {
|
||||||
const mkUseCase = () => ({ execute: vi.fn(async () => {}) });
|
// Mock use cases to return Result.ok()
|
||||||
|
const mkUseCase = (resultValue: any = { success: true }) => ({
|
||||||
|
execute: vi.fn(async () => Result.ok(resultValue))
|
||||||
|
});
|
||||||
|
|
||||||
const getAllRacesUseCase = mkUseCase();
|
const getAllRacesUseCase = mkUseCase({ races: [], leagues: [] });
|
||||||
const getTotalRacesUseCase = mkUseCase();
|
const getTotalRacesUseCase = mkUseCase({ totalRaces: 0 });
|
||||||
const importRaceResultsApiUseCase = mkUseCase();
|
const importRaceResultsApiUseCase = mkUseCase({ success: true, raceId: 'r1', driversProcessed: 0, resultsRecorded: 0, errors: [] });
|
||||||
const getRaceDetailUseCase = mkUseCase();
|
const getRaceDetailUseCase = mkUseCase({ race: null, league: null, drivers: [], isUserRegistered: false, canRegister: false });
|
||||||
const getRacesPageDataUseCase = mkUseCase();
|
const getRacesPageDataUseCase = mkUseCase({ races: [] });
|
||||||
const getAllRacesPageDataUseCase = mkUseCase();
|
const getAllRacesPageDataUseCase = mkUseCase({ races: [], filters: { statuses: [], leagues: [] } });
|
||||||
const getRaceResultsDetailUseCase = mkUseCase();
|
const getRaceResultsDetailUseCase = mkUseCase({ race: null, results: [], penalties: [] });
|
||||||
const getRaceWithSOFUseCase = mkUseCase();
|
const getRaceWithSOFUseCase = mkUseCase({ race: null, strengthOfField: 0, participantCount: 0, registeredCount: 0, maxParticipants: 0 });
|
||||||
const getRaceProtestsUseCase = mkUseCase();
|
const getRaceProtestsUseCase = mkUseCase({ protests: [], drivers: [] });
|
||||||
const getRacePenaltiesUseCase = mkUseCase();
|
const getRacePenaltiesUseCase = mkUseCase({ penalties: [], drivers: [] });
|
||||||
const registerForRaceUseCase = mkUseCase();
|
const registerForRaceUseCase = mkUseCase();
|
||||||
const withdrawFromRaceUseCase = mkUseCase();
|
const withdrawFromRaceUseCase = mkUseCase();
|
||||||
const cancelRaceUseCase = mkUseCase();
|
const cancelRaceUseCase = mkUseCase();
|
||||||
@@ -28,17 +32,17 @@ describe('RaceService', () => {
|
|||||||
|
|
||||||
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
||||||
|
|
||||||
const getAllRacesPresenter = {} as any;
|
const getAllRacesPresenter = { present: vi.fn() } as any;
|
||||||
const getTotalRacesPresenter = {} as any;
|
const getTotalRacesPresenter = { present: vi.fn() } as any;
|
||||||
const importRaceResultsApiPresenter = {} as any;
|
const importRaceResultsApiPresenter = { present: vi.fn() } as any;
|
||||||
const raceDetailPresenter = {} as any;
|
const raceDetailPresenter = { present: vi.fn() } as any;
|
||||||
const racesPageDataPresenter = {} as any;
|
const racesPageDataPresenter = { present: vi.fn() } as any;
|
||||||
const allRacesPageDataPresenter = {} as any;
|
const allRacesPageDataPresenter = { present: vi.fn() } as any;
|
||||||
const raceResultsDetailPresenter = {} as any;
|
const raceResultsDetailPresenter = { present: vi.fn() } as any;
|
||||||
const raceWithSOFPresenter = {} as any;
|
const raceWithSOFPresenter = { present: vi.fn() } as any;
|
||||||
const raceProtestsPresenter = {} as any;
|
const raceProtestsPresenter = { present: vi.fn() } as any;
|
||||||
const racePenaltiesPresenter = {} as any;
|
const racePenaltiesPresenter = { present: vi.fn() } as any;
|
||||||
const commandResultPresenter = {} as any;
|
const commandResultPresenter = { present: vi.fn() } as any;
|
||||||
|
|
||||||
const service = new RaceService(
|
const service = new RaceService(
|
||||||
getAllRacesUseCase as any,
|
getAllRacesUseCase as any,
|
||||||
@@ -77,62 +81,81 @@ describe('RaceService', () => {
|
|||||||
|
|
||||||
expect(await service.getAllRaces()).toBe(getAllRacesPresenter);
|
expect(await service.getAllRaces()).toBe(getAllRacesPresenter);
|
||||||
expect(getAllRacesUseCase.execute).toHaveBeenCalledWith({});
|
expect(getAllRacesUseCase.execute).toHaveBeenCalledWith({});
|
||||||
|
expect(getAllRacesPresenter.present).toHaveBeenCalledWith({ races: [], leagues: [] });
|
||||||
|
|
||||||
expect(await service.getTotalRaces()).toBe(getTotalRacesPresenter);
|
expect(await service.getTotalRaces()).toBe(getTotalRacesPresenter);
|
||||||
expect(getTotalRacesUseCase.execute).toHaveBeenCalledWith({});
|
expect(getTotalRacesUseCase.execute).toHaveBeenCalledWith({});
|
||||||
|
expect(getTotalRacesPresenter.present).toHaveBeenCalledWith({ totalRaces: 0 });
|
||||||
|
|
||||||
expect(await service.importRaceResults({ raceId: 'r1', resultsFileContent: 'x' } as any)).toBe(importRaceResultsApiPresenter);
|
expect(await service.importRaceResults({ raceId: 'r1', resultsFileContent: 'x' } as any)).toBe(importRaceResultsApiPresenter);
|
||||||
expect(importRaceResultsApiUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', resultsFileContent: 'x' });
|
expect(importRaceResultsApiUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', resultsFileContent: 'x' });
|
||||||
|
expect(importRaceResultsApiPresenter.present).toHaveBeenCalledWith({ success: true, raceId: 'r1', driversProcessed: 0, resultsRecorded: 0, errors: [] });
|
||||||
|
|
||||||
expect(await service.getRaceDetail({ raceId: 'r1' } as any)).toBe(raceDetailPresenter);
|
expect(await service.getRaceDetail({ raceId: 'r1' } as any)).toBe(raceDetailPresenter);
|
||||||
expect(getRaceDetailUseCase.execute).toHaveBeenCalled();
|
expect(getRaceDetailUseCase.execute).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.getRacesPageData('l1')).toBe(racesPageDataPresenter);
|
expect(await service.getRacesPageData('l1')).toBe(racesPageDataPresenter);
|
||||||
expect(getRacesPageDataUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
expect(getRacesPageDataUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||||
|
expect(racesPageDataPresenter.present).toHaveBeenCalledWith({ races: [] });
|
||||||
|
|
||||||
expect(await service.getAllRacesPageData()).toBe(allRacesPageDataPresenter);
|
expect(await service.getAllRacesPageData()).toBe(allRacesPageDataPresenter);
|
||||||
expect(getAllRacesPageDataUseCase.execute).toHaveBeenCalledWith({});
|
expect(getAllRacesPageDataUseCase.execute).toHaveBeenCalledWith({});
|
||||||
|
expect(allRacesPageDataPresenter.present).toHaveBeenCalledWith({ races: [], filters: { statuses: [], leagues: [] } });
|
||||||
|
|
||||||
expect(await service.getRaceResultsDetail('r1')).toBe(raceResultsDetailPresenter);
|
expect(await service.getRaceResultsDetail('r1')).toBe(raceResultsDetailPresenter);
|
||||||
expect(getRaceResultsDetailUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
expect(getRaceResultsDetailUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
||||||
|
expect(raceResultsDetailPresenter.present).toHaveBeenCalledWith({ race: null, results: [], penalties: [] });
|
||||||
|
|
||||||
expect(await service.getRaceWithSOF('r1')).toBe(raceWithSOFPresenter);
|
expect(await service.getRaceWithSOF('r1')).toBe(raceWithSOFPresenter);
|
||||||
expect(getRaceWithSOFUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
expect(getRaceWithSOFUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
||||||
|
expect(raceWithSOFPresenter.present).toHaveBeenCalledWith({ race: null, strengthOfField: 0, participantCount: 0, registeredCount: 0, maxParticipants: 0 });
|
||||||
|
|
||||||
expect(await service.getRaceProtests('r1')).toBe(raceProtestsPresenter);
|
expect(await service.getRaceProtests('r1')).toBe(raceProtestsPresenter);
|
||||||
expect(getRaceProtestsUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
expect(getRaceProtestsUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
||||||
|
expect(raceProtestsPresenter.present).toHaveBeenCalledWith({ protests: [], drivers: [] });
|
||||||
|
|
||||||
expect(await service.getRacePenalties('r1')).toBe(racePenaltiesPresenter);
|
expect(await service.getRacePenalties('r1')).toBe(racePenaltiesPresenter);
|
||||||
expect(getRacePenaltiesUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
expect(getRacePenaltiesUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
||||||
|
expect(racePenaltiesPresenter.present).toHaveBeenCalledWith({ penalties: [], drivers: [] });
|
||||||
|
|
||||||
expect(await service.registerForRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
|
expect(await service.registerForRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
|
||||||
expect(registerForRaceUseCase.execute).toHaveBeenCalled();
|
expect(registerForRaceUseCase.execute).toHaveBeenCalled();
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.withdrawFromRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
|
expect(await service.withdrawFromRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
|
||||||
expect(withdrawFromRaceUseCase.execute).toHaveBeenCalled();
|
expect(withdrawFromRaceUseCase.execute).toHaveBeenCalled();
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.cancelRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
|
expect(await service.cancelRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
|
||||||
expect(cancelRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', cancelledById: 'admin' });
|
expect(cancelRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', cancelledById: 'admin' });
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.completeRace({ raceId: 'r1' } as any)).toBe(commandResultPresenter);
|
expect(await service.completeRace({ raceId: 'r1' } as any)).toBe(commandResultPresenter);
|
||||||
expect(completeRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
expect(completeRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.reopenRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
|
expect(await service.reopenRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
|
||||||
expect(reopenRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', reopenedById: 'admin' });
|
expect(reopenRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', reopenedById: 'admin' });
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.fileProtest({} as any)).toBe(commandResultPresenter);
|
expect(await service.fileProtest({} as any)).toBe(commandResultPresenter);
|
||||||
expect(fileProtestUseCase.execute).toHaveBeenCalled();
|
expect(fileProtestUseCase.execute).toHaveBeenCalled();
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.applyQuickPenalty({} as any)).toBe(commandResultPresenter);
|
expect(await service.applyQuickPenalty({} as any)).toBe(commandResultPresenter);
|
||||||
expect(quickPenaltyUseCase.execute).toHaveBeenCalled();
|
expect(quickPenaltyUseCase.execute).toHaveBeenCalled();
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.applyPenalty({} as any)).toBe(commandResultPresenter);
|
expect(await service.applyPenalty({} as any)).toBe(commandResultPresenter);
|
||||||
expect(applyPenaltyUseCase.execute).toHaveBeenCalled();
|
expect(applyPenaltyUseCase.execute).toHaveBeenCalled();
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.requestProtestDefense({} as any)).toBe(commandResultPresenter);
|
expect(await service.requestProtestDefense({} as any)).toBe(commandResultPresenter);
|
||||||
expect(requestProtestDefenseUseCase.execute).toHaveBeenCalled();
|
expect(requestProtestDefenseUseCase.execute).toHaveBeenCalled();
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(await service.reviewProtest({} as any)).toBe(commandResultPresenter);
|
expect(await service.reviewProtest({} as any)).toBe(commandResultPresenter);
|
||||||
expect(reviewProtestUseCase.execute).toHaveBeenCalled();
|
expect(reviewProtestUseCase.execute).toHaveBeenCalled();
|
||||||
|
expect(commandResultPresenter.present).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
|
||||||
// DTOs
|
// DTOs
|
||||||
import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO';
|
import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO';
|
||||||
@@ -116,55 +116,127 @@ export class RaceService {
|
|||||||
|
|
||||||
async getAllRaces(): Promise<GetAllRacesPresenter> {
|
async getAllRaces(): Promise<GetAllRacesPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching all races.');
|
this.logger.debug('[RaceService] Fetching all races.');
|
||||||
await this.getAllRacesUseCase.execute({});
|
const result = await this.getAllRacesUseCase.execute({});
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get all races');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.getAllRacesPresenter.present(value);
|
||||||
return this.getAllRacesPresenter;
|
return this.getAllRacesPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTotalRaces(): Promise<GetTotalRacesPresenter> {
|
async getTotalRaces(): Promise<GetTotalRacesPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching total races count.');
|
this.logger.debug('[RaceService] Fetching total races count.');
|
||||||
await this.getTotalRacesUseCase.execute({});
|
const result = await this.getTotalRacesUseCase.execute({});
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get total races');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.getTotalRacesPresenter.present(value);
|
||||||
return this.getTotalRacesPresenter;
|
return this.getTotalRacesPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async importRaceResults(input: ImportRaceResultsDTO): Promise<ImportRaceResultsApiPresenter> {
|
async importRaceResults(input: ImportRaceResultsDTO): Promise<ImportRaceResultsApiPresenter> {
|
||||||
this.logger.debug('Importing race results:', input);
|
this.logger.debug('Importing race results:', input);
|
||||||
await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent });
|
const result = await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to import race results');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.importRaceResultsApiPresenter.present(value);
|
||||||
return this.importRaceResultsApiPresenter;
|
return this.importRaceResultsApiPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailPresenter> {
|
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching race detail:', params);
|
this.logger.debug('[RaceService] Fetching race detail:', params);
|
||||||
await this.getRaceDetailUseCase.execute(params);
|
const result = await this.getRaceDetailUseCase.execute(params);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get race detail');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.raceDetailPresenter.present(value);
|
||||||
return this.raceDetailPresenter;
|
return this.raceDetailPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRacesPageData(leagueId: string): Promise<RacesPageDataPresenter> {
|
async getRacesPageData(leagueId: string): Promise<RacesPageDataPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching races page data.');
|
this.logger.debug('[RaceService] Fetching races page data.');
|
||||||
await this.getRacesPageDataUseCase.execute({ leagueId });
|
const result = await this.getRacesPageDataUseCase.execute({ leagueId });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get races page data');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.racesPageDataPresenter.present(value);
|
||||||
return this.racesPageDataPresenter;
|
return this.racesPageDataPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllRacesPageData(): Promise<AllRacesPageDataPresenter> {
|
async getAllRacesPageData(): Promise<AllRacesPageDataPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching all races page data.');
|
this.logger.debug('[RaceService] Fetching all races page data.');
|
||||||
await this.getAllRacesPageDataUseCase.execute({});
|
const result = await this.getAllRacesPageDataUseCase.execute({});
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get all races page data');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.allRacesPageDataPresenter.present(value);
|
||||||
return this.allRacesPageDataPresenter;
|
return this.allRacesPageDataPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRaceResultsDetail(raceId: string): Promise<RaceResultsDetailPresenter> {
|
async getRaceResultsDetail(raceId: string): Promise<RaceResultsDetailPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching race results detail:', { raceId });
|
this.logger.debug('[RaceService] Fetching race results detail:', { raceId });
|
||||||
await this.getRaceResultsDetailUseCase.execute({ raceId });
|
const result = await this.getRaceResultsDetailUseCase.execute({ raceId });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get race results detail');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.raceResultsDetailPresenter.present(value);
|
||||||
return this.raceResultsDetailPresenter;
|
return this.raceResultsDetailPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRaceWithSOF(raceId: string): Promise<RaceWithSOFPresenter> {
|
async getRaceWithSOF(raceId: string): Promise<RaceWithSOFPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching race with SOF:', { raceId });
|
this.logger.debug('[RaceService] Fetching race with SOF:', { raceId });
|
||||||
await this.getRaceWithSOFUseCase.execute({ raceId });
|
const result = await this.getRaceWithSOFUseCase.execute({ raceId });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get race with SOF');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.raceWithSOFPresenter.present(value);
|
||||||
return this.raceWithSOFPresenter;
|
return this.raceWithSOFPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRaceProtests(raceId: string): Promise<RaceProtestsPresenter> {
|
async getRaceProtests(raceId: string): Promise<RaceProtestsPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching race protests:', { raceId });
|
this.logger.debug('[RaceService] Fetching race protests:', { raceId });
|
||||||
await this.getRaceProtestsUseCase.execute({ raceId });
|
const result = await this.getRaceProtestsUseCase.execute({ raceId });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get race protests');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.raceProtestsPresenter.present(value);
|
||||||
return this.raceProtestsPresenter;
|
return this.raceProtestsPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,67 +258,145 @@ export class RaceService {
|
|||||||
|
|
||||||
async getRacePenalties(raceId: string): Promise<RacePenaltiesPresenter> {
|
async getRacePenalties(raceId: string): Promise<RacePenaltiesPresenter> {
|
||||||
this.logger.debug('[RaceService] Fetching race penalties:', { raceId });
|
this.logger.debug('[RaceService] Fetching race penalties:', { raceId });
|
||||||
await this.getRacePenaltiesUseCase.execute({ raceId });
|
const result = await this.getRacePenaltiesUseCase.execute({ raceId });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to get race penalties');
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = result.unwrap();
|
||||||
|
this.racePenaltiesPresenter.present(value);
|
||||||
return this.racePenaltiesPresenter;
|
return this.racePenaltiesPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerForRace(params: RegisterForRaceParamsDTO): Promise<CommandResultPresenter> {
|
async registerForRace(params: RegisterForRaceParamsDTO): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Registering for race:', params);
|
this.logger.debug('[RaceService] Registering for race:', params);
|
||||||
await this.registerForRaceUseCase.execute(params);
|
const result = await this.registerForRaceUseCase.execute(params);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to register for race');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async withdrawFromRace(params: WithdrawFromRaceParamsDTO): Promise<CommandResultPresenter> {
|
async withdrawFromRace(params: WithdrawFromRaceParamsDTO): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Withdrawing from race:', params);
|
this.logger.debug('[RaceService] Withdrawing from race:', params);
|
||||||
await this.withdrawFromRaceUseCase.execute(params);
|
const result = await this.withdrawFromRaceUseCase.execute(params);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to withdraw from race');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelRace(params: RaceActionParamsDTO, cancelledById: string): Promise<CommandResultPresenter> {
|
async cancelRace(params: RaceActionParamsDTO, cancelledById: string): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Cancelling race:', params);
|
this.logger.debug('[RaceService] Cancelling race:', params);
|
||||||
await this.cancelRaceUseCase.execute({ raceId: params.raceId, cancelledById });
|
const result = await this.cancelRaceUseCase.execute({ raceId: params.raceId, cancelledById });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to cancel race');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async completeRace(params: RaceActionParamsDTO): Promise<CommandResultPresenter> {
|
async completeRace(params: RaceActionParamsDTO): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Completing race:', params);
|
this.logger.debug('[RaceService] Completing race:', params);
|
||||||
await this.completeRaceUseCase.execute({ raceId: params.raceId });
|
const result = await this.completeRaceUseCase.execute({ raceId: params.raceId });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to complete race');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async reopenRace(params: RaceActionParamsDTO, reopenedById: string): Promise<CommandResultPresenter> {
|
async reopenRace(params: RaceActionParamsDTO, reopenedById: string): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Re-opening race:', params);
|
this.logger.debug('[RaceService] Re-opening race:', params);
|
||||||
await this.reopenRaceUseCase.execute({ raceId: params.raceId, reopenedById });
|
const result = await this.reopenRaceUseCase.execute({ raceId: params.raceId, reopenedById });
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to reopen race');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fileProtest(command: FileProtestCommandDTO): Promise<CommandResultPresenter> {
|
async fileProtest(command: FileProtestCommandDTO): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Filing protest:', command);
|
this.logger.debug('[RaceService] Filing protest:', command);
|
||||||
await this.fileProtestUseCase.execute(command);
|
const result = await this.fileProtestUseCase.execute(command);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to file protest');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyQuickPenalty(command: QuickPenaltyCommandDTO): Promise<CommandResultPresenter> {
|
async applyQuickPenalty(command: QuickPenaltyCommandDTO): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Applying quick penalty:', command);
|
this.logger.debug('[RaceService] Applying quick penalty:', command);
|
||||||
await this.quickPenaltyUseCase.execute(command);
|
const result = await this.quickPenaltyUseCase.execute(command);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to apply quick penalty');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyPenalty(command: ApplyPenaltyCommandDTO): Promise<CommandResultPresenter> {
|
async applyPenalty(command: ApplyPenaltyCommandDTO): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Applying penalty:', command);
|
this.logger.debug('[RaceService] Applying penalty:', command);
|
||||||
await this.applyPenaltyUseCase.execute(command);
|
const result = await this.applyPenaltyUseCase.execute(command);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
// ApplyPenaltyUseCase errors don't have details, just code
|
||||||
|
throw new NotFoundException('Failed to apply penalty');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestProtestDefense(command: RequestProtestDefenseCommandDTO): Promise<CommandResultPresenter> {
|
async requestProtestDefense(command: RequestProtestDefenseCommandDTO): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Requesting protest defense:', command);
|
this.logger.debug('[RaceService] Requesting protest defense:', command);
|
||||||
await this.requestProtestDefenseUseCase.execute(command);
|
const result = await this.requestProtestDefenseUseCase.execute(command);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to request protest defense');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async reviewProtest(command: ReviewProtestCommandDTO): Promise<CommandResultPresenter> {
|
async reviewProtest(command: ReviewProtestCommandDTO): Promise<CommandResultPresenter> {
|
||||||
this.logger.debug('[RaceService] Reviewing protest:', command);
|
this.logger.debug('[RaceService] Reviewing protest:', command);
|
||||||
await this.reviewProtestUseCase.execute(command);
|
const result = await this.reviewProtestUseCase.execute(command);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
const error = result.unwrapErr();
|
||||||
|
throw new NotFoundException(error.details?.message ?? 'Failed to review protest');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandResultPresenter.present();
|
||||||
return this.commandResultPresenter;
|
return this.commandResultPresenter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,8 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { GetAllRacesPageDataResult } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import type {
|
|
||||||
GetAllRacesPageDataResult,
|
|
||||||
GetAllRacesPageDataErrorCode,
|
|
||||||
} from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
|
|
||||||
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
||||||
|
|
||||||
export type AllRacesPageDataResponseModel = AllRacesPageDTO;
|
export type AllRacesPageDataResponseModel = AllRacesPageDTO;
|
||||||
|
|
||||||
export type GetAllRacesPageDataApplicationError = ApplicationErrorCode<
|
|
||||||
GetAllRacesPageDataErrorCode,
|
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class AllRacesPageDataPresenter {
|
export class AllRacesPageDataPresenter {
|
||||||
private model: AllRacesPageDataResponseModel | null = null;
|
private model: AllRacesPageDataResponseModel | null = null;
|
||||||
|
|
||||||
@@ -20,19 +10,10 @@ export class AllRacesPageDataPresenter {
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(
|
present(result: GetAllRacesPageDataResult): void {
|
||||||
result: Result<GetAllRacesPageDataResult, GetAllRacesPageDataApplicationError>,
|
|
||||||
): void {
|
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get all races page data');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
this.model = {
|
this.model = {
|
||||||
races: output.races,
|
races: result.races,
|
||||||
filters: output.filters,
|
filters: result.filters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
|
|
||||||
export interface CommandResultDTO {
|
export interface CommandResultDTO {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
errorCode?: string;
|
errorCode?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CommandApplicationError = ApplicationErrorCode<
|
|
||||||
string,
|
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class CommandResultPresenter {
|
export class CommandResultPresenter {
|
||||||
private model: CommandResultDTO | null = null;
|
private model: CommandResultDTO | null = null;
|
||||||
|
|
||||||
@@ -19,17 +11,9 @@ export class CommandResultPresenter {
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: Result<unknown, CommandApplicationError>): void {
|
present(): void {
|
||||||
if (result.isErr()) {
|
// For command use cases, if we get here, it was successful
|
||||||
const error = result.unwrapErr();
|
// The service handles errors by throwing exceptions
|
||||||
this.model = {
|
|
||||||
success: false,
|
|
||||||
errorCode: error.code,
|
|
||||||
message: error.details?.message,
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.model = { success: true };
|
this.model = { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
|
||||||
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
|
||||||
|
|
||||||
export type GetAllRacesResponseModel = AllRacesPageDTO;
|
export type GetAllRacesResponseModel = AllRacesPageDTO;
|
||||||
|
|
||||||
export class GetAllRacesPresenter implements UseCaseOutputPort<GetAllRacesResult> {
|
export class GetAllRacesPresenter {
|
||||||
private model: GetAllRacesResponseModel | null = null;
|
private model: GetAllRacesResponseModel | null = null;
|
||||||
|
|
||||||
present(result: GetAllRacesResult): void {
|
present(result: GetAllRacesResult): void {
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { GetTotalRacesResult } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import type {
|
|
||||||
GetTotalRacesResult,
|
|
||||||
GetTotalRacesErrorCode,
|
|
||||||
} from '@core/racing/application/use-cases/GetTotalRacesUseCase';
|
|
||||||
import type { RaceStatsDTO } from '../dtos/RaceStatsDTO';
|
import type { RaceStatsDTO } from '../dtos/RaceStatsDTO';
|
||||||
|
|
||||||
export type GetTotalRacesResponseModel = RaceStatsDTO;
|
export type GetTotalRacesResponseModel = RaceStatsDTO;
|
||||||
|
|
||||||
export type GetTotalRacesApplicationError = ApplicationErrorCode<
|
|
||||||
GetTotalRacesErrorCode,
|
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class GetTotalRacesPresenter {
|
export class GetTotalRacesPresenter {
|
||||||
private model: GetTotalRacesResponseModel | null = null;
|
private model: GetTotalRacesResponseModel | null = null;
|
||||||
|
|
||||||
@@ -20,16 +10,9 @@ export class GetTotalRacesPresenter {
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: Result<GetTotalRacesResult, GetTotalRacesApplicationError>): void {
|
present(result: GetTotalRacesResult): void {
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get total races');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
this.model = {
|
this.model = {
|
||||||
totalRaces: output.totalRaces,
|
totalRaces: result.totalRaces,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { ImportRaceResultsApiResult } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import type {
|
|
||||||
ImportRaceResultsApiResult,
|
|
||||||
ImportRaceResultsApiErrorCode,
|
|
||||||
} from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
|
|
||||||
import { ImportRaceResultsSummaryDTO } from '../dtos/ImportRaceResultsSummaryDTO';
|
import { ImportRaceResultsSummaryDTO } from '../dtos/ImportRaceResultsSummaryDTO';
|
||||||
|
|
||||||
export type ImportRaceResultsApiResponseModel = ImportRaceResultsSummaryDTO;
|
export type ImportRaceResultsApiResponseModel = ImportRaceResultsSummaryDTO;
|
||||||
|
|
||||||
export type ImportRaceResultsApiApplicationError = ApplicationErrorCode<
|
|
||||||
ImportRaceResultsApiErrorCode,
|
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class ImportRaceResultsApiPresenter {
|
export class ImportRaceResultsApiPresenter {
|
||||||
private model: ImportRaceResultsApiResponseModel | null = null;
|
private model: ImportRaceResultsApiResponseModel | null = null;
|
||||||
|
|
||||||
@@ -20,22 +10,13 @@ export class ImportRaceResultsApiPresenter {
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(
|
present(result: ImportRaceResultsApiResult): void {
|
||||||
result: Result<ImportRaceResultsApiResult, ImportRaceResultsApiApplicationError>,
|
|
||||||
): void {
|
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to import race results');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
this.model = {
|
this.model = {
|
||||||
success: output.success,
|
success: result.success,
|
||||||
raceId: output.raceId,
|
raceId: result.raceId,
|
||||||
driversProcessed: output.driversProcessed,
|
driversProcessed: result.driversProcessed,
|
||||||
resultsRecorded: output.resultsRecorded,
|
resultsRecorded: result.resultsRecorded,
|
||||||
errors: output.errors,
|
errors: result.errors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import type { GetRaceDetailResult } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
|
import type { GetRaceDetailResult } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
|
||||||
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
|
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
|
||||||
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
|
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
|
||||||
@@ -12,7 +11,7 @@ import type { RaceDetailUserResultDTO } from '../dtos/RaceDetailUserResultDTO';
|
|||||||
|
|
||||||
export type GetRaceDetailResponseModel = RaceDetailDTO;
|
export type GetRaceDetailResponseModel = RaceDetailDTO;
|
||||||
|
|
||||||
export class RaceDetailPresenter implements UseCaseOutputPort<GetRaceDetailResult> {
|
export class RaceDetailPresenter {
|
||||||
private result: GetRaceDetailResult | null = null;
|
private result: GetRaceDetailResult | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { GetRacePenaltiesResult } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import type {
|
|
||||||
GetRacePenaltiesResult,
|
|
||||||
GetRacePenaltiesErrorCode,
|
|
||||||
} from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
|
|
||||||
import type { RacePenaltiesDTO } from '../dtos/RacePenaltiesDTO';
|
import type { RacePenaltiesDTO } from '../dtos/RacePenaltiesDTO';
|
||||||
import type { RacePenaltyDTO } from '../dtos/RacePenaltyDTO';
|
import type { RacePenaltyDTO } from '../dtos/RacePenaltyDTO';
|
||||||
|
|
||||||
export type GetRacePenaltiesResponseModel = RacePenaltiesDTO;
|
export type GetRacePenaltiesResponseModel = RacePenaltiesDTO;
|
||||||
|
|
||||||
export type GetRacePenaltiesApplicationError = ApplicationErrorCode<
|
|
||||||
GetRacePenaltiesErrorCode,
|
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class RacePenaltiesPresenter {
|
export class RacePenaltiesPresenter {
|
||||||
private model: GetRacePenaltiesResponseModel | null = null;
|
private model: GetRacePenaltiesResponseModel | null = null;
|
||||||
|
|
||||||
@@ -21,15 +11,8 @@ export class RacePenaltiesPresenter {
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: Result<GetRacePenaltiesResult, GetRacePenaltiesApplicationError>): void {
|
present(result: GetRacePenaltiesResult): void {
|
||||||
if (result.isErr()) {
|
const penalties: RacePenaltyDTO[] = result.penalties.map(penalty => ({
|
||||||
const error = result.unwrapErr();
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get race penalties');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
const penalties: RacePenaltyDTO[] = output.penalties.map(penalty => ({
|
|
||||||
id: penalty.id,
|
id: penalty.id,
|
||||||
driverId: penalty.driverId,
|
driverId: penalty.driverId,
|
||||||
type: penalty.type,
|
type: penalty.type,
|
||||||
@@ -41,7 +24,7 @@ export class RacePenaltiesPresenter {
|
|||||||
} as RacePenaltyDTO));
|
} as RacePenaltyDTO));
|
||||||
|
|
||||||
const driverMap: Record<string, string> = {};
|
const driverMap: Record<string, string> = {};
|
||||||
output.drivers.forEach(driver => {
|
result.drivers.forEach(driver => {
|
||||||
driverMap[driver.id] = driver.name.toString();
|
driverMap[driver.id] = driver.name.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { GetRaceProtestsResult } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import type {
|
|
||||||
GetRaceProtestsResult,
|
|
||||||
GetRaceProtestsErrorCode,
|
|
||||||
} from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
|
||||||
import type { RaceProtestsDTO } from '../dtos/RaceProtestsDTO';
|
import type { RaceProtestsDTO } from '../dtos/RaceProtestsDTO';
|
||||||
import type { RaceProtestDTO } from '../dtos/RaceProtestDTO';
|
import type { RaceProtestDTO } from '../dtos/RaceProtestDTO';
|
||||||
|
|
||||||
export type GetRaceProtestsResponseModel = RaceProtestsDTO;
|
export type GetRaceProtestsResponseModel = RaceProtestsDTO;
|
||||||
|
|
||||||
export type GetRaceProtestsApplicationError = ApplicationErrorCode<
|
|
||||||
GetRaceProtestsErrorCode,
|
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class RaceProtestsPresenter {
|
export class RaceProtestsPresenter {
|
||||||
private model: GetRaceProtestsResponseModel | null = null;
|
private model: GetRaceProtestsResponseModel | null = null;
|
||||||
|
|
||||||
@@ -21,15 +11,8 @@ export class RaceProtestsPresenter {
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: Result<GetRaceProtestsResult, GetRaceProtestsApplicationError>): void {
|
present(result: GetRaceProtestsResult): void {
|
||||||
if (result.isErr()) {
|
const protests: RaceProtestDTO[] = result.protests.map(protest => ({
|
||||||
const error = result.unwrapErr();
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get race protests');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
const protests: RaceProtestDTO[] = output.protests.map(protest => ({
|
|
||||||
id: protest.id,
|
id: protest.id,
|
||||||
protestingDriverId: protest.protestingDriverId,
|
protestingDriverId: protest.protestingDriverId,
|
||||||
accusedDriverId: protest.accusedDriverId,
|
accusedDriverId: protest.accusedDriverId,
|
||||||
@@ -42,7 +25,7 @@ export class RaceProtestsPresenter {
|
|||||||
} as RaceProtestDTO));
|
} as RaceProtestDTO));
|
||||||
|
|
||||||
const driverMap: Record<string, string> = {};
|
const driverMap: Record<string, string> = {};
|
||||||
output.drivers.forEach(driver => {
|
result.drivers.forEach(driver => {
|
||||||
driverMap[driver.id] = driver.name.toString();
|
driverMap[driver.id] = driver.name.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { GetRaceWithSOFResult } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import type {
|
|
||||||
GetRaceWithSOFResult,
|
|
||||||
GetRaceWithSOFErrorCode,
|
|
||||||
} from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
|
|
||||||
import type { RaceWithSOFDTO } from '../dtos/RaceWithSOFDTO';
|
import type { RaceWithSOFDTO } from '../dtos/RaceWithSOFDTO';
|
||||||
|
|
||||||
export type GetRaceWithSOFResponseModel = RaceWithSOFDTO;
|
export type GetRaceWithSOFResponseModel = RaceWithSOFDTO;
|
||||||
|
|
||||||
export type GetRaceWithSOFApplicationError = ApplicationErrorCode<
|
|
||||||
GetRaceWithSOFErrorCode,
|
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class RaceWithSOFPresenter {
|
export class RaceWithSOFPresenter {
|
||||||
private model: GetRaceWithSOFResponseModel | null = null;
|
private model: GetRaceWithSOFResponseModel | null = null;
|
||||||
|
|
||||||
@@ -20,27 +10,11 @@ export class RaceWithSOFPresenter {
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(result: Result<GetRaceWithSOFResult, GetRaceWithSOFApplicationError>): void {
|
present(result: GetRaceWithSOFResult): void {
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
if (error.code === 'RACE_NOT_FOUND') {
|
|
||||||
this.model = {
|
this.model = {
|
||||||
id: '',
|
id: result.race.id,
|
||||||
track: '',
|
track: result.race.track,
|
||||||
strengthOfField: null,
|
strengthOfField: result.strengthOfField,
|
||||||
} as RaceWithSOFDTO;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get race with SOF');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
this.model = {
|
|
||||||
id: output.race.id,
|
|
||||||
track: output.race.track,
|
|
||||||
strengthOfField: output.strengthOfField,
|
|
||||||
} as RaceWithSOFDTO;
|
} as RaceWithSOFDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
import type { Result } from '@core/shared/application/Result';
|
import type { GetRacesPageDataResult } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import type {
|
|
||||||
GetRacesPageDataResult,
|
|
||||||
GetRacesPageDataErrorCode,
|
|
||||||
} from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
|
|
||||||
import type { RacesPageDataDTO } from '../dtos/RacesPageDataDTO';
|
import type { RacesPageDataDTO } from '../dtos/RacesPageDataDTO';
|
||||||
import type { RacesPageDataRaceDTO } from '../dtos/RacesPageDataRaceDTO';
|
import type { RacesPageDataRaceDTO } from '../dtos/RacesPageDataRaceDTO';
|
||||||
|
|
||||||
export type GetRacesPageDataResponseModel = RacesPageDataDTO;
|
export type GetRacesPageDataResponseModel = RacesPageDataDTO;
|
||||||
|
|
||||||
export type GetRacesPageDataApplicationError = ApplicationErrorCode<
|
|
||||||
GetRacesPageDataErrorCode,
|
|
||||||
{ message: string }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class RacesPageDataPresenter {
|
export class RacesPageDataPresenter {
|
||||||
private model: GetRacesPageDataResponseModel | null = null;
|
private model: GetRacesPageDataResponseModel | null = null;
|
||||||
|
|
||||||
@@ -21,17 +11,8 @@ export class RacesPageDataPresenter {
|
|||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(
|
present(result: GetRacesPageDataResult): void {
|
||||||
result: Result<GetRacesPageDataResult, GetRacesPageDataApplicationError>,
|
const races: RacesPageDataRaceDTO[] = result.races.map(({ race, leagueName }) => ({
|
||||||
): void {
|
|
||||||
if (result.isErr()) {
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
throw new Error(error.details?.message ?? 'Failed to get races page data');
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.unwrap();
|
|
||||||
|
|
||||||
const races: RacesPageDataRaceDTO[] = output.races.map(({ race, leagueName }) => ({
|
|
||||||
id: race.id,
|
id: race.id,
|
||||||
track: race.track,
|
track: race.track,
|
||||||
car: race.car,
|
car: race.car,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { SponsorRaceDTO } from './dtos/RaceDTO';
|
|||||||
import { SponsorProfileDTO } from './dtos/SponsorProfileDTO';
|
import { SponsorProfileDTO } from './dtos/SponsorProfileDTO';
|
||||||
import { NotificationSettingsDTO } from './dtos/NotificationSettingsDTO';
|
import { NotificationSettingsDTO } from './dtos/NotificationSettingsDTO';
|
||||||
import { PrivacySettingsDTO } from './dtos/PrivacySettingsDTO';
|
import { PrivacySettingsDTO } from './dtos/PrivacySettingsDTO';
|
||||||
import type { AcceptSponsorshipRequestResultViewModel } from './presenters/AcceptSponsorshipRequestPresenter';
|
import { AcceptSponsorshipRequestResultViewModel } from './presenters/AcceptSponsorshipRequestPresenter';
|
||||||
import type { RejectSponsorshipRequestResult } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
import type { RejectSponsorshipRequestResult } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||||
|
|
||||||
@ApiTags('sponsors')
|
@ApiTags('sponsors')
|
||||||
@@ -166,7 +166,7 @@ export class SponsorController {
|
|||||||
async acceptSponsorshipRequest(
|
async acceptSponsorshipRequest(
|
||||||
@Param('requestId') requestId: string,
|
@Param('requestId') requestId: string,
|
||||||
@Body() input: AcceptSponsorshipRequestInputDTO,
|
@Body() input: AcceptSponsorshipRequestInputDTO,
|
||||||
): Promise<AcceptSponsorshipRequestResultViewModel | null> {
|
): Promise<AcceptSponsorshipRequestResultViewModel> {
|
||||||
return await this.sponsorService.acceptSponsorshipRequest(
|
return await this.sponsorService.acceptSponsorshipRequest(
|
||||||
requestId,
|
requestId,
|
||||||
input.respondedBy,
|
input.respondedBy,
|
||||||
@@ -185,7 +185,7 @@ export class SponsorController {
|
|||||||
async rejectSponsorshipRequest(
|
async rejectSponsorshipRequest(
|
||||||
@Param('requestId') requestId: string,
|
@Param('requestId') requestId: string,
|
||||||
@Body() input: RejectSponsorshipRequestInputDTO,
|
@Body() input: RejectSponsorshipRequestInputDTO,
|
||||||
): Promise<RejectSponsorshipRequestResult | null> {
|
): Promise<RejectSponsorshipRequestResult> {
|
||||||
return await this.sponsorService.rejectSponsorshipRequest(
|
return await this.sponsorService.rejectSponsorshipRequest(
|
||||||
requestId,
|
requestId,
|
||||||
input.respondedBy,
|
input.respondedBy,
|
||||||
@@ -219,9 +219,13 @@ export class SponsorController {
|
|||||||
description: 'Available leagues',
|
description: 'Available leagues',
|
||||||
type: [AvailableLeagueDTO],
|
type: [AvailableLeagueDTO],
|
||||||
})
|
})
|
||||||
async getAvailableLeagues(): Promise<AvailableLeagueDTO[] | null> {
|
async getAvailableLeagues(): Promise<AvailableLeagueDTO[]> {
|
||||||
const presenter = await this.sponsorService.getAvailableLeagues();
|
const presenter = await this.sponsorService.getAvailableLeagues();
|
||||||
return presenter.viewModel;
|
const viewModel = presenter.viewModel;
|
||||||
|
if (!viewModel) {
|
||||||
|
throw new Error('Available leagues not found');
|
||||||
|
}
|
||||||
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('leagues/:leagueId/detail')
|
@Get('leagues/:leagueId/detail')
|
||||||
@@ -236,9 +240,13 @@ export class SponsorController {
|
|||||||
league: LeagueDetailDTO;
|
league: LeagueDetailDTO;
|
||||||
drivers: SponsorDriverDTO[];
|
drivers: SponsorDriverDTO[];
|
||||||
races: SponsorRaceDTO[];
|
races: SponsorRaceDTO[];
|
||||||
} | null> {
|
}> {
|
||||||
const presenter = await this.sponsorService.getLeagueDetail(leagueId);
|
const presenter = await this.sponsorService.getLeagueDetail(leagueId);
|
||||||
return presenter.viewModel;
|
const viewModel = presenter.viewModel;
|
||||||
|
if (!viewModel) {
|
||||||
|
throw new Error('League detail not found');
|
||||||
|
}
|
||||||
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('settings/:sponsorId')
|
@Get('settings/:sponsorId')
|
||||||
@@ -253,9 +261,13 @@ export class SponsorController {
|
|||||||
profile: SponsorProfileDTO;
|
profile: SponsorProfileDTO;
|
||||||
notifications: NotificationSettingsDTO;
|
notifications: NotificationSettingsDTO;
|
||||||
privacy: PrivacySettingsDTO;
|
privacy: PrivacySettingsDTO;
|
||||||
} | null> {
|
}> {
|
||||||
const presenter = await this.sponsorService.getSponsorSettings(sponsorId);
|
const presenter = await this.sponsorService.getSponsorSettings(sponsorId);
|
||||||
return presenter.viewModel;
|
const viewModel = presenter.viewModel;
|
||||||
|
if (!viewModel) {
|
||||||
|
throw new Error('Sponsor settings not found');
|
||||||
|
}
|
||||||
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('settings/:sponsorId')
|
@Put('settings/:sponsorId')
|
||||||
@@ -273,8 +285,12 @@ export class SponsorController {
|
|||||||
notifications?: Partial<NotificationSettingsDTO>;
|
notifications?: Partial<NotificationSettingsDTO>;
|
||||||
privacy?: Partial<PrivacySettingsDTO>;
|
privacy?: Partial<PrivacySettingsDTO>;
|
||||||
},
|
},
|
||||||
): Promise<{ success: boolean; errorCode?: string; message?: string } | null> {
|
): Promise<{ success: boolean; errorCode?: string; message?: string }> {
|
||||||
const presenter = await this.sponsorService.updateSponsorSettings(sponsorId, input);
|
const presenter = await this.sponsorService.updateSponsorSettings(sponsorId, input);
|
||||||
return presenter.viewModel;
|
const viewModel = presenter.viewModel;
|
||||||
|
if (!viewModel) {
|
||||||
|
throw new Error('Update failed');
|
||||||
|
}
|
||||||
|
return viewModel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,6 @@ import { SponsorService } from './SponsorService';
|
|||||||
import type { NotificationService } from '@core/notifications/application/ports/NotificationService';
|
import type { NotificationService } from '@core/notifications/application/ports/NotificationService';
|
||||||
import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository';
|
import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository';
|
||||||
import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||||
// Remove the missing import
|
|
||||||
// import { IPaymentGateway } from '@core/payments/domain/ports/IPaymentGateway';
|
|
||||||
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
import { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
||||||
import { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
import { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||||
import { ILeagueWalletRepository } from '@core/racing/domain/repositories/ILeagueWalletRepository';
|
import { ILeagueWalletRepository } from '@core/racing/domain/repositories/ILeagueWalletRepository';
|
||||||
@@ -16,7 +14,7 @@ import { ISeasonSponsorshipRepository } from '@core/racing/domain/repositories/I
|
|||||||
import { ISponsorRepository } from '@core/racing/domain/repositories/ISponsorRepository';
|
import { ISponsorRepository } from '@core/racing/domain/repositories/ISponsorRepository';
|
||||||
import { ISponsorshipPricingRepository } from '@core/racing/domain/repositories/ISponsorshipPricingRepository';
|
import { ISponsorshipPricingRepository } from '@core/racing/domain/repositories/ISponsorshipPricingRepository';
|
||||||
import { ISponsorshipRequestRepository } from '@core/racing/domain/repositories/ISponsorshipRequestRepository';
|
import { ISponsorshipRequestRepository } from '@core/racing/domain/repositories/ISponsorshipRequestRepository';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
import { GetSponsorBillingUseCase } from '@core/payments/application/use-cases/GetSponsorBillingUseCase';
|
import { GetSponsorBillingUseCase } from '@core/payments/application/use-cases/GetSponsorBillingUseCase';
|
||||||
import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
|
import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
|
||||||
@@ -24,7 +22,6 @@ import { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateS
|
|||||||
import { GetEntitySponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
import { GetEntitySponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
||||||
import { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
|
import { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
|
||||||
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||||
import { GetSponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
|
||||||
import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
||||||
import { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
import { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
||||||
import { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
|
import { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
|
||||||
@@ -35,18 +32,6 @@ import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
|||||||
import { InMemoryPaymentRepository } from '@adapters/payments/persistence/inmemory/InMemoryPaymentRepository';
|
import { InMemoryPaymentRepository } from '@adapters/payments/persistence/inmemory/InMemoryPaymentRepository';
|
||||||
import { InMemoryWalletRepository } from '@adapters/payments/persistence/inmemory/InMemoryWalletRepository';
|
import { InMemoryWalletRepository } from '@adapters/payments/persistence/inmemory/InMemoryWalletRepository';
|
||||||
|
|
||||||
// Import presenters
|
|
||||||
import { GetEntitySponsorshipPricingPresenter } from './presenters/GetEntitySponsorshipPricingPresenter';
|
|
||||||
import { GetSponsorsPresenter } from './presenters/GetSponsorsPresenter';
|
|
||||||
import { CreateSponsorPresenter } from './presenters/CreateSponsorPresenter';
|
|
||||||
import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPresenter';
|
|
||||||
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
|
|
||||||
import { GetSponsorPresenter } from './presenters/GetSponsorPresenter';
|
|
||||||
import { GetPendingSponsorshipRequestsPresenter } from './presenters/GetPendingSponsorshipRequestsPresenter';
|
|
||||||
import { AcceptSponsorshipRequestPresenter } from './presenters/AcceptSponsorshipRequestPresenter';
|
|
||||||
import { RejectSponsorshipRequestPresenter } from './presenters/RejectSponsorshipRequestPresenter';
|
|
||||||
import { SponsorBillingPresenter } from './presenters/SponsorBillingPresenter';
|
|
||||||
|
|
||||||
// Define injection tokens
|
// Define injection tokens
|
||||||
export const SPONSOR_REPOSITORY_TOKEN = 'ISponsorRepository';
|
export const SPONSOR_REPOSITORY_TOKEN = 'ISponsorRepository';
|
||||||
export const SEASON_SPONSORSHIP_REPOSITORY_TOKEN = 'ISeasonSponsorshipRepository';
|
export const SEASON_SPONSORSHIP_REPOSITORY_TOKEN = 'ISeasonSponsorshipRepository';
|
||||||
@@ -62,20 +47,7 @@ export const LEAGUE_WALLET_REPOSITORY_TOKEN = 'ILeagueWalletRepository';
|
|||||||
export const NOTIFICATION_SERVICE_TOKEN = 'INotificationService';
|
export const NOTIFICATION_SERVICE_TOKEN = 'INotificationService';
|
||||||
export const LOGGER_TOKEN = 'Logger';
|
export const LOGGER_TOKEN = 'Logger';
|
||||||
|
|
||||||
// Presenter tokens
|
|
||||||
export const GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN = 'GetEntitySponsorshipPricingPresenter';
|
|
||||||
export const GET_SPONSORS_PRESENTER_TOKEN = 'GetSponsorsPresenter';
|
|
||||||
export const CREATE_SPONSOR_PRESENTER_TOKEN = 'CreateSponsorPresenter';
|
|
||||||
export const GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN = 'GetSponsorDashboardPresenter';
|
|
||||||
export const GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN = 'GetSponsorSponsorshipsPresenter';
|
|
||||||
export const GET_SPONSOR_PRESENTER_TOKEN = 'GetSponsorPresenter';
|
|
||||||
export const GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN = 'GetPendingSponsorshipRequestsPresenter';
|
|
||||||
export const ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN = 'AcceptSponsorshipRequestPresenter';
|
|
||||||
export const REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN = 'RejectSponsorshipRequestPresenter';
|
|
||||||
export const GET_SPONSOR_BILLING_PRESENTER_TOKEN = 'SponsorBillingPresenter';
|
|
||||||
|
|
||||||
// Use case / application service tokens
|
// Use case / application service tokens
|
||||||
export const GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetSponsorshipPricingUseCase';
|
|
||||||
export const GET_SPONSORS_USE_CASE_TOKEN = 'GetSponsorsUseCase';
|
export const GET_SPONSORS_USE_CASE_TOKEN = 'GetSponsorsUseCase';
|
||||||
export const CREATE_SPONSOR_USE_CASE_TOKEN = 'CreateSponsorUseCase';
|
export const CREATE_SPONSOR_USE_CASE_TOKEN = 'CreateSponsorUseCase';
|
||||||
export const GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN = 'GetSponsorDashboardUseCase';
|
export const GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN = 'GetSponsorDashboardUseCase';
|
||||||
@@ -87,19 +59,6 @@ export const ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'AcceptSponsorshipReque
|
|||||||
export const REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'RejectSponsorshipRequestUseCase';
|
export const REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'RejectSponsorshipRequestUseCase';
|
||||||
export const GET_SPONSOR_BILLING_USE_CASE_TOKEN = 'GetSponsorBillingUseCase';
|
export const GET_SPONSOR_BILLING_USE_CASE_TOKEN = 'GetSponsorBillingUseCase';
|
||||||
|
|
||||||
// Output port tokens
|
|
||||||
export const GET_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN = 'GetSponsorshipPricingOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSORS_OUTPUT_PORT_TOKEN = 'GetSponsorsOutputPort_TOKEN';
|
|
||||||
export const CREATE_SPONSOR_OUTPUT_PORT_TOKEN = 'CreateSponsorOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSOR_DASHBOARD_OUTPUT_PORT_TOKEN = 'GetSponsorDashboardOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSOR_SPONSORSHIPS_OUTPUT_PORT_TOKEN = 'GetSponsorSponsorshipsOutputPort_TOKEN';
|
|
||||||
export const GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN = 'GetEntitySponsorshipPricingOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSOR_OUTPUT_PORT_TOKEN = 'GetSponsorOutputPort_TOKEN';
|
|
||||||
export const GET_PENDING_SPONSORSHIP_REQUESTS_OUTPUT_PORT_TOKEN = 'GetPendingSponsorshipRequestsOutputPort_TOKEN';
|
|
||||||
export const ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN = 'AcceptSponsorshipRequestOutputPort_TOKEN';
|
|
||||||
export const REJECT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN = 'RejectSponsorshipRequestOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSOR_BILLING_OUTPUT_PORT_TOKEN = 'GetSponsorBillingOutputPort_TOKEN';
|
|
||||||
|
|
||||||
export const SponsorProviders: Provider[] = [
|
export const SponsorProviders: Provider[] = [
|
||||||
SponsorService,
|
SponsorService,
|
||||||
// Repositories (payments repos are local to this module; racing repos come from InMemoryRacingPersistenceModule)
|
// Repositories (payments repos are local to this module; racing repos come from InMemoryRacingPersistenceModule)
|
||||||
@@ -126,77 +85,16 @@ export const SponsorProviders: Provider[] = [
|
|||||||
provide: LOGGER_TOKEN,
|
provide: LOGGER_TOKEN,
|
||||||
useClass: ConsoleLogger,
|
useClass: ConsoleLogger,
|
||||||
},
|
},
|
||||||
// Presenters
|
|
||||||
GetEntitySponsorshipPricingPresenter,
|
|
||||||
GetSponsorsPresenter,
|
|
||||||
CreateSponsorPresenter,
|
|
||||||
GetSponsorDashboardPresenter,
|
|
||||||
GetSponsorSponsorshipsPresenter,
|
|
||||||
GetSponsorPresenter,
|
|
||||||
GetPendingSponsorshipRequestsPresenter,
|
|
||||||
AcceptSponsorshipRequestPresenter,
|
|
||||||
RejectSponsorshipRequestPresenter,
|
|
||||||
SponsorBillingPresenter,
|
|
||||||
// Output ports
|
|
||||||
{
|
|
||||||
provide: GET_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetEntitySponsorshipPricingPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetEntitySponsorshipPricingPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_SPONSORS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetSponsorsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: CREATE_SPONSOR_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: CreateSponsorPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_SPONSOR_DASHBOARD_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetSponsorDashboardPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_SPONSOR_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetSponsorSponsorshipsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_SPONSOR_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetSponsorPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_PENDING_SPONSORSHIP_REQUESTS_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: GetPendingSponsorshipRequestsPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: AcceptSponsorshipRequestPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: REJECT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: RejectSponsorshipRequestPresenter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GET_SPONSOR_BILLING_OUTPUT_PORT_TOKEN,
|
|
||||||
useExisting: SponsorBillingPresenter,
|
|
||||||
},
|
|
||||||
// Use cases
|
// Use cases
|
||||||
{
|
|
||||||
provide: GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN,
|
|
||||||
useFactory: (output: UseCaseOutputPort<unknown>) => new GetSponsorshipPricingUseCase(output),
|
|
||||||
inject: [GET_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: GET_SPONSORS_USE_CASE_TOKEN,
|
provide: GET_SPONSORS_USE_CASE_TOKEN,
|
||||||
useFactory: (sponsorRepo: ISponsorRepository, output: UseCaseOutputPort<unknown>) => new GetSponsorsUseCase(sponsorRepo, output),
|
useFactory: (sponsorRepo: ISponsorRepository) => new GetSponsorsUseCase(sponsorRepo),
|
||||||
inject: [SPONSOR_REPOSITORY_TOKEN, GET_SPONSORS_OUTPUT_PORT_TOKEN],
|
inject: [SPONSOR_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CREATE_SPONSOR_USE_CASE_TOKEN,
|
provide: CREATE_SPONSOR_USE_CASE_TOKEN,
|
||||||
useFactory: (sponsorRepo: ISponsorRepository, logger: Logger, output: UseCaseOutputPort<unknown>) => new CreateSponsorUseCase(sponsorRepo, logger, output),
|
useFactory: (sponsorRepo: ISponsorRepository, logger: Logger) => new CreateSponsorUseCase(sponsorRepo, logger),
|
||||||
inject: [SPONSOR_REPOSITORY_TOKEN, LOGGER_TOKEN, CREATE_SPONSOR_OUTPUT_PORT_TOKEN],
|
inject: [SPONSOR_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN,
|
provide: GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN,
|
||||||
@@ -207,8 +105,7 @@ export const SponsorProviders: Provider[] = [
|
|||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
output: UseCaseOutputPort<unknown>,
|
) => new GetSponsorDashboardUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo),
|
||||||
) => new GetSponsorDashboardUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
SPONSOR_REPOSITORY_TOKEN,
|
SPONSOR_REPOSITORY_TOKEN,
|
||||||
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
||||||
@@ -216,7 +113,6 @@ export const SponsorProviders: Provider[] = [
|
|||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
GET_SPONSOR_DASHBOARD_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -228,8 +124,7 @@ export const SponsorProviders: Provider[] = [
|
|||||||
leagueRepo: ILeagueRepository,
|
leagueRepo: ILeagueRepository,
|
||||||
leagueMembershipRepo: ILeagueMembershipRepository,
|
leagueMembershipRepo: ILeagueMembershipRepository,
|
||||||
raceRepo: IRaceRepository,
|
raceRepo: IRaceRepository,
|
||||||
output: UseCaseOutputPort<unknown>,
|
) => new GetSponsorSponsorshipsUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo),
|
||||||
) => new GetSponsorSponsorshipsUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
SPONSOR_REPOSITORY_TOKEN,
|
SPONSOR_REPOSITORY_TOKEN,
|
||||||
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
|
||||||
@@ -237,7 +132,6 @@ export const SponsorProviders: Provider[] = [
|
|||||||
LEAGUE_REPOSITORY_TOKEN,
|
LEAGUE_REPOSITORY_TOKEN,
|
||||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||||
RACE_REPOSITORY_TOKEN,
|
RACE_REPOSITORY_TOKEN,
|
||||||
GET_SPONSOR_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -255,27 +149,24 @@ export const SponsorProviders: Provider[] = [
|
|||||||
useFactory: (
|
useFactory: (
|
||||||
sponsorshipPricingRepo: ISponsorshipPricingRepository,
|
sponsorshipPricingRepo: ISponsorshipPricingRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<unknown>,
|
) => new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, logger),
|
||||||
) => new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, logger, output),
|
|
||||||
inject: [
|
inject: [
|
||||||
SPONSORSHIP_PRICING_REPOSITORY_TOKEN,
|
SPONSORSHIP_PRICING_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_SPONSOR_USE_CASE_TOKEN,
|
provide: GET_SPONSOR_USE_CASE_TOKEN,
|
||||||
useFactory: (sponsorRepo: ISponsorRepository, output: UseCaseOutputPort<unknown>) => new GetSponsorUseCase(sponsorRepo, output),
|
useFactory: (sponsorRepo: ISponsorRepository) => new GetSponsorUseCase(sponsorRepo),
|
||||||
inject: [SPONSOR_REPOSITORY_TOKEN, GET_SPONSOR_OUTPUT_PORT_TOKEN],
|
inject: [SPONSOR_REPOSITORY_TOKEN],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN,
|
provide: GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||||
sponsorRepo: ISponsorRepository,
|
sponsorRepo: ISponsorRepository,
|
||||||
output: UseCaseOutputPort<unknown>,
|
) => new GetPendingSponsorshipRequestsUseCase(sponsorshipRequestRepo, sponsorRepo),
|
||||||
) => new GetPendingSponsorshipRequestsUseCase(sponsorshipRequestRepo, sponsorRepo, output),
|
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SPONSOR_REPOSITORY_TOKEN],
|
||||||
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SPONSOR_REPOSITORY_TOKEN, GET_PENDING_SPONSORSHIP_REQUESTS_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
provide: ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
||||||
@@ -287,7 +178,6 @@ export const SponsorProviders: Provider[] = [
|
|||||||
walletRepository: IWalletRepository,
|
walletRepository: IWalletRepository,
|
||||||
leagueWalletRepository: ILeagueWalletRepository,
|
leagueWalletRepository: ILeagueWalletRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<unknown>,
|
|
||||||
) => {
|
) => {
|
||||||
// Create a mock payment processor function
|
// Create a mock payment processor function
|
||||||
const paymentProcessor = async (input: unknown) => {
|
const paymentProcessor = async (input: unknown) => {
|
||||||
@@ -303,8 +193,7 @@ export const SponsorProviders: Provider[] = [
|
|||||||
paymentProcessor,
|
paymentProcessor,
|
||||||
walletRepository,
|
walletRepository,
|
||||||
leagueWalletRepository,
|
leagueWalletRepository,
|
||||||
logger,
|
logger
|
||||||
output
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
@@ -315,7 +204,6 @@ export const SponsorProviders: Provider[] = [
|
|||||||
WALLET_REPOSITORY_TOKEN,
|
WALLET_REPOSITORY_TOKEN,
|
||||||
LEAGUE_WALLET_REPOSITORY_TOKEN,
|
LEAGUE_WALLET_REPOSITORY_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -323,8 +211,7 @@ export const SponsorProviders: Provider[] = [
|
|||||||
useFactory: (
|
useFactory: (
|
||||||
sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
output: UseCaseOutputPort<unknown>,
|
) => new RejectSponsorshipRequestUseCase(sponsorshipRequestRepo, logger),
|
||||||
) => new RejectSponsorshipRequestUseCase(sponsorshipRequestRepo, logger, output),
|
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||||
inject: [SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, LOGGER_TOKEN, REJECT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -3,7 +3,7 @@ import type { AcceptSponsorshipRequestUseCase } from '@core/racing/application/u
|
|||||||
import type { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
|
import type { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
|
||||||
import type { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
|
import type { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
|
||||||
import type { GetSponsorDashboardInput, GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
import type { GetSponsorDashboardInput, GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||||
import type { GetSponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
import type { GetEntitySponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
||||||
import type { GetSponsorSponsorshipsInput, GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
import type { GetSponsorSponsorshipsInput, GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
||||||
import type { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
import type { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
||||||
import type { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
|
import type { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
|
||||||
@@ -14,21 +14,11 @@ import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
|
|||||||
import type { CreateSponsorInputDTO } from './dtos/CreateSponsorInputDTO';
|
import type { CreateSponsorInputDTO } from './dtos/CreateSponsorInputDTO';
|
||||||
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||||
import { AcceptSponsorshipRequestPresenter } from './presenters/AcceptSponsorshipRequestPresenter';
|
|
||||||
import { CreateSponsorPresenter } from './presenters/CreateSponsorPresenter';
|
|
||||||
import { GetEntitySponsorshipPricingPresenter } from './presenters/GetEntitySponsorshipPricingPresenter';
|
|
||||||
import { GetPendingSponsorshipRequestsPresenter } from './presenters/GetPendingSponsorshipRequestsPresenter';
|
|
||||||
import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPresenter';
|
|
||||||
import { GetSponsorPresenter } from './presenters/GetSponsorPresenter';
|
|
||||||
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
|
|
||||||
import { GetSponsorsPresenter } from './presenters/GetSponsorsPresenter';
|
|
||||||
import { RejectSponsorshipRequestPresenter } from './presenters/RejectSponsorshipRequestPresenter';
|
|
||||||
import { SponsorBillingPresenter } from './presenters/SponsorBillingPresenter';
|
|
||||||
import { SponsorService } from './SponsorService';
|
import { SponsorService } from './SponsorService';
|
||||||
|
|
||||||
describe('SponsorService', () => {
|
describe('SponsorService', () => {
|
||||||
let service: SponsorService;
|
let service: SponsorService;
|
||||||
let getSponsorshipPricingUseCase: { execute: Mock };
|
let getEntitySponsorshipPricingUseCase: { execute: Mock };
|
||||||
let getSponsorsUseCase: { execute: Mock };
|
let getSponsorsUseCase: { execute: Mock };
|
||||||
let createSponsorUseCase: { execute: Mock };
|
let createSponsorUseCase: { execute: Mock };
|
||||||
let getSponsorDashboardUseCase: { execute: Mock };
|
let getSponsorDashboardUseCase: { execute: Mock };
|
||||||
@@ -40,20 +30,8 @@ describe('SponsorService', () => {
|
|||||||
let getSponsorBillingUseCase: { execute: Mock };
|
let getSponsorBillingUseCase: { execute: Mock };
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
|
|
||||||
// Presenters
|
|
||||||
let getEntitySponsorshipPricingPresenter: GetEntitySponsorshipPricingPresenter;
|
|
||||||
let getSponsorsPresenter: GetSponsorsPresenter;
|
|
||||||
let createSponsorPresenter: CreateSponsorPresenter;
|
|
||||||
let getSponsorDashboardPresenter: GetSponsorDashboardPresenter;
|
|
||||||
let getSponsorSponsorshipsPresenter: GetSponsorSponsorshipsPresenter;
|
|
||||||
let getSponsorPresenter: GetSponsorPresenter;
|
|
||||||
let getPendingSponsorshipRequestsPresenter: GetPendingSponsorshipRequestsPresenter;
|
|
||||||
let acceptSponsorshipRequestPresenter: AcceptSponsorshipRequestPresenter;
|
|
||||||
let rejectSponsorshipRequestPresenter: RejectSponsorshipRequestPresenter;
|
|
||||||
let sponsorBillingPresenter: SponsorBillingPresenter;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getSponsorshipPricingUseCase = { execute: vi.fn() };
|
getEntitySponsorshipPricingUseCase = { execute: vi.fn() };
|
||||||
getSponsorsUseCase = { execute: vi.fn() };
|
getSponsorsUseCase = { execute: vi.fn() };
|
||||||
createSponsorUseCase = { execute: vi.fn() };
|
createSponsorUseCase = { execute: vi.fn() };
|
||||||
getSponsorDashboardUseCase = { execute: vi.fn() };
|
getSponsorDashboardUseCase = { execute: vi.fn() };
|
||||||
@@ -70,20 +48,8 @@ describe('SponsorService', () => {
|
|||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
|
|
||||||
// Initialize presenters
|
|
||||||
getEntitySponsorshipPricingPresenter = new GetEntitySponsorshipPricingPresenter();
|
|
||||||
getSponsorsPresenter = new GetSponsorsPresenter();
|
|
||||||
createSponsorPresenter = new CreateSponsorPresenter();
|
|
||||||
getSponsorDashboardPresenter = new GetSponsorDashboardPresenter();
|
|
||||||
getSponsorSponsorshipsPresenter = new GetSponsorSponsorshipsPresenter();
|
|
||||||
getSponsorPresenter = new GetSponsorPresenter();
|
|
||||||
getPendingSponsorshipRequestsPresenter = new GetPendingSponsorshipRequestsPresenter();
|
|
||||||
acceptSponsorshipRequestPresenter = new AcceptSponsorshipRequestPresenter();
|
|
||||||
rejectSponsorshipRequestPresenter = new RejectSponsorshipRequestPresenter();
|
|
||||||
sponsorBillingPresenter = new SponsorBillingPresenter();
|
|
||||||
|
|
||||||
service = new SponsorService(
|
service = new SponsorService(
|
||||||
getSponsorshipPricingUseCase as unknown as GetSponsorshipPricingUseCase,
|
getEntitySponsorshipPricingUseCase as unknown as GetEntitySponsorshipPricingUseCase,
|
||||||
getSponsorsUseCase as unknown as GetSponsorsUseCase,
|
getSponsorsUseCase as unknown as GetSponsorsUseCase,
|
||||||
createSponsorUseCase as unknown as CreateSponsorUseCase,
|
createSponsorUseCase as unknown as CreateSponsorUseCase,
|
||||||
getSponsorDashboardUseCase as unknown as GetSponsorDashboardUseCase,
|
getSponsorDashboardUseCase as unknown as GetSponsorDashboardUseCase,
|
||||||
@@ -94,31 +60,19 @@ describe('SponsorService', () => {
|
|||||||
rejectSponsorshipRequestUseCase as unknown as RejectSponsorshipRequestUseCase,
|
rejectSponsorshipRequestUseCase as unknown as RejectSponsorshipRequestUseCase,
|
||||||
getSponsorBillingUseCase as unknown as GetSponsorBillingUseCase,
|
getSponsorBillingUseCase as unknown as GetSponsorBillingUseCase,
|
||||||
logger,
|
logger,
|
||||||
getEntitySponsorshipPricingPresenter,
|
|
||||||
getSponsorsPresenter,
|
|
||||||
createSponsorPresenter,
|
|
||||||
getSponsorDashboardPresenter,
|
|
||||||
getSponsorSponsorshipsPresenter,
|
|
||||||
getSponsorPresenter,
|
|
||||||
getPendingSponsorshipRequestsPresenter,
|
|
||||||
acceptSponsorshipRequestPresenter,
|
|
||||||
rejectSponsorshipRequestPresenter,
|
|
||||||
sponsorBillingPresenter,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getEntitySponsorshipPricing', () => {
|
describe('getEntitySponsorshipPricing', () => {
|
||||||
it('returns pricing data on success', async () => {
|
it('returns pricing data on success', async () => {
|
||||||
const outputPort = {
|
const output = {
|
||||||
entityType: 'season',
|
entityType: 'season',
|
||||||
entityId: 'season-1',
|
entityId: 'season-1',
|
||||||
|
acceptingApplications: true,
|
||||||
tiers: [{ name: 'Gold', price: { amount: 500, currency: 'USD' }, benefits: ['Main slot'] }],
|
tiers: [{ name: 'Gold', price: { amount: 500, currency: 'USD' }, benefits: ['Main slot'] }],
|
||||||
};
|
};
|
||||||
|
|
||||||
getSponsorshipPricingUseCase.execute.mockImplementation(async () => {
|
getEntitySponsorshipPricingUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||||
getEntitySponsorshipPricingPresenter.present(outputPort as any);
|
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getEntitySponsorshipPricing();
|
const result = await service.getEntitySponsorshipPricing();
|
||||||
|
|
||||||
@@ -130,7 +84,7 @@ describe('SponsorService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns empty pricing on error', async () => {
|
it('returns empty pricing on error', async () => {
|
||||||
getSponsorshipPricingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
|
getEntitySponsorshipPricingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
|
||||||
|
|
||||||
const result = await service.getEntitySponsorshipPricing();
|
const result = await service.getEntitySponsorshipPricing();
|
||||||
|
|
||||||
@@ -153,10 +107,7 @@ describe('SponsorService', () => {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
getSponsorsUseCase.execute.mockImplementation(async () => {
|
getSponsorsUseCase.execute.mockResolvedValue(Result.ok({ sponsors }));
|
||||||
getSponsorsPresenter.present(sponsors);
|
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getSponsors();
|
const result = await service.getSponsors();
|
||||||
|
|
||||||
@@ -191,10 +142,7 @@ describe('SponsorService', () => {
|
|||||||
createdAt: new Date('2024-01-01T00:00:00Z'),
|
createdAt: new Date('2024-01-01T00:00:00Z'),
|
||||||
});
|
});
|
||||||
|
|
||||||
createSponsorUseCase.execute.mockImplementation(async () => {
|
createSponsorUseCase.execute.mockResolvedValue(Result.ok({ sponsor }));
|
||||||
createSponsorPresenter.present(sponsor);
|
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.createSponsor(input);
|
const result = await service.createSponsor(input);
|
||||||
|
|
||||||
@@ -235,7 +183,7 @@ describe('SponsorService', () => {
|
|||||||
describe('getSponsorDashboard', () => {
|
describe('getSponsorDashboard', () => {
|
||||||
it('returns dashboard on success', async () => {
|
it('returns dashboard on success', async () => {
|
||||||
const params: GetSponsorDashboardInput = { sponsorId: 's1' };
|
const params: GetSponsorDashboardInput = { sponsorId: 's1' };
|
||||||
const outputPort = {
|
const output = {
|
||||||
sponsorId: 's1',
|
sponsorId: 's1',
|
||||||
sponsorName: 'S1',
|
sponsorName: 'S1',
|
||||||
metrics: {
|
metrics: {
|
||||||
@@ -254,19 +202,25 @@ describe('SponsorService', () => {
|
|||||||
totalInvestment: Money.create(0, 'USD'),
|
totalInvestment: Money.create(0, 'USD'),
|
||||||
costPerThousandViews: 0,
|
costPerThousandViews: 0,
|
||||||
},
|
},
|
||||||
|
sponsorships: {
|
||||||
|
leagues: [],
|
||||||
|
teams: [],
|
||||||
|
drivers: [],
|
||||||
|
races: [],
|
||||||
|
platform: [],
|
||||||
|
},
|
||||||
|
recentActivity: [],
|
||||||
|
upcomingRenewals: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
getSponsorDashboardUseCase.execute.mockImplementation(async () => {
|
getSponsorDashboardUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||||
getSponsorDashboardPresenter.present(outputPort as any);
|
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getSponsorDashboard(params);
|
const result = await service.getSponsorDashboard(params);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
sponsorId: 's1',
|
sponsorId: 's1',
|
||||||
sponsorName: 'S1',
|
sponsorName: 'S1',
|
||||||
metrics: outputPort.metrics,
|
metrics: output.metrics,
|
||||||
sponsoredLeagues: [],
|
sponsoredLeagues: [],
|
||||||
investment: {
|
investment: {
|
||||||
activeSponsorships: 0,
|
activeSponsorships: 0,
|
||||||
@@ -296,7 +250,7 @@ describe('SponsorService', () => {
|
|||||||
describe('getSponsorSponsorships', () => {
|
describe('getSponsorSponsorships', () => {
|
||||||
it('returns sponsorships on success', async () => {
|
it('returns sponsorships on success', async () => {
|
||||||
const params: GetSponsorSponsorshipsInput = { sponsorId: 's1' };
|
const params: GetSponsorSponsorshipsInput = { sponsorId: 's1' };
|
||||||
const outputPort = {
|
const output = {
|
||||||
sponsor: Sponsor.create({
|
sponsor: Sponsor.create({
|
||||||
id: 's1',
|
id: 's1',
|
||||||
name: 'S1',
|
name: 'S1',
|
||||||
@@ -311,10 +265,7 @@ describe('SponsorService', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
getSponsorSponsorshipsUseCase.execute.mockImplementation(async () => {
|
getSponsorSponsorshipsUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||||
getSponsorSponsorshipsPresenter.present(outputPort as any);
|
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getSponsorSponsorships(params);
|
const result = await service.getSponsorSponsorships(params);
|
||||||
|
|
||||||
@@ -345,16 +296,25 @@ describe('SponsorService', () => {
|
|||||||
describe('getSponsor', () => {
|
describe('getSponsor', () => {
|
||||||
it('returns sponsor when found', async () => {
|
it('returns sponsor when found', async () => {
|
||||||
const sponsorId = 's1';
|
const sponsorId = 's1';
|
||||||
const output = { sponsor: { id: sponsorId, name: 'S1' } };
|
const sponsor = Sponsor.create({
|
||||||
|
id: sponsorId,
|
||||||
getSponsorUseCase.execute.mockImplementation(async () => {
|
name: 'S1',
|
||||||
getSponsorPresenter.present(output);
|
contactEmail: 's1@example.com',
|
||||||
return Result.ok(undefined);
|
createdAt: new Date('2024-01-01T00:00:00Z'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getSponsorUseCase.execute.mockResolvedValue(Result.ok({ sponsor }));
|
||||||
|
|
||||||
const result = await service.getSponsor(sponsorId);
|
const result = await service.getSponsor(sponsorId);
|
||||||
|
|
||||||
expect(result).toEqual(output);
|
expect(result).toEqual({
|
||||||
|
sponsor: {
|
||||||
|
id: sponsorId,
|
||||||
|
name: 'S1',
|
||||||
|
logoUrl: undefined,
|
||||||
|
websiteUrl: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when not found', async () => {
|
it('throws when not found', async () => {
|
||||||
@@ -375,21 +335,18 @@ describe('SponsorService', () => {
|
|||||||
describe('getPendingSponsorshipRequests', () => {
|
describe('getPendingSponsorshipRequests', () => {
|
||||||
it('returns requests on success', async () => {
|
it('returns requests on success', async () => {
|
||||||
const params = { entityType: 'season' as const, entityId: 'season-1' };
|
const params = { entityType: 'season' as const, entityId: 'season-1' };
|
||||||
const outputPort = {
|
const output = {
|
||||||
entityType: 'season',
|
entityType: 'season',
|
||||||
entityId: 'season-1',
|
entityId: 'season-1',
|
||||||
requests: [],
|
requests: [],
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
getPendingSponsorshipRequestsUseCase.execute.mockImplementation(async () => {
|
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||||
getPendingSponsorshipRequestsPresenter.present(outputPort as any);
|
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.getPendingSponsorshipRequests(params);
|
const result = await service.getPendingSponsorshipRequests(params);
|
||||||
|
|
||||||
expect(result).toEqual(outputPort);
|
expect(result).toEqual(output);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns empty result on error', async () => {
|
it('returns empty result on error', async () => {
|
||||||
@@ -405,20 +362,13 @@ describe('SponsorService', () => {
|
|||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when presenter viewModel is missing on success', async () => {
|
|
||||||
const params = { entityType: 'season' as const, entityId: 'season-1' };
|
|
||||||
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(Result.ok(undefined));
|
|
||||||
|
|
||||||
await expect(service.getPendingSponsorshipRequests(params)).rejects.toThrow('Pending sponsorship requests not found');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('SponsorshipRequest', () => {
|
describe('SponsorshipRequest', () => {
|
||||||
it('returns accept result on success', async () => {
|
it('returns accept result on success', async () => {
|
||||||
const requestId = 'r1';
|
const requestId = 'r1';
|
||||||
const respondedBy = 'u1';
|
const respondedBy = 'u1';
|
||||||
const outputPort = {
|
const output = {
|
||||||
requestId,
|
requestId,
|
||||||
sponsorshipId: 'sp1',
|
sponsorshipId: 'sp1',
|
||||||
status: 'accepted' as const,
|
status: 'accepted' as const,
|
||||||
@@ -427,14 +377,11 @@ describe('SponsorService', () => {
|
|||||||
netAmount: 90,
|
netAmount: 90,
|
||||||
};
|
};
|
||||||
|
|
||||||
acceptSponsorshipRequestUseCase.execute.mockImplementation(async () => {
|
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||||
acceptSponsorshipRequestPresenter.present(outputPort as any);
|
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.acceptSponsorshipRequest(requestId, respondedBy);
|
const result = await service.acceptSponsorshipRequest(requestId, respondedBy);
|
||||||
|
|
||||||
expect(result).toEqual(outputPort);
|
expect(result).toEqual(output);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws on error', async () => {
|
it('throws on error', async () => {
|
||||||
@@ -448,16 +395,6 @@ describe('SponsorService', () => {
|
|||||||
'Accept sponsorship request failed',
|
'Accept sponsorship request failed',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when presenter viewModel is missing on success', async () => {
|
|
||||||
const requestId = 'r1';
|
|
||||||
const respondedBy = 'u1';
|
|
||||||
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(undefined));
|
|
||||||
|
|
||||||
await expect(service.acceptSponsorshipRequest(requestId, respondedBy)).rejects.toThrow(
|
|
||||||
'Accept sponsorship request failed',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rejectSponsorshipRequest', () => {
|
describe('rejectSponsorshipRequest', () => {
|
||||||
@@ -472,10 +409,7 @@ describe('SponsorService', () => {
|
|||||||
rejectionReason: reason,
|
rejectionReason: reason,
|
||||||
};
|
};
|
||||||
|
|
||||||
rejectSponsorshipRequestUseCase.execute.mockImplementation(async () => {
|
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||||
rejectSponsorshipRequestPresenter.present(output as any);
|
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await service.rejectSponsorshipRequest(requestId, respondedBy, reason);
|
const result = await service.rejectSponsorshipRequest(requestId, respondedBy, reason);
|
||||||
|
|
||||||
@@ -485,19 +419,18 @@ describe('SponsorService', () => {
|
|||||||
it('passes no reason when reason is undefined', async () => {
|
it('passes no reason when reason is undefined', async () => {
|
||||||
const requestId = 'r1';
|
const requestId = 'r1';
|
||||||
const respondedBy = 'u1';
|
const respondedBy = 'u1';
|
||||||
|
const output = {
|
||||||
rejectSponsorshipRequestUseCase.execute.mockImplementation(async (input: any) => {
|
|
||||||
expect(input).toEqual({ requestId, respondedBy });
|
|
||||||
rejectSponsorshipRequestPresenter.present({
|
|
||||||
requestId,
|
requestId,
|
||||||
status: 'rejected' as const,
|
status: 'rejected' as const,
|
||||||
respondedAt: new Date(),
|
respondedAt: new Date(),
|
||||||
rejectionReason: '',
|
rejectionReason: '',
|
||||||
} as any);
|
};
|
||||||
return Result.ok(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(service.rejectSponsorshipRequest(requestId, respondedBy)).resolves.toMatchObject({
|
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(output));
|
||||||
|
|
||||||
|
const result = await service.rejectSponsorshipRequest(requestId, respondedBy);
|
||||||
|
|
||||||
|
expect(result).toMatchObject({
|
||||||
requestId,
|
requestId,
|
||||||
status: 'rejected',
|
status: 'rejected',
|
||||||
});
|
});
|
||||||
@@ -514,16 +447,6 @@ describe('SponsorService', () => {
|
|||||||
'Reject sponsorship request failed',
|
'Reject sponsorship request failed',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when presenter viewModel is missing on success', async () => {
|
|
||||||
const requestId = 'r1';
|
|
||||||
const respondedBy = 'u1';
|
|
||||||
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(undefined));
|
|
||||||
|
|
||||||
await expect(service.rejectSponsorshipRequest(requestId, respondedBy)).rejects.toThrow(
|
|
||||||
'Reject sponsorship request failed',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSponsorBilling', () => {
|
describe('getSponsorBilling', () => {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { InvoiceDTO } from './dtos/InvoiceDTO';
|
|||||||
import { BillingStatsDTO } from './dtos/BillingStatsDTO';
|
import { BillingStatsDTO } from './dtos/BillingStatsDTO';
|
||||||
|
|
||||||
// Use cases
|
// Use cases
|
||||||
import { GetSponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
import { GetEntitySponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
||||||
import { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
import { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
|
||||||
import { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
|
import { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
|
||||||
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||||
@@ -37,26 +37,8 @@ import { GetSponsorBillingUseCase } from '@core/payments/application/use-cases/G
|
|||||||
import type { SponsorableEntityType } from '@core/racing/domain/entities/SponsorshipRequest';
|
import type { SponsorableEntityType } from '@core/racing/domain/entities/SponsorshipRequest';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
// Presenters
|
|
||||||
import { GetEntitySponsorshipPricingPresenter } from './presenters/GetEntitySponsorshipPricingPresenter';
|
|
||||||
import { GetSponsorsPresenter } from './presenters/GetSponsorsPresenter';
|
|
||||||
import { CreateSponsorPresenter } from './presenters/CreateSponsorPresenter';
|
|
||||||
import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPresenter';
|
|
||||||
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
|
|
||||||
import { GetSponsorPresenter } from './presenters/GetSponsorPresenter';
|
|
||||||
import { GetPendingSponsorshipRequestsPresenter } from './presenters/GetPendingSponsorshipRequestsPresenter';
|
|
||||||
import { AcceptSponsorshipRequestPresenter, AcceptSponsorshipRequestResultViewModel } from './presenters/AcceptSponsorshipRequestPresenter';
|
|
||||||
import { RejectSponsorshipRequestPresenter } from './presenters/RejectSponsorshipRequestPresenter';
|
|
||||||
import { SponsorBillingPresenter } from './presenters/SponsorBillingPresenter';
|
|
||||||
import { AvailableLeaguesPresenter } from './presenters/AvailableLeaguesPresenter';
|
|
||||||
import { LeagueDetailPresenter } from './presenters/LeagueDetailPresenter';
|
|
||||||
import { SponsorSettingsPresenter } from './presenters/SponsorSettingsPresenter';
|
|
||||||
import { SponsorSettingsUpdatePresenter } from './presenters/SponsorSettingsUpdatePresenter';
|
|
||||||
import type { RejectSponsorshipRequestResult } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
|
||||||
|
|
||||||
// Tokens
|
// Tokens
|
||||||
import {
|
import {
|
||||||
GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN,
|
|
||||||
GET_SPONSORS_USE_CASE_TOKEN,
|
GET_SPONSORS_USE_CASE_TOKEN,
|
||||||
CREATE_SPONSOR_USE_CASE_TOKEN,
|
CREATE_SPONSOR_USE_CASE_TOKEN,
|
||||||
GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN,
|
GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN,
|
||||||
@@ -66,14 +48,33 @@ import {
|
|||||||
ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
||||||
REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
|
||||||
GET_SPONSOR_BILLING_USE_CASE_TOKEN,
|
GET_SPONSOR_BILLING_USE_CASE_TOKEN,
|
||||||
|
GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN,
|
||||||
LOGGER_TOKEN,
|
LOGGER_TOKEN,
|
||||||
} from './SponsorTokens';
|
} from './SponsorTokens';
|
||||||
|
|
||||||
|
// Presenters (for view model transformation only)
|
||||||
|
import { GetEntitySponsorshipPricingPresenter } from './presenters/GetEntitySponsorshipPricingPresenter';
|
||||||
|
import { GetSponsorsPresenter } from './presenters/GetSponsorsPresenter';
|
||||||
|
import { CreateSponsorPresenter } from './presenters/CreateSponsorPresenter';
|
||||||
|
import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPresenter';
|
||||||
|
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
|
||||||
|
import { GetSponsorPresenter } from './presenters/GetSponsorPresenter';
|
||||||
|
import { GetPendingSponsorshipRequestsPresenter } from './presenters/GetPendingSponsorshipRequestsPresenter';
|
||||||
|
import { AcceptSponsorshipRequestPresenter } from './presenters/AcceptSponsorshipRequestPresenter';
|
||||||
|
import { RejectSponsorshipRequestPresenter } from './presenters/RejectSponsorshipRequestPresenter';
|
||||||
|
import { SponsorBillingPresenter } from './presenters/SponsorBillingPresenter';
|
||||||
|
import { AvailableLeaguesPresenter } from './presenters/AvailableLeaguesPresenter';
|
||||||
|
import { LeagueDetailPresenter } from './presenters/LeagueDetailPresenter';
|
||||||
|
import { SponsorSettingsPresenter } from './presenters/SponsorSettingsPresenter';
|
||||||
|
import { SponsorSettingsUpdatePresenter } from './presenters/SponsorSettingsUpdatePresenter';
|
||||||
|
import type { RejectSponsorshipRequestResult } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
|
||||||
|
import type { AcceptSponsorshipRequestResultViewModel } from './presenters/AcceptSponsorshipRequestPresenter';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SponsorService {
|
export class SponsorService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN)
|
@Inject(GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN)
|
||||||
private readonly getSponsorshipPricingUseCase: GetSponsorshipPricingUseCase,
|
private readonly getEntitySponsorshipPricingUseCase: GetEntitySponsorshipPricingUseCase,
|
||||||
@Inject(GET_SPONSORS_USE_CASE_TOKEN)
|
@Inject(GET_SPONSORS_USE_CASE_TOKEN)
|
||||||
private readonly getSponsorsUseCase: GetSponsorsUseCase,
|
private readonly getSponsorsUseCase: GetSponsorsUseCase,
|
||||||
@Inject(CREATE_SPONSOR_USE_CASE_TOKEN)
|
@Inject(CREATE_SPONSOR_USE_CASE_TOKEN)
|
||||||
@@ -94,22 +95,14 @@ export class SponsorService {
|
|||||||
private readonly getSponsorBillingUseCase: GetSponsorBillingUseCase,
|
private readonly getSponsorBillingUseCase: GetSponsorBillingUseCase,
|
||||||
@Inject(LOGGER_TOKEN)
|
@Inject(LOGGER_TOKEN)
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
// Injected presenters
|
|
||||||
private readonly getEntitySponsorshipPricingPresenter: GetEntitySponsorshipPricingPresenter,
|
|
||||||
private readonly getSponsorsPresenter: GetSponsorsPresenter,
|
|
||||||
private readonly createSponsorPresenter: CreateSponsorPresenter,
|
|
||||||
private readonly getSponsorDashboardPresenter: GetSponsorDashboardPresenter,
|
|
||||||
private readonly getSponsorSponsorshipsPresenter: GetSponsorSponsorshipsPresenter,
|
|
||||||
private readonly getSponsorPresenter: GetSponsorPresenter,
|
|
||||||
private readonly getPendingSponsorshipRequestsPresenter: GetPendingSponsorshipRequestsPresenter,
|
|
||||||
private readonly acceptSponsorshipRequestPresenter: AcceptSponsorshipRequestPresenter,
|
|
||||||
private readonly rejectSponsorshipRequestPresenter: RejectSponsorshipRequestPresenter,
|
|
||||||
private readonly sponsorBillingPresenter: SponsorBillingPresenter,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getEntitySponsorshipPricing(): Promise<GetEntitySponsorshipPricingResultDTO> {
|
async getEntitySponsorshipPricing(): Promise<GetEntitySponsorshipPricingResultDTO> {
|
||||||
this.logger.debug('[SponsorService] Fetching sponsorship pricing.');
|
this.logger.debug('[SponsorService] Fetching sponsorship pricing.');
|
||||||
const result = await this.getSponsorshipPricingUseCase.execute({});
|
const result = await this.getEntitySponsorshipPricingUseCase.execute({
|
||||||
|
entityType: 'season',
|
||||||
|
entityId: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
return {
|
return {
|
||||||
@@ -119,18 +112,22 @@ export class SponsorService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getEntitySponsorshipPricingPresenter.viewModel;
|
const presenter = new GetEntitySponsorshipPricingPresenter();
|
||||||
|
presenter.present(result.value);
|
||||||
|
return presenter.viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSponsors(): Promise<GetSponsorsOutputDTO> {
|
async getSponsors(): Promise<GetSponsorsOutputDTO> {
|
||||||
this.logger.debug('[SponsorService] Fetching sponsors.');
|
this.logger.debug('[SponsorService] Fetching sponsors.');
|
||||||
const result = await this.getSponsorsUseCase.execute();
|
const result = await this.getSponsorsUseCase.execute({});
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
return { sponsors: [] };
|
return { sponsors: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getSponsorsPresenter.responseModel;
|
const presenter = new GetSponsorsPresenter();
|
||||||
|
presenter.present(result.unwrap().sponsors);
|
||||||
|
return presenter.responseModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSponsor(input: CreateSponsorInputDTO): Promise<CreateSponsorOutputDTO> {
|
async createSponsor(input: CreateSponsorInputDTO): Promise<CreateSponsorOutputDTO> {
|
||||||
@@ -142,7 +139,14 @@ export class SponsorService {
|
|||||||
throw new Error(error.details?.message ?? error.message ?? 'Failed to create sponsor');
|
throw new Error(error.details?.message ?? error.message ?? 'Failed to create sponsor');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.createSponsorPresenter.viewModel;
|
const resultValue = result.value;
|
||||||
|
if (!resultValue) {
|
||||||
|
throw new Error('Create sponsor failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const presenter = new CreateSponsorPresenter();
|
||||||
|
presenter.present(resultValue.sponsor);
|
||||||
|
return presenter.viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSponsorDashboard(
|
async getSponsorDashboard(
|
||||||
@@ -155,7 +159,14 @@ export class SponsorService {
|
|||||||
throw new Error('Sponsor dashboard not found');
|
throw new Error('Sponsor dashboard not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getSponsorDashboardPresenter.viewModel;
|
const resultValue = result.value;
|
||||||
|
if (!resultValue) {
|
||||||
|
throw new Error('Sponsor dashboard not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const presenter = new GetSponsorDashboardPresenter();
|
||||||
|
presenter.present(resultValue);
|
||||||
|
return presenter.viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSponsorSponsorships(
|
async getSponsorSponsorships(
|
||||||
@@ -168,7 +179,14 @@ export class SponsorService {
|
|||||||
throw new Error('Sponsor sponsorships not found');
|
throw new Error('Sponsor sponsorships not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getSponsorSponsorshipsPresenter.viewModel;
|
const resultValue = result.value;
|
||||||
|
if (!resultValue) {
|
||||||
|
throw new Error('Sponsor sponsorships not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const presenter = new GetSponsorSponsorshipsPresenter();
|
||||||
|
presenter.present(resultValue);
|
||||||
|
return presenter.viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO> {
|
async getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO> {
|
||||||
@@ -179,7 +197,14 @@ export class SponsorService {
|
|||||||
throw new Error('Sponsor not found');
|
throw new Error('Sponsor not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewModel = this.getSponsorPresenter.viewModel;
|
const resultValue = result.value;
|
||||||
|
if (!resultValue) {
|
||||||
|
throw new Error('Sponsor not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const presenter = new GetSponsorPresenter();
|
||||||
|
presenter.present(resultValue.sponsor);
|
||||||
|
const viewModel = presenter.viewModel;
|
||||||
if (!viewModel) {
|
if (!viewModel) {
|
||||||
throw new Error('Sponsor not found');
|
throw new Error('Sponsor not found');
|
||||||
}
|
}
|
||||||
@@ -205,7 +230,14 @@ export class SponsorService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewModel = this.getPendingSponsorshipRequestsPresenter.viewModel;
|
const resultValue = result.value;
|
||||||
|
if (!resultValue) {
|
||||||
|
throw new Error('Pending sponsorship requests not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const presenter = new GetPendingSponsorshipRequestsPresenter();
|
||||||
|
presenter.present(resultValue);
|
||||||
|
const viewModel = presenter.viewModel;
|
||||||
if (!viewModel) {
|
if (!viewModel) {
|
||||||
throw new Error('Pending sponsorship requests not found');
|
throw new Error('Pending sponsorship requests not found');
|
||||||
}
|
}
|
||||||
@@ -231,7 +263,14 @@ export class SponsorService {
|
|||||||
throw new Error('Accept sponsorship request failed');
|
throw new Error('Accept sponsorship request failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewModel = this.acceptSponsorshipRequestPresenter.viewModel;
|
const resultValue = result.value;
|
||||||
|
if (!resultValue) {
|
||||||
|
throw new Error('Accept sponsorship request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const presenter = new AcceptSponsorshipRequestPresenter();
|
||||||
|
presenter.present(resultValue);
|
||||||
|
const viewModel = presenter.viewModel;
|
||||||
if (!viewModel) {
|
if (!viewModel) {
|
||||||
throw new Error('Accept sponsorship request failed');
|
throw new Error('Accept sponsorship request failed');
|
||||||
}
|
}
|
||||||
@@ -263,7 +302,14 @@ export class SponsorService {
|
|||||||
throw new Error('Reject sponsorship request failed');
|
throw new Error('Reject sponsorship request failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewModel = this.rejectSponsorshipRequestPresenter.viewModel;
|
const resultValue = result.value;
|
||||||
|
if (!resultValue) {
|
||||||
|
throw new Error('Reject sponsorship request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const presenter = new RejectSponsorshipRequestPresenter();
|
||||||
|
presenter.present(resultValue);
|
||||||
|
const viewModel = presenter.viewModel;
|
||||||
if (!viewModel) {
|
if (!viewModel) {
|
||||||
throw new Error('Reject sponsorship request failed');
|
throw new Error('Reject sponsorship request failed');
|
||||||
}
|
}
|
||||||
@@ -284,7 +330,8 @@ export class SponsorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const billingData = result.unwrap();
|
const billingData = result.unwrap();
|
||||||
this.sponsorBillingPresenter.present({
|
const presenter = new SponsorBillingPresenter();
|
||||||
|
presenter.present({
|
||||||
paymentMethods: billingData.paymentMethods,
|
paymentMethods: billingData.paymentMethods,
|
||||||
invoices: billingData.invoices,
|
invoices: billingData.invoices,
|
||||||
stats: {
|
stats: {
|
||||||
@@ -294,7 +341,7 @@ export class SponsorService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.sponsorBillingPresenter.viewModel;
|
return presenter.viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAvailableLeagues(): Promise<AvailableLeaguesPresenter> {
|
async getAvailableLeagues(): Promise<AvailableLeaguesPresenter> {
|
||||||
|
|||||||
@@ -9,18 +9,6 @@ export const SPONSORSHIP_REQUEST_REPOSITORY_TOKEN = 'ISponsorshipRequestReposito
|
|||||||
export const LOGGER_TOKEN = 'Logger';
|
export const LOGGER_TOKEN = 'Logger';
|
||||||
export const MEDIA_RESOLVER_TOKEN = 'MediaResolverPort';
|
export const MEDIA_RESOLVER_TOKEN = 'MediaResolverPort';
|
||||||
|
|
||||||
// Presenter tokens
|
|
||||||
export const GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN = 'GetEntitySponsorshipPricingPresenter';
|
|
||||||
export const GET_SPONSORS_PRESENTER_TOKEN = 'GetSponsorsPresenter';
|
|
||||||
export const CREATE_SPONSOR_PRESENTER_TOKEN = 'CreateSponsorPresenter';
|
|
||||||
export const GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN = 'GetSponsorDashboardPresenter';
|
|
||||||
export const GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN = 'GetSponsorSponsorshipsPresenter';
|
|
||||||
export const GET_SPONSOR_PRESENTER_TOKEN = 'GetSponsorPresenter';
|
|
||||||
export const GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN = 'GetPendingSponsorshipRequestsPresenter';
|
|
||||||
export const ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN = 'AcceptSponsorshipRequestPresenter';
|
|
||||||
export const REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN = 'RejectSponsorshipRequestPresenter';
|
|
||||||
export const GET_SPONSOR_BILLING_PRESENTER_TOKEN = 'SponsorBillingPresenter';
|
|
||||||
|
|
||||||
// Use case / application service tokens
|
// Use case / application service tokens
|
||||||
export const GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetSponsorshipPricingUseCase';
|
export const GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetSponsorshipPricingUseCase';
|
||||||
export const GET_SPONSORS_USE_CASE_TOKEN = 'GetSponsorsUseCase';
|
export const GET_SPONSORS_USE_CASE_TOKEN = 'GetSponsorsUseCase';
|
||||||
@@ -33,16 +21,3 @@ export const GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN = 'GetPendingSponso
|
|||||||
export const ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'AcceptSponsorshipRequestUseCase';
|
export const ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'AcceptSponsorshipRequestUseCase';
|
||||||
export const REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'RejectSponsorshipRequestUseCase';
|
export const REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'RejectSponsorshipRequestUseCase';
|
||||||
export const GET_SPONSOR_BILLING_USE_CASE_TOKEN = 'GetSponsorBillingUseCase';
|
export const GET_SPONSOR_BILLING_USE_CASE_TOKEN = 'GetSponsorBillingUseCase';
|
||||||
|
|
||||||
// Output port tokens
|
|
||||||
export const GET_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN = 'GetSponsorshipPricingOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSORS_OUTPUT_PORT_TOKEN = 'GetSponsorsOutputPort_TOKEN';
|
|
||||||
export const CREATE_SPONSOR_OUTPUT_PORT_TOKEN = 'CreateSponsorOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSOR_DASHBOARD_OUTPUT_PORT_TOKEN = 'GetSponsorDashboardOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSOR_SPONSORSHIPS_OUTPUT_PORT_TOKEN = 'GetSponsorSponsorshipsOutputPort_TOKEN';
|
|
||||||
export const GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN = 'GetEntitySponsorshipPricingOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSOR_OUTPUT_PORT_TOKEN = 'GetSponsorOutputPort_TOKEN';
|
|
||||||
export const GET_PENDING_SPONSORSHIP_REQUESTS_OUTPUT_PORT_TOKEN = 'GetPendingSponsorshipRequestsOutputPort_TOKEN';
|
|
||||||
export const ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN = 'AcceptSponsorshipRequestOutputPort_TOKEN';
|
|
||||||
export const REJECT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN = 'RejectSponsorshipRequestOutputPort_TOKEN';
|
|
||||||
export const GET_SPONSOR_BILLING_OUTPUT_PORT_TOKEN = 'GetSponsorBillingOutputPort_TOKEN';
|
|
||||||
@@ -8,7 +8,7 @@ export class GetEntitySponsorshipPricingPresenter {
|
|||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(output: GetEntitySponsorshipPricingResult | null) {
|
present(output: GetEntitySponsorshipPricingResult | null | undefined) {
|
||||||
if (!output) {
|
if (!output) {
|
||||||
this.result = {
|
this.result = {
|
||||||
entityType: 'season',
|
entityType: 'season',
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
import { GetSponsorOutputDTO } from '../dtos/GetSponsorOutputDTO';
|
import { GetSponsorOutputDTO } from '../dtos/GetSponsorOutputDTO';
|
||||||
|
import type { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||||
interface GetSponsorOutputPort {
|
|
||||||
sponsor: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
logoUrl?: string;
|
|
||||||
websiteUrl?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GetSponsorPresenter {
|
export class GetSponsorPresenter {
|
||||||
private result: GetSponsorOutputDTO | null = null;
|
private result: GetSponsorOutputDTO | null = null;
|
||||||
@@ -16,18 +8,18 @@ export class GetSponsorPresenter {
|
|||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
present(output: GetSponsorOutputPort | null) {
|
present(sponsor: Sponsor) {
|
||||||
if (!output) {
|
if (!sponsor) {
|
||||||
this.result = null;
|
this.result = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.result = {
|
this.result = {
|
||||||
sponsor: {
|
sponsor: {
|
||||||
id: output.sponsor.id,
|
id: sponsor.id.toString(),
|
||||||
name: output.sponsor.name,
|
name: sponsor.name.toString(),
|
||||||
...(output.sponsor.logoUrl !== undefined ? { logoUrl: output.sponsor.logoUrl } : {}),
|
...(sponsor.logoUrl !== undefined ? { logoUrl: sponsor.logoUrl.toString() } : {}),
|
||||||
...(output.sponsor.websiteUrl !== undefined ? { websiteUrl: output.sponsor.websiteUrl } : {}),
|
...(sponsor.websiteUrl !== undefined ? { websiteUrl: sponsor.websiteUrl.toString() } : {}),
|
||||||
},
|
},
|
||||||
} as GetSponsorOutputDTO;
|
} as GetSponsorOutputDTO;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import { GetSponsorshipPricingPresenter } from './GetSponsorshipPricingPresenter';
|
|
||||||
import type { GetSponsorshipPricingResult } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
|
||||||
|
|
||||||
describe('GetSponsorshipPricingPresenter', () => {
|
|
||||||
let presenter: GetSponsorshipPricingPresenter;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
presenter = new GetSponsorshipPricingPresenter();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reset', () => {
|
|
||||||
it('should reset the result to null', () => {
|
|
||||||
const mockResult: GetSponsorshipPricingResult = {
|
|
||||||
entityType: 'season',
|
|
||||||
entityId: 'season-1',
|
|
||||||
pricing: []
|
|
||||||
};
|
|
||||||
presenter.present(mockResult);
|
|
||||||
|
|
||||||
const expectedViewModel = {
|
|
||||||
entityType: 'season',
|
|
||||||
entityId: 'season-1',
|
|
||||||
pricing: []
|
|
||||||
};
|
|
||||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
|
||||||
|
|
||||||
presenter.reset();
|
|
||||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('present', () => {
|
|
||||||
it('should store the result', () => {
|
|
||||||
const mockResult: GetSponsorshipPricingResult = {
|
|
||||||
entityType: 'season',
|
|
||||||
entityId: 'season-1',
|
|
||||||
pricing: []
|
|
||||||
};
|
|
||||||
|
|
||||||
presenter.present(mockResult);
|
|
||||||
|
|
||||||
const expectedViewModel = {
|
|
||||||
entityType: 'season',
|
|
||||||
entityId: 'season-1',
|
|
||||||
pricing: []
|
|
||||||
};
|
|
||||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getViewModel', () => {
|
|
||||||
it('should return null when not presented', () => {
|
|
||||||
expect(presenter.getViewModel()).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the result when presented', () => {
|
|
||||||
const mockResult: GetSponsorshipPricingResult = {
|
|
||||||
entityType: 'season',
|
|
||||||
entityId: 'season-1',
|
|
||||||
pricing: []
|
|
||||||
};
|
|
||||||
presenter.present(mockResult);
|
|
||||||
|
|
||||||
const expectedViewModel = {
|
|
||||||
entityType: 'season',
|
|
||||||
entityId: 'season-1',
|
|
||||||
pricing: []
|
|
||||||
};
|
|
||||||
expect(presenter.getViewModel()).toEqual(expectedViewModel);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('viewModel', () => {
|
|
||||||
it('should throw error when not presented', () => {
|
|
||||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the result when presented', () => {
|
|
||||||
const mockResult: GetSponsorshipPricingResult = {
|
|
||||||
entityType: 'season',
|
|
||||||
entityId: 'season-1',
|
|
||||||
pricing: []
|
|
||||||
};
|
|
||||||
presenter.present(mockResult);
|
|
||||||
|
|
||||||
const expectedViewModel = {
|
|
||||||
entityType: 'season',
|
|
||||||
entityId: 'season-1',
|
|
||||||
pricing: []
|
|
||||||
};
|
|
||||||
expect(presenter.viewModel).toEqual(expectedViewModel);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import type { GetSponsorshipPricingResult } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
|
|
||||||
import { GetEntitySponsorshipPricingResultDTO } from '../dtos/GetEntitySponsorshipPricingResultDTO';
|
|
||||||
|
|
||||||
export class GetSponsorshipPricingPresenter {
|
|
||||||
private result: GetEntitySponsorshipPricingResultDTO | null = null;
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
present(outputPort: GetSponsorshipPricingResult): void {
|
|
||||||
this.result = {
|
|
||||||
entityType: outputPort.entityType,
|
|
||||||
entityId: outputPort.entityId,
|
|
||||||
pricing: outputPort.pricing.map(item => ({
|
|
||||||
id: item.id,
|
|
||||||
level: item.level,
|
|
||||||
price: item.price,
|
|
||||||
currency: item.currency,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewModel(): GetEntitySponsorshipPricingResultDTO | null {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewModel(): GetEntitySponsorshipPricingResultDTO {
|
|
||||||
if (!this.result) throw new Error('Presenter not presented');
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -124,42 +124,13 @@ describe('TeamService', () => {
|
|||||||
clear: vi.fn(),
|
clear: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const resultRepository = {
|
|
||||||
findAll: vi.fn().mockResolvedValue([]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock presenter that stores result synchronously
|
|
||||||
const allTeamsPresenter = {
|
|
||||||
reset: vi.fn(),
|
|
||||||
present: vi.fn((result: any) => {
|
|
||||||
// Store immediately and synchronously
|
|
||||||
allTeamsPresenter.responseModel = {
|
|
||||||
teams: result.teams.map((t: any) => ({
|
|
||||||
id: t.id,
|
|
||||||
name: t.name,
|
|
||||||
tag: t.tag,
|
|
||||||
description: t.description,
|
|
||||||
memberCount: t.memberCount,
|
|
||||||
leagues: t.leagues,
|
|
||||||
logoUrl: t.logoUrl ?? null,
|
|
||||||
})),
|
|
||||||
totalCount: result.totalCount,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
getResponseModel: vi.fn(() => allTeamsPresenter.responseModel || { teams: [], totalCount: 0 }),
|
|
||||||
responseModel: { teams: [], totalCount: 0 },
|
|
||||||
setMediaResolver: vi.fn(),
|
|
||||||
setBaseUrl: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
service = new TeamService(
|
service = new TeamService(
|
||||||
teamRepository as unknown as never,
|
teamRepository as unknown as never,
|
||||||
membershipRepository as unknown as never,
|
membershipRepository as unknown as never,
|
||||||
driverRepository as unknown as never,
|
driverRepository as unknown as never,
|
||||||
logger,
|
logger,
|
||||||
teamStatsRepository as unknown as never,
|
teamStatsRepository as unknown as never
|
||||||
resultRepository as unknown as never,
|
|
||||||
allTeamsPresenter as any
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,7 +149,15 @@ describe('TeamService', () => {
|
|||||||
description: 'Desc',
|
description: 'Desc',
|
||||||
memberCount: 3,
|
memberCount: 3,
|
||||||
leagues: ['league-1'],
|
leagues: ['league-1'],
|
||||||
logoUrl: null,
|
totalWins: 0,
|
||||||
|
totalRaces: 0,
|
||||||
|
performanceLevel: 'intermediate',
|
||||||
|
specialization: 'mixed',
|
||||||
|
region: '',
|
||||||
|
languages: [],
|
||||||
|
rating: 0,
|
||||||
|
logoUrl: '/media/teams/team-1/logo',
|
||||||
|
isRecruiting: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalCount: 1,
|
totalCount: 1,
|
||||||
@@ -283,8 +262,16 @@ describe('TeamService', () => {
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
avatarUrl: '',
|
avatarUrl: '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
driverId: '',
|
||||||
|
driverName: '',
|
||||||
|
role: 'owner',
|
||||||
|
joinedAt: '2023-02-02T00:00:00.000Z',
|
||||||
|
isActive: true,
|
||||||
|
avatarUrl: '',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
totalCount: 1,
|
totalCount: 2,
|
||||||
ownerCount: 1,
|
ownerCount: 1,
|
||||||
managerCount: 0,
|
managerCount: 0,
|
||||||
memberCount: 1,
|
memberCount: 1,
|
||||||
|
|||||||
@@ -26,20 +26,9 @@ import { UpdateTeamUseCase, UpdateTeamInput } from '@core/racing/application/use
|
|||||||
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
||||||
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
|
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
|
||||||
|
|
||||||
// API Presenters
|
|
||||||
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
|
|
||||||
import { TeamDetailsPresenter } from './presenters/TeamDetailsPresenter';
|
|
||||||
import { TeamMembersPresenter } from './presenters/TeamMembersPresenter';
|
|
||||||
import { TeamJoinRequestsPresenter } from './presenters/TeamJoinRequestsPresenter';
|
|
||||||
import { DriverTeamPresenter } from './presenters/DriverTeamPresenter';
|
|
||||||
import { TeamMembershipPresenter } from './presenters/TeamMembershipPresenter';
|
|
||||||
import { CreateTeamPresenter } from './presenters/CreateTeamPresenter';
|
|
||||||
import { UpdateTeamPresenter } from './presenters/UpdateTeamPresenter';
|
|
||||||
|
|
||||||
// Tokens
|
// Tokens
|
||||||
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, RESULT_REPOSITORY_TOKEN } from './TeamTokens';
|
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN } from './TeamTokens';
|
||||||
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
|
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
|
||||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamService {
|
export class TeamService {
|
||||||
@@ -49,8 +38,6 @@ export class TeamService {
|
|||||||
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
|
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
|
||||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||||
@Inject(TEAM_STATS_REPOSITORY_TOKEN) private readonly teamStatsRepository: ITeamStatsRepository,
|
@Inject(TEAM_STATS_REPOSITORY_TOKEN) private readonly teamStatsRepository: ITeamStatsRepository,
|
||||||
@Inject(RESULT_REPOSITORY_TOKEN) private readonly resultRepository: IResultRepository,
|
|
||||||
private readonly allTeamsPresenter: AllTeamsPresenter,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||||
@@ -60,38 +47,82 @@ export class TeamService {
|
|||||||
this.teamRepository,
|
this.teamRepository,
|
||||||
this.membershipRepository,
|
this.membershipRepository,
|
||||||
this.teamStatsRepository,
|
this.teamStatsRepository,
|
||||||
this.resultRepository,
|
this.logger
|
||||||
this.logger,
|
|
||||||
this.allTeamsPresenter
|
|
||||||
);
|
);
|
||||||
const result = await useCase.execute();
|
const result = await useCase.execute({});
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error('Error fetching all teams', new Error(result.error?.details?.message || 'Unknown error'));
|
this.logger.error('Error fetching all teams', new Error(result.error?.details?.message || 'Unknown error'));
|
||||||
return { teams: [], totalCount: 0 };
|
return { teams: [], totalCount: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.allTeamsPresenter.getResponseModel()!;
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
return { teams: [], totalCount: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
teams: value.teams.map(t => ({
|
||||||
|
id: t.team.id,
|
||||||
|
name: t.team.name.toString(),
|
||||||
|
tag: t.team.tag.toString(),
|
||||||
|
description: t.description,
|
||||||
|
memberCount: t.memberCount,
|
||||||
|
leagues: t.leagues,
|
||||||
|
totalWins: t.totalWins,
|
||||||
|
totalRaces: t.totalRaces,
|
||||||
|
performanceLevel: t.performanceLevel,
|
||||||
|
specialization: t.specialization,
|
||||||
|
region: t.region,
|
||||||
|
languages: t.languages,
|
||||||
|
rating: t.rating,
|
||||||
|
logoUrl: t.logoUrl,
|
||||||
|
isRecruiting: t.isRecruiting,
|
||||||
|
})),
|
||||||
|
totalCount: value.totalCount,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
|
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
|
||||||
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);
|
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);
|
||||||
|
|
||||||
const presenter = new TeamDetailsPresenter();
|
const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository);
|
||||||
const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository, presenter);
|
|
||||||
const result = await useCase.execute({ teamId, driverId: userId || '' });
|
const result = await useCase.execute({ teamId, driverId: userId || '' });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return presenter.getResponseModel()!;
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to DTO
|
||||||
|
return {
|
||||||
|
team: {
|
||||||
|
id: value.team.id,
|
||||||
|
name: value.team.name.toString(),
|
||||||
|
tag: value.team.tag.toString(),
|
||||||
|
description: value.team.description.toString(),
|
||||||
|
ownerId: value.team.ownerId.toString(),
|
||||||
|
leagues: value.team.leagues.map(l => l.toString()),
|
||||||
|
isRecruiting: value.team.isRecruiting,
|
||||||
|
createdAt: value.team.createdAt?.toDate()?.toISOString?.() || new Date().toISOString(),
|
||||||
|
category: undefined,
|
||||||
|
},
|
||||||
|
membership: value.membership ? {
|
||||||
|
role: value.membership.role === 'driver' ? 'member' : (value.membership.role as 'owner' | 'manager' | 'member'),
|
||||||
|
joinedAt: value.membership.joinedAt.toISOString(),
|
||||||
|
isActive: value.membership.status === 'active',
|
||||||
|
} : null,
|
||||||
|
canManage: value.canManage,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
|
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
|
||||||
|
|
||||||
const presenter = new TeamMembersPresenter();
|
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger);
|
||||||
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger, presenter);
|
|
||||||
const result = await useCase.execute({ teamId });
|
const result = await useCase.execute({ teamId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
@@ -104,14 +135,37 @@ export class TeamService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return presenter.getResponseModel()!;
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
return {
|
||||||
|
members: [],
|
||||||
|
totalCount: 0,
|
||||||
|
ownerCount: 0,
|
||||||
|
managerCount: 0,
|
||||||
|
memberCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
members: value.members.map(m => ({
|
||||||
|
driverId: m.driver?.id || '',
|
||||||
|
driverName: m.driver?.name?.toString() || '',
|
||||||
|
role: m.membership.role === 'driver' ? 'member' : (m.membership.role as 'owner' | 'manager' | 'member'),
|
||||||
|
joinedAt: m.membership.joinedAt.toISOString(),
|
||||||
|
isActive: m.membership.status === 'active',
|
||||||
|
avatarUrl: '', // Would need MediaResolver here
|
||||||
|
})),
|
||||||
|
totalCount: value.members.length,
|
||||||
|
ownerCount: value.members.filter(m => m.membership.role === 'owner').length,
|
||||||
|
managerCount: value.members.filter(m => m.membership.role === 'manager').length,
|
||||||
|
memberCount: value.members.filter(m => m.membership.role === 'driver').length,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`);
|
this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`);
|
||||||
|
|
||||||
const presenter = new TeamJoinRequestsPresenter();
|
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository);
|
||||||
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, presenter);
|
|
||||||
const result = await useCase.execute({ teamId });
|
const result = await useCase.execute({ teamId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.error?.details?.message || 'Unknown error'));
|
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.error?.details?.message || 'Unknown error'));
|
||||||
@@ -122,14 +176,33 @@ export class TeamService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return presenter.getResponseModel()!;
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
return {
|
||||||
|
requests: [],
|
||||||
|
pendingCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
requests: value.joinRequests.map(r => ({
|
||||||
|
requestId: r.id,
|
||||||
|
driverId: r.driverId,
|
||||||
|
driverName: r.driver.name.toString(),
|
||||||
|
teamId: r.teamId,
|
||||||
|
status: 'pending',
|
||||||
|
requestedAt: r.requestedAt.toISOString(),
|
||||||
|
avatarUrl: '', // Would need MediaResolver here
|
||||||
|
})),
|
||||||
|
pendingCount: value.joinRequests.length,
|
||||||
|
totalCount: value.joinRequests.length,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(input: CreateTeamInputDTO, userId?: string): Promise<CreateTeamOutputDTO> {
|
async create(input: CreateTeamInputDTO, userId?: string): Promise<CreateTeamOutputDTO> {
|
||||||
this.logger.debug('[TeamService] Creating team', { input, userId });
|
this.logger.debug('[TeamService] Creating team', { input, userId });
|
||||||
|
|
||||||
const presenter = new CreateTeamPresenter();
|
|
||||||
|
|
||||||
const command: CreateTeamInput = {
|
const command: CreateTeamInput = {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
tag: input.tag,
|
tag: input.tag,
|
||||||
@@ -138,21 +211,24 @@ export class TeamService {
|
|||||||
leagues: [],
|
leagues: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
|
||||||
const result = await useCase.execute(command);
|
const result = await useCase.execute(command);
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`);
|
this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
return { id: '', success: false };
|
return { id: '', success: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
return presenter.responseModel;
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
return { id: '', success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id: value.team.id, success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise<UpdateTeamOutputDTO> {
|
async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise<UpdateTeamOutputDTO> {
|
||||||
this.logger.debug(`[TeamService] Updating team ${teamId}`, { input, userId });
|
this.logger.debug(`[TeamService] Updating team ${teamId}`, { input, userId });
|
||||||
|
|
||||||
const presenter = new UpdateTeamPresenter();
|
|
||||||
|
|
||||||
const command: UpdateTeamInput = {
|
const command: UpdateTeamInput = {
|
||||||
teamId,
|
teamId,
|
||||||
updates: {
|
updates: {
|
||||||
@@ -163,41 +239,72 @@ export class TeamService {
|
|||||||
updatedBy: userId || '',
|
updatedBy: userId || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository, presenter);
|
const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository);
|
||||||
const result = await useCase.execute(command);
|
const result = await useCase.execute(command);
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error updating team ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
this.logger.error(`Error updating team ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
return { success: false };
|
return { success: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
return presenter.responseModel;
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||||
this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`);
|
this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`);
|
||||||
|
|
||||||
const presenter = new DriverTeamPresenter();
|
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
|
||||||
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
|
||||||
const result = await useCase.execute({ driverId });
|
const result = await useCase.execute({ driverId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return presenter.getResponseModel();
|
const value = result.value;
|
||||||
|
if (!value || !value.team) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
team: {
|
||||||
|
id: value.team.id,
|
||||||
|
name: value.team.name.toString(),
|
||||||
|
tag: value.team.tag.toString(),
|
||||||
|
description: value.team.description.toString(),
|
||||||
|
ownerId: value.team.ownerId.toString(),
|
||||||
|
leagues: value.team.leagues.map(l => l.toString()),
|
||||||
|
isRecruiting: value.team.isRecruiting,
|
||||||
|
createdAt: value.team.createdAt?.toDate?.()?.toISOString?.() || new Date().toISOString(),
|
||||||
|
category: undefined,
|
||||||
|
},
|
||||||
|
membership: {
|
||||||
|
role: value.membership.role === 'driver' ? 'member' : (value.membership.role as 'owner' | 'manager' | 'member'),
|
||||||
|
joinedAt: value.membership.joinedAt.toISOString(),
|
||||||
|
isActive: value.membership.status === 'active',
|
||||||
|
},
|
||||||
|
isOwner: value.membership.role === 'owner',
|
||||||
|
canManage: value.membership.role === 'owner' || value.membership.role === 'manager',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||||
this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`);
|
this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`);
|
||||||
|
|
||||||
const presenter = new TeamMembershipPresenter();
|
const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger);
|
||||||
const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger, presenter);
|
|
||||||
const result = await useCase.execute({ teamId, driverId });
|
const result = await useCase.execute({ teamId, driverId });
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return presenter.getResponseModel();
|
const value = result.value;
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.membership ? {
|
||||||
|
role: value.membership.role,
|
||||||
|
joinedAt: value.membership.joinedAt,
|
||||||
|
isActive: value.membership.isActive,
|
||||||
|
} : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,40 +19,40 @@ export class AllTeamsPresenter implements UseCaseOutputPort<GetAllTeamsResult> {
|
|||||||
|
|
||||||
async present(result: GetAllTeamsResult): Promise<void> {
|
async present(result: GetAllTeamsResult): Promise<void> {
|
||||||
const teams: TeamListItemDTO[] = await Promise.all(
|
const teams: TeamListItemDTO[] = await Promise.all(
|
||||||
result.teams.map(async (team) => {
|
result.teams.map(async (enrichedTeam) => {
|
||||||
const dto = new TeamListItemDTO();
|
const dto = new TeamListItemDTO();
|
||||||
dto.id = team.id;
|
dto.id = enrichedTeam.team.id;
|
||||||
dto.name = team.name;
|
dto.name = enrichedTeam.team.name.toString();
|
||||||
dto.tag = team.tag;
|
dto.tag = enrichedTeam.team.tag.toString();
|
||||||
dto.description = team.description || '';
|
dto.description = enrichedTeam.team.description.toString() || '';
|
||||||
dto.memberCount = team.memberCount;
|
dto.memberCount = enrichedTeam.memberCount;
|
||||||
dto.leagues = team.leagues || [];
|
dto.leagues = enrichedTeam.team.leagues.map(l => l.toString()) || [];
|
||||||
dto.totalWins = team.totalWins ?? 0;
|
dto.totalWins = enrichedTeam.totalWins;
|
||||||
dto.totalRaces = team.totalRaces ?? 0;
|
dto.totalRaces = enrichedTeam.totalRaces;
|
||||||
dto.performanceLevel = (team.performanceLevel as 'beginner' | 'intermediate' | 'advanced' | 'pro') ?? 'intermediate';
|
dto.performanceLevel = enrichedTeam.performanceLevel;
|
||||||
dto.specialization = (team.specialization as 'endurance' | 'sprint' | 'mixed') ?? 'mixed';
|
dto.specialization = enrichedTeam.specialization;
|
||||||
dto.region = team.region ?? '';
|
dto.region = enrichedTeam.region;
|
||||||
dto.languages = team.languages ?? [];
|
dto.languages = enrichedTeam.languages;
|
||||||
|
|
||||||
// Resolve logo URL using MediaResolverPort if available
|
// Resolve logo URL using MediaResolverPort if available
|
||||||
if (this.mediaResolver && team.logoRef) {
|
if (this.mediaResolver && enrichedTeam.team.logoRef) {
|
||||||
const ref = team.logoRef instanceof MediaReference ? team.logoRef : MediaReference.fromJSON(team.logoRef);
|
const ref = enrichedTeam.team.logoRef instanceof MediaReference ? enrichedTeam.team.logoRef : MediaReference.fromJSON(enrichedTeam.team.logoRef);
|
||||||
dto.logoUrl = await this.mediaResolver.resolve(ref);
|
dto.logoUrl = await this.mediaResolver.resolve(ref);
|
||||||
} else {
|
} else {
|
||||||
// Fallback to existing logoUrl or null
|
// Fallback to enriched logoUrl or null
|
||||||
dto.logoUrl = team.logoUrl ?? null;
|
dto.logoUrl = enrichedTeam.logoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.rating = team.rating ?? 0;
|
dto.rating = enrichedTeam.rating;
|
||||||
dto.category = team.category;
|
dto.category = enrichedTeam.team.category;
|
||||||
dto.isRecruiting = team.isRecruiting;
|
dto.isRecruiting = enrichedTeam.team.isRecruiting;
|
||||||
return dto;
|
return dto;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.model = {
|
this.model = {
|
||||||
teams,
|
teams,
|
||||||
totalCount: result.totalCount ?? result.teams.length,
|
totalCount: result.totalCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export class SessionGateway {
|
|||||||
cookie: cookieString,
|
cookie: cookieString,
|
||||||
},
|
},
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[SESSION] Response status:`, response.status);
|
console.log(`[SESSION] Response status:`, response.status);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|||||||
import { ListUsersUseCase, ListUsersResult } from './ListUsersUseCase';
|
import { ListUsersUseCase, ListUsersResult } from './ListUsersUseCase';
|
||||||
import { IAdminUserRepository } from '../ports/IAdminUserRepository';
|
import { IAdminUserRepository } from '../ports/IAdminUserRepository';
|
||||||
import { AdminUser } from '../../domain/entities/AdminUser';
|
import { AdminUser } from '../../domain/entities/AdminUser';
|
||||||
import { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import { AuthorizationService } from '../../domain/services/AuthorizationService';
|
import { AuthorizationService } from '../../domain/services/AuthorizationService';
|
||||||
|
|
||||||
// Mock the authorization service
|
// Mock the authorization service
|
||||||
@@ -20,11 +19,6 @@ const mockRepository = {
|
|||||||
delete: vi.fn(),
|
delete: vi.fn(),
|
||||||
} as unknown as IAdminUserRepository;
|
} as unknown as IAdminUserRepository;
|
||||||
|
|
||||||
// Mock output port
|
|
||||||
const mockOutputPort = {
|
|
||||||
present: vi.fn(),
|
|
||||||
} as unknown as UseCaseOutputPort<ListUsersResult>;
|
|
||||||
|
|
||||||
describe('ListUsersUseCase', () => {
|
describe('ListUsersUseCase', () => {
|
||||||
let useCase: ListUsersUseCase;
|
let useCase: ListUsersUseCase;
|
||||||
let actor: AdminUser;
|
let actor: AdminUser;
|
||||||
@@ -41,7 +35,7 @@ describe('ListUsersUseCase', () => {
|
|||||||
// Setup default successful authorization
|
// Setup default successful authorization
|
||||||
vi.mocked(AuthorizationService.canListUsers).mockReturnValue(true);
|
vi.mocked(AuthorizationService.canListUsers).mockReturnValue(true);
|
||||||
|
|
||||||
useCase = new ListUsersUseCase(mockRepository, mockOutputPort);
|
useCase = new ListUsersUseCase(mockRepository);
|
||||||
|
|
||||||
// Create actor (owner)
|
// Create actor (owner)
|
||||||
actor = AdminUser.create({
|
actor = AdminUser.create({
|
||||||
@@ -76,7 +70,8 @@ describe('ListUsersUseCase', () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(mockOutputPort.present).toHaveBeenCalledWith({
|
const data = result.unwrap();
|
||||||
|
expect(data).toEqual({
|
||||||
users: [],
|
users: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -120,13 +115,12 @@ describe('ListUsersUseCase', () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(mockOutputPort.present).toHaveBeenCalledWith({
|
const data = result.unwrap();
|
||||||
users: [user1, user2],
|
expect(data.users).toEqual([user1, user2]);
|
||||||
total: 2,
|
expect(data.total).toBe(2);
|
||||||
page: 1,
|
expect(data.page).toBe(1);
|
||||||
limit: 10,
|
expect(data.limit).toBe(10);
|
||||||
totalPages: 1,
|
expect(data.totalPages).toBe(1);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter by role', async () => {
|
it('should filter by role', async () => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
import type { IAdminUserRepository } from '../ports/IAdminUserRepository';
|
import type { IAdminUserRepository } from '../ports/IAdminUserRepository';
|
||||||
import { AuthorizationService } from '../../domain/services/AuthorizationService';
|
import { AuthorizationService } from '../../domain/services/AuthorizationService';
|
||||||
import { UserId } from '../../domain/value-objects/UserId';
|
import { UserId } from '../../domain/value-objects/UserId';
|
||||||
@@ -46,14 +45,13 @@ export type ListUsersApplicationError = ApplicationErrorCode<ListUsersErrorCode,
|
|||||||
export class ListUsersUseCase {
|
export class ListUsersUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly adminUserRepository: IAdminUserRepository,
|
private readonly adminUserRepository: IAdminUserRepository,
|
||||||
private readonly output: UseCaseOutputPort<ListUsersResult>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
input: ListUsersInput,
|
input: ListUsersInput,
|
||||||
): Promise<
|
): Promise<
|
||||||
Result<
|
Result<
|
||||||
void,
|
ListUsersResult,
|
||||||
ListUsersApplicationError
|
ListUsersApplicationError
|
||||||
>
|
>
|
||||||
> {
|
> {
|
||||||
@@ -137,16 +135,15 @@ export class ListUsersUseCase {
|
|||||||
|
|
||||||
const result = await this.adminUserRepository.list(query);
|
const result = await this.adminUserRepository.list(query);
|
||||||
|
|
||||||
// Pass domain objects to output port
|
const output: ListUsersResult = {
|
||||||
this.output.present({
|
|
||||||
users: result.users,
|
users: result.users,
|
||||||
total: result.total,
|
total: result.total,
|
||||||
page: result.page,
|
page: result.page,
|
||||||
limit: result.limit,
|
limit: result.limit,
|
||||||
totalPages: result.totalPages,
|
totalPages: result.totalPages,
|
||||||
});
|
};
|
||||||
|
|
||||||
return Result.ok(undefined);
|
return Result.ok(output);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Failed to list users';
|
const message = error instanceof Error ? error.message : 'Failed to list users';
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import { GetAnalyticsMetricsUseCase, type GetAnalyticsMetricsInput, type GetAnalyticsMetricsOutput } from './GetAnalyticsMetricsUseCase';
|
import { GetAnalyticsMetricsUseCase, type GetAnalyticsMetricsInput } from './GetAnalyticsMetricsUseCase';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
describe('GetAnalyticsMetricsUseCase', () => {
|
describe('GetAnalyticsMetricsUseCase', () => {
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<GetAnalyticsMetricsOutput> & { present: Mock };
|
|
||||||
let useCase: GetAnalyticsMetricsUseCase;
|
let useCase: GetAnalyticsMetricsUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -15,21 +14,15 @@ describe('GetAnalyticsMetricsUseCase', () => {
|
|||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
|
|
||||||
output = {
|
useCase = new GetAnalyticsMetricsUseCase(logger);
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
useCase = new GetAnalyticsMetricsUseCase(
|
|
||||||
logger,
|
|
||||||
output,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('presents default metrics and logs retrieval when no input is provided', async () => {
|
it('returns default metrics when no input is provided', async () => {
|
||||||
const result = await useCase.execute();
|
const result = await useCase.execute();
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(output.present).toHaveBeenCalledWith({
|
const data = result.unwrap();
|
||||||
|
expect(data).toEqual({
|
||||||
pageViews: 0,
|
pageViews: 0,
|
||||||
uniqueVisitors: 0,
|
uniqueVisitors: 0,
|
||||||
averageSessionDuration: 0,
|
averageSessionDuration: 0,
|
||||||
@@ -38,7 +31,21 @@ describe('GetAnalyticsMetricsUseCase', () => {
|
|||||||
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
|
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses provided date range and presents error when execute throws', async () => {
|
it('uses provided date range and returns metrics', async () => {
|
||||||
|
const input: GetAnalyticsMetricsInput = {
|
||||||
|
startDate: new Date('2024-01-01'),
|
||||||
|
endDate: new Date('2024-01-31'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await useCase.execute(input);
|
||||||
|
|
||||||
|
expect(result.isOk()).toBe(true);
|
||||||
|
const data = result.unwrap();
|
||||||
|
expect(data.pageViews).toBe(0);
|
||||||
|
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error when execute throws', async () => {
|
||||||
const input: GetAnalyticsMetricsInput = {
|
const input: GetAnalyticsMetricsInput = {
|
||||||
startDate: new Date('2024-01-01'),
|
startDate: new Date('2024-01-01'),
|
||||||
endDate: new Date('2024-01-31'),
|
endDate: new Date('2024-01-31'),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Logger, UseCase, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger, UseCase } from '@core/shared/application';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
|
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
|
||||||
@@ -17,16 +17,15 @@ export interface GetAnalyticsMetricsOutput {
|
|||||||
|
|
||||||
export type GetAnalyticsMetricsErrorCode = 'REPOSITORY_ERROR';
|
export type GetAnalyticsMetricsErrorCode = 'REPOSITORY_ERROR';
|
||||||
|
|
||||||
export class GetAnalyticsMetricsUseCase implements UseCase<GetAnalyticsMetricsInput, void, GetAnalyticsMetricsErrorCode> {
|
export class GetAnalyticsMetricsUseCase implements UseCase<GetAnalyticsMetricsInput, GetAnalyticsMetricsOutput, GetAnalyticsMetricsErrorCode> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<GetAnalyticsMetricsOutput>,
|
|
||||||
private readonly pageViewRepository?: IPageViewRepository,
|
private readonly pageViewRepository?: IPageViewRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
input: GetAnalyticsMetricsInput = {},
|
input: GetAnalyticsMetricsInput = {},
|
||||||
): Promise<Result<void, ApplicationErrorCode<GetAnalyticsMetricsErrorCode, { message: string }>>> {
|
): Promise<Result<GetAnalyticsMetricsOutput, ApplicationErrorCode<GetAnalyticsMetricsErrorCode, { message: string }>>> {
|
||||||
try {
|
try {
|
||||||
const startDate = input.startDate ?? new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
|
const startDate = input.startDate ?? new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
|
||||||
const endDate = input.endDate ?? new Date();
|
const endDate = input.endDate ?? new Date();
|
||||||
@@ -47,8 +46,6 @@ export class GetAnalyticsMetricsUseCase implements UseCase<GetAnalyticsMetricsIn
|
|||||||
bounceRate,
|
bounceRate,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.output.present(resultModel);
|
|
||||||
|
|
||||||
this.logger.info('Analytics metrics retrieved', {
|
this.logger.info('Analytics metrics retrieved', {
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
@@ -56,7 +53,7 @@ export class GetAnalyticsMetricsUseCase implements UseCase<GetAnalyticsMetricsIn
|
|||||||
uniqueVisitors,
|
uniqueVisitors,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.ok(undefined);
|
return Result.ok(resultModel);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
this.logger.error('Failed to get analytics metrics', err, { input });
|
this.logger.error('Failed to get analytics metrics', err, { input });
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import { GetDashboardDataUseCase, type GetDashboardDataOutput } from './GetDashboardDataUseCase';
|
import { GetDashboardDataUseCase } from './GetDashboardDataUseCase';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
describe('GetDashboardDataUseCase', () => {
|
describe('GetDashboardDataUseCase', () => {
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<GetDashboardDataOutput> & { present: Mock };
|
|
||||||
let useCase: GetDashboardDataUseCase;
|
let useCase: GetDashboardDataUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -15,18 +14,15 @@ describe('GetDashboardDataUseCase', () => {
|
|||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
|
|
||||||
output = {
|
useCase = new GetDashboardDataUseCase(logger);
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
useCase = new GetDashboardDataUseCase(logger, output);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('presents placeholder dashboard metrics and logs retrieval', async () => {
|
it('returns placeholder dashboard metrics and logs retrieval', async () => {
|
||||||
const result = await useCase.execute();
|
const result = await useCase.execute();
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(output.present).toHaveBeenCalledWith({
|
const data = result.unwrap();
|
||||||
|
expect(data).toEqual({
|
||||||
totalUsers: 0,
|
totalUsers: 0,
|
||||||
activeUsers: 0,
|
activeUsers: 0,
|
||||||
totalRaces: 0,
|
totalRaces: 0,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Logger, UseCaseOutputPort, UseCase } from '@core/shared/application';
|
import type { Logger, UseCase } from '@core/shared/application';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
|
|
||||||
@@ -13,13 +13,12 @@ export interface GetDashboardDataOutput {
|
|||||||
|
|
||||||
export type GetDashboardDataErrorCode = 'REPOSITORY_ERROR';
|
export type GetDashboardDataErrorCode = 'REPOSITORY_ERROR';
|
||||||
|
|
||||||
export class GetDashboardDataUseCase implements UseCase<GetDashboardDataInput, void, GetDashboardDataErrorCode> {
|
export class GetDashboardDataUseCase implements UseCase<GetDashboardDataInput, GetDashboardDataOutput, GetDashboardDataErrorCode> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<GetDashboardDataOutput>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(): Promise<Result<void, ApplicationErrorCode<GetDashboardDataErrorCode, { message: string }>>> {
|
async execute(): Promise<Result<GetDashboardDataOutput, ApplicationErrorCode<GetDashboardDataErrorCode, { message: string }>>> {
|
||||||
try {
|
try {
|
||||||
// Placeholder implementation - would need repositories from identity and racing domains
|
// Placeholder implementation - would need repositories from identity and racing domains
|
||||||
const totalUsers = 0;
|
const totalUsers = 0;
|
||||||
@@ -34,8 +33,6 @@ export class GetDashboardDataUseCase implements UseCase<GetDashboardDataInput, v
|
|||||||
totalLeagues,
|
totalLeagues,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.output.present(resultModel);
|
|
||||||
|
|
||||||
this.logger.info('Dashboard data retrieved', {
|
this.logger.info('Dashboard data retrieved', {
|
||||||
totalUsers,
|
totalUsers,
|
||||||
activeUsers,
|
activeUsers,
|
||||||
@@ -43,7 +40,7 @@ export class GetDashboardDataUseCase implements UseCase<GetDashboardDataInput, v
|
|||||||
totalLeagues,
|
totalLeagues,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.ok(undefined);
|
return Result.ok(resultModel);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
this.logger.error('Failed to get dashboard data', err);
|
this.logger.error('Failed to get dashboard data', err);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import { RecordEngagementUseCase, type RecordEngagementInput, type RecordEngagementOutput } from './RecordEngagementUseCase';
|
import { RecordEngagementUseCase, type RecordEngagementInput } from './RecordEngagementUseCase';
|
||||||
import type { IEngagementRepository } from '../../domain/repositories/IEngagementRepository';
|
import type { IEngagementRepository } from '../../domain/repositories/IEngagementRepository';
|
||||||
import { EngagementEvent } from '../../domain/entities/EngagementEvent';
|
import { EngagementEvent } from '../../domain/entities/EngagementEvent';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { EngagementAction, EngagementEntityType } from '../../domain/types/EngagementEvent';
|
import type { EngagementAction, EngagementEntityType } from '../../domain/types/EngagementEvent';
|
||||||
|
|
||||||
describe('RecordEngagementUseCase', () => {
|
describe('RecordEngagementUseCase', () => {
|
||||||
@@ -10,7 +10,6 @@ describe('RecordEngagementUseCase', () => {
|
|||||||
save: Mock;
|
save: Mock;
|
||||||
};
|
};
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<RecordEngagementOutput> & { present: Mock };
|
|
||||||
let useCase: RecordEngagementUseCase;
|
let useCase: RecordEngagementUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -25,18 +24,13 @@ describe('RecordEngagementUseCase', () => {
|
|||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
|
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
useCase = new RecordEngagementUseCase(
|
useCase = new RecordEngagementUseCase(
|
||||||
engagementRepository as unknown as IEngagementRepository,
|
engagementRepository as unknown as IEngagementRepository,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates and saves an EngagementEvent and presents its id and weight', async () => {
|
it('creates and saves an EngagementEvent and returns its id and weight', async () => {
|
||||||
const input: RecordEngagementInput = {
|
const input: RecordEngagementInput = {
|
||||||
action: 'view' as EngagementAction,
|
action: 'view' as EngagementAction,
|
||||||
entityType: 'league' as EngagementEntityType,
|
entityType: 'league' as EngagementEntityType,
|
||||||
@@ -52,6 +46,7 @@ describe('RecordEngagementUseCase', () => {
|
|||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
|
const data = result.unwrap();
|
||||||
expect(engagementRepository.save).toHaveBeenCalledTimes(1);
|
expect(engagementRepository.save).toHaveBeenCalledTimes(1);
|
||||||
const saved = (engagementRepository.save as unknown as Mock).mock.calls?.[0]?.[0] as EngagementEvent;
|
const saved = (engagementRepository.save as unknown as Mock).mock.calls?.[0]?.[0] as EngagementEvent;
|
||||||
|
|
||||||
@@ -60,14 +55,12 @@ describe('RecordEngagementUseCase', () => {
|
|||||||
expect(saved.entityId).toBe(input.entityId);
|
expect(saved.entityId).toBe(input.entityId);
|
||||||
expect(saved.entityType).toBe(input.entityType);
|
expect(saved.entityType).toBe(input.entityType);
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledWith({
|
expect(data.eventId).toBe(saved.id);
|
||||||
eventId: saved.id,
|
expect(data.engagementWeight).toBe(saved.getEngagementWeight());
|
||||||
engagementWeight: saved.getEngagementWeight(),
|
|
||||||
});
|
|
||||||
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
|
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs and presents error when repository save fails', async () => {
|
it('logs and returns error when repository save fails', async () => {
|
||||||
const input: RecordEngagementInput = {
|
const input: RecordEngagementInput = {
|
||||||
action: 'view' as EngagementAction,
|
action: 'view' as EngagementAction,
|
||||||
entityType: 'league' as EngagementEntityType,
|
entityType: 'league' as EngagementEntityType,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Logger, UseCase, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger, UseCase } from '@core/shared/application';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import { EngagementEvent } from '../../domain/entities/EngagementEvent';
|
import { EngagementEvent } from '../../domain/entities/EngagementEvent';
|
||||||
@@ -22,14 +22,13 @@ export interface RecordEngagementOutput {
|
|||||||
|
|
||||||
export type RecordEngagementErrorCode = 'REPOSITORY_ERROR';
|
export type RecordEngagementErrorCode = 'REPOSITORY_ERROR';
|
||||||
|
|
||||||
export class RecordEngagementUseCase implements UseCase<RecordEngagementInput, void, RecordEngagementErrorCode> {
|
export class RecordEngagementUseCase implements UseCase<RecordEngagementInput, RecordEngagementOutput, RecordEngagementErrorCode> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly engagementRepository: IEngagementRepository,
|
private readonly engagementRepository: IEngagementRepository,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<RecordEngagementOutput>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(input: RecordEngagementInput): Promise<Result<void, ApplicationErrorCode<RecordEngagementErrorCode, { message: string }>>> {
|
async execute(input: RecordEngagementInput): Promise<Result<RecordEngagementOutput, ApplicationErrorCode<RecordEngagementErrorCode, { message: string }>>> {
|
||||||
try {
|
try {
|
||||||
const engagementEvent = EngagementEvent.create({
|
const engagementEvent = EngagementEvent.create({
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
@@ -49,8 +48,6 @@ export class RecordEngagementUseCase implements UseCase<RecordEngagementInput, v
|
|||||||
engagementWeight: engagementEvent.getEngagementWeight(),
|
engagementWeight: engagementEvent.getEngagementWeight(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.output.present(resultModel);
|
|
||||||
|
|
||||||
this.logger.info('Engagement event recorded', {
|
this.logger.info('Engagement event recorded', {
|
||||||
engagementId: engagementEvent.id,
|
engagementId: engagementEvent.id,
|
||||||
action: input.action,
|
action: input.action,
|
||||||
@@ -58,7 +55,7 @@ export class RecordEngagementUseCase implements UseCase<RecordEngagementInput, v
|
|||||||
entityType: input.entityType,
|
entityType: input.entityType,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.ok(undefined);
|
return Result.ok(resultModel);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
this.logger.error('Failed to record engagement event', err, { input });
|
this.logger.error('Failed to record engagement event', err, { input });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import { RecordPageViewUseCase, type RecordPageViewInput, type RecordPageViewOutput } from './RecordPageViewUseCase';
|
import { RecordPageViewUseCase, type RecordPageViewInput } from './RecordPageViewUseCase';
|
||||||
import { PageView } from '../../domain/entities/PageView';
|
import { PageView } from '../../domain/entities/PageView';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { EntityType, VisitorType } from '../../domain/types/PageView';
|
import type { EntityType, VisitorType } from '../../domain/types/PageView';
|
||||||
|
|
||||||
describe('RecordPageViewUseCase', () => {
|
describe('RecordPageViewUseCase', () => {
|
||||||
@@ -9,7 +9,6 @@ describe('RecordPageViewUseCase', () => {
|
|||||||
save: Mock;
|
save: Mock;
|
||||||
};
|
};
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<RecordPageViewOutput> & { present: Mock };
|
|
||||||
let useCase: RecordPageViewUseCase;
|
let useCase: RecordPageViewUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -26,18 +25,13 @@ describe('RecordPageViewUseCase', () => {
|
|||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
|
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
useCase = new RecordPageViewUseCase(
|
useCase = new RecordPageViewUseCase(
|
||||||
pageViewRepository as unknown as PageViewRepository,
|
pageViewRepository as unknown as PageViewRepository,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates and saves a PageView and presents its id', async () => {
|
it('creates and saves a PageView and returns its id', async () => {
|
||||||
const input: RecordPageViewInput = {
|
const input: RecordPageViewInput = {
|
||||||
entityType: 'league' as EntityType,
|
entityType: 'league' as EntityType,
|
||||||
entityId: 'league-1',
|
entityId: 'league-1',
|
||||||
@@ -54,6 +48,7 @@ describe('RecordPageViewUseCase', () => {
|
|||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
|
const data = result.unwrap();
|
||||||
expect(pageViewRepository.save).toHaveBeenCalledTimes(1);
|
expect(pageViewRepository.save).toHaveBeenCalledTimes(1);
|
||||||
const saved = (pageViewRepository.save as unknown as Mock).mock.calls?.[0]?.[0] as PageView;
|
const saved = (pageViewRepository.save as unknown as Mock).mock.calls?.[0]?.[0] as PageView;
|
||||||
|
|
||||||
@@ -62,13 +57,11 @@ describe('RecordPageViewUseCase', () => {
|
|||||||
expect(saved.entityId).toBe(input.entityId);
|
expect(saved.entityId).toBe(input.entityId);
|
||||||
expect(saved.entityType).toBe(input.entityType);
|
expect(saved.entityType).toBe(input.entityType);
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledWith({
|
expect(data.pageViewId).toBe(saved.id);
|
||||||
pageViewId: saved.id,
|
|
||||||
});
|
|
||||||
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
|
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs and presents error when repository save fails', async () => {
|
it('logs and returns error when repository save fails', async () => {
|
||||||
const input: RecordPageViewInput = {
|
const input: RecordPageViewInput = {
|
||||||
entityType: 'league' as EntityType,
|
entityType: 'league' as EntityType,
|
||||||
entityId: 'league-1',
|
entityId: 'league-1',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Logger, UseCaseOutputPort, UseCase } from '@core/shared/application';
|
import type { Logger, UseCase } from '@core/shared/application';
|
||||||
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
|
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
|
||||||
import { PageView } from '../../domain/entities/PageView';
|
import { PageView } from '../../domain/entities/PageView';
|
||||||
import type { EntityType, VisitorType } from '../../domain/types/PageView';
|
import type { EntityType, VisitorType } from '../../domain/types/PageView';
|
||||||
@@ -22,14 +22,13 @@ export interface RecordPageViewOutput {
|
|||||||
|
|
||||||
export type RecordPageViewErrorCode = 'REPOSITORY_ERROR';
|
export type RecordPageViewErrorCode = 'REPOSITORY_ERROR';
|
||||||
|
|
||||||
export class RecordPageViewUseCase implements UseCase<RecordPageViewInput, void, RecordPageViewErrorCode> {
|
export class RecordPageViewUseCase implements UseCase<RecordPageViewInput, RecordPageViewOutput, RecordPageViewErrorCode> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly pageViewRepository: IPageViewRepository,
|
private readonly pageViewRepository: IPageViewRepository,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<RecordPageViewOutput>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(input: RecordPageViewInput): Promise<Result<void, ApplicationErrorCode<RecordPageViewErrorCode, { message: string }>>> {
|
async execute(input: RecordPageViewInput): Promise<Result<RecordPageViewOutput, ApplicationErrorCode<RecordPageViewErrorCode, { message: string }>>> {
|
||||||
try {
|
try {
|
||||||
type PageViewCreateProps = Parameters<(typeof PageView)['create']>[0];
|
type PageViewCreateProps = Parameters<(typeof PageView)['create']>[0];
|
||||||
|
|
||||||
@@ -53,15 +52,13 @@ export class RecordPageViewUseCase implements UseCase<RecordPageViewInput, void,
|
|||||||
pageViewId: pageView.id,
|
pageViewId: pageView.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.output.present(resultModel);
|
|
||||||
|
|
||||||
this.logger.info('Page view recorded', {
|
this.logger.info('Page view recorded', {
|
||||||
pageViewId: pageView.id,
|
pageViewId: pageView.id,
|
||||||
entityId: input.entityId,
|
entityId: input.entityId,
|
||||||
entityType: input.entityType,
|
entityType: input.entityType,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.ok(undefined);
|
return Result.ok(resultModel);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
this.logger.error('Failed to record page view', err, { input });
|
this.logger.error('Failed to record page view', err, { input });
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
import { describe, it, expect, vi, type Mock, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import { ForgotPasswordUseCase } from './ForgotPasswordUseCase';
|
import { ForgotPasswordUseCase } from './ForgotPasswordUseCase';
|
||||||
import { EmailAddress } from '../../domain/value-objects/EmailAddress';
|
|
||||||
import { UserId } from '../../domain/value-objects/UserId';
|
|
||||||
import { User } from '../../domain/entities/User';
|
|
||||||
import type { IAuthRepository } from '../../domain/repositories/IAuthRepository';
|
import type { IAuthRepository } from '../../domain/repositories/IAuthRepository';
|
||||||
import type { IMagicLinkRepository } from '../../domain/repositories/IMagicLinkRepository';
|
import type { IMagicLinkRepository } from '../../domain/repositories/IMagicLinkRepository';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { IMagicLinkNotificationPort } from '../../domain/ports/IMagicLinkNotificationPort';
|
||||||
|
import type { Logger } from '@core/shared/application';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
import { User } from '../../domain/entities/User';
|
||||||
type ForgotPasswordOutput = {
|
import { UserId } from '../../domain/value-objects/UserId';
|
||||||
message: string;
|
import { PasswordHash } from '../../domain/value-objects/PasswordHash';
|
||||||
magicLink?: string | null;
|
import { EmailAddress } from '../../domain/value-objects/EmailAddress';
|
||||||
};
|
|
||||||
|
|
||||||
describe('ForgotPasswordUseCase', () => {
|
describe('ForgotPasswordUseCase', () => {
|
||||||
let authRepo: {
|
let authRepo: {
|
||||||
findByEmail: Mock;
|
findByEmail: Mock;
|
||||||
save: Mock;
|
|
||||||
};
|
};
|
||||||
let magicLinkRepo: {
|
let magicLinkRepo: {
|
||||||
checkRateLimit: Mock;
|
checkRateLimit: Mock;
|
||||||
@@ -26,218 +22,89 @@ describe('ForgotPasswordUseCase', () => {
|
|||||||
sendMagicLink: Mock;
|
sendMagicLink: Mock;
|
||||||
};
|
};
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<ForgotPasswordOutput> & { present: Mock };
|
|
||||||
let useCase: ForgotPasswordUseCase;
|
let useCase: ForgotPasswordUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
authRepo = {
|
authRepo = {
|
||||||
findByEmail: vi.fn(),
|
findByEmail: vi.fn(),
|
||||||
save: vi.fn(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
magicLinkRepo = {
|
magicLinkRepo = {
|
||||||
checkRateLimit: vi.fn(),
|
checkRateLimit: vi.fn(),
|
||||||
createPasswordResetRequest: vi.fn(),
|
createPasswordResetRequest: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
notificationPort = {
|
notificationPort = {
|
||||||
sendMagicLink: vi.fn(),
|
sendMagicLink: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
logger = {
|
logger = {
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
warn: vi.fn(),
|
warn: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
useCase = new ForgotPasswordUseCase(
|
useCase = new ForgotPasswordUseCase(
|
||||||
authRepo as unknown as IAuthRepository,
|
authRepo as unknown as IAuthRepository,
|
||||||
magicLinkRepo as unknown as IMagicLinkRepository,
|
magicLinkRepo as unknown as IMagicLinkRepository,
|
||||||
notificationPort as any,
|
notificationPort as unknown as IMagicLinkNotificationPort,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create magic link for existing user', async () => {
|
it('generates and sends magic link when user exists', async () => {
|
||||||
const input = { email: 'test@example.com' };
|
|
||||||
const user = User.create({
|
const user = User.create({
|
||||||
id: UserId.create(),
|
id: UserId.create(),
|
||||||
displayName: 'John Smith',
|
displayName: 'John Smith',
|
||||||
email: input.email,
|
email: 'test@example.com',
|
||||||
|
passwordHash: PasswordHash.fromHash('hashed-password'),
|
||||||
});
|
});
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(user);
|
authRepo.findByEmail.mockResolvedValue(user);
|
||||||
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
||||||
|
|
||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute({ email: 'test@example.com' });
|
||||||
|
|
||||||
expect(authRepo.findByEmail).toHaveBeenCalledWith(EmailAddress.create(input.email));
|
|
||||||
expect(magicLinkRepo.checkRateLimit).toHaveBeenCalledWith(input.email);
|
|
||||||
expect(magicLinkRepo.createPasswordResetRequest).toHaveBeenCalled();
|
|
||||||
expect(output.present).toHaveBeenCalled();
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
|
const forgotPasswordResult = result.unwrap();
|
||||||
|
expect(forgotPasswordResult.message).toBe('Password reset link generated successfully');
|
||||||
|
expect(forgotPasswordResult.magicLink).toBeDefined();
|
||||||
|
expect(magicLinkRepo.createPasswordResetRequest).toHaveBeenCalled();
|
||||||
|
expect(notificationPort.sendMagicLink).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return success for non-existent email (security)', async () => {
|
it('returns success even when user does not exist (for security)', async () => {
|
||||||
const input = { email: 'nonexistent@example.com' };
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(null);
|
authRepo.findByEmail.mockResolvedValue(null);
|
||||||
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
||||||
|
|
||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute({ email: 'nonexistent@example.com' });
|
||||||
|
|
||||||
expect(authRepo.findByEmail).toHaveBeenCalledWith(EmailAddress.create(input.email));
|
|
||||||
expect(magicLinkRepo.createPasswordResetRequest).not.toHaveBeenCalled();
|
|
||||||
expect(output.present).toHaveBeenCalledWith({
|
|
||||||
message: 'If an account exists with this email, a password reset link will be sent',
|
|
||||||
magicLink: null,
|
|
||||||
});
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
|
const forgotPasswordResult = result.unwrap();
|
||||||
|
expect(forgotPasswordResult.message).toBe('If an account exists with this email, a password reset link will be sent');
|
||||||
|
expect(forgotPasswordResult.magicLink).toBeNull();
|
||||||
|
expect(magicLinkRepo.createPasswordResetRequest).not.toHaveBeenCalled();
|
||||||
|
expect(notificationPort.sendMagicLink).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle rate limiting', async () => {
|
it('returns error when rate limit exceeded', async () => {
|
||||||
const input = { email: 'test@example.com' };
|
|
||||||
const user = User.create({
|
|
||||||
id: UserId.create(),
|
|
||||||
displayName: 'John Smith',
|
|
||||||
email: input.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(user);
|
|
||||||
magicLinkRepo.checkRateLimit.mockResolvedValue(
|
magicLinkRepo.checkRateLimit.mockResolvedValue(
|
||||||
Result.err({ code: 'RATE_LIMIT_EXCEEDED', details: { message: 'Rate limited' } })
|
Result.err({ code: 'RATE_LIMIT_EXCEEDED', details: { message: 'Rate limit exceeded' } })
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute({ email: 'test@example.com' });
|
||||||
|
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
const error = result.unwrapErr();
|
expect(result.unwrapErr().code).toBe('RATE_LIMIT_EXCEEDED');
|
||||||
expect(error.code).toBe('RATE_LIMIT_EXCEEDED');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate email format', async () => {
|
it('returns error when repository call fails', async () => {
|
||||||
const input = { email: 'invalid-email' };
|
|
||||||
|
|
||||||
const result = await useCase.execute(input);
|
|
||||||
|
|
||||||
expect(result.isErr()).toBe(true);
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
expect(error.code).toBe('REPOSITORY_ERROR');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate secure tokens', async () => {
|
|
||||||
const input = { email: 'test@example.com' };
|
|
||||||
const user = User.create({
|
|
||||||
id: UserId.create(),
|
|
||||||
displayName: 'John Smith',
|
|
||||||
email: input.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(user);
|
|
||||||
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
|
||||||
|
|
||||||
let capturedToken: string | undefined;
|
|
||||||
magicLinkRepo.createPasswordResetRequest.mockImplementation((data) => {
|
|
||||||
capturedToken = data.token;
|
|
||||||
return Promise.resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
await useCase.execute(input);
|
|
||||||
|
|
||||||
expect(capturedToken).toMatch(/^[a-f0-9]{64}$/); // 32 bytes = 64 hex chars
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set correct expiration time (15 minutes)', async () => {
|
|
||||||
const input = { email: 'test@example.com' };
|
|
||||||
const user = User.create({
|
|
||||||
id: UserId.create(),
|
|
||||||
displayName: 'John Smith',
|
|
||||||
email: input.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(user);
|
|
||||||
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
|
||||||
|
|
||||||
const beforeCreate = Date.now();
|
|
||||||
let capturedExpiresAt: Date | undefined;
|
|
||||||
magicLinkRepo.createPasswordResetRequest.mockImplementation((data) => {
|
|
||||||
capturedExpiresAt = data.expiresAt;
|
|
||||||
return Promise.resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
await useCase.execute(input);
|
|
||||||
|
|
||||||
const afterCreate = Date.now();
|
|
||||||
expect(capturedExpiresAt).toBeDefined();
|
|
||||||
const timeDiff = capturedExpiresAt!.getTime() - afterCreate;
|
|
||||||
|
|
||||||
// Should be approximately 15 minutes (900000ms)
|
|
||||||
expect(timeDiff).toBeGreaterThan(890000);
|
|
||||||
expect(timeDiff).toBeLessThan(910000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return magic link in development mode', async () => {
|
|
||||||
const originalEnv = process.env.NODE_ENV;
|
|
||||||
process.env.NODE_ENV = 'development';
|
|
||||||
|
|
||||||
const input = { email: 'test@example.com' };
|
|
||||||
const user = User.create({
|
|
||||||
id: UserId.create(),
|
|
||||||
displayName: 'John Smith',
|
|
||||||
email: input.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(user);
|
|
||||||
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
|
||||||
|
|
||||||
await useCase.execute(input);
|
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
magicLink: expect.stringContaining('token='),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
process.env.NODE_ENV = originalEnv ?? 'test';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not return magic link in production mode', async () => {
|
|
||||||
const originalEnv = process.env.NODE_ENV;
|
|
||||||
process.env.NODE_ENV = 'production';
|
|
||||||
|
|
||||||
const input = { email: 'test@example.com' };
|
|
||||||
const user = User.create({
|
|
||||||
id: UserId.create(),
|
|
||||||
displayName: 'John Smith',
|
|
||||||
email: input.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(user);
|
|
||||||
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
|
||||||
|
|
||||||
await useCase.execute(input);
|
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
magicLink: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
process.env.NODE_ENV = originalEnv ?? 'test';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle repository errors', async () => {
|
|
||||||
const input = { email: 'test@example.com' };
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockRejectedValue(new Error('Database error'));
|
authRepo.findByEmail.mockRejectedValue(new Error('Database error'));
|
||||||
|
magicLinkRepo.checkRateLimit.mockResolvedValue(Result.ok(undefined));
|
||||||
|
|
||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute({ email: 'test@example.com' });
|
||||||
|
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
const error = result.unwrapErr();
|
expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR');
|
||||||
expect(error.code).toBe('REPOSITORY_ERROR');
|
|
||||||
expect(error.details.message).toContain('Database error');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -4,7 +4,7 @@ import { IMagicLinkRepository } from '../../domain/repositories/IMagicLinkReposi
|
|||||||
import { IMagicLinkNotificationPort } from '../../domain/ports/IMagicLinkNotificationPort';
|
import { IMagicLinkNotificationPort } from '../../domain/ports/IMagicLinkNotificationPort';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort, Logger, UseCase } from '@core/shared/application';
|
import type { Logger, UseCase } from '@core/shared/application';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
|
|
||||||
export type ForgotPasswordInput = {
|
export type ForgotPasswordInput = {
|
||||||
@@ -27,16 +27,15 @@ export type ForgotPasswordApplicationError = ApplicationErrorCode<ForgotPassword
|
|||||||
* In production, this would send an email with the magic link.
|
* In production, this would send an email with the magic link.
|
||||||
* In development, it returns the link for testing purposes.
|
* In development, it returns the link for testing purposes.
|
||||||
*/
|
*/
|
||||||
export class ForgotPasswordUseCase implements UseCase<ForgotPasswordInput, void, ForgotPasswordErrorCode> {
|
export class ForgotPasswordUseCase implements UseCase<ForgotPasswordInput, ForgotPasswordResult, ForgotPasswordErrorCode> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly authRepo: IAuthRepository,
|
private readonly authRepo: IAuthRepository,
|
||||||
private readonly magicLinkRepo: IMagicLinkRepository,
|
private readonly magicLinkRepo: IMagicLinkRepository,
|
||||||
private readonly notificationPort: IMagicLinkNotificationPort,
|
private readonly notificationPort: IMagicLinkNotificationPort,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<ForgotPasswordResult>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(input: ForgotPasswordInput): Promise<Result<void, ForgotPasswordApplicationError>> {
|
async execute(input: ForgotPasswordInput): Promise<Result<ForgotPasswordResult, ForgotPasswordApplicationError>> {
|
||||||
try {
|
try {
|
||||||
// Validate email format
|
// Validate email format
|
||||||
const emailVO = EmailAddress.create(input.email);
|
const emailVO = EmailAddress.create(input.email);
|
||||||
@@ -86,7 +85,7 @@ export class ForgotPasswordUseCase implements UseCase<ForgotPasswordInput, void,
|
|||||||
expiresAt,
|
expiresAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.output.present({
|
return Result.ok({
|
||||||
message: 'Password reset link generated successfully',
|
message: 'Password reset link generated successfully',
|
||||||
magicLink: process.env.NODE_ENV === 'development' ? magicLink : null,
|
magicLink: process.env.NODE_ENV === 'development' ? magicLink : null,
|
||||||
});
|
});
|
||||||
@@ -96,13 +95,11 @@ export class ForgotPasswordUseCase implements UseCase<ForgotPasswordInput, void,
|
|||||||
email: input.email,
|
email: input.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.output.present({
|
return Result.ok({
|
||||||
message: 'If an account exists with this email, a password reset link will be sent',
|
message: 'If an account exists with this email, a password reset link will be sent',
|
||||||
magicLink: null,
|
magicLink: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(undefined);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error && error.message
|
error instanceof Error && error.message
|
||||||
|
|||||||
@@ -2,11 +2,8 @@ import { vi, type Mock } from 'vitest';
|
|||||||
import { GetCurrentSessionUseCase } from './GetCurrentSessionUseCase';
|
import { GetCurrentSessionUseCase } from './GetCurrentSessionUseCase';
|
||||||
import { User } from '../../domain/entities/User';
|
import { User } from '../../domain/entities/User';
|
||||||
import { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository';
|
import { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
import { Result } from '@core/shared/application/Result';
|
||||||
type GetCurrentSessionOutput = {
|
|
||||||
user: User;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('GetCurrentSessionUseCase', () => {
|
describe('GetCurrentSessionUseCase', () => {
|
||||||
let useCase: GetCurrentSessionUseCase;
|
let useCase: GetCurrentSessionUseCase;
|
||||||
@@ -18,7 +15,6 @@ describe('GetCurrentSessionUseCase', () => {
|
|||||||
emailExists: Mock;
|
emailExists: Mock;
|
||||||
};
|
};
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<GetCurrentSessionOutput> & { present: Mock };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockUserRepo = {
|
mockUserRepo = {
|
||||||
@@ -34,13 +30,9 @@ describe('GetCurrentSessionUseCase', () => {
|
|||||||
warn: vi.fn(),
|
warn: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
useCase = new GetCurrentSessionUseCase(
|
useCase = new GetCurrentSessionUseCase(
|
||||||
mockUserRepo as IUserRepository,
|
mockUserRepo as IUserRepository,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,11 +52,10 @@ describe('GetCurrentSessionUseCase', () => {
|
|||||||
|
|
||||||
expect(mockUserRepo.findById).toHaveBeenCalledWith(userId);
|
expect(mockUserRepo.findById).toHaveBeenCalledWith(userId);
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(output.present).toHaveBeenCalled();
|
const sessionResult = result.unwrap();
|
||||||
const callArgs = output.present.mock.calls?.[0]?.[0];
|
expect(sessionResult.user).toBeInstanceOf(User);
|
||||||
expect(callArgs?.user).toBeInstanceOf(User);
|
expect(sessionResult.user.getId().value).toBe(userId);
|
||||||
expect(callArgs?.user.getId().value).toBe(userId);
|
expect(sessionResult.user.getDisplayName()).toBe('John Smith');
|
||||||
expect(callArgs?.user.getDisplayName()).toBe('John Smith');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error when user does not exist', async () => {
|
it('should return error when user does not exist', async () => {
|
||||||
@@ -75,5 +66,6 @@ describe('GetCurrentSessionUseCase', () => {
|
|||||||
|
|
||||||
expect(mockUserRepo.findById).toHaveBeenCalledWith(userId);
|
expect(mockUserRepo.findById).toHaveBeenCalledWith(userId);
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
|
expect(result.unwrapErr().code).toBe('USER_NOT_FOUND');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,7 +2,7 @@ import { User } from '../../domain/entities/User';
|
|||||||
import { IUserRepository } from '../../domain/repositories/IUserRepository';
|
import { IUserRepository } from '../../domain/repositories/IUserRepository';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort, Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
export type GetCurrentSessionInput = {
|
export type GetCurrentSessionInput = {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -28,11 +28,10 @@ export class GetCurrentSessionUseCase {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly userRepo: IUserRepository,
|
private readonly userRepo: IUserRepository,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<GetCurrentSessionResult>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(input: GetCurrentSessionInput): Promise<
|
async execute(input: GetCurrentSessionInput): Promise<
|
||||||
Result<void, GetCurrentSessionApplicationError>
|
Result<GetCurrentSessionResult, GetCurrentSessionApplicationError>
|
||||||
> {
|
> {
|
||||||
try {
|
try {
|
||||||
const stored = await this.userRepo.findById(input.userId);
|
const stored = await this.userRepo.findById(input.userId);
|
||||||
@@ -45,9 +44,8 @@ export class GetCurrentSessionUseCase {
|
|||||||
|
|
||||||
const user = User.fromStored(stored);
|
const user = User.fromStored(stored);
|
||||||
const result: GetCurrentSessionResult = { user };
|
const result: GetCurrentSessionResult = { user };
|
||||||
this.output.present(result);
|
|
||||||
|
|
||||||
return Result.ok(undefined);
|
return Result.ok(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error && error.message
|
error instanceof Error && error.message
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import { GetCurrentUserSessionUseCase } from './GetCurrentUserSessionUseCase';
|
import { GetCurrentUserSessionUseCase } from './GetCurrentUserSessionUseCase';
|
||||||
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
|
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
|
||||||
describe('GetCurrentUserSessionUseCase', () => {
|
describe('GetCurrentUserSessionUseCase', () => {
|
||||||
let sessionPort: {
|
let sessionPort: {
|
||||||
@@ -10,7 +11,6 @@ describe('GetCurrentUserSessionUseCase', () => {
|
|||||||
clearSession: Mock;
|
clearSession: Mock;
|
||||||
};
|
};
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<AuthSession | null> & { present: Mock };
|
|
||||||
let useCase: GetCurrentUserSessionUseCase;
|
let useCase: GetCurrentUserSessionUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -27,14 +27,9 @@ describe('GetCurrentUserSessionUseCase', () => {
|
|||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
|
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
useCase = new GetCurrentUserSessionUseCase(
|
useCase = new GetCurrentUserSessionUseCase(
|
||||||
sessionPort as unknown as IdentitySessionPort,
|
sessionPort as unknown as IdentitySessionPort,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,7 +52,7 @@ describe('GetCurrentUserSessionUseCase', () => {
|
|||||||
|
|
||||||
expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1);
|
expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1);
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(output.present).toHaveBeenCalledWith(session);
|
expect(result.unwrap()).toBe(session);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns null when there is no active session', async () => {
|
it('returns null when there is no active session', async () => {
|
||||||
@@ -67,6 +62,6 @@ describe('GetCurrentUserSessionUseCase', () => {
|
|||||||
|
|
||||||
expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1);
|
expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1);
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(output.present).toHaveBeenCalledWith(null);
|
expect(result.unwrap()).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
|
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort, Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
export type GetCurrentUserSessionInput = void;
|
export type GetCurrentUserSessionInput = void;
|
||||||
|
|
||||||
@@ -18,16 +18,13 @@ export class GetCurrentUserSessionUseCase {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly sessionPort: IdentitySessionPort,
|
private readonly sessionPort: IdentitySessionPort,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<GetCurrentUserSessionResult>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(): Promise<Result<void, GetCurrentUserSessionApplicationError>> {
|
async execute(): Promise<Result<GetCurrentUserSessionResult, GetCurrentUserSessionApplicationError>> {
|
||||||
try {
|
try {
|
||||||
const session = await this.sessionPort.getCurrentSession();
|
const session = await this.sessionPort.getCurrentSession();
|
||||||
|
|
||||||
this.output.present(session);
|
return Result.ok(session);
|
||||||
|
|
||||||
return Result.ok(undefined);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error && error.message
|
error instanceof Error && error.message
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import { GetUserUseCase } from './GetUserUseCase';
|
import { GetUserUseCase } from './GetUserUseCase';
|
||||||
import { User } from '../../domain/entities/User';
|
import type { IUserRepository } from '../../domain/repositories/IUserRepository';
|
||||||
import type { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
import { User } from '../../domain/entities/User';
|
||||||
type GetUserOutput = Result<{ user: User }, unknown>;
|
import { UserId } from '../../domain/value-objects/UserId';
|
||||||
|
import { PasswordHash } from '../../domain/value-objects/PasswordHash';
|
||||||
|
import { EmailAddress } from '../../domain/value-objects/EmailAddress';
|
||||||
|
|
||||||
describe('GetUserUseCase', () => {
|
describe('GetUserUseCase', () => {
|
||||||
let userRepository: {
|
let userRepo: {
|
||||||
findById: Mock;
|
findById: Mock;
|
||||||
};
|
};
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<GetUserOutput> & { present: Mock };
|
|
||||||
let useCase: GetUserUseCase;
|
let useCase: GetUserUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
userRepository = {
|
userRepo = {
|
||||||
findById: vi.fn(),
|
findById: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,48 +27,48 @@ describe('GetUserUseCase', () => {
|
|||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
|
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
useCase = new GetUserUseCase(
|
useCase = new GetUserUseCase(
|
||||||
userRepository as unknown as IUserRepository,
|
userRepo as unknown as IUserRepository,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a User when the user exists', async () => {
|
it('returns user when found', async () => {
|
||||||
const storedUser: StoredUser = {
|
const storedUser = {
|
||||||
id: 'user-1',
|
id: 'user-1',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
displayName: 'John Smith',
|
displayName: 'John Smith',
|
||||||
passwordHash: 'hash',
|
passwordHash: 'hashed-password',
|
||||||
primaryDriverId: 'driver-1',
|
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
};
|
};
|
||||||
|
userRepo.findById.mockResolvedValue(storedUser);
|
||||||
userRepository.findById.mockResolvedValue(storedUser);
|
|
||||||
|
|
||||||
const result = await useCase.execute({ userId: 'user-1' });
|
const result = await useCase.execute({ userId: 'user-1' });
|
||||||
|
|
||||||
expect(userRepository.findById).toHaveBeenCalledWith('user-1');
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(output.present).toHaveBeenCalled();
|
const getUserResult = result.unwrap();
|
||||||
const callArgs = output.present.mock.calls?.[0]?.[0];
|
expect(getUserResult.user).toBeDefined();
|
||||||
expect(callArgs).toBeInstanceOf(Result);
|
expect(getUserResult.user.getId().value).toBe('user-1');
|
||||||
const user = (callArgs as GetUserOutput).unwrap().user;
|
expect(getUserResult.user.getEmail()).toBe('test@example.com');
|
||||||
expect(user).toBeInstanceOf(User);
|
expect(userRepo.findById).toHaveBeenCalledWith('user-1');
|
||||||
expect(user.getId().value).toBe('user-1');
|
|
||||||
expect(user.getDisplayName()).toBe('John Smith');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when the user does not exist', async () => {
|
it('returns error when user not found', async () => {
|
||||||
userRepository.findById.mockResolvedValue(null);
|
userRepo.findById.mockResolvedValue(null);
|
||||||
|
|
||||||
const result = await useCase.execute({ userId: 'missing-user' });
|
const result = await useCase.execute({ userId: 'nonexistent' });
|
||||||
|
|
||||||
expect(userRepository.findById).toHaveBeenCalledWith('missing-user');
|
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
|
expect(result.unwrapErr().code).toBe('USER_NOT_FOUND');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error on repository failure', async () => {
|
||||||
|
userRepo.findById.mockRejectedValue(new Error('Database error'));
|
||||||
|
|
||||||
|
const result = await useCase.execute({ userId: 'user-1' });
|
||||||
|
|
||||||
|
expect(result.isErr()).toBe(true);
|
||||||
|
expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR');
|
||||||
|
expect(logger.error).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,7 +2,7 @@ import { User } from '../../domain/entities/User';
|
|||||||
import { IUserRepository } from '../../domain/repositories/IUserRepository';
|
import { IUserRepository } from '../../domain/repositories/IUserRepository';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort, Logger, UseCase } from '@core/shared/application';
|
import type { Logger, UseCase } from '@core/shared/application';
|
||||||
|
|
||||||
export type GetUserInput = {
|
export type GetUserInput = {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -23,25 +23,20 @@ export class GetUserUseCase implements UseCase<GetUserInput, GetUserResult, GetU
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly userRepo: IUserRepository,
|
private readonly userRepo: IUserRepository,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<Result<GetUserResult, GetUserApplicationError>>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(input: GetUserInput): Promise<Result<GetUserResult, GetUserApplicationError>> {
|
async execute(input: GetUserInput): Promise<Result<GetUserResult, GetUserApplicationError>> {
|
||||||
try {
|
try {
|
||||||
const stored = await this.userRepo.findById(input.userId);
|
const stored = await this.userRepo.findById(input.userId);
|
||||||
if (!stored) {
|
if (!stored) {
|
||||||
const result = Result.err<GetUserResult, GetUserApplicationError>({
|
return Result.err<GetUserResult, GetUserApplicationError>({
|
||||||
code: 'USER_NOT_FOUND',
|
code: 'USER_NOT_FOUND',
|
||||||
details: { message: 'User not found' },
|
details: { message: 'User not found' },
|
||||||
});
|
});
|
||||||
this.output.present(result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = User.fromStored(stored);
|
const user = User.fromStored(stored);
|
||||||
const result = Result.ok<GetUserResult, GetUserApplicationError>({ user });
|
return Result.ok<GetUserResult, GetUserApplicationError>({ user });
|
||||||
this.output.present(result);
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error && error.message ? error.message : 'Failed to get user';
|
error instanceof Error && error.message ? error.message : 'Failed to get user';
|
||||||
@@ -50,12 +45,10 @@ export class GetUserUseCase implements UseCase<GetUserInput, GetUserResult, GetU
|
|||||||
input,
|
input,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = Result.err<GetUserResult, GetUserApplicationError>({
|
return Result.err<GetUserResult, GetUserApplicationError>({
|
||||||
code: 'REPOSITORY_ERROR',
|
code: 'REPOSITORY_ERROR',
|
||||||
details: { message },
|
details: { message },
|
||||||
});
|
});
|
||||||
this.output.present(result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import { HandleAuthCallbackUseCase } from './HandleAuthCallbackUseCase';
|
import { HandleAuthCallbackUseCase } from './HandleAuthCallbackUseCase';
|
||||||
import type {
|
import type { IdentityProviderPort } from '../ports/IdentityProviderPort';
|
||||||
AuthCallbackCommand,
|
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
|
||||||
AuthenticatedUser,
|
import type { Logger } from '@core/shared/application';
|
||||||
IdentityProviderPort,
|
import { Result } from '@core/shared/application/Result';
|
||||||
} from '../ports/IdentityProviderPort';
|
|
||||||
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
|
|
||||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
|
||||||
|
|
||||||
describe('HandleAuthCallbackUseCase', () => {
|
describe('HandleAuthCallbackUseCase', () => {
|
||||||
let provider: {
|
let provider: {
|
||||||
@@ -14,69 +11,97 @@ describe('HandleAuthCallbackUseCase', () => {
|
|||||||
};
|
};
|
||||||
let sessionPort: {
|
let sessionPort: {
|
||||||
createSession: Mock;
|
createSession: Mock;
|
||||||
getCurrentSession: Mock;
|
|
||||||
clearSession: Mock;
|
|
||||||
};
|
};
|
||||||
let logger: Logger;
|
let logger: Logger & { error: Mock };
|
||||||
let output: UseCaseOutputPort<AuthSession> & { present: Mock };
|
|
||||||
let useCase: HandleAuthCallbackUseCase;
|
let useCase: HandleAuthCallbackUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
provider = {
|
provider = {
|
||||||
completeAuth: vi.fn(),
|
completeAuth: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
sessionPort = {
|
sessionPort = {
|
||||||
createSession: vi.fn(),
|
createSession: vi.fn(),
|
||||||
getCurrentSession: vi.fn(),
|
|
||||||
clearSession: vi.fn(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
logger = {
|
logger = {
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
warn: vi.fn(),
|
warn: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger & { error: Mock };
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
useCase = new HandleAuthCallbackUseCase(
|
useCase = new HandleAuthCallbackUseCase(
|
||||||
provider as unknown as IdentityProviderPort,
|
provider as unknown as IdentityProviderPort,
|
||||||
sessionPort as unknown as IdentitySessionPort,
|
sessionPort as unknown as IdentitySessionPort,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('completes auth and creates a session', async () => {
|
it('successfully handles auth callback and creates session', async () => {
|
||||||
const command: AuthCallbackCommand = {
|
const authenticatedUser = {
|
||||||
provider: 'IRACING_DEMO',
|
|
||||||
code: 'auth-code',
|
|
||||||
state: 'state-123',
|
|
||||||
returnTo: 'https://app/callback',
|
|
||||||
};
|
|
||||||
|
|
||||||
const user: AuthenticatedUser = {
|
|
||||||
id: 'user-1',
|
id: 'user-1',
|
||||||
email: 'test@example.com',
|
|
||||||
displayName: 'Test User',
|
displayName: 'Test User',
|
||||||
|
email: 'test@example.com',
|
||||||
};
|
};
|
||||||
|
const session = {
|
||||||
const session: AuthSession = {
|
token: 'session-token',
|
||||||
user,
|
user: authenticatedUser,
|
||||||
issuedAt: Date.now(),
|
issuedAt: Date.now(),
|
||||||
expiresAt: Date.now() + 1000,
|
expiresAt: Date.now() + 1000,
|
||||||
token: 'session-token',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
provider.completeAuth.mockResolvedValue(user);
|
provider.completeAuth.mockResolvedValue(authenticatedUser);
|
||||||
sessionPort.createSession.mockResolvedValue(session);
|
sessionPort.createSession.mockResolvedValue(session);
|
||||||
|
|
||||||
const result = await useCase.execute(command);
|
const result = await useCase.execute({
|
||||||
|
code: 'auth-code',
|
||||||
|
state: 'state-123',
|
||||||
|
returnTo: '/dashboard',
|
||||||
|
});
|
||||||
|
|
||||||
expect(provider.completeAuth).toHaveBeenCalledWith(command);
|
|
||||||
expect(sessionPort.createSession).toHaveBeenCalledWith(user);
|
|
||||||
expect(output.present).toHaveBeenCalledWith(session);
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
|
const callbackResult = result.unwrap();
|
||||||
|
expect(callbackResult.token).toBe('session-token');
|
||||||
|
expect(callbackResult.user).toBe(authenticatedUser);
|
||||||
|
expect(provider.completeAuth).toHaveBeenCalledWith({
|
||||||
|
code: 'auth-code',
|
||||||
|
state: 'state-123',
|
||||||
|
returnTo: '/dashboard',
|
||||||
|
});
|
||||||
|
expect(sessionPort.createSession).toHaveBeenCalledWith(authenticatedUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error when provider call fails', async () => {
|
||||||
|
provider.completeAuth.mockRejectedValue(new Error('Auth failed'));
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
code: 'invalid-code',
|
||||||
|
state: 'state-123',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.isErr()).toBe(true);
|
||||||
|
expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR');
|
||||||
|
expect(logger.error).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error when session creation fails', async () => {
|
||||||
|
const authenticatedUser = {
|
||||||
|
id: 'user-1',
|
||||||
|
displayName: 'Test User',
|
||||||
|
email: 'test@example.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
provider.completeAuth.mockResolvedValue(authenticatedUser);
|
||||||
|
sessionPort.createSession.mockRejectedValue(new Error('Session creation failed'));
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
code: 'auth-code',
|
||||||
|
state: 'state-123',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.isErr()).toBe(true);
|
||||||
|
expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR');
|
||||||
|
expect(logger.error).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,7 +2,7 @@ import type { AuthCallbackCommand, AuthenticatedUser, IdentityProviderPort } fro
|
|||||||
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
|
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort, Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
export type HandleAuthCallbackInput = AuthCallbackCommand;
|
export type HandleAuthCallbackInput = AuthCallbackCommand;
|
||||||
|
|
||||||
@@ -20,19 +20,16 @@ export class HandleAuthCallbackUseCase {
|
|||||||
private readonly provider: IdentityProviderPort,
|
private readonly provider: IdentityProviderPort,
|
||||||
private readonly sessionPort: IdentitySessionPort,
|
private readonly sessionPort: IdentitySessionPort,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<HandleAuthCallbackResult>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(input: HandleAuthCallbackInput): Promise<
|
async execute(input: HandleAuthCallbackInput): Promise<
|
||||||
Result<void, HandleAuthCallbackApplicationError>
|
Result<HandleAuthCallbackResult, HandleAuthCallbackApplicationError>
|
||||||
> {
|
> {
|
||||||
try {
|
try {
|
||||||
const user: AuthenticatedUser = await this.provider.completeAuth(input);
|
const user: AuthenticatedUser = await this.provider.completeAuth(input);
|
||||||
const session = await this.sessionPort.createSession(user);
|
const session = await this.sessionPort.createSession(user);
|
||||||
|
|
||||||
this.output.present(session);
|
return Result.ok(session);
|
||||||
|
|
||||||
return Result.ok(undefined);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error && error.message
|
error instanceof Error && error.message
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||||
import {
|
import { LoginUseCase } from './LoginUseCase';
|
||||||
LoginUseCase,
|
|
||||||
type LoginInput,
|
|
||||||
type LoginResult,
|
|
||||||
type LoginErrorCode,
|
|
||||||
} from './LoginUseCase';
|
|
||||||
import { EmailAddress } from '../../domain/value-objects/EmailAddress';
|
|
||||||
import { UserId } from '../../domain/value-objects/UserId';
|
|
||||||
import { PasswordHash } from '../../domain/value-objects/PasswordHash';
|
|
||||||
import type { IAuthRepository } from '../../domain/repositories/IAuthRepository';
|
import type { IAuthRepository } from '../../domain/repositories/IAuthRepository';
|
||||||
import type { IPasswordHashingService } from '../../domain/services/PasswordHashingService';
|
import type { IPasswordHashingService } from '../../domain/services/PasswordHashingService';
|
||||||
import { User } from '../../domain/entities/User';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { UseCaseOutputPort, Logger } from '@core/shared/application';
|
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
import { User } from '../../domain/entities/User';
|
||||||
|
import { UserId } from '../../domain/value-objects/UserId';
|
||||||
|
import { PasswordHash } from '../../domain/value-objects/PasswordHash';
|
||||||
|
import { EmailAddress } from '../../domain/value-objects/EmailAddress';
|
||||||
|
|
||||||
describe('LoginUseCase', () => {
|
describe('LoginUseCase', () => {
|
||||||
let authRepo: {
|
let authRepo: {
|
||||||
@@ -22,129 +16,82 @@ describe('LoginUseCase', () => {
|
|||||||
let passwordService: {
|
let passwordService: {
|
||||||
verify: Mock;
|
verify: Mock;
|
||||||
};
|
};
|
||||||
let logger: Logger & { error: Mock };
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<LoginResult> & { present: Mock };
|
|
||||||
let useCase: LoginUseCase;
|
let useCase: LoginUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
authRepo = {
|
authRepo = {
|
||||||
findByEmail: vi.fn(),
|
findByEmail: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
passwordService = {
|
passwordService = {
|
||||||
verify: vi.fn(),
|
verify: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
logger = {
|
logger = {
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
warn: vi.fn(),
|
warn: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
};
|
} as unknown as Logger;
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
} as unknown as UseCaseOutputPort<LoginResult> & { present: Mock };
|
|
||||||
useCase = new LoginUseCase(
|
useCase = new LoginUseCase(
|
||||||
authRepo as unknown as IAuthRepository,
|
authRepo as unknown as IAuthRepository,
|
||||||
passwordService as unknown as IPasswordHashingService,
|
passwordService as unknown as IPasswordHashingService,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns ok and presents user when credentials are valid', async () => {
|
it('successfully logs in with valid credentials', async () => {
|
||||||
const input: LoginInput = {
|
|
||||||
email: 'test@example.com',
|
|
||||||
password: 'password123',
|
|
||||||
};
|
|
||||||
const emailVO = EmailAddress.create(input.email);
|
|
||||||
|
|
||||||
const user = User.create({
|
const user = User.create({
|
||||||
id: UserId.fromString('user-1'),
|
id: UserId.create(),
|
||||||
displayName: 'John Smith',
|
displayName: 'John Smith',
|
||||||
email: emailVO.value,
|
email: 'test@example.com',
|
||||||
passwordHash: PasswordHash.fromHash('stored-hash'),
|
passwordHash: PasswordHash.fromHash('hashed-password'),
|
||||||
});
|
});
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(user);
|
authRepo.findByEmail.mockResolvedValue(user);
|
||||||
passwordService.verify.mockResolvedValue(true);
|
passwordService.verify.mockResolvedValue(true);
|
||||||
|
|
||||||
const result: Result<void, ApplicationErrorCode<LoginErrorCode, { message: string }>> =
|
const result = await useCase.execute({
|
||||||
await useCase.execute(input);
|
email: 'test@example.com',
|
||||||
|
password: 'Password123',
|
||||||
|
});
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toBeUndefined();
|
const loginResult = result.unwrap();
|
||||||
|
expect(loginResult.user).toBe(user);
|
||||||
expect(authRepo.findByEmail).toHaveBeenCalledWith(emailVO);
|
expect(authRepo.findByEmail).toHaveBeenCalledTimes(1);
|
||||||
expect(passwordService.verify).toHaveBeenCalledWith(input.password, 'stored-hash');
|
expect(passwordService.verify).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
|
||||||
const presented = output.present.mock.calls[0]![0] as LoginResult;
|
|
||||||
expect(presented.user).toBe(user);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns INVALID_CREDENTIALS when user is not found', async () => {
|
it('returns error for invalid credentials', async () => {
|
||||||
const input: LoginInput = {
|
|
||||||
email: 'missing@example.com',
|
|
||||||
password: 'password123',
|
|
||||||
};
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(null);
|
|
||||||
|
|
||||||
const result: Result<void, ApplicationErrorCode<LoginErrorCode, { message: string }>> =
|
|
||||||
await useCase.execute(input);
|
|
||||||
|
|
||||||
expect(result.isErr()).toBe(true);
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
|
|
||||||
expect(error.code).toBe('INVALID_CREDENTIALS');
|
|
||||||
expect(error.details?.message).toBe('Invalid credentials');
|
|
||||||
expect(output.present).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns INVALID_CREDENTIALS when password is invalid', async () => {
|
|
||||||
const input: LoginInput = {
|
|
||||||
email: 'test@example.com',
|
|
||||||
password: 'wrong-password',
|
|
||||||
};
|
|
||||||
const emailVO = EmailAddress.create(input.email);
|
|
||||||
|
|
||||||
const user = User.create({
|
const user = User.create({
|
||||||
id: UserId.fromString('user-1'),
|
id: UserId.create(),
|
||||||
displayName: 'Jane Smith',
|
displayName: 'John Smith',
|
||||||
email: emailVO.value,
|
email: 'test@example.com',
|
||||||
passwordHash: PasswordHash.fromHash('stored-hash'),
|
passwordHash: PasswordHash.fromHash('hashed-password'),
|
||||||
});
|
});
|
||||||
|
|
||||||
authRepo.findByEmail.mockResolvedValue(user);
|
authRepo.findByEmail.mockResolvedValue(user);
|
||||||
passwordService.verify.mockResolvedValue(false);
|
passwordService.verify.mockResolvedValue(false);
|
||||||
|
|
||||||
const result: Result<void, ApplicationErrorCode<LoginErrorCode, { message: string }>> =
|
const result = await useCase.execute({
|
||||||
await useCase.execute(input);
|
email: 'test@example.com',
|
||||||
|
password: 'WrongPassword',
|
||||||
expect(result.isErr()).toBe(true);
|
|
||||||
const error = result.unwrapErr();
|
|
||||||
|
|
||||||
expect(error.code).toBe('INVALID_CREDENTIALS');
|
|
||||||
expect(error.details?.message).toBe('Invalid credentials');
|
|
||||||
expect(output.present).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('wraps unexpected errors as REPOSITORY_ERROR and logs them', async () => {
|
expect(result.isErr()).toBe(true);
|
||||||
const input: LoginInput = {
|
expect(result.unwrapErr().code).toBe('INVALID_CREDENTIALS');
|
||||||
email: 'test@example.com',
|
});
|
||||||
password: 'password123',
|
|
||||||
};
|
|
||||||
|
|
||||||
authRepo.findByEmail.mockRejectedValue(new Error('DB failure'));
|
it('returns error when user does not exist', async () => {
|
||||||
|
authRepo.findByEmail.mockResolvedValue(null);
|
||||||
|
|
||||||
const result: Result<void, ApplicationErrorCode<LoginErrorCode, { message: string }>> =
|
const result = await useCase.execute({
|
||||||
await useCase.execute(input);
|
email: 'nonexistent@example.com',
|
||||||
|
password: 'Password123',
|
||||||
|
});
|
||||||
|
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
const error = result.unwrapErr();
|
expect(result.unwrapErr().code).toBe('INVALID_CREDENTIALS');
|
||||||
|
|
||||||
expect(error.code).toBe('REPOSITORY_ERROR');
|
|
||||||
expect(error.details?.message).toBe('DB failure');
|
|
||||||
expect(output.present).not.toHaveBeenCalled();
|
|
||||||
expect(logger.error).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -4,7 +4,7 @@ import { IAuthRepository } from '../../domain/repositories/IAuthRepository';
|
|||||||
import { IPasswordHashingService } from '../../domain/services/PasswordHashingService';
|
import { IPasswordHashingService } from '../../domain/services/PasswordHashingService';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort, Logger, UseCase } from '@core/shared/application';
|
import type { Logger, UseCase } from '@core/shared/application';
|
||||||
|
|
||||||
export type LoginInput = {
|
export type LoginInput = {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -24,15 +24,14 @@ export type LoginApplicationError = ApplicationErrorCode<LoginErrorCode, { messa
|
|||||||
*
|
*
|
||||||
* Handles user login by verifying credentials.
|
* Handles user login by verifying credentials.
|
||||||
*/
|
*/
|
||||||
export class LoginUseCase implements UseCase<LoginInput, void, LoginErrorCode> {
|
export class LoginUseCase implements UseCase<LoginInput, LoginResult, LoginErrorCode> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly authRepo: IAuthRepository,
|
private readonly authRepo: IAuthRepository,
|
||||||
private readonly passwordService: IPasswordHashingService,
|
private readonly passwordService: IPasswordHashingService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<LoginResult>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(input: LoginInput): Promise<Result<void, LoginApplicationError>> {
|
async execute(input: LoginInput): Promise<Result<LoginResult, LoginApplicationError>> {
|
||||||
try {
|
try {
|
||||||
const emailVO = EmailAddress.create(input.email);
|
const emailVO = EmailAddress.create(input.email);
|
||||||
const user = await this.authRepo.findByEmail(emailVO);
|
const user = await this.authRepo.findByEmail(emailVO);
|
||||||
@@ -48,14 +47,13 @@ export class LoginUseCase implements UseCase<LoginInput, void, LoginErrorCode> {
|
|||||||
const isValid = await this.passwordService.verify(input.password, passwordHash.value);
|
const isValid = await this.passwordService.verify(input.password, passwordHash.value);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return Result.err<void, LoginApplicationError>({
|
return Result.err<LoginResult, LoginApplicationError>({
|
||||||
code: 'INVALID_CREDENTIALS',
|
code: 'INVALID_CREDENTIALS',
|
||||||
details: { message: 'Invalid credentials' },
|
details: { message: 'Invalid credentials' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.output.present({ user });
|
return Result.ok({ user });
|
||||||
return Result.ok(undefined);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error && error.message
|
error instanceof Error && error.message
|
||||||
@@ -66,7 +64,7 @@ export class LoginUseCase implements UseCase<LoginInput, void, LoginErrorCode> {
|
|||||||
input,
|
input,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.err<void, LoginApplicationError>({
|
return Result.err<LoginResult, LoginApplicationError>({
|
||||||
code: 'REPOSITORY_ERROR',
|
code: 'REPOSITORY_ERROR',
|
||||||
details: { message },
|
details: { message },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
import { describe, it, expect, vi, type Mock, beforeEach } from 'vitest';
|
||||||
import {
|
import { LoginWithEmailUseCase } from './LoginWithEmailUseCase';
|
||||||
LoginWithEmailUseCase,
|
import type { IUserRepository } from '../../domain/repositories/IUserRepository';
|
||||||
type LoginWithEmailInput,
|
|
||||||
type LoginWithEmailResult,
|
|
||||||
type LoginWithEmailErrorCode,
|
|
||||||
} from './LoginWithEmailUseCase';
|
|
||||||
import type { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository';
|
|
||||||
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
|
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
|
||||||
import type { UseCaseOutputPort, Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import { Result } from '@core/shared/application/Result';
|
// Mock the PasswordHash module
|
||||||
|
vi.mock('@core/identity/domain/value-objects/PasswordHash', () => ({
|
||||||
|
PasswordHash: {
|
||||||
|
fromHash: vi.fn((hash: string) => ({
|
||||||
|
verify: vi.fn().mockResolvedValue(hash === 'hashed-password'),
|
||||||
|
value: hash,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
describe('LoginWithEmailUseCase', () => {
|
describe('LoginWithEmailUseCase', () => {
|
||||||
let userRepository: {
|
let userRepository: {
|
||||||
@@ -17,169 +20,119 @@ describe('LoginWithEmailUseCase', () => {
|
|||||||
};
|
};
|
||||||
let sessionPort: {
|
let sessionPort: {
|
||||||
createSession: Mock;
|
createSession: Mock;
|
||||||
getCurrentSession: Mock;
|
|
||||||
clearSession: Mock;
|
|
||||||
};
|
};
|
||||||
let logger: Logger & { error: Mock };
|
let logger: Logger;
|
||||||
let output: UseCaseOutputPort<LoginWithEmailResult> & { present: Mock };
|
|
||||||
let useCase: LoginWithEmailUseCase;
|
let useCase: LoginWithEmailUseCase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
userRepository = {
|
userRepository = {
|
||||||
findByEmail: vi.fn(),
|
findByEmail: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
sessionPort = {
|
sessionPort = {
|
||||||
createSession: vi.fn(),
|
createSession: vi.fn(),
|
||||||
getCurrentSession: vi.fn(),
|
|
||||||
clearSession: vi.fn(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
logger = {
|
logger = {
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
warn: vi.fn(),
|
warn: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger & { error: Mock };
|
} as unknown as Logger;
|
||||||
output = {
|
|
||||||
present: vi.fn(),
|
|
||||||
} as unknown as UseCaseOutputPort<LoginWithEmailResult> & { present: Mock };
|
|
||||||
|
|
||||||
useCase = new LoginWithEmailUseCase(
|
useCase = new LoginWithEmailUseCase(
|
||||||
userRepository as unknown as IUserRepository,
|
userRepository as unknown as IUserRepository,
|
||||||
sessionPort as unknown as IdentitySessionPort,
|
sessionPort as unknown as IdentitySessionPort,
|
||||||
logger,
|
logger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns ok and presents session result for valid credentials', async () => {
|
it('returns ok and presents session result for valid credentials', async () => {
|
||||||
const input: LoginWithEmailInput = {
|
const storedUser = {
|
||||||
email: 'Test@Example.com',
|
|
||||||
password: 'password123',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Import PasswordHash to create a proper hash
|
|
||||||
const { PasswordHash } = await import('@core/identity/domain/value-objects/PasswordHash');
|
|
||||||
const passwordHash = await PasswordHash.create('password123');
|
|
||||||
|
|
||||||
const storedUser: StoredUser = {
|
|
||||||
id: 'user-1',
|
id: 'user-1',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
displayName: 'Test User',
|
displayName: 'John Smith',
|
||||||
passwordHash: passwordHash.value,
|
passwordHash: 'hashed-password',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
};
|
};
|
||||||
|
userRepository.findByEmail.mockResolvedValue(storedUser);
|
||||||
const session = {
|
sessionPort.createSession.mockResolvedValue({
|
||||||
|
token: 'token-123',
|
||||||
user: {
|
user: {
|
||||||
id: storedUser.id,
|
id: 'user-1',
|
||||||
email: storedUser.email,
|
email: 'test@example.com',
|
||||||
displayName: storedUser.displayName,
|
displayName: 'John Smith',
|
||||||
},
|
},
|
||||||
issuedAt: Date.now(),
|
issuedAt: Date.now(),
|
||||||
expiresAt: Date.now() + 1000,
|
expiresAt: Date.now() + 1000,
|
||||||
token: 'token-123',
|
|
||||||
};
|
|
||||||
|
|
||||||
userRepository.findByEmail.mockResolvedValue(storedUser);
|
|
||||||
sessionPort.createSession.mockResolvedValue(session);
|
|
||||||
|
|
||||||
const result: Result<void, ApplicationErrorCode<LoginWithEmailErrorCode, { message: string }>> =
|
|
||||||
await useCase.execute(input);
|
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
|
||||||
expect(result.unwrap()).toBeUndefined();
|
|
||||||
|
|
||||||
expect(userRepository.findByEmail).toHaveBeenCalledWith('test@example.com');
|
|
||||||
expect(sessionPort.createSession).toHaveBeenCalledWith({
|
|
||||||
id: storedUser.id,
|
|
||||||
displayName: storedUser.displayName,
|
|
||||||
email: storedUser.email,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
const result = await useCase.execute({
|
||||||
const presented = output.present.mock.calls[0]![0] as LoginWithEmailResult;
|
email: 'test@example.com',
|
||||||
expect(presented.sessionToken).toBe('token-123');
|
password: 'Password123',
|
||||||
expect(presented.userId).toBe(storedUser.id);
|
});
|
||||||
expect(presented.displayName).toBe(storedUser.displayName);
|
|
||||||
expect(presented.email).toBe(storedUser.email);
|
expect(result.isOk()).toBe(true);
|
||||||
|
const loginResult = result.unwrap();
|
||||||
|
expect(loginResult.sessionToken).toBe('token-123');
|
||||||
|
expect(loginResult.userId).toBe('user-1');
|
||||||
|
expect(loginResult.displayName).toBe('John Smith');
|
||||||
|
expect(loginResult.email).toBe('test@example.com');
|
||||||
|
expect(userRepository.findByEmail).toHaveBeenCalledWith('test@example.com');
|
||||||
|
expect(sessionPort.createSession).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns INVALID_INPUT when email or password is missing', async () => {
|
it('returns INVALID_INPUT when email or password is missing', async () => {
|
||||||
const result1 = await useCase.execute({ email: '', password: 'x' });
|
const result = await useCase.execute({
|
||||||
const result2 = await useCase.execute({ email: 'a@example.com', password: '' });
|
email: '',
|
||||||
|
password: 'Password123',
|
||||||
|
});
|
||||||
|
|
||||||
expect(result1.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
expect(result1.unwrapErr().code).toBe('INVALID_INPUT');
|
expect(result.unwrapErr().code).toBe('INVALID_INPUT');
|
||||||
expect(result2.isErr()).toBe(true);
|
|
||||||
expect(result2.unwrapErr().code).toBe('INVALID_INPUT');
|
|
||||||
expect(output.present).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns INVALID_CREDENTIALS when user does not exist', async () => {
|
it('returns INVALID_CREDENTIALS when user does not exist', async () => {
|
||||||
const input: LoginWithEmailInput = {
|
|
||||||
email: 'missing@example.com',
|
|
||||||
password: 'password',
|
|
||||||
};
|
|
||||||
|
|
||||||
userRepository.findByEmail.mockResolvedValue(null);
|
userRepository.findByEmail.mockResolvedValue(null);
|
||||||
|
|
||||||
const result: Result<void, ApplicationErrorCode<LoginWithEmailErrorCode, { message: string }>> =
|
const result = await useCase.execute({
|
||||||
await useCase.execute(input);
|
email: 'nonexistent@example.com',
|
||||||
|
password: 'Password123',
|
||||||
|
});
|
||||||
|
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
const error = result.unwrapErr();
|
expect(result.unwrapErr().code).toBe('INVALID_CREDENTIALS');
|
||||||
expect(error.code).toBe('INVALID_CREDENTIALS');
|
|
||||||
expect(error.details.message).toBe('Invalid email or password');
|
|
||||||
expect(output.present).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns INVALID_CREDENTIALS when password is invalid', async () => {
|
it('returns INVALID_CREDENTIALS when password is invalid', async () => {
|
||||||
const input: LoginWithEmailInput = {
|
const storedUser = {
|
||||||
email: 'test@example.com',
|
|
||||||
password: 'wrong',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a hash for a different password
|
|
||||||
const { PasswordHash } = await import('@core/identity/domain/value-objects/PasswordHash');
|
|
||||||
const passwordHash = await PasswordHash.create('correct-password');
|
|
||||||
|
|
||||||
const storedUser: StoredUser = {
|
|
||||||
id: 'user-1',
|
id: 'user-1',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
displayName: 'Test User',
|
displayName: 'John Smith',
|
||||||
passwordHash: passwordHash.value,
|
passwordHash: 'wrong-hash', // Different hash to simulate wrong password
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
userRepository.findByEmail.mockResolvedValue(storedUser);
|
userRepository.findByEmail.mockResolvedValue(storedUser);
|
||||||
|
|
||||||
const result: Result<void, ApplicationErrorCode<LoginWithEmailErrorCode, { message: string }>> =
|
const result = await useCase.execute({
|
||||||
await useCase.execute(input);
|
email: 'test@example.com',
|
||||||
|
password: 'WrongPassword',
|
||||||
|
});
|
||||||
|
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
const error = result.unwrapErr();
|
expect(result.unwrapErr().code).toBe('INVALID_CREDENTIALS');
|
||||||
expect(error.code).toBe('INVALID_CREDENTIALS');
|
|
||||||
expect(error.details.message).toBe('Invalid email or password');
|
|
||||||
expect(output.present).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('wraps unexpected errors as REPOSITORY_ERROR and logs them', async () => {
|
it('wraps unexpected errors as REPOSITORY_ERROR and logs them', async () => {
|
||||||
const input: LoginWithEmailInput = {
|
userRepository.findByEmail.mockRejectedValue(new Error('Database connection failed'));
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'password123',
|
password: 'Password123',
|
||||||
};
|
});
|
||||||
|
|
||||||
userRepository.findByEmail.mockRejectedValue(new Error('DB failure'));
|
|
||||||
|
|
||||||
const result: Result<void, ApplicationErrorCode<LoginWithEmailErrorCode, { message: string }>> =
|
|
||||||
await useCase.execute(input);
|
|
||||||
|
|
||||||
expect(result.isErr()).toBe(true);
|
expect(result.isErr()).toBe(true);
|
||||||
const error = result.unwrapErr();
|
expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR');
|
||||||
|
|
||||||
expect(error.code).toBe('REPOSITORY_ERROR');
|
|
||||||
expect(error.details.message).toBe('DB failure');
|
|
||||||
expect(output.present).not.toHaveBeenCalled();
|
|
||||||
expect(logger.error).toHaveBeenCalled();
|
expect(logger.error).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user