import redis from '../config/redis.js'; import { env } from '../config/env.js'; import { emitCacheEvent } from '../config/socket.js'; import logger from '../utils/logger.js'; import type { GetInfoResponse, CacheEntry } from '../types/index.js'; /** * Cache key prefix for Netflix content */ const CACHE_PREFIX = 'netflix:content:'; /** * Generate cache key from URL */ function getCacheKey(url: string): string { // Use URL hash or title ID as key const titleId = url.match(/\/title\/(\d+)/)?.[1] || url; return `${CACHE_PREFIX}${titleId}`; } /** * Cache Service for Redis operations * Handles caching with TTL support */ export class CacheService { /** * Get cached content by URL */ static async get(url: string): Promise { const key = getCacheKey(url); try { const cached = await redis.get(key); if (!cached) { logger.debug('Cache miss', { url }); return null; } logger.debug('Cache hit', { url }); const entry: CacheEntry = JSON.parse(cached); return entry.data; } catch (error) { logger.error('Cache get error', { url, error: error instanceof Error ? error.message : 'Unknown error', }); return null; } } /** * Set cache entry with TTL */ static async set(url: string, data: GetInfoResponse): Promise { const key = getCacheKey(url); const ttl = env.REDIS_TTL_SECONDS; const entry: CacheEntry = { data, cachedAt: Date.now(), ttl, }; try { await redis.setex(key, ttl, JSON.stringify(entry)); emitCacheEvent({ action: 'written', key, ttlSeconds: ttl, occurredAt: new Date().toISOString(), }); logger.debug('Cache set', { url, ttl }); } catch (error) { logger.error('Cache set error', { url, error: error instanceof Error ? error.message : 'Unknown error', }); } } /** * Delete cached content */ static async delete(url: string): Promise { const key = getCacheKey(url); try { await redis.del(key); emitCacheEvent({ action: 'deleted', key, occurredAt: new Date().toISOString(), }); logger.debug('Cache deleted', { url }); } catch (error) { logger.error('Cache delete error', { url, error: error instanceof Error ? error.message : 'Unknown error', }); } } /** * Check if cache exists */ static async exists(url: string): Promise { const key = getCacheKey(url); try { const result = await redis.exists(key); return result === 1; } catch (error) { logger.error('Cache exists check error', { url, error: error instanceof Error ? error.message : 'Unknown error', }); return false; } } /** * Get cache TTL remaining */ static async getTTL(url: string): Promise { const key = getCacheKey(url); try { return await redis.ttl(key); } catch (error) { logger.error('Cache TTL check error', { url, error: error instanceof Error ? error.message : 'Unknown error', }); return -1; } } /** * Clear all Netflix content cache */ static async clearAll(): Promise { try { const keys = await redis.keys(`${CACHE_PREFIX}*`); if (keys.length > 0) { await redis.del(...keys); emitCacheEvent({ action: 'cleared', count: keys.length, occurredAt: new Date().toISOString(), }); logger.info('Cache cleared', { count: keys.length }); } } catch (error) { logger.error('Cache clear error', { error: error instanceof Error ? error.message : 'Unknown error', }); } } } export default CacheService;