Spaces:
Paused
Paused
Download Services/cacheService.js from imkrish/campverse-backend: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/spaces/imkrish/campverse-backend/resolve/main/Services/cacheService.js
- Command line
-
hf download hf://spaces/imkrish/campverse-backend/Services/cacheService.js
-
curl -L -o cacheService.js https://huggingface.co/spaces/imkrish/campverse-backend/resolve/main/Services/cacheService.js
10.4 kB
| /** | |
| * Redis Caching Service | |
| * Provides centralized caching functionality with TTL and invalidation | |
| */ | |
| const { createClient } = require('redis'); | |
| const { logger } = require('../Middleware/errorHandler'); | |
| class CacheService { | |
| constructor() { | |
| this.client = null; | |
| this.isConnected = false; | |
| this.defaultTTL = 300; // 5 minutes | |
| this.keyPrefix = 'campverse:'; | |
| } | |
| /** | |
| * Initialize Redis connection | |
| */ | |
| async connect() { | |
| try { | |
| this.client = createClient({ | |
| url: process.env.REDIS_URL || 'redis://127.0.0.1:6379', | |
| retry_strategy: (options) => { | |
| if (options.error && options.error.code === 'ECONNREFUSED') { | |
| logger.error('Redis connection refused'); | |
| return new Error('Redis connection refused'); | |
| } | |
| if (options.total_retry_time > 1000 * 60 * 60) { | |
| logger.error('Redis retry time exhausted'); | |
| return new Error('Retry time exhausted'); | |
| } | |
| if (options.attempt > 10) { | |
| logger.error('Redis max retry attempts reached'); | |
| return undefined; | |
| } | |
| return Math.min(options.attempt * 100, 3000); | |
| } | |
| }); | |
| this.client.on('error', (err) => { | |
| logger.error('Redis Client Error:', err); | |
| this.isConnected = false; | |
| }); | |
| this.client.on('connect', () => { | |
| logger.info('Redis connected'); | |
| this.isConnected = true; | |
| }); | |
| this.client.on('disconnect', () => { | |
| logger.warn('Redis disconnected'); | |
| this.isConnected = false; | |
| }); | |
| await this.client.connect(); | |
| } catch (error) { | |
| logger.error('Failed to connect to Redis:', error); | |
| this.isConnected = false; | |
| } | |
| } | |
| /** | |
| * Generate cache key with prefix | |
| */ | |
| _generateKey(key) { | |
| return `${this.keyPrefix}${key}`; | |
| } | |
| /** | |
| * Get value from cache | |
| */ | |
| async get(key) { | |
| if (!this.isConnected) { | |
| logger.warn(`Cache not connected, returning null for key: ${key}`); | |
| return null; | |
| } | |
| try { | |
| const cacheKey = this._generateKey(key); | |
| const value = await this.client.get(cacheKey); | |
| if (value) { | |
| logger.debug(`Cache hit for key: ${key}`); | |
| return JSON.parse(value); | |
| } | |
| logger.debug(`Cache miss for key: ${key}`); | |
| return null; | |
| } catch (error) { | |
| logger.error(`Cache get error for key ${key}:`, error); | |
| // Don't throw, just return null to allow fallback | |
| return null; | |
| } | |
| } | |
| /** | |
| * Set value in cache with TTL | |
| */ | |
| async set(key, value, ttl = this.defaultTTL) { | |
| if (!this.isConnected) { | |
| logger.warn(`Cache not connected, cannot set key: ${key}`); | |
| return false; | |
| } | |
| try { | |
| const cacheKey = this._generateKey(key); | |
| const serializedValue = JSON.stringify(value); | |
| await this.client.setEx(cacheKey, ttl, serializedValue); | |
| logger.debug(`Cache set for key: ${key}, TTL: ${ttl}s`); | |
| return true; | |
| } catch (error) { | |
| logger.error(`Cache set error for key ${key}:`, error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Delete value from cache | |
| */ | |
| async del(key) { | |
| if (!this.isConnected) return false; | |
| try { | |
| const cacheKey = this._generateKey(key); | |
| const result = await this.client.del(cacheKey); | |
| logger.debug(`Cache delete for key: ${key}`); | |
| return result > 0; | |
| } catch (error) { | |
| logger.error(`Cache delete error for key ${key}:`, error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Delete multiple keys by pattern | |
| */ | |
| async delPattern(pattern) { | |
| if (!this.isConnected) return false; | |
| try { | |
| const searchPattern = this._generateKey(pattern); | |
| const keys = await this.client.keys(searchPattern); | |
| if (keys.length > 0) { | |
| await this.client.del(keys); | |
| logger.debug(`Cache pattern delete: ${pattern}, ${keys.length} keys deleted`); | |
| } | |
| return true; | |
| } catch (error) { | |
| logger.error(`Cache pattern delete error for pattern ${pattern}:`, error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Check if key exists | |
| */ | |
| async exists(key) { | |
| if (!this.isConnected) return false; | |
| try { | |
| const cacheKey = this._generateKey(key); | |
| const result = await this.client.exists(cacheKey); | |
| return result === 1; | |
| } catch (error) { | |
| logger.error(`Cache exists error for key ${key}:`, error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Increment counter | |
| */ | |
| async incr(key, ttl = this.defaultTTL) { | |
| if (!this.isConnected) return null; | |
| try { | |
| const cacheKey = this._generateKey(key); | |
| const value = await this.client.incr(cacheKey); | |
| if (value === 1) { | |
| // Set TTL only for new keys | |
| await this.client.expire(cacheKey, ttl); | |
| } | |
| return value; | |
| } catch (error) { | |
| logger.error(`Cache incr error for key ${key}:`, error); | |
| return null; | |
| } | |
| } | |
| /** | |
| * Get or set pattern - fetch from cache or execute function and cache result | |
| */ | |
| async getOrSet(key, fetchFunction, ttl = this.defaultTTL) { | |
| // Try to get from cache first | |
| const cachedValue = await this.get(key); | |
| if (cachedValue !== null) { | |
| return cachedValue; | |
| } | |
| try { | |
| // Execute function to get fresh data | |
| const freshValue = await fetchFunction(); | |
| // Cache the result | |
| await this.set(key, freshValue, ttl); | |
| return freshValue; | |
| } catch (error) { | |
| logger.error(`Cache getOrSet error for key ${key}:`, error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Cache invalidation patterns | |
| */ | |
| async invalidateUser(userId) { | |
| await this.delPattern(`user:${userId}:*`); | |
| await this.delPattern(`dashboard:user:${userId}`); | |
| } | |
| async invalidateInstitution(institutionId) { | |
| await this.delPattern(`institution:${institutionId}:*`); | |
| await this.delPattern(`dashboard:institution:${institutionId}`); | |
| await this.delPattern('institutions:*'); | |
| } | |
| async invalidateEvent(eventId) { | |
| await this.delPattern(`event:${eventId}:*`); | |
| await this.delPattern('events:*'); | |
| await this.delPattern('recommendations:*'); | |
| } | |
| async invalidateGlobal() { | |
| await this.delPattern('events:*'); | |
| await this.delPattern('institutions:*'); | |
| await this.delPattern('analytics:*'); | |
| } | |
| /** | |
| * Invalidate verifier-related cache (pending verifications, etc.) | |
| */ | |
| async invalidateVerifierCache() { | |
| await this.delPattern('verifier:*'); | |
| await this.delPattern('pending:*'); | |
| await this.delPattern('verification:*'); | |
| await this.delPattern('institutions:pending:*'); | |
| await this.delPattern('certificates:pending:*'); | |
| } | |
| /** | |
| * Invalidate admin-related cache | |
| */ | |
| async invalidateAdminCache() { | |
| await this.delPattern('admin:*'); | |
| await this.delPattern('platform:*'); | |
| await this.delPattern('users:*'); | |
| await this.delPattern('institutions:*'); | |
| await this.delPattern('analytics:*'); | |
| } | |
| /** | |
| * Invalidate certificate verification cache | |
| */ | |
| async invalidateCertificateVerification(eventId) { | |
| await this.delPattern(`event:${eventId}:*`); | |
| await this.delPattern('certificates:pending:*'); | |
| await this.delPattern('verification:certificates:*'); | |
| await this.delPattern('verifier:*'); | |
| } | |
| /** | |
| * Invalidate institution verification cache | |
| */ | |
| async invalidateInstitutionVerification(institutionId) { | |
| await this.delPattern(`institution:${institutionId}:*`); | |
| await this.delPattern('institutions:pending:*'); | |
| await this.delPattern('verification:institutions:*'); | |
| await this.delPattern('verifier:*'); | |
| await this.delPattern('institutions:*'); | |
| } | |
| /** | |
| * Health check | |
| */ | |
| async healthCheck() { | |
| if (!this.isConnected) return false; | |
| try { | |
| await this.client.ping(); | |
| return true; | |
| } catch (error) { | |
| logger.error('Redis health check failed:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Get cache statistics | |
| */ | |
| async getStats() { | |
| if (!this.isConnected) return null; | |
| try { | |
| const info = await this.client.info('memory'); | |
| const keyspace = await this.client.info('keyspace'); | |
| return { | |
| connected: this.isConnected, | |
| memory: info, | |
| keyspace, | |
| timestamp: new Date().toISOString() | |
| }; | |
| } catch (error) { | |
| logger.error('Failed to get cache stats:', error); | |
| return null; | |
| } | |
| } | |
| /** | |
| * Flush all cache (use with caution) | |
| */ | |
| async flushAll() { | |
| if (!this.isConnected) return false; | |
| try { | |
| await this.client.flushAll(); | |
| logger.warn('Cache flushed - all keys deleted'); | |
| return true; | |
| } catch (error) { | |
| logger.error('Cache flush error:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Close connection | |
| */ | |
| async disconnect() { | |
| if (this.client) { | |
| await this.client.disconnect(); | |
| this.isConnected = false; | |
| logger.info('Redis disconnected'); | |
| } | |
| } | |
| } | |
| // Cache key generators for consistency | |
| const CacheKeys = { | |
| user: (userId) => `user:${userId}`, | |
| userDashboard: (userId) => `dashboard:user:${userId}`, | |
| userEvents: (userId) => `user:${userId}:events`, | |
| userCertificates: (userId) => `user:${userId}:certificates`, | |
| institution: (institutionId) => `institution:${institutionId}`, | |
| institutionDashboard: (institutionId) => `dashboard:institution:${institutionId}`, | |
| institutionEvents: (institutionId) => `institution:${institutionId}:events`, | |
| institutionUsers: (institutionId) => `institution:${institutionId}:users`, | |
| event: (eventId) => `event:${eventId}`, | |
| eventParticipants: (eventId) => `event:${eventId}:participants`, | |
| eventAnalytics: (eventId) => `event:${eventId}:analytics`, | |
| events: (filters = '') => `events:${filters}`, | |
| institutions: (filters = '') => `institutions:${filters}`, | |
| recommendations: (userId) => `recommendations:${userId}`, | |
| analytics: (type, id = '') => `analytics:${type}:${id}`, | |
| rateLimit: (ip, endpoint) => `ratelimit:${ip}:${endpoint}`, | |
| session: (sessionId) => `session:${sessionId}`, | |
| }; | |
| // TTL constants (in seconds) | |
| const CacheTTL = { | |
| SHORT: 60, // 1 minute | |
| MEDIUM: 300, // 5 minutes | |
| LONG: 1800, // 30 minutes | |
| VERY_LONG: 3600, // 1 hour | |
| DAY: 86400, // 24 hours | |
| }; | |
| // Create singleton instance | |
| const cacheService = new CacheService(); | |
| module.exports = { | |
| cacheService, | |
| CacheKeys, | |
| CacheTTL | |
| }; |