From 7363d7f57c53edd76bd7ff5d12308ad8482f0fe0 Mon Sep 17 00:00:00 2001 From: Yggdrasil Date: Wed, 14 Oct 2026 04:15:23 -0400 Subject: [PATCH] fix(reputation): throttle recompute + skip the insert when unchanged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit recompute rode the 120/min default — a user could append ~170k snapshot rows/day. @Throttle(long:10/hr) + computeAndSave returns the latest snapshot unchanged instead of inserting a duplicate. Resolves #584 --- src/reputation/reputation.service.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/reputation/reputation.service.ts b/src/reputation/reputation.service.ts index 0533cd2f..3dde7b38 100644 --- a/src/reputation/reputation.service.ts +++ b/src/reputation/reputation.service.ts @@ -33,7 +33,7 @@ export class ReputationService { this.paymentRepo, ); - const snapshot = this.snapshotRepo.create({ + const fields = { userId, totalEarnings: stats.lifetimeEarnings.toFixed(7), mergedPrCount: stats.mergedCount, @@ -43,8 +43,28 @@ export class ReputationService { onTimeDeliveryPercentage: stats.onTimeDeliveryPercentage.toFixed(2), languages: stats.languages, orgsContributedTo: stats.orgs, + }; + + // Skip the insert when nothing changed — an unthrottled recompute could + // otherwise append a row per call (~170k/day for one user, #584). If the + // stats match the latest snapshot, return it instead of a duplicate. + const latest = await this.snapshotRepo.findOne({ + where: { userId }, + order: { computedAt: 'DESC' }, }); - return this.snapshotRepo.save(snapshot); + if ( + latest && + latest.totalEarnings === fields.totalEarnings && + latest.mergedPrCount === fields.mergedPrCount && + latest.openBountiesClaimed === fields.openBountiesClaimed && + latest.completionRate === fields.completionRate && + latest.avgReviewTimeHours === fields.avgReviewTimeHours && + latest.onTimeDeliveryPercentage === fields.onTimeDeliveryPercentage + ) { + return latest; + } + + return this.snapshotRepo.save(this.snapshotRepo.create(fields)); } async getLatest(userId: string): Promise {