add changes

This commit is contained in:
AD2025
2025-12-26 23:56:32 +02:00
parent 410c3d725f
commit e7d26bc981
127 changed files with 36162 additions and 0 deletions

318
config/redis.js Normal file
View File

@@ -0,0 +1,318 @@
const Redis = require('ioredis');
const logger = require('./logger');
/**
* Redis Connection Configuration
* Supports both single instance and cluster modes
*/
const redisConfig = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT) || 6379,
password: process.env.REDIS_PASSWORD || undefined,
db: parseInt(process.env.REDIS_DB) || 0,
retryStrategy: (times) => {
// Stop retrying after 3 attempts in development
if (process.env.NODE_ENV === 'development' && times > 3) {
logger.info('Redis unavailable - caching disabled (optional feature)');
return null; // Stop retrying
}
const delay = Math.min(times * 50, 2000);
return delay;
},
maxRetriesPerRequest: 3,
enableReadyCheck: true,
enableOfflineQueue: true,
lazyConnect: true, // Don't connect immediately
connectTimeout: 5000, // Reduced timeout
keepAlive: 30000,
family: 4, // IPv4
// Connection pool settings
minReconnectInterval: 100,
maxReconnectInterval: 3000,
// Reduce logging noise
showFriendlyErrorStack: process.env.NODE_ENV !== 'development'
};
// Create Redis client
let redisClient = null;
let isConnected = false;
try {
redisClient = new Redis(redisConfig);
// Attempt initial connection
redisClient.connect().catch(() => {
// Silently fail if Redis is not available in development
if (process.env.NODE_ENV === 'development') {
logger.info('Redis not available - continuing without cache (optional)');
}
});
// Connection events
redisClient.on('connect', () => {
logger.info('Redis client connecting...');
});
redisClient.on('ready', () => {
isConnected = true;
logger.info('Redis client connected and ready');
});
redisClient.on('error', (err) => {
isConnected = false;
// Only log errors in production or first error
if (process.env.NODE_ENV === 'production' || !errorLogged) {
logger.error('Redis client error:', err.message || err);
errorLogged = true;
}
});
redisClient.on('close', () => {
if (isConnected) {
isConnected = false;
logger.warn('Redis client connection closed');
}
});
redisClient.on('reconnecting', () => {
// Only log once
if (isConnected === false) {
logger.info('Redis client reconnecting...');
}
});
redisClient.on('end', () => {
if (isConnected) {
isConnected = false;
logger.info('Redis connection ended');
}
});
} catch (error) {
logger.error('Failed to create Redis client:', error);
}
// Track if error has been logged
let errorLogged = false;
/**
* Check if Redis is connected
*/
const isRedisConnected = () => {
return isConnected && redisClient && redisClient.status === 'ready';
};
/**
* Get Redis client
*/
const getRedisClient = () => {
if (!isRedisConnected()) {
logger.warn('Redis client not connected');
return null;
}
return redisClient;
};
/**
* Close Redis connection gracefully
*/
const closeRedis = async () => {
if (redisClient) {
await redisClient.quit();
logger.info('Redis connection closed');
}
};
/**
* Cache helper functions
*/
/**
* Get cached data
* @param {string} key - Cache key
* @returns {Promise<any>} - Parsed JSON data or null
*/
const getCache = async (key) => {
try {
if (!isRedisConnected()) {
logger.warn('Redis not connected, cache miss');
return null;
}
const data = await redisClient.get(key);
if (!data) return null;
logger.debug(`Cache hit: ${key}`);
return JSON.parse(data);
} catch (error) {
logger.error(`Cache get error for key ${key}:`, error);
return null;
}
};
/**
* Set cached data
* @param {string} key - Cache key
* @param {any} value - Data to cache
* @param {number} ttl - Time to live in seconds (default: 300 = 5 minutes)
* @returns {Promise<boolean>} - Success status
*/
const setCache = async (key, value, ttl = 300) => {
try {
if (!isRedisConnected()) {
logger.warn('Redis not connected, skipping cache set');
return false;
}
const serialized = JSON.stringify(value);
await redisClient.setex(key, ttl, serialized);
logger.debug(`Cache set: ${key} (TTL: ${ttl}s)`);
return true;
} catch (error) {
logger.error(`Cache set error for key ${key}:`, error);
return false;
}
};
/**
* Delete cached data
* @param {string} key - Cache key or pattern
* @returns {Promise<boolean>} - Success status
*/
const deleteCache = async (key) => {
try {
if (!isRedisConnected()) {
return false;
}
// Support pattern deletion (e.g., "user:*")
if (key.includes('*')) {
const keys = await redisClient.keys(key);
if (keys.length > 0) {
await redisClient.del(...keys);
logger.debug(`Cache deleted: ${keys.length} keys matching ${key}`);
}
} else {
await redisClient.del(key);
logger.debug(`Cache deleted: ${key}`);
}
return true;
} catch (error) {
logger.error(`Cache delete error for key ${key}:`, error);
return false;
}
};
/**
* Clear all cache
* @returns {Promise<boolean>} - Success status
*/
const clearCache = async () => {
try {
if (!isRedisConnected()) {
return false;
}
await redisClient.flushdb();
logger.info('All cache cleared');
return true;
} catch (error) {
logger.error('Cache clear error:', error);
return false;
}
};
/**
* Get multiple keys at once
* @param {string[]} keys - Array of cache keys
* @returns {Promise<object>} - Object with key-value pairs
*/
const getCacheMultiple = async (keys) => {
try {
if (!isRedisConnected() || !keys || keys.length === 0) {
return {};
}
const values = await redisClient.mget(...keys);
const result = {};
keys.forEach((key, index) => {
if (values[index]) {
try {
result[key] = JSON.parse(values[index]);
} catch (err) {
result[key] = null;
}
} else {
result[key] = null;
}
});
return result;
} catch (error) {
logger.error('Cache mget error:', error);
return {};
}
};
/**
* Increment a counter
* @param {string} key - Cache key
* @param {number} increment - Amount to increment (default: 1)
* @param {number} ttl - Time to live in seconds (optional)
* @returns {Promise<number>} - New value
*/
const incrementCache = async (key, increment = 1, ttl = null) => {
try {
if (!isRedisConnected()) {
return 0;
}
const newValue = await redisClient.incrby(key, increment);
if (ttl) {
await redisClient.expire(key, ttl);
}
return newValue;
} catch (error) {
logger.error(`Cache increment error for key ${key}:`, error);
return 0;
}
};
/**
* Check if key exists
* @param {string} key - Cache key
* @returns {Promise<boolean>} - Exists status
*/
const cacheExists = async (key) => {
try {
if (!isRedisConnected()) {
return false;
}
const exists = await redisClient.exists(key);
return exists === 1;
} catch (error) {
logger.error(`Cache exists error for key ${key}:`, error);
return false;
}
};
module.exports = {
redisClient,
isRedisConnected,
getRedisClient,
closeRedis,
getCache,
setCache,
deleteCache,
clearCache,
getCacheMultiple,
incrementCache,
cacheExists
};