Move dashboard server into project
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
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,
|
||||
cacheKey,
|
||||
isPreviousPeriod,
|
||||
customFilters
|
||||
} = params;
|
||||
|
||||
let key = `klaviyo:${type}`;
|
||||
|
||||
// Handle "stats:details" for daily or metric-based keys
|
||||
if (type === 'stats:details') {
|
||||
// Add metric to key
|
||||
key += `:${metric || 'all'}`;
|
||||
|
||||
// Add daily flag if present
|
||||
if (daily) {
|
||||
key += ':daily';
|
||||
}
|
||||
|
||||
// Add custom filters hash if present
|
||||
if (customFilters?.length) {
|
||||
const filterHash = customFilters.join('').replace(/[^a-zA-Z0-9]/g, '');
|
||||
key += `:${filterHash}`;
|
||||
}
|
||||
}
|
||||
|
||||
// If a specific cache key is provided, use it (highest priority)
|
||||
if (cacheKey) {
|
||||
key += `:${cacheKey}`;
|
||||
}
|
||||
// Otherwise, build a default cache key
|
||||
else if (timeRange) {
|
||||
key += `:${timeRange}`;
|
||||
if (metricId) {
|
||||
key += `:${metricId}`;
|
||||
}
|
||||
if (isPreviousPeriod) {
|
||||
key += ':prev';
|
||||
}
|
||||
} else if (startDate && endDate) {
|
||||
// For custom date ranges, include both dates in the key
|
||||
key += `:custom:${startDate}:${endDate}`;
|
||||
if (metricId) {
|
||||
key += `:${metricId}`;
|
||||
}
|
||||
if (isPreviousPeriod) {
|
||||
key += ':prev';
|
||||
}
|
||||
}
|
||||
|
||||
// Add order type to key if present
|
||||
if (['pre_orders', 'local_pickup', 'on_hold'].includes(metric)) {
|
||||
key += `:${metric}`;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user