Files
SkyArtShop/backend/middleware/cache.js
Local Server 1919f6f8bb updateweb
2026-01-01 22:24:30 -06:00

144 lines
3.3 KiB
JavaScript

/**
* In-Memory Cache Middleware
* Caches API responses to reduce database load
*/
const logger = require("../config/logger");
class CacheManager {
constructor(defaultTTL = 300000) {
// 5 minutes default
this.cache = new Map();
this.defaultTTL = defaultTTL;
}
set(key, value, ttl = this.defaultTTL) {
const expiresAt = Date.now() + ttl;
this.cache.set(key, { value, expiresAt });
logger.debug(`Cache set: ${key} (TTL: ${ttl}ms)`);
}
get(key) {
const cached = this.cache.get(key);
if (!cached) return null;
if (Date.now() > cached.expiresAt) {
this.cache.delete(key);
logger.debug(`Cache expired: ${key}`);
return null;
}
logger.debug(`Cache hit: ${key}`);
return cached.value;
}
delete(key) {
const deleted = this.cache.delete(key);
if (deleted) logger.debug(`Cache invalidated: ${key}`);
return deleted;
}
deletePattern(pattern) {
let count = 0;
for (const key of this.cache.keys()) {
if (key.includes(pattern)) {
this.cache.delete(key);
count++;
}
}
if (count > 0)
logger.debug(`Cache pattern invalidated: ${pattern} (${count} keys)`);
return count;
}
clear() {
const size = this.cache.size;
this.cache.clear();
logger.info(`Cache cleared (${size} keys)`);
}
size() {
return this.cache.size;
}
// Clean up expired entries
cleanup() {
const now = Date.now();
let cleaned = 0;
for (const [key, { expiresAt }] of this.cache.entries()) {
if (now > expiresAt) {
this.cache.delete(key);
cleaned++;
}
}
if (cleaned > 0)
logger.debug(`Cache cleanup: removed ${cleaned} expired entries`);
return cleaned;
}
}
// Global cache instance
const cache = new CacheManager();
// Cleanup interval reference (for graceful shutdown)
let cleanupInterval = null;
// Start automatic cleanup (optional, call from server startup)
const startCleanup = () => {
if (!cleanupInterval) {
cleanupInterval = setInterval(() => cache.cleanup(), 300000); // 5 minutes
logger.debug("Cache cleanup scheduler started");
}
};
// Stop automatic cleanup (for graceful shutdown)
const stopCleanup = () => {
if (cleanupInterval) {
clearInterval(cleanupInterval);
cleanupInterval = null;
logger.debug("Cache cleanup scheduler stopped");
}
};
/**
* Cache middleware factory
* @param {number} ttl - Time to live in milliseconds
* @param {function} keyGenerator - Function to generate cache key from req
*/
const cacheMiddleware = (ttl = 300000, keyGenerator = null) => {
return (req, res, next) => {
// Skip cache for authenticated requests
if (req.session && req.session.userId) {
return next();
}
const key = keyGenerator
? keyGenerator(req)
: `${req.method}:${req.originalUrl}`;
const cachedResponse = cache.get(key);
if (cachedResponse) {
res.setHeader("X-Cache", "HIT");
return res.json(cachedResponse);
}
// Store original json method
const originalJson = res.json.bind(res);
// Override json method to cache response
res.json = function (data) {
cache.set(key, data, ttl);
res.setHeader("X-Cache", "MISS");
return originalJson(data);
};
next();
};
};
module.exports = {
cache,
cacheMiddleware,
startCleanup,
stopCleanup,
};