import Redis from 'ioredis'; const REDIS_HOST = process.env.REDIS_HOST || 'localhost'; const REDIS_PORT = parseInt(process.env.REDIS_PORT || '6379', 10); const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined; const REDIS_DB = parseInt(process.env.REDIS_DB || '0', 10); export const INDEX_NAME = 'hasher'; // Create Redis client with connection pooling export const redisClient = new Redis({ host: REDIS_HOST, port: REDIS_PORT, password: REDIS_PASSWORD, db: REDIS_DB, retryStrategy: (times) => { const delay = Math.min(times * 50, 2000); return delay; }, maxRetriesPerRequest: 3, enableReadyCheck: true, lazyConnect: false, }); // Handle connection errors redisClient.on('error', (err) => { console.error('Redis Client Error:', err); }); redisClient.on('connect', () => { console.log('Redis connected successfully'); }); /** * Redis Keys Structure: * * 1. Hash documents: hash:plaintext:{plaintext} = JSON string * - Stores all hash data for a plaintext * * 2. Hash indexes: hash:index:{algorithm}:{hash} = plaintext * - Allows reverse lookup from hash to plaintext * - One key per algorithm (md5, sha1, sha256, sha512) * * 3. Statistics: hash:stats = Hash {count, size} * - count: total number of unique plaintexts * - size: approximate total size in bytes */ export interface HashDocument { plaintext: string; md5: string; sha1: string; sha256: string; sha512: string; created_at: string; } /** * Store a hash document in Redis */ export async function storeHashDocument(doc: HashDocument): Promise { const pipeline = redisClient.pipeline(); // Store main document const key = `hash:plaintext:${doc.plaintext}`; pipeline.set(key, JSON.stringify(doc)); // Create indexes for each hash type pipeline.set(`hash:index:md5:${doc.md5}`, doc.plaintext); pipeline.set(`hash:index:sha1:${doc.sha1}`, doc.plaintext); pipeline.set(`hash:index:sha256:${doc.sha256}`, doc.plaintext); pipeline.set(`hash:index:sha512:${doc.sha512}`, doc.plaintext); // Update statistics pipeline.hincrby('hash:stats', 'count', 1); pipeline.hincrby('hash:stats', 'size', JSON.stringify(doc).length); await pipeline.exec(); } /** * Find a hash document by plaintext */ export async function findByPlaintext(plaintext: string): Promise { const key = `hash:plaintext:${plaintext}`; const data = await redisClient.get(key); if (!data) return null; return JSON.parse(data) as HashDocument; } /** * Find a hash document by any hash value */ export async function findByHash(algorithm: string, hash: string): Promise { const indexKey = `hash:index:${algorithm}:${hash}`; const plaintext = await redisClient.get(indexKey); if (!plaintext) return null; return findByPlaintext(plaintext); } /** * Check if plaintext or any of its hashes exist */ export async function checkExistence(plaintext: string, hashes: { md5: string; sha1: string; sha256: string; sha512: string; }): Promise { const pipeline = redisClient.pipeline(); pipeline.exists(`hash:plaintext:${plaintext}`); pipeline.exists(`hash:index:md5:${hashes.md5}`); pipeline.exists(`hash:index:sha1:${hashes.sha1}`); pipeline.exists(`hash:index:sha256:${hashes.sha256}`); pipeline.exists(`hash:index:sha512:${hashes.sha512}`); const results = await pipeline.exec(); if (!results) return false; // Check if any key exists return results.some(([err, value]) => !err && value === 1); } /** * Get index statistics */ export async function getStats(): Promise<{ count: number; size: number }> { const stats = await redisClient.hgetall('hash:stats'); return { count: parseInt(stats.count || '0', 10), size: parseInt(stats.size || '0', 10) }; } /** * Initialize Redis (compatibility function, Redis doesn't need explicit initialization) */ export async function initializeRedis(): Promise { // Check connection await redisClient.ping(); console.log('Redis initialized successfully'); } /** * Get Redis info for health check */ export async function getRedisInfo(): Promise<{ connected: boolean; version: string; usedMemory: number; dbSize: number; }> { const info = await redisClient.info('server'); const memory = await redisClient.info('memory'); const dbSize = await redisClient.dbsize(); // Parse Redis info string const parseInfo = (infoStr: string, key: string): string => { const match = infoStr.match(new RegExp(`${key}:(.+)`)); return match ? match[1].trim() : 'unknown'; }; return { connected: redisClient.status === 'ready', version: parseInfo(info, 'redis_version'), usedMemory: parseInt(parseInfo(memory, 'used_memory'), 10) || 0, dbSize }; } export { REDIS_HOST, REDIS_PORT };