const NodeCache = require('node-cache'); const logger = require('../utils/logger'); // Initialize cache with default TTL of 5 minutes and check period of 2 minutes const cache = new NodeCache({ stdTTL: 300, checkperiod: 120 }); /** * Cache middleware factory * @param {number} ttlSeconds - Time to live in seconds for cached responses * @returns {Function} Express middleware function */ const getCacheMiddleware = (ttlSeconds) => (req, res, next) => { // Disable caching in test environment to avoid cross-test interference if (process.env.NODE_ENV === 'test') { return next(); } const key = req.originalUrl; const cachedResponse = cache.get(key); if (cachedResponse) { logger.info(`Cache hit for key: ${key}`); return res.json(cachedResponse); } logger.info(`Cache miss for key: ${key}`); // Store the original res.json method const originalJson = res.json.bind(res); // Override res.json to cache the response res.json = (body) => { cache.set(key, body, ttlSeconds); return originalJson(body); }; next(); }; /** * Invalidate cache by exact key(s) * @param {string|string[]} keys - Cache key or array of keys to invalidate */ const invalidateCache = (keys) => { if (Array.isArray(keys)) { keys.forEach(key => { logger.info(`Invalidating cache for key: ${key}`); cache.del(key); }); } else { logger.info(`Invalidating cache for key: ${keys}`); cache.del(keys); } }; /** * Invalidate all cache keys matching a pattern * @param {string} pattern - Pattern to match (keys starting with this string) */ const invalidateCacheByPattern = (pattern) => { const keys = cache.keys(); const keysToDelete = keys.filter(key => key.startsWith(pattern)); if (keysToDelete.length > 0) { logger.info(`Invalidating cache by pattern: ${pattern}, deleted ${keysToDelete.length} keys`); cache.del(keysToDelete); } else { logger.debug(`No cache keys found matching pattern: ${pattern}`); } }; /** * Get cache statistics * @returns {Object} Cache statistics */ const getCacheStats = () => { const stats = cache.getStats(); const keys = cache.keys(); return { keys: keys.length, hits: stats.hits, misses: stats.misses, hitRate: stats.hits > 0 ? (stats.hits / (stats.hits + stats.misses) * 100).toFixed(2) + '%' : '0%', ksize: stats.ksize, vsize: stats.vsize }; }; /** * Clear all cache */ const clearCache = () => { const keyCount = cache.keys().length; cache.flushAll(); logger.info(`Cleared all cache (${keyCount} keys)`); }; module.exports = { getCacheMiddleware, invalidateCache, invalidateCacheByPattern, getCacheStats, clearCache };