214 lines
6.0 KiB
JavaScript
214 lines
6.0 KiB
JavaScript
import Redis from 'ioredis';
|
|
import { TimeManager } from '../utils/time.utils.js';
|
|
import dotenv from 'dotenv';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
// Get directory name in ES modules
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// Load environment variables again (redundant but safe)
|
|
const envPath = path.resolve(__dirname, '../.env');
|
|
console.log('[RedisService] Loading .env file from:', envPath);
|
|
dotenv.config({ path: envPath });
|
|
|
|
export class RedisService {
|
|
constructor() {
|
|
this.timeManager = new TimeManager();
|
|
this.DEFAULT_TTL = 5 * 60; // 5 minutes default TTL
|
|
this.isConnected = false;
|
|
this._initializeRedis();
|
|
}
|
|
|
|
_initializeRedis() {
|
|
try {
|
|
// Debug: Print all environment variables we're looking for
|
|
console.log('[RedisService] Environment variables state:', {
|
|
REDIS_HOST: process.env.REDIS_HOST ? '(set)' : '(not set)',
|
|
REDIS_PORT: process.env.REDIS_PORT ? '(set)' : '(not set)',
|
|
REDIS_USERNAME: process.env.REDIS_USERNAME ? '(set)' : '(not set)',
|
|
REDIS_PASSWORD: process.env.REDIS_PASSWORD ? '(set)' : '(not set)',
|
|
});
|
|
|
|
// Log Redis configuration (without password)
|
|
const host = process.env.REDIS_HOST || 'localhost';
|
|
const port = parseInt(process.env.REDIS_PORT) || 6379;
|
|
const username = process.env.REDIS_USERNAME || 'default';
|
|
const password = process.env.REDIS_PASSWORD;
|
|
|
|
console.log('[RedisService] Initializing Redis with config:', {
|
|
host,
|
|
port,
|
|
username,
|
|
hasPassword: !!password
|
|
});
|
|
|
|
const config = {
|
|
host,
|
|
port,
|
|
username,
|
|
retryStrategy: (times) => {
|
|
const delay = Math.min(times * 50, 2000);
|
|
return delay;
|
|
},
|
|
maxRetriesPerRequest: 3,
|
|
enableReadyCheck: true,
|
|
connectTimeout: 10000,
|
|
showFriendlyErrorStack: true,
|
|
retryUnfulfilled: true,
|
|
maxRetryAttempts: 5
|
|
};
|
|
|
|
// Only add password if it exists
|
|
if (password) {
|
|
console.log('[RedisService] Adding password to config');
|
|
config.password = password;
|
|
} else {
|
|
console.warn('[RedisService] No Redis password found in environment variables!');
|
|
}
|
|
|
|
this.client = new Redis(config);
|
|
|
|
// Handle connection events
|
|
this.client.on('connect', () => {
|
|
console.log('[RedisService] Connected to Redis');
|
|
this.isConnected = true;
|
|
});
|
|
|
|
this.client.on('ready', () => {
|
|
console.log('[RedisService] Redis is ready');
|
|
this.isConnected = true;
|
|
});
|
|
|
|
this.client.on('error', (err) => {
|
|
console.error('[RedisService] Redis error:', err);
|
|
this.isConnected = false;
|
|
// Log more details about the error
|
|
if (err.code === 'WRONGPASS') {
|
|
console.error('[RedisService] Authentication failed. Please check your Redis password.');
|
|
}
|
|
});
|
|
|
|
this.client.on('close', () => {
|
|
console.log('[RedisService] Redis connection closed');
|
|
this.isConnected = false;
|
|
});
|
|
|
|
this.client.on('reconnecting', (params) => {
|
|
console.log('[RedisService] Reconnecting to Redis:', params);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[RedisService] Error initializing Redis:', error);
|
|
this.isConnected = false;
|
|
}
|
|
}
|
|
|
|
async get(key) {
|
|
if (!this.isConnected) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const data = await this.client.get(key);
|
|
return data ? JSON.parse(data) : null;
|
|
} catch (error) {
|
|
console.error('[RedisService] Error getting data:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async set(key, data, ttl = this.DEFAULT_TTL) {
|
|
if (!this.isConnected) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await this.client.setex(key, ttl, JSON.stringify(data));
|
|
} catch (error) {
|
|
console.error('[RedisService] Error setting data:', error);
|
|
}
|
|
}
|
|
|
|
// Helper to generate cache keys
|
|
_getCacheKey(type, params = {}) {
|
|
const { timeRange, startDate, endDate, metricId, metric, daily } = params;
|
|
let key = `klaviyo:${type}`;
|
|
|
|
if (type === 'stats:details') {
|
|
key += `:${metric}${daily ? ':daily' : ''}`;
|
|
}
|
|
|
|
if (timeRange) {
|
|
key += `:${timeRange}${metricId ? `:${metricId}` : ''}`;
|
|
} else if (startDate && endDate) {
|
|
key += `:custom:${startDate}:${endDate}${metricId ? `:${metricId}` : ''}`;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
// Get TTL based on time range
|
|
_getTTL(timeRange) {
|
|
const TTL_MAP = {
|
|
'today': 2 * 60, // 2 minutes
|
|
'yesterday': 30 * 60, // 30 minutes
|
|
'thisWeek': 5 * 60, // 5 minutes
|
|
'lastWeek': 60 * 60, // 1 hour
|
|
'thisMonth': 10 * 60, // 10 minutes
|
|
'lastMonth': 2 * 60 * 60, // 2 hours
|
|
'last7days': 5 * 60, // 5 minutes
|
|
'last30days': 15 * 60, // 15 minutes
|
|
'custom': 15 * 60 // 15 minutes
|
|
};
|
|
return TTL_MAP[timeRange] || this.DEFAULT_TTL;
|
|
}
|
|
|
|
async getEventData(type, params) {
|
|
if (!this.isConnected) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const baseKey = this._getCacheKey('events', params);
|
|
const data = await this.get(`${baseKey}:${type}`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('[RedisService] Error getting event data:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async cacheEventData(type, params, data) {
|
|
if (!this.isConnected) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const ttl = this._getTTL(params.timeRange);
|
|
const baseKey = this._getCacheKey('events', params);
|
|
|
|
// Cache raw event data
|
|
await this.set(`${baseKey}:${type}`, data, ttl);
|
|
} catch (error) {
|
|
console.error('[RedisService] Error caching event data:', error);
|
|
}
|
|
}
|
|
|
|
async clearCache(params = {}) {
|
|
if (!this.isConnected) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const pattern = this._getCacheKey('events', params) + '*';
|
|
const keys = await this.client.keys(pattern);
|
|
if (keys.length > 0) {
|
|
await this.client.del(...keys);
|
|
}
|
|
} catch (error) {
|
|
console.error('[RedisService] Error clearing cache:', error);
|
|
}
|
|
}
|
|
}
|