import { Injectable } from '@nestjs/common'; import { PrismaService } from '../database/prisma.service'; import { ILeaderboardRankingRepository } from '../../domain/repositories/leaderboard-ranking.repository.interface'; import { LeaderboardRanking } from '../../domain/aggregates/leaderboard-ranking/leaderboard-ranking.aggregate'; import { LeaderboardType } from '../../domain/value-objects/leaderboard-type.enum'; import { LeaderboardPeriod } from '../../domain/value-objects/leaderboard-period.vo'; @Injectable() export class LeaderboardRankingRepositoryImpl implements ILeaderboardRankingRepository { constructor(private readonly prisma: PrismaService) {} async save(ranking: LeaderboardRanking): Promise { const data = { leaderboardType: ranking.leaderboardType, periodKey: ranking.periodKey, userId: ranking.userId, isVirtual: ranking.isVirtual, rankPosition: ranking.rankPosition.value, displayPosition: ranking.displayPosition.value, previousRank: ranking.previousRank?.value || null, totalTeamPlanting: ranking.score.totalTeamPlanting, maxDirectTeamPlanting: ranking.score.maxDirectTeamPlanting, effectiveScore: ranking.score.effectiveScore, userSnapshot: ranking.userSnapshot.toJson(), periodStartAt: ranking.period.startAt, periodEndAt: ranking.period.endAt, calculatedAt: ranking.calculatedAt, }; if (ranking.id) { await this.prisma.leaderboardRanking.update({ where: { id: ranking.id }, data, }); } else { const result = await this.prisma.leaderboardRanking.upsert({ where: { uk_type_period_user: { leaderboardType: ranking.leaderboardType, periodKey: ranking.periodKey, userId: ranking.userId, }, }, update: data, create: data, }); ranking.setId(result.id); } } async saveAll(rankings: LeaderboardRanking[]): Promise { // 使用事务批量保存 await this.prisma.$transaction( rankings.map((ranking) => this.prisma.leaderboardRanking.upsert({ where: { uk_type_period_user: { leaderboardType: ranking.leaderboardType, periodKey: ranking.periodKey, userId: ranking.userId, }, }, update: { rankPosition: ranking.rankPosition.value, displayPosition: ranking.displayPosition.value, previousRank: ranking.previousRank?.value || null, totalTeamPlanting: ranking.score.totalTeamPlanting, maxDirectTeamPlanting: ranking.score.maxDirectTeamPlanting, effectiveScore: ranking.score.effectiveScore, userSnapshot: ranking.userSnapshot.toJson(), calculatedAt: ranking.calculatedAt, }, create: { leaderboardType: ranking.leaderboardType, periodKey: ranking.periodKey, userId: ranking.userId, isVirtual: ranking.isVirtual, rankPosition: ranking.rankPosition.value, displayPosition: ranking.displayPosition.value, previousRank: ranking.previousRank?.value || null, totalTeamPlanting: ranking.score.totalTeamPlanting, maxDirectTeamPlanting: ranking.score.maxDirectTeamPlanting, effectiveScore: ranking.score.effectiveScore, userSnapshot: ranking.userSnapshot.toJson(), periodStartAt: ranking.period.startAt, periodEndAt: ranking.period.endAt, calculatedAt: ranking.calculatedAt, }, }) ) ); } async findById(id: bigint): Promise { const record = await this.prisma.leaderboardRanking.findUnique({ where: { id }, }); if (!record) return null; return this.toDomain(record); } async findByTypeAndPeriod( type: LeaderboardType, periodKey: string, options?: { limit?: number; includeVirtual?: boolean; }, ): Promise { const records = await this.prisma.leaderboardRanking.findMany({ where: { leaderboardType: type, periodKey, ...(options?.includeVirtual === false ? { isVirtual: false } : {}), }, orderBy: { displayPosition: 'asc' }, take: options?.limit, }); return records.map((r) => this.toDomain(r)); } async findUserRanking( type: LeaderboardType, periodKey: string, userId: bigint, ): Promise { const record = await this.prisma.leaderboardRanking.findUnique({ where: { uk_type_period_user: { leaderboardType: type, periodKey, userId, }, }, }); if (!record) return null; return this.toDomain(record); } async findUserPreviousRanking( type: LeaderboardType, userId: bigint, ): Promise { const currentPeriod = LeaderboardPeriod.current(type); const previousPeriod = currentPeriod.getPreviousPeriod(); return this.findUserRanking(type, previousPeriod.key, userId); } async deleteByTypeAndPeriod( type: LeaderboardType, periodKey: string, ): Promise { await this.prisma.leaderboardRanking.deleteMany({ where: { leaderboardType: type, periodKey, }, }); } async countByTypeAndPeriod( type: LeaderboardType, periodKey: string, ): Promise { return this.prisma.leaderboardRanking.count({ where: { leaderboardType: type, periodKey, isVirtual: false, }, }); } async getTopScore( type: LeaderboardType, periodKey: string, ): Promise { const result = await this.prisma.leaderboardRanking.findFirst({ where: { leaderboardType: type, periodKey, isVirtual: false, }, orderBy: { effectiveScore: 'desc' }, select: { effectiveScore: true }, }); return result?.effectiveScore || 0; } private toDomain(record: any): LeaderboardRanking { return LeaderboardRanking.reconstitute({ id: record.id, leaderboardType: record.leaderboardType as LeaderboardType, periodKey: record.periodKey, periodStartAt: record.periodStartAt, periodEndAt: record.periodEndAt, userId: record.userId, isVirtual: record.isVirtual, rankPosition: record.rankPosition, displayPosition: record.displayPosition, previousRank: record.previousRank, totalTeamPlanting: record.totalTeamPlanting, maxDirectTeamPlanting: record.maxDirectTeamPlanting, effectiveScore: record.effectiveScore, userSnapshot: record.userSnapshot as Record, calculatedAt: record.calculatedAt, }); } }