refactor dtos to ports
This commit is contained in:
@@ -10,7 +10,6 @@ import type { ISponsorshipRequestRepository } from '../../domain/repositories/IS
|
||||
import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository';
|
||||
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
|
||||
import type { INotificationService } from '@core/notifications/application/ports/INotificationService';
|
||||
import type { IPaymentGateway } from '../ports/IPaymentGateway';
|
||||
import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
|
||||
import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository';
|
||||
import { SeasonSponsorship } from '../../domain/entities/SeasonSponsorship';
|
||||
@@ -18,22 +17,24 @@ import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { AcceptSponsorshipRequestDTO } from '../dto/AcceptSponsorshipRequestDTO';
|
||||
import type { AcceptSponsorshipRequestResultDTO } from '../dto/AcceptSponsorshipRequestResultDTO';
|
||||
import type { AcceptSponsorshipOutputPort } from '../ports/output/AcceptSponsorshipOutputPort';
|
||||
import type { ProcessPaymentInputPort } from '../ports/input/ProcessPaymentInputPort';
|
||||
import type { ProcessPaymentOutputPort } from '../ports/output/ProcessPaymentOutputPort';
|
||||
|
||||
export class AcceptSponsorshipRequestUseCase
|
||||
implements AsyncUseCase<AcceptSponsorshipRequestDTO, AcceptSponsorshipRequestResultDTO, string> {
|
||||
implements AsyncUseCase<AcceptSponsorshipRequestDTO, AcceptSponsorshipOutputPort, string> {
|
||||
constructor(
|
||||
private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||
private readonly seasonSponsorshipRepo: ISeasonSponsorshipRepository,
|
||||
private readonly seasonRepository: ISeasonRepository,
|
||||
private readonly notificationService: INotificationService,
|
||||
private readonly paymentGateway: IPaymentGateway,
|
||||
private readonly paymentProcessor: (input: ProcessPaymentInputPort) => Promise<ProcessPaymentOutputPort>,
|
||||
private readonly walletRepository: IWalletRepository,
|
||||
private readonly leagueWalletRepository: ILeagueWalletRepository,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: AcceptSponsorshipRequestDTO): Promise<Result<AcceptSponsorshipRequestResultDTO, ApplicationErrorCode<string>>> {
|
||||
async execute(dto: AcceptSponsorshipRequestDTO): Promise<Result<AcceptSponsorshipOutputPort, ApplicationErrorCode<string>>> {
|
||||
this.logger.debug(`Attempting to accept sponsorship request: ${dto.requestId}`, { requestId: dto.requestId, respondedBy: dto.respondedBy });
|
||||
|
||||
// Find the request
|
||||
@@ -92,13 +93,15 @@ export class AcceptSponsorshipRequestUseCase
|
||||
},
|
||||
});
|
||||
|
||||
// Process payment
|
||||
const paymentResult = await this.paymentGateway.processPayment(
|
||||
request.offeredAmount,
|
||||
request.sponsorId,
|
||||
`Sponsorship payment for ${request.entityType} ${request.entityId}`,
|
||||
{ requestId: request.id }
|
||||
);
|
||||
// Process payment using clean input/output ports with primitive types
|
||||
const paymentInput: ProcessPaymentInputPort = {
|
||||
amount: request.offeredAmount.amount, // Extract primitive number from value object
|
||||
payerId: request.sponsorId,
|
||||
description: `Sponsorship payment for ${request.entityType} ${request.entityId}`,
|
||||
metadata: { requestId: request.id }
|
||||
};
|
||||
|
||||
const paymentResult = await this.paymentProcessor(paymentInput);
|
||||
if (!paymentResult.success) {
|
||||
this.logger.error(`Payment failed for sponsorship request ${request.id}: ${paymentResult.error}`, undefined, { requestId: request.id });
|
||||
return Result.err({ code: 'PAYMENT_PROCESSING_FAILED' });
|
||||
@@ -142,4 +145,4 @@ export class AcceptSponsorshipRequestUseCase
|
||||
netAmount: acceptedRequest.getNetAmount().amount,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,11 @@ import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { ApplyForSponsorshipDTO } from '../dto/ApplyForSponsorshipDTO';
|
||||
import type { ApplyForSponsorshipResultDTO } from '../dto/ApplyForSponsorshipResultDTO';
|
||||
import type { ApplyForSponsorshipPort } from '../ports/input/ApplyForSponsorshipPort';
|
||||
import type { ApplyForSponsorshipResultPort } from '../ports/output/ApplyForSponsorshipResultPort';
|
||||
|
||||
export class ApplyForSponsorshipUseCase
|
||||
implements AsyncUseCase<ApplyForSponsorshipDTO, ApplyForSponsorshipResultDTO, string>
|
||||
implements AsyncUseCase<ApplyForSponsorshipPort, ApplyForSponsorshipResultPort, string>
|
||||
{
|
||||
constructor(
|
||||
private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||
@@ -27,7 +27,7 @@ export class ApplyForSponsorshipUseCase
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: ApplyForSponsorshipDTO): Promise<Result<ApplyForSponsorshipResultDTO, ApplicationErrorCode<string>>> {
|
||||
async execute(dto: ApplyForSponsorshipPort): Promise<Result<ApplyForSponsorshipResultPort, ApplicationErrorCode<string>>> {
|
||||
this.logger.debug('Attempting to apply for sponsorship', { dto });
|
||||
|
||||
// Validate sponsor exists
|
||||
|
||||
@@ -15,10 +15,10 @@ import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { ApplyPenaltyCommand } from '../dto/ApplyPenaltyCommand';
|
||||
import type { ApplyPenaltyCommandPort } from '../ports/input/ApplyPenaltyCommandPort';
|
||||
|
||||
export class ApplyPenaltyUseCase
|
||||
implements AsyncUseCase<ApplyPenaltyCommand, { penaltyId: string }, string> {
|
||||
implements AsyncUseCase<ApplyPenaltyCommandPort, { penaltyId: string }, string> {
|
||||
constructor(
|
||||
private readonly penaltyRepository: IPenaltyRepository,
|
||||
private readonly protestRepository: IProtestRepository,
|
||||
@@ -27,7 +27,7 @@ export class ApplyPenaltyUseCase
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(command: ApplyPenaltyCommand): Promise<Result<{ penaltyId: string }, ApplicationErrorCode<string>>> {
|
||||
async execute(command: ApplyPenaltyCommandPort): Promise<Result<{ penaltyId: string }, ApplicationErrorCode<string>>> {
|
||||
this.logger.debug('ApplyPenaltyUseCase: Executing with command', command);
|
||||
|
||||
// Validate race exists
|
||||
|
||||
@@ -4,13 +4,13 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
|
||||
import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { randomUUID } from 'crypto';
|
||||
import type { ApproveLeagueJoinRequestUseCaseParams } from '../dto/ApproveLeagueJoinRequestUseCaseParams';
|
||||
import type { ApproveLeagueJoinRequestResultDTO } from '../dto/ApproveLeagueJoinRequestResultDTO';
|
||||
import type { ApproveLeagueJoinRequestResultPort } from '../ports/output/ApproveLeagueJoinRequestResultPort';
|
||||
import { JoinedAt } from '../../domain/value-objects/JoinedAt';
|
||||
|
||||
export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase<ApproveLeagueJoinRequestUseCaseParams, ApproveLeagueJoinRequestResultDTO, string> {
|
||||
export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase<ApproveLeagueJoinRequestUseCaseParams, ApproveLeagueJoinRequestResultPort, string> {
|
||||
constructor(private readonly leagueMembershipRepository: ILeagueMembershipRepository) {}
|
||||
|
||||
async execute(params: ApproveLeagueJoinRequestUseCaseParams): Promise<Result<ApproveLeagueJoinRequestResultDTO, ApplicationErrorCode<string>>> {
|
||||
async execute(params: ApproveLeagueJoinRequestUseCaseParams): Promise<Result<ApproveLeagueJoinRequestResultPort, ApplicationErrorCode<string>>> {
|
||||
const requests = await this.leagueMembershipRepository.getJoinRequests(params.leagueId);
|
||||
const request = requests.find(r => r.id === params.requestId);
|
||||
if (!request) {
|
||||
@@ -25,7 +25,7 @@ export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase<ApproveLeag
|
||||
status: 'active',
|
||||
joinedAt: JoinedAt.create(new Date()),
|
||||
});
|
||||
const dto: ApproveLeagueJoinRequestResultDTO = { success: true, message: 'Join request approved.' };
|
||||
const dto: ApproveLeagueJoinRequestResultPort = { success: true, message: 'Join request approved.' };
|
||||
return Result.ok(dto);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { LeagueVisibilityInput } from '../dto/LeagueVisibilityInput';
|
||||
import type { CreateLeagueWithSeasonAndScoringResultDTO } from '../dto/CreateLeagueWithSeasonAndScoringResultDTO';
|
||||
import type { CreateLeagueWithSeasonAndScoringOutputPort } from '../ports/output/CreateLeagueWithSeasonAndScoringOutputPort';
|
||||
|
||||
export interface CreateLeagueWithSeasonAndScoringCommand {
|
||||
name: string;
|
||||
@@ -40,7 +40,7 @@ export interface CreateLeagueWithSeasonAndScoringCommand {
|
||||
}
|
||||
|
||||
export class CreateLeagueWithSeasonAndScoringUseCase
|
||||
implements AsyncUseCase<CreateLeagueWithSeasonAndScoringCommand, CreateLeagueWithSeasonAndScoringResultDTO, 'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR'> {
|
||||
implements AsyncUseCase<CreateLeagueWithSeasonAndScoringCommand, CreateLeagueWithSeasonAndScoringOutputPort, 'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR'> {
|
||||
constructor(
|
||||
private readonly leagueRepository: ILeagueRepository,
|
||||
private readonly seasonRepository: ISeasonRepository,
|
||||
@@ -51,7 +51,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase
|
||||
|
||||
async execute(
|
||||
command: CreateLeagueWithSeasonAndScoringCommand,
|
||||
): Promise<Result<CreateLeagueWithSeasonAndScoringResultDTO, ApplicationErrorCode<'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
): Promise<Result<CreateLeagueWithSeasonAndScoringOutputPort, ApplicationErrorCode<'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
this.logger.debug('Executing CreateLeagueWithSeasonAndScoringUseCase', { command });
|
||||
const validation = this.validate(command);
|
||||
if (validation.isErr()) {
|
||||
@@ -112,7 +112,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase
|
||||
await this.leagueScoringConfigRepository.save(finalConfig);
|
||||
this.logger.info(`Scoring configuration saved for season ${seasonId}.`);
|
||||
|
||||
const result: CreateLeagueWithSeasonAndScoringResultDTO = {
|
||||
const result: CreateLeagueWithSeasonAndScoringOutputPort = {
|
||||
leagueId: league.id.toString(),
|
||||
seasonId,
|
||||
scoringPresetId: preset.id,
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { AsyncUseCase } from '@core/shared/application';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { CreateSponsorResultDTO } from '../dto/CreateSponsorResultDTO';
|
||||
import type { CreateSponsorOutputPort } from '../ports/output/CreateSponsorOutputPort';
|
||||
|
||||
export interface CreateSponsorCommand {
|
||||
name: string;
|
||||
@@ -20,7 +20,7 @@ export interface CreateSponsorCommand {
|
||||
}
|
||||
|
||||
export class CreateSponsorUseCase
|
||||
implements AsyncUseCase<CreateSponsorCommand, CreateSponsorResultDTO, 'VALIDATION_ERROR' | 'REPOSITORY_ERROR'>
|
||||
implements AsyncUseCase<CreateSponsorCommand, CreateSponsorOutputPort, 'VALIDATION_ERROR' | 'REPOSITORY_ERROR'>
|
||||
{
|
||||
constructor(
|
||||
private readonly sponsorRepository: ISponsorRepository,
|
||||
@@ -29,7 +29,7 @@ export class CreateSponsorUseCase
|
||||
|
||||
async execute(
|
||||
command: CreateSponsorCommand,
|
||||
): Promise<Result<CreateSponsorResultDTO, ApplicationErrorCode<'VALIDATION_ERROR' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
): Promise<Result<CreateSponsorOutputPort, ApplicationErrorCode<'VALIDATION_ERROR' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
this.logger.debug('Executing CreateSponsorUseCase', { command });
|
||||
const validation = this.validate(command);
|
||||
if (validation.isErr()) {
|
||||
@@ -51,7 +51,7 @@ export class CreateSponsorUseCase
|
||||
await this.sponsorRepository.create(sponsor);
|
||||
this.logger.info(`Sponsor ${sponsor.name} (${sponsor.id}) created successfully.`);
|
||||
|
||||
const result: CreateSponsorResultDTO = {
|
||||
const result: CreateSponsorOutputPort = {
|
||||
sponsor: {
|
||||
id: sponsor.id,
|
||||
name: sponsor.name,
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { AsyncUseCase } from '@core/shared/application';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { CreateTeamOutputPort } from '../ports/output/CreateTeamOutputPort';
|
||||
|
||||
export interface CreateTeamCommandDTO {
|
||||
name: string;
|
||||
@@ -25,12 +26,8 @@ export interface CreateTeamCommandDTO {
|
||||
leagues: string[];
|
||||
}
|
||||
|
||||
export interface CreateTeamResultDTO {
|
||||
team: Team;
|
||||
}
|
||||
|
||||
export class CreateTeamUseCase
|
||||
implements AsyncUseCase<CreateTeamCommandDTO, CreateTeamResultDTO, 'ALREADY_IN_TEAM' | 'REPOSITORY_ERROR'>
|
||||
implements AsyncUseCase<CreateTeamCommandDTO, CreateTeamOutputPort, 'ALREADY_IN_TEAM' | 'REPOSITORY_ERROR'>
|
||||
{
|
||||
constructor(
|
||||
private readonly teamRepository: ITeamRepository,
|
||||
@@ -40,7 +37,7 @@ export class CreateTeamUseCase
|
||||
|
||||
async execute(
|
||||
command: CreateTeamCommandDTO,
|
||||
): Promise<Result<CreateTeamResultDTO, ApplicationErrorCode<'ALREADY_IN_TEAM' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
): Promise<Result<CreateTeamOutputPort, ApplicationErrorCode<'ALREADY_IN_TEAM' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
this.logger.debug('Executing CreateTeamUseCase', { command });
|
||||
const { name, tag, description, ownerId, leagues } = command;
|
||||
|
||||
@@ -80,7 +77,7 @@ export class CreateTeamUseCase
|
||||
await this.membershipRepository.saveMembership(membership);
|
||||
this.logger.debug('Team membership created successfully.');
|
||||
|
||||
const result: CreateTeamResultDTO = { team: createdTeam };
|
||||
const result: CreateTeamOutputPort = { team: createdTeam };
|
||||
this.logger.debug('CreateTeamUseCase completed successfully.', { result });
|
||||
return Result.ok(result);
|
||||
} catch (error) {
|
||||
|
||||
@@ -11,11 +11,11 @@ import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISe
|
||||
import type { AsyncUseCase, Logger } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { GetEntitySponsorshipPricingDTO } from '../dto/GetEntitySponsorshipPricingDTO';
|
||||
import type { GetEntitySponsorshipPricingResultDTO } from '../dto/GetEntitySponsorshipPricingResultDTO';
|
||||
import type { GetEntitySponsorshipPricingInputPort } from '../ports/input/GetEntitySponsorshipPricingInputPort';
|
||||
import type { GetEntitySponsorshipPricingOutputPort } from '../ports/output/GetEntitySponsorshipPricingOutputPort';
|
||||
|
||||
export class GetEntitySponsorshipPricingUseCase
|
||||
implements AsyncUseCase<GetEntitySponsorshipPricingDTO, GetEntitySponsorshipPricingResultDTO | null, 'REPOSITORY_ERROR'>
|
||||
implements AsyncUseCase<GetEntitySponsorshipPricingInputPort, GetEntitySponsorshipPricingOutputPort | null, 'REPOSITORY_ERROR'>
|
||||
{
|
||||
constructor(
|
||||
private readonly sponsorshipPricingRepo: ISponsorshipPricingRepository,
|
||||
@@ -24,7 +24,7 @@ export class GetEntitySponsorshipPricingUseCase
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: GetEntitySponsorshipPricingDTO): Promise<Result<GetEntitySponsorshipPricingResultDTO | null, ApplicationErrorCode<'REPOSITORY_ERROR', { message: string }>>> {
|
||||
async execute(dto: GetEntitySponsorshipPricingInputPort): Promise<Result<GetEntitySponsorshipPricingOutputPort | null, ApplicationErrorCode<'REPOSITORY_ERROR', { message: string }>>> {
|
||||
this.logger.debug(`Executing GetEntitySponsorshipPricingUseCase for entityType: ${dto.entityType}, entityId: ${dto.entityId}`);
|
||||
try {
|
||||
const pricing = await this.sponsorshipPricingRepo.findByEntity(dto.entityType, dto.entityId);
|
||||
@@ -53,7 +53,7 @@ export class GetEntitySponsorshipPricingUseCase
|
||||
filledSecondarySlots = activeSponsorships.filter(s => s.tier === 'secondary').length;
|
||||
}
|
||||
|
||||
const result: GetEntitySponsorshipPricingResultDTO = {
|
||||
const result: GetEntitySponsorshipPricingOutputPort = {
|
||||
entityType: dto.entityType,
|
||||
entityId: dto.entityId,
|
||||
acceptingApplications: pricing.acceptingApplications,
|
||||
|
||||
@@ -2,15 +2,15 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit
|
||||
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
|
||||
import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { GetLeagueAdminPermissionsResultDTO } from '../dto/GetLeagueAdminPermissionsResultDTO';
|
||||
import type { GetLeagueAdminPermissionsOutputPort } from '../ports/output/GetLeagueAdminPermissionsOutputPort';
|
||||
|
||||
export class GetLeagueAdminPermissionsUseCase implements AsyncUseCase<{ leagueId: string; performerDriverId: string }, GetLeagueAdminPermissionsResultDTO, 'NO_ERROR'> {
|
||||
export class GetLeagueAdminPermissionsUseCase implements AsyncUseCase<{ leagueId: string; performerDriverId: string }, GetLeagueAdminPermissionsOutputPort, 'NO_ERROR'> {
|
||||
constructor(
|
||||
private readonly leagueRepository: ILeagueRepository,
|
||||
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
||||
) {}
|
||||
|
||||
async execute(params: { leagueId: string; performerDriverId: string }): Promise<Result<GetLeagueAdminPermissionsResultDTO, never>> {
|
||||
async execute(params: { leagueId: string; performerDriverId: string }): Promise<Result<GetLeagueAdminPermissionsOutputPort, never>> {
|
||||
const league = await this.leagueRepository.findById(params.leagueId);
|
||||
if (!league) {
|
||||
return Result.ok({ canRemoveMember: false, canUpdateRoles: false });
|
||||
|
||||
@@ -2,20 +2,20 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit
|
||||
import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { GetLeagueAdminResultDTO } from '../dto/GetLeagueAdminResultDTO';
|
||||
import type { GetLeagueAdminOutputPort } from '../ports/output/GetLeagueAdminOutputPort';
|
||||
|
||||
export class GetLeagueAdminUseCase implements AsyncUseCase<{ leagueId: string }, GetLeagueAdminResultDTO, 'LEAGUE_NOT_FOUND'> {
|
||||
export class GetLeagueAdminUseCase implements AsyncUseCase<{ leagueId: string }, GetLeagueAdminOutputPort, 'LEAGUE_NOT_FOUND'> {
|
||||
constructor(
|
||||
private readonly leagueRepository: ILeagueRepository,
|
||||
) {}
|
||||
|
||||
async execute(params: { leagueId: string }): Promise<Result<GetLeagueAdminResultDTO, ApplicationErrorCode<'LEAGUE_NOT_FOUND', { message: string }>>> {
|
||||
async execute(params: { leagueId: string }): Promise<Result<GetLeagueAdminOutputPort, ApplicationErrorCode<'LEAGUE_NOT_FOUND', { message: string }>>> {
|
||||
const league = await this.leagueRepository.findById(params.leagueId);
|
||||
if (!league) {
|
||||
return Result.err({ code: 'LEAGUE_NOT_FOUND', details: { message: 'League not found' } });
|
||||
}
|
||||
|
||||
const dto: GetLeagueAdminResultDTO = {
|
||||
const dto: GetLeagueAdminOutputPort = {
|
||||
league: {
|
||||
id: league.id,
|
||||
ownerId: league.ownerId,
|
||||
|
||||
@@ -3,19 +3,19 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit
|
||||
import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { GetLeagueJoinRequestsResultDTO } from '../dto/GetLeagueJoinRequestsResultDTO';
|
||||
import type { GetLeagueJoinRequestsOutputPort } from '../ports/output/GetLeagueJoinRequestsOutputPort';
|
||||
|
||||
export interface GetLeagueJoinRequestsUseCaseParams {
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class GetLeagueJoinRequestsUseCase implements AsyncUseCase<GetLeagueJoinRequestsUseCaseParams, GetLeagueJoinRequestsResultDTO, 'NO_ERROR'> {
|
||||
export class GetLeagueJoinRequestsUseCase implements AsyncUseCase<GetLeagueJoinRequestsUseCaseParams, GetLeagueJoinRequestsOutputPort, 'NO_ERROR'> {
|
||||
constructor(
|
||||
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
||||
private readonly driverRepository: IDriverRepository,
|
||||
) {}
|
||||
|
||||
async execute(params: GetLeagueJoinRequestsUseCaseParams): Promise<Result<GetLeagueJoinRequestsResultDTO, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
async execute(params: GetLeagueJoinRequestsUseCaseParams): Promise<Result<GetLeagueJoinRequestsOutputPort, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
const joinRequests = await this.leagueMembershipRepository.getJoinRequests(params.leagueId);
|
||||
const driverIds = [...new Set(joinRequests.map(r => r.driverId))];
|
||||
const drivers = await Promise.all(driverIds.map(id => this.driverRepository.findById(id)));
|
||||
|
||||
@@ -3,19 +3,19 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit
|
||||
import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { GetLeagueMembershipsResultDTO } from '../dto/GetLeagueMembershipsResultDTO';
|
||||
import type { GetLeagueMembershipsOutputPort } from '../ports/output/GetLeagueMembershipsOutputPort';
|
||||
|
||||
export interface GetLeagueMembershipsUseCaseParams {
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class GetLeagueMembershipsUseCase implements AsyncUseCase<GetLeagueMembershipsUseCaseParams, GetLeagueMembershipsResultDTO, 'NO_ERROR'> {
|
||||
export class GetLeagueMembershipsUseCase implements AsyncUseCase<GetLeagueMembershipsUseCaseParams, GetLeagueMembershipsOutputPort, 'NO_ERROR'> {
|
||||
constructor(
|
||||
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
||||
private readonly driverRepository: IDriverRepository,
|
||||
) {}
|
||||
|
||||
async execute(params: GetLeagueMembershipsUseCaseParams): Promise<Result<GetLeagueMembershipsResultDTO, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
async execute(params: GetLeagueMembershipsUseCaseParams): Promise<Result<GetLeagueMembershipsOutputPort, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
const memberships = await this.leagueMembershipRepository.getLeagueMembers(params.leagueId);
|
||||
const drivers: { id: string; name: string }[] = [];
|
||||
|
||||
@@ -27,7 +27,7 @@ export class GetLeagueMembershipsUseCase implements AsyncUseCase<GetLeagueMember
|
||||
}
|
||||
}
|
||||
|
||||
const dto: GetLeagueMembershipsResultDTO = {
|
||||
const dto: GetLeagueMembershipsOutputPort = {
|
||||
memberships,
|
||||
drivers,
|
||||
};
|
||||
|
||||
@@ -2,16 +2,16 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit
|
||||
import type { AsyncUseCase } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { GetLeagueOwnerSummaryResultDTO } from '../dto/GetLeagueOwnerSummaryResultDTO';
|
||||
import type { GetLeagueOwnerSummaryOutputPort } from '../ports/output/GetLeagueOwnerSummaryOutputPort';
|
||||
|
||||
export interface GetLeagueOwnerSummaryUseCaseParams {
|
||||
ownerId: string;
|
||||
}
|
||||
|
||||
export class GetLeagueOwnerSummaryUseCase implements AsyncUseCase<GetLeagueOwnerSummaryUseCaseParams, GetLeagueOwnerSummaryResultDTO, 'NO_ERROR'> {
|
||||
export class GetLeagueOwnerSummaryUseCase implements AsyncUseCase<GetLeagueOwnerSummaryUseCaseParams, GetLeagueOwnerSummaryOutputPort, 'NO_ERROR'> {
|
||||
constructor(private readonly driverRepository: IDriverRepository) {}
|
||||
|
||||
async execute(params: GetLeagueOwnerSummaryUseCaseParams): Promise<Result<GetLeagueOwnerSummaryResultDTO, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
async execute(params: GetLeagueOwnerSummaryUseCaseParams): Promise<Result<GetLeagueOwnerSummaryOutputPort, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
const driver = await this.driverRepository.findById(params.ownerId);
|
||||
const summary = driver ? { driver: { id: driver.id, name: driver.name }, rating: 0, rank: 0 } : null;
|
||||
return Result.ok({ summary });
|
||||
|
||||
@@ -4,8 +4,10 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit
|
||||
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
|
||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
|
||||
import type { DriverRatingProvider } from '../ports/DriverRatingProvider';
|
||||
import type { IImageServicePort } from '../ports/IImageServicePort';
|
||||
import type { GetDriverRatingInputPort } from '../ports/input/GetDriverRatingInputPort';
|
||||
import type { GetDriverRatingOutputPort } from '../ports/output/GetDriverRatingOutputPort';
|
||||
import type { GetDriverAvatarInputPort } from '../ports/input/GetDriverAvatarInputPort';
|
||||
import type { GetDriverAvatarOutputPort } from '../ports/output/GetDriverAvatarOutputPort';
|
||||
import type {
|
||||
RaceDetailViewModel,
|
||||
RaceDetailRaceViewModel,
|
||||
@@ -44,8 +46,8 @@ export class GetRaceDetailUseCase
|
||||
private readonly raceRegistrationRepository: IRaceRegistrationRepository,
|
||||
private readonly resultRepository: IResultRepository,
|
||||
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
||||
private readonly driverRatingProvider: DriverRatingProvider,
|
||||
private readonly imageService: IImageServicePort,
|
||||
private readonly getDriverRating: (input: GetDriverRatingInputPort) => Promise<GetDriverRatingOutputPort>,
|
||||
private readonly getDriverAvatar: (input: GetDriverAvatarInputPort) => Promise<GetDriverAvatarOutputPort>,
|
||||
) {}
|
||||
|
||||
async execute(params: GetRaceDetailQueryParams): Promise<Result<RaceDetailViewModel, ApplicationErrorCode<GetRaceDetailErrorCode>>> {
|
||||
@@ -62,22 +64,26 @@ export class GetRaceDetailUseCase
|
||||
this.leagueMembershipRepository.getMembership(race.leagueId, driverId),
|
||||
]);
|
||||
|
||||
const ratings = this.driverRatingProvider.getRatings(registeredDriverIds);
|
||||
|
||||
const drivers = await Promise.all(
|
||||
registeredDriverIds.map(id => this.driverRepository.findById(id)),
|
||||
);
|
||||
|
||||
const entryList: RaceDetailEntryViewModel[] = drivers
|
||||
.filter((d): d is NonNullable<typeof d> => d !== null)
|
||||
.map(driver => ({
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
country: driver.country,
|
||||
avatarUrl: this.imageService.getDriverAvatar(driver.id),
|
||||
rating: ratings.get(driver.id) ?? null,
|
||||
isCurrentUser: driver.id === driverId,
|
||||
}));
|
||||
const entryList: RaceDetailEntryViewModel[] = [];
|
||||
for (const driver of drivers) {
|
||||
if (driver) {
|
||||
const ratingResult = await this.getDriverRating({ driverId: driver.id });
|
||||
const avatarResult = await this.getDriverAvatar({ driverId: driver.id });
|
||||
|
||||
entryList.push({
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
country: driver.country,
|
||||
avatarUrl: avatarResult.avatarUrl,
|
||||
rating: ratingResult.rating,
|
||||
isCurrentUser: driver.id === driverId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const isUserRegistered = registeredDriverIds.includes(driverId);
|
||||
const isUpcoming = race.status === 'scheduled' && race.scheduledAt > new Date();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
|
||||
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
||||
import type { IImageServicePort } from '../ports/IImageServicePort';
|
||||
import type { GetDriverAvatarInputPort } from '../ports/input/GetDriverAvatarInputPort';
|
||||
import type { GetDriverAvatarOutputPort } from '../ports/output/GetDriverAvatarOutputPort';
|
||||
import type { TeamMembersResultDTO } from '../presenters/ITeamMembersPresenter';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
@@ -15,7 +16,7 @@ export class GetTeamMembersUseCase implements AsyncUseCase<{ teamId: string }, T
|
||||
constructor(
|
||||
private readonly membershipRepository: ITeamMembershipRepository,
|
||||
private readonly driverRepository: IDriverRepository,
|
||||
private readonly imageService: IImageServicePort,
|
||||
private readonly getDriverAvatar: (input: GetDriverAvatarInputPort) => Promise<GetDriverAvatarOutputPort>,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -37,7 +38,9 @@ export class GetTeamMembersUseCase implements AsyncUseCase<{ teamId: string }, T
|
||||
} else {
|
||||
this.logger.warn(`Driver with ID ${membership.driverId} not found while fetching team members for team ${input.teamId}.`);
|
||||
}
|
||||
avatarUrls[membership.driverId] = this.imageService.getDriverAvatar(membership.driverId);
|
||||
|
||||
const avatarResult = await this.getDriverAvatar({ driverId: membership.driverId });
|
||||
avatarUrls[membership.driverId] = avatarResult.avatarUrl;
|
||||
}
|
||||
|
||||
const dto: TeamMembersResultDTO = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LeagueScoringPresetProvider } from '../ports/LeagueScoringPresetProvider';
|
||||
import type { LeagueScoringPresetOutputPort } from '../ports/output/LeagueScoringPresetOutputPort';
|
||||
import type { LeagueScoringPresetsResultDTO } from '../presenters/ILeagueScoringPresetsPresenter';
|
||||
import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
@@ -6,18 +6,16 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
|
||||
|
||||
/**
|
||||
* Use Case for listing league scoring presets.
|
||||
* Orchestrates domain logic and returns result.
|
||||
* Returns preset data without business logic.
|
||||
*/
|
||||
export class ListLeagueScoringPresetsUseCase
|
||||
implements AsyncUseCase<void, LeagueScoringPresetsResultDTO, 'NO_ERROR'>
|
||||
{
|
||||
constructor(private readonly presetProvider: LeagueScoringPresetProvider) {}
|
||||
constructor(private readonly presets: LeagueScoringPresetOutputPort[]) {}
|
||||
|
||||
async execute(): Promise<Result<LeagueScoringPresetsResultDTO, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
const presets = await this.presetProvider.listPresets();
|
||||
|
||||
const dto: LeagueScoringPresetsResultDTO = {
|
||||
presets,
|
||||
presets: this.presets,
|
||||
};
|
||||
|
||||
return Result.ok(dto);
|
||||
|
||||
@@ -11,10 +11,8 @@ import type { ChampionshipStanding } from '@core/racing/domain/entities/champion
|
||||
import { EventScoringService } from '@core/racing/domain/services/EventScoringService';
|
||||
import { ChampionshipAggregator } from '@core/racing/domain/services/ChampionshipAggregator';
|
||||
|
||||
import type {
|
||||
ChampionshipStandingsDTO,
|
||||
ChampionshipStandingsRowDTO,
|
||||
} from '../dto/ChampionshipStandingsDTO';
|
||||
import type { ChampionshipStandingsOutputPort } from '../ports/output/ChampionshipStandingsOutputPort';
|
||||
import type { ChampionshipStandingsRowOutputPort } from '../ports/output/ChampionshipStandingsRowOutputPort';
|
||||
|
||||
import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
@@ -31,7 +29,7 @@ type RecalculateChampionshipStandingsErrorCode =
|
||||
| 'CHAMPIONSHIP_CONFIG_NOT_FOUND';
|
||||
|
||||
export class RecalculateChampionshipStandingsUseCase
|
||||
implements AsyncUseCase<RecalculateChampionshipStandingsParams, ChampionshipStandingsDTO, RecalculateChampionshipStandingsErrorCode>
|
||||
implements AsyncUseCase<RecalculateChampionshipStandingsParams, ChampionshipStandingsOutputPort, RecalculateChampionshipStandingsErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly seasonRepository: ISeasonRepository,
|
||||
@@ -44,7 +42,7 @@ export class RecalculateChampionshipStandingsUseCase
|
||||
private readonly championshipAggregator: ChampionshipAggregator,
|
||||
) {}
|
||||
|
||||
async execute(params: RecalculateChampionshipStandingsParams): Promise<Result<ChampionshipStandingsDTO, ApplicationErrorCode<RecalculateChampionshipStandingsErrorCode>>> {
|
||||
async execute(params: RecalculateChampionshipStandingsParams): Promise<Result<ChampionshipStandingsOutputPort, ApplicationErrorCode<RecalculateChampionshipStandingsErrorCode>>> {
|
||||
const { seasonId, championshipId } = params;
|
||||
|
||||
const season = await this.seasonRepository.findById(seasonId);
|
||||
@@ -107,7 +105,7 @@ export class RecalculateChampionshipStandingsUseCase
|
||||
resultsDropped: s.resultsDropped.toNumber(),
|
||||
}));
|
||||
|
||||
const dto: ChampionshipStandingsDTO = {
|
||||
const dto: ChampionshipStandingsOutputPort = {
|
||||
seasonId,
|
||||
championshipId: championship.id,
|
||||
championshipName: championship.name,
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { UpdateDriverProfileUseCase } from './UpdateDriverProfileUseCase';
|
||||
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
||||
import { EntityMappers } from '../mappers/EntityMappers';
|
||||
|
||||
vi.mock('../mappers/EntityMappers', () => ({
|
||||
EntityMappers: {
|
||||
toDriverDTO: vi.fn(),
|
||||
},
|
||||
}));
|
||||
import type { Driver } from '../../domain/entities/Driver';
|
||||
|
||||
describe('UpdateDriverProfileUseCase', () => {
|
||||
it('updates driver profile successfully', async () => {
|
||||
const mockDriver = {
|
||||
id: 'driver-1',
|
||||
update: vi.fn().mockReturnValue({}),
|
||||
};
|
||||
} as unknown as Driver;
|
||||
|
||||
const mockUpdatedDriver = {};
|
||||
|
||||
const mockDTO = { id: 'driver-1', bio: 'New bio' };
|
||||
const mockUpdatedDriver = {
|
||||
id: 'driver-1',
|
||||
bio: 'New bio',
|
||||
country: 'US',
|
||||
} as Driver;
|
||||
|
||||
const mockDriverRepository = {
|
||||
findById: vi.fn().mockResolvedValue(mockDriver),
|
||||
update: vi.fn().mockResolvedValue(mockUpdatedDriver),
|
||||
} as unknown as IDriverRepository;
|
||||
|
||||
(EntityMappers.toDriverDTO as any).mockReturnValue(mockDTO);
|
||||
|
||||
const useCase = new UpdateDriverProfileUseCase(mockDriverRepository);
|
||||
|
||||
const input = {
|
||||
@@ -38,11 +32,10 @@ describe('UpdateDriverProfileUseCase', () => {
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(result.unwrap()).toEqual(mockDTO);
|
||||
expect(result.unwrap()).toEqual(mockUpdatedDriver);
|
||||
expect(mockDriverRepository.findById).toHaveBeenCalledWith('driver-1');
|
||||
expect(mockDriver.update).toHaveBeenCalledWith({ bio: 'New bio', country: 'US' });
|
||||
expect(mockDriverRepository.update).toHaveBeenCalledWith({});
|
||||
expect(EntityMappers.toDriverDTO).toHaveBeenCalledWith(mockUpdatedDriver);
|
||||
});
|
||||
|
||||
it('returns error when driver not found', async () => {
|
||||
@@ -67,19 +60,18 @@ describe('UpdateDriverProfileUseCase', () => {
|
||||
const mockDriver = {
|
||||
id: 'driver-1',
|
||||
update: vi.fn().mockReturnValue({}),
|
||||
};
|
||||
} as unknown as Driver;
|
||||
|
||||
const mockUpdatedDriver = {};
|
||||
|
||||
const mockDTO = { id: 'driver-1', country: 'US' };
|
||||
const mockUpdatedDriver = {
|
||||
id: 'driver-1',
|
||||
country: 'US',
|
||||
} as Driver;
|
||||
|
||||
const mockDriverRepository = {
|
||||
findById: vi.fn().mockResolvedValue(mockDriver),
|
||||
update: vi.fn().mockResolvedValue(mockUpdatedDriver),
|
||||
} as unknown as IDriverRepository;
|
||||
|
||||
(EntityMappers.toDriverDTO as any).mockReturnValue(mockDTO);
|
||||
|
||||
const useCase = new UpdateDriverProfileUseCase(mockDriverRepository);
|
||||
|
||||
const input = {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
||||
import type { DriverDTO } from '../dto/DriverDTO';
|
||||
import { EntityMappers } from '../mappers/EntityMappers';
|
||||
import type { Driver } from '../../domain/entities/Driver';
|
||||
|
||||
export interface UpdateDriverProfileInput {
|
||||
driverId: string;
|
||||
@@ -12,12 +11,13 @@ export interface UpdateDriverProfileInput {
|
||||
|
||||
/**
|
||||
* Application use case responsible for updating basic driver profile details.
|
||||
* Encapsulates domain entity mutation and mapping to a DriverDTO.
|
||||
* Encapsulates domain entity mutation and returns the updated entity.
|
||||
* Mapping to DTOs is handled by presenters in the presentation layer.
|
||||
*/
|
||||
export class UpdateDriverProfileUseCase {
|
||||
constructor(private readonly driverRepository: IDriverRepository) {}
|
||||
|
||||
async execute(input: UpdateDriverProfileInput): Promise<Result<DriverDTO, ApplicationErrorCode<'DRIVER_NOT_FOUND'>>> {
|
||||
async execute(input: UpdateDriverProfileInput): Promise<Result<Driver, ApplicationErrorCode<'DRIVER_NOT_FOUND'>>> {
|
||||
const { driverId, bio, country } = input;
|
||||
|
||||
const existing = await this.driverRepository.findById(driverId);
|
||||
@@ -31,7 +31,6 @@ export class UpdateDriverProfileUseCase {
|
||||
});
|
||||
|
||||
const persisted = await this.driverRepository.update(updated);
|
||||
const dto = EntityMappers.toDriverDTO(persisted);
|
||||
return Result.ok(dto!);
|
||||
return Result.ok(persisted);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user