rwadurian/backend/services/mpc-service/src/infrastructure/redis/cache/session-cache.service.ts

166 lines
4.4 KiB
TypeScript

/**
* Session Cache Service
*
* Redis-based caching for MPC session data.
*/
import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';
export interface CachedSessionInfo {
sessionId: string;
sessionType: string;
participants: string[];
thresholdN: number;
thresholdT: number;
status: string;
createdAt: string;
}
@Injectable()
export class SessionCacheService implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(SessionCacheService.name);
private redis: Redis;
private readonly keyPrefix = 'mpc:session:';
private readonly defaultTTL = 3600; // 1 hour
constructor(private readonly configService: ConfigService) {}
async onModuleInit() {
const host = this.configService.get<string>('REDIS_HOST') || 'localhost';
const port = this.configService.get<number>('REDIS_PORT') || 6379;
const password = this.configService.get<string>('REDIS_PASSWORD');
const db = this.configService.get<number>('REDIS_DB') || 5;
this.redis = new Redis({
host,
port,
password,
db,
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
});
this.redis.on('connect', () => {
this.logger.log('Redis connected');
});
this.redis.on('error', (err) => {
this.logger.error('Redis error', err);
});
}
async onModuleDestroy() {
await this.redis.quit();
this.logger.log('Redis disconnected');
}
/**
* Cache session info
*/
async cacheSession(sessionId: string, info: CachedSessionInfo, ttl?: number): Promise<void> {
const key = this.keyPrefix + sessionId;
await this.redis.setex(key, ttl || this.defaultTTL, JSON.stringify(info));
this.logger.debug(`Cached session: ${sessionId}`);
}
/**
* Get cached session info
*/
async getSession(sessionId: string): Promise<CachedSessionInfo | null> {
const key = this.keyPrefix + sessionId;
const data = await this.redis.get(key);
if (!data) {
return null;
}
return JSON.parse(data);
}
/**
* Update session status in cache
*/
async updateSessionStatus(sessionId: string, status: string): Promise<void> {
const session = await this.getSession(sessionId);
if (session) {
session.status = status;
await this.cacheSession(sessionId, session);
}
}
/**
* Remove session from cache
*/
async removeSession(sessionId: string): Promise<void> {
const key = this.keyPrefix + sessionId;
await this.redis.del(key);
this.logger.debug(`Removed session from cache: ${sessionId}`);
}
/**
* Check if session exists in cache
*/
async hasSession(sessionId: string): Promise<boolean> {
const key = this.keyPrefix + sessionId;
return (await this.redis.exists(key)) === 1;
}
/**
* Cache session message for relay
*/
async cacheMessage(sessionId: string, messageId: string, message: any, ttl?: number): Promise<void> {
const key = `${this.keyPrefix}${sessionId}:msg:${messageId}`;
await this.redis.setex(key, ttl || 300, JSON.stringify(message));
}
/**
* Get pending messages for a session
*/
async getPendingMessages(sessionId: string, partyId: string): Promise<any[]> {
const pattern = `${this.keyPrefix}${sessionId}:msg:*`;
const keys = await this.redis.keys(pattern);
const messages: any[] = [];
for (const key of keys) {
const data = await this.redis.get(key);
if (data) {
const message = JSON.parse(data);
// Filter messages for this party
if (!message.toParties || message.toParties.includes(partyId)) {
messages.push(message);
}
}
}
return messages;
}
/**
* Generic key-value operations
*/
async set(key: string, value: any, ttl?: number): Promise<void> {
const fullKey = this.keyPrefix + key;
if (ttl) {
await this.redis.setex(fullKey, ttl, JSON.stringify(value));
} else {
await this.redis.set(fullKey, JSON.stringify(value));
}
}
async get<T>(key: string): Promise<T | null> {
const fullKey = this.keyPrefix + key;
const data = await this.redis.get(fullKey);
return data ? JSON.parse(data) : null;
}
async delete(key: string): Promise<void> {
const fullKey = this.keyPrefix + key;
await this.redis.del(fullKey);
}
}