refactor racing use cases

This commit is contained in:
2025-12-21 00:43:42 +01:00
parent e9d6f90bb2
commit c12656d671
308 changed files with 14401 additions and 7419 deletions

View File

@@ -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';
}
},
};
}
}