rwadurian/backend/services/mining-service/src/application/queries/get-mining-stats.query.ts

107 lines
3.6 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { MiningAccountRepository } from '../../infrastructure/persistence/repositories/mining-account.repository';
import { MiningConfigRepository } from '../../infrastructure/persistence/repositories/mining-config.repository';
import { PriceSnapshotRepository } from '../../infrastructure/persistence/repositories/price-snapshot.repository';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
export interface MiningStatsDto {
// 配置信息
isActive: boolean;
currentEra: number;
eraStartDate: Date | null;
activatedAt: Date | null;
// 分配信息
totalShares: string;
distributionPool: string;
remainingDistribution: string;
secondDistribution: string;
// 参与信息
totalContribution: string;
participantCount: number;
totalMined: string;
// 价格信息
currentPrice: string;
priceChangePercent24h: number | null;
}
export interface MiningRankingDto {
rank: number;
accountSequence: string;
totalMined: string;
totalContribution: string;
}
@Injectable()
export class GetMiningStatsQuery {
constructor(
private readonly accountRepository: MiningAccountRepository,
private readonly configRepository: MiningConfigRepository,
private readonly priceRepository: PriceSnapshotRepository,
private readonly prisma: PrismaService,
) {}
async execute(): Promise<MiningStatsDto> {
const [config, latestPrice, price24hAgo, totalMined, participantCount, totalContribution] =
await Promise.all([
this.configRepository.getConfig(),
this.priceRepository.getLatestSnapshot(),
this.get24hAgoPrice(),
this.getTotalMined(),
this.accountRepository.countAccountsWithContribution(),
this.accountRepository.getTotalContribution(),
]);
let priceChangePercent24h: number | null = null;
if (latestPrice && price24hAgo) {
const currentPrice = latestPrice.price.value.toNumber();
const oldPrice = price24hAgo.price.value.toNumber();
if (oldPrice > 0) {
priceChangePercent24h = ((currentPrice - oldPrice) / oldPrice) * 100;
}
}
return {
isActive: config?.isActive || false,
currentEra: config?.currentEra || 1,
eraStartDate: config?.eraStartDate || null,
activatedAt: config?.activatedAt || null,
totalShares: config?.totalShares.toString() || '0',
distributionPool: config?.distributionPool.toString() || '0',
remainingDistribution: config?.remainingDistribution.toString() || '0',
secondDistribution: config?.secondDistribution.toString() || '0',
totalContribution: totalContribution.toString(),
participantCount,
totalMined: totalMined.toString(),
currentPrice: latestPrice?.price.toString() || '1',
priceChangePercent24h,
};
}
async getRanking(limit: number = 100): Promise<MiningRankingDto[]> {
const topMiners = await this.accountRepository.getTopMiners(limit);
return topMiners.map((account, index) => ({
rank: index + 1,
accountSequence: account.accountSequence,
totalMined: account.totalMined.toString(),
totalContribution: account.totalContribution.toString(),
}));
}
private async getTotalMined(): Promise<string> {
const result = await this.prisma.miningAccount.aggregate({
_sum: { totalMined: true },
});
return (result._sum.totalMined || 0).toString();
}
private async get24hAgoPrice() {
const time24hAgo = new Date();
time24hAgo.setHours(time24hAgo.getHours() - 24);
return this.priceRepository.getSnapshotAt(time24hAgo);
}
}