import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; import { ClientEntity } from './client.entity'; const DEFAULT_CLIENT_ACTIVE_WINDOW_MS = 30 * 60 * 1000; @Injectable() export class ClientService { private readonly activeWindowMs = this.readPositiveInt('CLIENT_REPORT_ACTIVE_WINDOW_MS', DEFAULT_CLIENT_ACTIVE_WINDOW_MS); constructor( @InjectRepository(ClientEntity) private clientRepository: Repository ) { } public async insert(partialClient: Partial): Promise { const insertResult = await this.clientRepository.insert(partialClient); const client = { ...partialClient, ...insertResult.generatedMaps[0] }; return client as ClientEntity; } public async delete(id: string) { return await this.clientRepository.softDelete({ id }); } public async deleteOldClients() { const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); return await this.clientRepository .createQueryBuilder() .delete() .from(ClientEntity) .where('deletedAt < :deletedAt', { deletedAt: oneDayAgo }) .execute(); } public async updateBestDifficulty(id: string, bestDifficulty: number) { return await this.clientRepository.update({ id }, { bestDifficulty }); } public async updateBestDifficultyIfHigher(id: string, bestDifficulty: number) { return await this.clientRepository .createQueryBuilder() .update(ClientEntity) .set({ bestDifficulty }) .where('id = :id', { id }) .andWhere('"bestDifficulty" < :bestDifficulty', { bestDifficulty }) .execute(); } public async updateHashRate(id: string, hashRate: number, updatedAt = new Date()) { return await this.clientRepository.update({ id }, { hashRate, updatedAt, deletedAt: null }); } public async connectedClientCount(): Promise { return await this.clientRepository.count(); } public async getActiveIds(ids: string[]): Promise> { if (ids.length === 0) { return new Set(); } const clients = await this.clientRepository.find({ select: { id: true, }, where: { id: In(ids), }, }); return new Set(clients.map(client => client.id)); } public async getByAddress(address: string): Promise { const activeSince = new Date(Date.now() - this.activeWindowMs); return await this.clientRepository .createQueryBuilder('client') .where('client.address = :address', { address }) .andWhere('client.deletedAt IS NULL') .andWhere('client.updatedAt > :activeSince', { activeSince }) .andWhere('client.hashRate > 0') .orderBy('client.updatedAt', 'DESC') .getMany(); } public async getByName(address: string, clientName: string): Promise { return await this.clientRepository.find({ where: { address, clientName } }) } public async getBySessionId(address: string, clientName: string, sessionId: string): Promise { return await this.clientRepository.findOne({ where: { address, clientName, sessionId } }) } public async deleteAll() { return await this.clientRepository .createQueryBuilder() .delete() .from(ClientEntity) .where('"deletedAt" IS NULL') .execute(); } // public async getUserAgents() { // const result = await this.clientRepository.createQueryBuilder('client') // .select('client.userAgent as "userAgent"') // .addSelect('COUNT(client.userAgent)', 'count') // .addSelect('MAX(client.bestDifficulty)', 'bestDifficulty') // .addSelect('SUM(client.hashRate)', 'totalHashRate') // .groupBy('client.userAgent') // .orderBy('"totalHashRate"', 'DESC') // .getRawMany(); // return result; // } private readPositiveInt(name: string, defaultValue: number): number { const value = Number(process.env[name]); if (Number.isInteger(value) && value > 0) { return value; } return defaultValue; } }