/** * Cache Service for Award Progress * * Provides in-memory caching for award progress calculations to avoid * expensive database aggregations on every request. * * Cache TTL: 5 minutes (balances freshness with performance) * * Usage: * - Check cache before calculating award progress * - Invalidate cache when QSOs are synced/updated * - Automatic expiry after TTL */ const awardCache = new Map(); const CACHE_TTL = 5 * 60 * 1000; // 5 minutes /** * Get cached award progress if available and not expired * @param {number} userId - User ID * @param {string} awardId - Award ID * @returns {object|null} Cached progress data or null if not found/expired */ export function getCachedAwardProgress(userId, awardId) { const key = `${userId}:${awardId}`; const cached = awardCache.get(key); if (!cached) { return null; } // Check if cache has expired const age = Date.now() - cached.timestamp; if (age > CACHE_TTL) { awardCache.delete(key); return null; } return cached.data; } /** * Set award progress in cache * @param {number} userId - User ID * @param {string} awardId - Award ID * @param {object} data - Award progress data to cache */ export function setCachedAwardProgress(userId, awardId, data) { const key = `${userId}:${awardId}`; awardCache.set(key, { data, timestamp: Date.now() }); } /** * Invalidate all cached awards for a specific user * Call this after syncing or updating QSOs * @param {number} userId - User ID */ export function invalidateUserCache(userId) { const prefix = `${userId}:`; let deleted = 0; for (const [key] of awardCache) { if (key.startsWith(prefix)) { awardCache.delete(key); deleted++; } } return deleted; } /** * Clear all cached awards (use sparingly) *主要用于测试或紧急情况 */ export function clearAllCache() { const size = awardCache.size; awardCache.clear(); return size; } /** * Get cache statistics (for monitoring/debugging) * @returns {object} Cache stats */ export function getCacheStats() { const now = Date.now(); let expired = 0; let valid = 0; for (const [, value] of awardCache) { const age = now - value.timestamp; if (age > CACHE_TTL) { expired++; } else { valid++; } } return { total: awardCache.size, valid, expired, ttl: CACHE_TTL }; } /** * Clean up expired cache entries (maintenance function) * Can be called periodically to free memory * @returns {number} Number of entries cleaned up */ export function cleanupExpiredCache() { const now = Date.now(); let cleaned = 0; for (const [key, value] of awardCache) { const age = now - value.timestamp; if (age > CACHE_TTL) { awardCache.delete(key); cleaned++; } } return cleaned; }