Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions src/reputation/reputation.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { IsNull, Not, Repository } from 'typeorm';
import { Bounty, Payment, ReputationSnapshot } from '../common/entities';
import { queryContributorCoreStats } from '../common/stats/contributor-stats.sql';

Expand All @@ -14,6 +15,8 @@ export interface ReputationHistoryOptions {

@Injectable()
export class ReputationService {
private readonly logger = new Logger(ReputationService.name);

constructor(
@InjectRepository(Bounty) private readonly bountyRepo: Repository<Bounty>,
@InjectRepository(Payment)
Expand Down Expand Up @@ -47,6 +50,33 @@ export class ReputationService {
return this.snapshotRepo.save(snapshot);
}

/**
* Scheduled sweep — previously the ONLY way a snapshot was written was the
* owner-only POST /reputation/:userId/recompute, so GET returned nothing
* for anyone who never manually recomputed (#589). Nightly, recompute for
* every contributor who has a claimed bounty.
*/
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
async recomputeAllActiveContributors(): Promise<void> {
const claimed = await this.bountyRepo.find({
where: { claimedById: Not(IsNull()) },
select: { claimedById: true },
});
const userIds = [...new Set(claimed.map((b) => b.claimedById!))];
for (const userId of userIds) {
try {
await this.computeAndSave(userId);
} catch (err) {
this.logger.warn(`Reputation recompute failed for ${userId}: ${err}`);
}
}
if (userIds.length) {
this.logger.log(
`Recomputed reputation for ${userIds.length} contributor(s)`,
);
}
}

async getLatest(userId: string): Promise<ReputationSnapshot | null> {
return this.snapshotRepo.findOne({
where: { userId },
Expand Down