From bed275fe7b92b44d30694423b2fd29a8e1569ebb Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 20 Jun 2026 23:41:18 -0400 Subject: [PATCH] Filter stale clients from live miner report --- .../user-agent-report.service.spec.ts | 6 ++++++ .../user-agent-report/user-agent-report.service.ts | 13 +++++++++++++ src/ORM/client/client.service.ts | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/ORM/_views/user-agent-report/user-agent-report.service.spec.ts b/src/ORM/_views/user-agent-report/user-agent-report.service.spec.ts index 5eeca58..cd9d664 100644 --- a/src/ORM/_views/user-agent-report/user-agent-report.service.spec.ts +++ b/src/ORM/_views/user-agent-report/user-agent-report.service.spec.ts @@ -51,6 +51,10 @@ describe('UserAgentReportService', () => { ]); expect(userAgentReport.find).not.toHaveBeenCalled(); expect(clientRepository.createQueryBuilder).toHaveBeenCalled(); + expect(clientRepository.queryBuilder.andWhere).toHaveBeenCalledWith( + 'client.updatedAt > :activeSince', + expect.objectContaining({ activeSince: expect.any(Date) }), + ); }); it('falls back to the materialized view when no active database clients exist', async () => { @@ -114,6 +118,7 @@ function createClientRepository(rows: unknown[]) { select: jest.fn().mockReturnThis(), addSelect: jest.fn().mockReturnThis(), where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), groupBy: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), getRawMany: jest.fn().mockResolvedValue(rows), @@ -121,5 +126,6 @@ function createClientRepository(rows: unknown[]) { return { createQueryBuilder: jest.fn().mockReturnValue(queryBuilder), + queryBuilder, }; } diff --git a/src/ORM/_views/user-agent-report/user-agent-report.service.ts b/src/ORM/_views/user-agent-report/user-agent-report.service.ts index 7a88662..c494a3d 100644 --- a/src/ORM/_views/user-agent-report/user-agent-report.service.ts +++ b/src/ORM/_views/user-agent-report/user-agent-report.service.ts @@ -6,9 +6,12 @@ import { ClientEntity } from '../../client/client.entity'; import { UserAgentReportView } from './user-agent-report.view'; import { RedisMessagingService } from '../../../services/redis-messaging.service'; +const DEFAULT_CLIENT_REPORT_ACTIVE_WINDOW_MS = 30 * 60 * 1000; + @Injectable() export class UserAgentReportService { private readonly liveReportCacheKey = 'presence:user-agent-report'; + private readonly activeWindowMs = this.readPositiveInt('CLIENT_REPORT_ACTIVE_WINDOW_MS', DEFAULT_CLIENT_REPORT_ACTIVE_WINDOW_MS); private liveRefreshPromise: Promise | null = null; constructor( @@ -60,6 +63,7 @@ export class UserAgentReportService { } private async buildLiveReport() { + const activeSince = new Date(Date.now() - this.activeWindowMs); const rows = await this.clientRepository .createQueryBuilder('client') .select('COALESCE(NULLIF(client.userAgent, \'\'), \'Other\')', 'userAgent') @@ -67,6 +71,7 @@ export class UserAgentReportService { .addSelect('MAX(client.bestDifficulty)', 'bestDifficulty') .addSelect('COALESCE(SUM(client.hashRate), 0)', 'totalHashRate') .where('client.deletedAt IS NULL') + .andWhere('client.updatedAt > :activeSince', { activeSince }) .groupBy('COALESCE(NULLIF(client.userAgent, \'\'), \'Other\')') .orderBy('"totalHashRate"', 'DESC') .getRawMany(); @@ -94,4 +99,12 @@ export class UserAgentReportService { } } + + private readPositiveInt(name: string, defaultValue: number): number { + const value = Number(process.env[name]); + if (Number.isInteger(value) && value > 0) { + return value; + } + return defaultValue; + } } diff --git a/src/ORM/client/client.service.ts b/src/ORM/client/client.service.ts index 255fca1..d3ef562 100644 --- a/src/ORM/client/client.service.ts +++ b/src/ORM/client/client.service.ts @@ -59,7 +59,7 @@ export class ClientService { } public async updateHashRate(id: string, hashRate: number, updatedAt = new Date()) { - return await this.clientRepository.update({ id }, { hashRate, updatedAt }); + return await this.clientRepository.update({ id }, { hashRate, updatedAt, deletedAt: null }); } public async connectedClientCount(): Promise {