add changes
This commit is contained in:
267
middleware/cache.js
Normal file
267
middleware/cache.js
Normal file
@@ -0,0 +1,267 @@
|
||||
const { getCache, setCache, deleteCache } = require('../config/redis');
|
||||
const logger = require('../config/logger');
|
||||
|
||||
/**
|
||||
* Cache middleware for GET requests
|
||||
* @param {number} ttl - Time to live in seconds (default: 300 = 5 minutes)
|
||||
* @param {function} keyGenerator - Function to generate cache key from req
|
||||
*/
|
||||
const cacheMiddleware = (ttl = 300, keyGenerator = null) => {
|
||||
return async (req, res, next) => {
|
||||
// Only cache GET requests
|
||||
if (req.method !== 'GET') {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate cache key
|
||||
const cacheKey = keyGenerator
|
||||
? keyGenerator(req)
|
||||
: `cache:${req.originalUrl}`;
|
||||
|
||||
// Try to get from cache
|
||||
const cachedData = await getCache(cacheKey);
|
||||
|
||||
if (cachedData) {
|
||||
logger.debug(`Cache hit for: ${cacheKey}`);
|
||||
return res.status(200).json(cachedData);
|
||||
}
|
||||
|
||||
// Cache miss - store original json method
|
||||
const originalJson = res.json.bind(res);
|
||||
|
||||
// Override json method to cache response
|
||||
res.json = function(data) {
|
||||
// Only cache successful responses
|
||||
if (res.statusCode === 200) {
|
||||
setCache(cacheKey, data, ttl).catch(err => {
|
||||
logger.error('Failed to cache response:', err);
|
||||
});
|
||||
}
|
||||
|
||||
// Call original json method
|
||||
return originalJson(data);
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.error('Cache middleware error:', error);
|
||||
next(); // Continue even if cache fails
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache categories (rarely change)
|
||||
*/
|
||||
const cacheCategories = cacheMiddleware(3600, (req) => {
|
||||
// Cache for 1 hour
|
||||
return 'cache:categories:list';
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache single category
|
||||
*/
|
||||
const cacheSingleCategory = cacheMiddleware(3600, (req) => {
|
||||
return `cache:category:${req.params.id}`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache guest settings (rarely change)
|
||||
*/
|
||||
const cacheGuestSettings = cacheMiddleware(1800, (req) => {
|
||||
// Cache for 30 minutes
|
||||
return 'cache:guest:settings';
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache system statistics (update frequently)
|
||||
*/
|
||||
const cacheStatistics = cacheMiddleware(300, (req) => {
|
||||
// Cache for 5 minutes
|
||||
return 'cache:admin:statistics';
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache guest analytics
|
||||
*/
|
||||
const cacheGuestAnalytics = cacheMiddleware(600, (req) => {
|
||||
// Cache for 10 minutes
|
||||
return 'cache:admin:guest-analytics';
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache user dashboard
|
||||
*/
|
||||
const cacheUserDashboard = cacheMiddleware(300, (req) => {
|
||||
// Cache for 5 minutes
|
||||
const userId = req.params.userId || req.user?.id;
|
||||
return `cache:user:${userId}:dashboard`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache questions list (with filters)
|
||||
*/
|
||||
const cacheQuestions = cacheMiddleware(600, (req) => {
|
||||
// Cache for 10 minutes
|
||||
const { categoryId, difficulty, questionType, visibility } = req.query;
|
||||
const filters = [categoryId, difficulty, questionType, visibility]
|
||||
.filter(Boolean)
|
||||
.join(':');
|
||||
return `cache:questions:${filters || 'all'}`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache single question
|
||||
*/
|
||||
const cacheSingleQuestion = cacheMiddleware(1800, (req) => {
|
||||
return `cache:question:${req.params.id}`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache user bookmarks
|
||||
*/
|
||||
const cacheUserBookmarks = cacheMiddleware(300, (req) => {
|
||||
const userId = req.params.userId || req.user?.id;
|
||||
return `cache:user:${userId}:bookmarks`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache user history
|
||||
*/
|
||||
const cacheUserHistory = cacheMiddleware(300, (req) => {
|
||||
const userId = req.params.userId || req.user?.id;
|
||||
const page = req.query.page || 1;
|
||||
return `cache:user:${userId}:history:page:${page}`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Invalidate cache patterns
|
||||
*/
|
||||
const invalidateCache = {
|
||||
/**
|
||||
* Invalidate user-related cache
|
||||
*/
|
||||
user: async (userId) => {
|
||||
await deleteCache(`cache:user:${userId}:*`);
|
||||
logger.debug(`Invalidated cache for user ${userId}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate category cache
|
||||
*/
|
||||
category: async (categoryId = null) => {
|
||||
if (categoryId) {
|
||||
await deleteCache(`cache:category:${categoryId}`);
|
||||
}
|
||||
await deleteCache('cache:categories:*');
|
||||
logger.debug('Invalidated category cache');
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate question cache
|
||||
*/
|
||||
question: async (questionId = null) => {
|
||||
if (questionId) {
|
||||
await deleteCache(`cache:question:${questionId}`);
|
||||
}
|
||||
await deleteCache('cache:questions:*');
|
||||
logger.debug('Invalidated question cache');
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate statistics cache
|
||||
*/
|
||||
statistics: async () => {
|
||||
await deleteCache('cache:admin:statistics');
|
||||
await deleteCache('cache:admin:guest-analytics');
|
||||
logger.debug('Invalidated statistics cache');
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate guest settings cache
|
||||
*/
|
||||
guestSettings: async () => {
|
||||
await deleteCache('cache:guest:settings');
|
||||
logger.debug('Invalidated guest settings cache');
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate all quiz-related cache
|
||||
*/
|
||||
quiz: async (userId = null, guestId = null) => {
|
||||
if (userId) {
|
||||
await deleteCache(`cache:user:${userId}:*`);
|
||||
}
|
||||
if (guestId) {
|
||||
await deleteCache(`cache:guest:${guestId}:*`);
|
||||
}
|
||||
await invalidateCache.statistics();
|
||||
logger.debug('Invalidated quiz cache');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware to invalidate cache after mutations
|
||||
*/
|
||||
const invalidateCacheMiddleware = (pattern) => {
|
||||
return async (req, res, next) => {
|
||||
// Store original json method
|
||||
const originalJson = res.json.bind(res);
|
||||
|
||||
// Override json method
|
||||
res.json = async function(data) {
|
||||
// Only invalidate on successful mutations
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
try {
|
||||
if (typeof pattern === 'function') {
|
||||
await pattern(req);
|
||||
} else {
|
||||
await deleteCache(pattern);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Cache invalidation error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Call original json method
|
||||
return originalJson(data);
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache warming - preload frequently accessed data
|
||||
*/
|
||||
const warmCache = async () => {
|
||||
try {
|
||||
logger.info('Warming cache...');
|
||||
|
||||
// This would typically fetch and cache common data
|
||||
// For now, we'll just log the intent
|
||||
// In a real scenario, you'd fetch categories, popular questions, etc.
|
||||
|
||||
logger.info('Cache warming complete');
|
||||
} catch (error) {
|
||||
logger.error('Cache warming error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
cacheMiddleware,
|
||||
cacheCategories,
|
||||
cacheSingleCategory,
|
||||
cacheGuestSettings,
|
||||
cacheStatistics,
|
||||
cacheGuestAnalytics,
|
||||
cacheUserDashboard,
|
||||
cacheQuestions,
|
||||
cacheSingleQuestion,
|
||||
cacheUserBookmarks,
|
||||
cacheUserHistory,
|
||||
invalidateCache,
|
||||
invalidateCacheMiddleware,
|
||||
warmCache
|
||||
};
|
||||
Reference in New Issue
Block a user