CoolFace
Apppublic

sanket3280/code-execution

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
cache.js147 linesDownload Raw Back to middleware
1/**2 * Cache Middleware3 * Provides route-level caching with Redis or in-memory fallback4 */5 6const redisClient = require('../config/redis');7const logger = require('../utils/logger');8 9// In-memory cache fallback10const memoryCache = new Map();11const cacheTimestamps = new Map();12 13/**14 * Cache middleware for routes15 * @param {string} keyPrefix - Prefix for cache key16 * @param {number} ttlSeconds - Time to live in seconds (default: 300 = 5 minutes)17 * @param {function} keyGenerator - Optional function to generate dynamic cache key from req18 */19const cache = (keyPrefix, ttlSeconds = 300, keyGenerator = null) => {20  return async (req, res, next) => {21    try {22      // Generate cache key23      let cacheKey;24      if (keyGenerator && typeof keyGenerator === 'function') {25        cacheKey = keyGenerator(req);26      } else {27        // Default: use query params and user ID if available28        const userId = req.user?.id || 'guest';29        const queryString = JSON.stringify(req.query);30        cacheKey = `${keyPrefix}:${userId}:${queryString}`;31      }32 33      // Try to get from cache (with timeout for Redis)34      let cached = null;35      let cacheSource = 'none';36      37      // Check memory cache first (fastest)38      const memoryCached = memoryCache.get(cacheKey);39      const timestamp = cacheTimestamps.get(cacheKey);40      if (memoryCached && timestamp && (Date.now() - timestamp < ttlSeconds * 1000)) {41        cached = memoryCached;42        cacheSource = 'memory';43      } else if (memoryCached) {44        // Expired - remove from memory45        memoryCache.delete(cacheKey);46        cacheTimestamps.delete(cacheKey);47      }48      49      // If not in memory, try Redis (with very short timeout)50      if (!cached) {51        try {52          const redisPromise = redisClient.get(cacheKey);53          const timeoutPromise = new Promise((_, reject) => 54            setTimeout(() => reject(new Error('Redis timeout')), 30) // 30ms timeout55          );56          const redisResult = await Promise.race([redisPromise, timeoutPromise]);57          if (redisResult) {58            cached = redisResult;59            cacheSource = 'redis';60            // Also store in memory for next time61            memoryCache.set(cacheKey, typeof cached === 'string' ? JSON.parse(cached) : cached);62            cacheTimestamps.set(cacheKey, Date.now());63          }64        } catch (err) {65          // Redis failed or timed out - continue without cache66        }67      }68      69      if (cached) {70        // Cache hit - return immediately with 304 or 20071        const cachedData = typeof cached === 'string' ? JSON.parse(cached) : cached;72        const etag = `"${Buffer.from(JSON.stringify(cachedData)).toString('base64').slice(0, 16)}"`;73        74        // Check if client has matching ETag75        const clientEtag = req.headers['if-none-match'];76        if (clientEtag === etag) {77          res.setHeader('X-Cache', 'HIT');78          res.setHeader('X-Cache-Source', cacheSource);79          return res.status(304).end(); // Not Modified80        }81        82        res.setHeader('X-Cache', 'HIT');83        res.setHeader('X-Cache-Source', cacheSource);84        res.setHeader('ETag', etag);85        res.setHeader('Cache-Control', `public, max-age=${ttlSeconds}`);86        return res.json(cachedData);87      }88 89      // Silent cache miss90      res.setHeader('X-Cache', 'MISS');91 92      // Store original res.json93      const originalJson = res.json.bind(res);94 95      // Override res.json to cache the response96      res.json = (body) => {97        const bodyStr = JSON.stringify(body);98        const etag = `"${Buffer.from(bodyStr).toString('base64').slice(0, 16)}"`;99        100        // Set ETag and Cache-Control headers101        res.setHeader('ETag', etag);102        res.setHeader('Cache-Control', `public, max-age=${ttlSeconds}`);103        104        // Cache in background (non-blocking)105        Promise.race([106          redisClient.set(cacheKey, bodyStr, 'EX', ttlSeconds),107          new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 100))108        ]).catch(err => {109          // Redis failed or timed out, use memory cache as fallback110          memoryCache.set(cacheKey, body);111          cacheTimestamps.set(cacheKey, Date.now());112        });113        114        // Send the response immediately (don't wait for cache)115        return originalJson(body);116      };117 118      next();119    } catch (error) {120      logger.error('Cache middleware error', error);121      // Continue without caching on error122      next();123    }124  };125};126 127/**128 * Invalidate cache by pattern129 * @param {string} pattern - Redis key pattern (e.g., 'problems:*')130 */131const invalidateCache = async (pattern) => {132  try {133    const keys = await redisClient.keys(pattern);134    if (keys.length > 0) {135      await redisClient.del(...keys);136      logger.debug(`Cache invalidated: ${keys.length} keys matching ${pattern}`);137    }138  } catch (error) {139    logger.error('Cache invalidation error', error);140  }141};142 143module.exports = {144  cache,145  invalidateCache146};147