refactor racing use cases
This commit is contained in:
@@ -1,105 +1,136 @@
|
||||
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
||||
import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository';
|
||||
import type { ITransactionRepository } from '../../domain/repositories/ITransactionRepository';
|
||||
import type { GetLeagueWalletOutputPort, WalletTransactionOutputPort } from '../ports/output/GetLeagueWalletOutputPort';
|
||||
import type { TransactionType } from '../../domain/entities/league-wallet/Transaction';
|
||||
import type { Transaction } from '../../domain/entities/league-wallet/Transaction';
|
||||
import type { LeagueWallet } from '../../domain/entities/league-wallet/LeagueWallet';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
export interface GetLeagueWalletUseCaseParams {
|
||||
import { Money } from '../../domain/value-objects/Money';
|
||||
|
||||
export type GetLeagueWalletErrorCode =
|
||||
| 'LEAGUE_NOT_FOUND'
|
||||
| 'WALLET_NOT_FOUND'
|
||||
| 'REPOSITORY_ERROR';
|
||||
|
||||
export interface GetLeagueWalletInput {
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
|
||||
export interface GetLeagueWalletAggregates {
|
||||
balance: Money;
|
||||
totalRevenue: Money;
|
||||
totalFees: Money;
|
||||
totalWithdrawals: Money;
|
||||
pendingPayouts: Money;
|
||||
}
|
||||
|
||||
export interface GetLeagueWalletResult {
|
||||
wallet: LeagueWallet;
|
||||
transactions: Transaction[];
|
||||
aggregates: GetLeagueWalletAggregates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use Case for retrieving league wallet information.
|
||||
*/
|
||||
export class GetLeagueWalletUseCase {
|
||||
constructor(
|
||||
private readonly leagueRepository: ILeagueRepository,
|
||||
private readonly leagueWalletRepository: ILeagueWalletRepository,
|
||||
private readonly transactionRepository: ITransactionRepository,
|
||||
private readonly output: UseCaseOutputPort<GetLeagueWalletResult>,
|
||||
) {}
|
||||
|
||||
|
||||
async execute(
|
||||
params: GetLeagueWalletUseCaseParams,
|
||||
): Promise<Result<GetLeagueWalletOutputPort, ApplicationErrorCode<'REPOSITORY_ERROR'>>> {
|
||||
input: GetLeagueWalletInput,
|
||||
): Promise<
|
||||
Result<void, ApplicationErrorCode<GetLeagueWalletErrorCode, { message: string }>>
|
||||
> {
|
||||
try {
|
||||
const wallet = await this.leagueWalletRepository.findByLeagueId(params.leagueId);
|
||||
|
||||
if (!wallet) {
|
||||
return Result.err({ code: 'REPOSITORY_ERROR', message: 'Wallet not found' });
|
||||
}
|
||||
|
||||
const transactions = await this.transactionRepository.findByWalletId(wallet.id.toString());
|
||||
|
||||
let totalRevenue = 0;
|
||||
let totalFees = 0;
|
||||
let totalWithdrawals = 0;
|
||||
let pendingPayouts = 0;
|
||||
|
||||
const transactionViewModels: WalletTransactionOutputPort[] = transactions
|
||||
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
|
||||
.map(transaction => {
|
||||
const amount = transaction.amount.amount;
|
||||
const fee = transaction.platformFee.amount;
|
||||
const netAmount = transaction.netAmount.amount;
|
||||
|
||||
if (
|
||||
transaction.type === 'sponsorship_payment' ||
|
||||
transaction.type === 'membership_payment' ||
|
||||
transaction.type === 'prize_payout'
|
||||
) {
|
||||
totalRevenue += amount;
|
||||
totalFees += fee;
|
||||
}
|
||||
|
||||
if (transaction.type === 'withdrawal' && transaction.status === 'completed') {
|
||||
totalWithdrawals += netAmount;
|
||||
}
|
||||
|
||||
if (transaction.type === 'prize_payout' && transaction.status === 'pending') {
|
||||
pendingPayouts += netAmount;
|
||||
}
|
||||
|
||||
return {
|
||||
id: transaction.id.toString(),
|
||||
type: this.mapTransactionType(transaction.type),
|
||||
description: transaction.description ?? '',
|
||||
amount,
|
||||
fee,
|
||||
netAmount,
|
||||
date: transaction.createdAt.toISOString(),
|
||||
status: transaction.status === 'cancelled' ? 'failed' : transaction.status,
|
||||
};
|
||||
const leagueExists = await this.leagueRepository.exists(input.leagueId);
|
||||
if (!leagueExists) {
|
||||
return Result.err({
|
||||
code: 'LEAGUE_NOT_FOUND',
|
||||
details: { message: 'League not found' },
|
||||
});
|
||||
|
||||
const output: GetLeagueWalletOutputPort = {
|
||||
balance: wallet.balance.amount,
|
||||
currency: wallet.balance.currency,
|
||||
}
|
||||
|
||||
const wallet = await this.leagueWalletRepository.findByLeagueId(input.leagueId);
|
||||
|
||||
if (!wallet) {
|
||||
return Result.err({
|
||||
code: 'WALLET_NOT_FOUND',
|
||||
details: { message: 'League wallet not found' },
|
||||
});
|
||||
}
|
||||
|
||||
const transactions = await this.transactionRepository.findByWalletId(
|
||||
wallet.id.toString(),
|
||||
);
|
||||
|
||||
const { aggregates } = this.computeAggregates(wallet.balance, transactions);
|
||||
|
||||
const result: GetLeagueWalletResult = {
|
||||
wallet,
|
||||
transactions: transactions
|
||||
.slice()
|
||||
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()),
|
||||
aggregates,
|
||||
};
|
||||
|
||||
this.output.present(result);
|
||||
|
||||
return Result.ok(undefined);
|
||||
} catch (error) {
|
||||
return Result.err({
|
||||
code: 'REPOSITORY_ERROR',
|
||||
details: {
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to fetch league wallet',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private computeAggregates(
|
||||
balance: Money,
|
||||
transactions: Transaction[],
|
||||
): { aggregates: GetLeagueWalletAggregates } {
|
||||
let totalRevenue = Money.create(0, balance.currency);
|
||||
let totalFees = Money.create(0, balance.currency);
|
||||
let totalWithdrawals = Money.create(0, balance.currency);
|
||||
let pendingPayouts = Money.create(0, balance.currency);
|
||||
|
||||
for (const transaction of transactions) {
|
||||
if (
|
||||
transaction.type === 'sponsorship_payment' ||
|
||||
transaction.type === 'membership_payment' ||
|
||||
transaction.type === 'prize_payout'
|
||||
) {
|
||||
totalRevenue = totalRevenue.add(transaction.amount);
|
||||
totalFees = totalFees.add(transaction.platformFee);
|
||||
}
|
||||
|
||||
if (transaction.type === 'withdrawal' && transaction.status === 'completed') {
|
||||
totalWithdrawals = totalWithdrawals.add(transaction.netAmount);
|
||||
}
|
||||
|
||||
if (transaction.type === 'prize_payout' && transaction.status === 'pending') {
|
||||
pendingPayouts = pendingPayouts.add(transaction.netAmount);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
aggregates: {
|
||||
balance,
|
||||
totalRevenue,
|
||||
totalFees,
|
||||
totalWithdrawals,
|
||||
pendingPayouts,
|
||||
canWithdraw: true,
|
||||
transactions: transactionViewModels,
|
||||
};
|
||||
|
||||
return Result.ok(output);
|
||||
} catch {
|
||||
return Result.err({ code: 'REPOSITORY_ERROR', message: 'Failed to fetch league wallet' });
|
||||
}
|
||||
}
|
||||
|
||||
private mapTransactionType(type: TransactionType): WalletTransactionOutputPort['type'] {
|
||||
switch (type) {
|
||||
case 'sponsorship_payment':
|
||||
return 'sponsorship';
|
||||
case 'membership_payment':
|
||||
return 'membership';
|
||||
case 'prize_payout':
|
||||
return 'prize';
|
||||
case 'withdrawal':
|
||||
return 'withdrawal';
|
||||
case 'refund':
|
||||
return 'sponsorship';
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user