60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import { Injectable, OnModuleDestroy } from '@nestjs/common';
|
|
import Redis from 'ioredis';
|
|
|
|
const ONLINE_KEY = 'genex:presence:online';
|
|
const DAU_KEY_PREFIX = 'genex:dau:';
|
|
const ONLINE_WINDOW = 180; // 3 minutes
|
|
|
|
@Injectable()
|
|
export class PresenceRedisService implements OnModuleDestroy {
|
|
private readonly redis: Redis;
|
|
|
|
constructor() {
|
|
this.redis = new Redis({
|
|
host: process.env.REDIS_HOST || 'localhost',
|
|
port: parseInt(process.env.REDIS_PORT || '6379', 10),
|
|
password: process.env.REDIS_PASSWORD || undefined,
|
|
db: parseInt(process.env.REDIS_DB || '0', 10),
|
|
});
|
|
}
|
|
|
|
async onModuleDestroy() {
|
|
await this.redis.quit();
|
|
}
|
|
|
|
/** Update user heartbeat timestamp */
|
|
async updatePresence(userId: string): Promise<void> {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
await this.redis.zadd(ONLINE_KEY, now, userId);
|
|
}
|
|
|
|
/** Count users online within window */
|
|
async countOnline(): Promise<number> {
|
|
const threshold = Math.floor(Date.now() / 1000) - ONLINE_WINDOW;
|
|
return this.redis.zcount(ONLINE_KEY, threshold, '+inf');
|
|
}
|
|
|
|
/** Add user/installId to HyperLogLog DAU */
|
|
async addDauIdentifier(date: string, identifier: string): Promise<void> {
|
|
const key = `${DAU_KEY_PREFIX}${date}`;
|
|
await this.redis.pfadd(key, identifier);
|
|
// Auto-expire after 48 hours
|
|
await this.redis.expire(key, 48 * 3600);
|
|
}
|
|
|
|
/** Get approximate DAU from HyperLogLog */
|
|
async getApproxDau(date: string): Promise<number> {
|
|
return this.redis.pfcount(`${DAU_KEY_PREFIX}${date}`);
|
|
}
|
|
|
|
/** Clean up expired presence data (>24h old) */
|
|
async cleanupExpired(): Promise<number> {
|
|
const cutoff = Math.floor(Date.now() / 1000) - 24 * 3600;
|
|
return this.redis.zremrangebyscore(ONLINE_KEY, '-inf', cutoff);
|
|
}
|
|
|
|
getWindowSeconds(): number {
|
|
return ONLINE_WINDOW;
|
|
}
|
|
}
|