147 lines
4.1 KiB
JavaScript
147 lines
4.1 KiB
JavaScript
// Klaviyo cache wrapper. Was a self-instantiating ioredis client per service in
|
|
// the standalone klaviyo-server; now accepts an injected client so the merged
|
|
// dashboard-server shares one connection across all vendors (Phase 4).
|
|
//
|
|
// Public surface kept identical to the original so the ~3K LOC of klaviyo
|
|
// service code (events/campaigns/reporting) needs no other changes:
|
|
// - get(key)
|
|
// - set(key, data, ttl)
|
|
// - _getCacheKey(type, params)
|
|
// - _getTTL(timeRange)
|
|
// - getEventData(type, params) / cacheEventData(type, params, data)
|
|
// - clearCache(params)
|
|
//
|
|
// Reads short-circuit to null when the client isn't ready; writes are no-ops.
|
|
// Same "Redis hiccup → fall through to upstream" behavior as before.
|
|
|
|
import { TimeManager } from '../../utils/time.utils.js';
|
|
|
|
export class RedisService {
|
|
constructor(redis) {
|
|
if (!redis) {
|
|
throw new Error('RedisService requires an ioredis client (Phase 4: injected, no longer self-constructed)');
|
|
}
|
|
this.client = redis;
|
|
this.timeManager = new TimeManager();
|
|
this.DEFAULT_TTL = 5 * 60;
|
|
}
|
|
|
|
get isConnected() {
|
|
// ioredis: 'wait' | 'reconnecting' | 'connecting' | 'connect' | 'ready' | 'close' | 'end'
|
|
return this.client.status === 'ready' || this.client.status === 'connect';
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
_getCacheKey(type, params = {}) {
|
|
const {
|
|
timeRange,
|
|
startDate,
|
|
endDate,
|
|
metricId,
|
|
metric,
|
|
daily,
|
|
cacheKey,
|
|
isPreviousPeriod,
|
|
customFilters,
|
|
} = params;
|
|
|
|
let key = `klaviyo:${type}`;
|
|
|
|
if (type === 'stats:details') {
|
|
key += `:${metric || 'all'}`;
|
|
if (daily) key += ':daily';
|
|
if (customFilters?.length) {
|
|
const filterHash = customFilters.join('').replace(/[^a-zA-Z0-9]/g, '');
|
|
key += `:${filterHash}`;
|
|
}
|
|
}
|
|
|
|
if (cacheKey) {
|
|
key += `:${cacheKey}`;
|
|
} else if (timeRange) {
|
|
key += `:${timeRange}`;
|
|
if (metricId) key += `:${metricId}`;
|
|
if (isPreviousPeriod) key += ':prev';
|
|
} else if (startDate && endDate) {
|
|
key += `:custom:${startDate}:${endDate}`;
|
|
if (metricId) key += `:${metricId}`;
|
|
if (isPreviousPeriod) key += ':prev';
|
|
}
|
|
|
|
if (['pre_orders', 'local_pickup', 'on_hold'].includes(metric)) {
|
|
key += `:${metric}`;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
_getTTL(timeRange) {
|
|
const TTL_MAP = {
|
|
today: 2 * 60,
|
|
yesterday: 30 * 60,
|
|
thisWeek: 5 * 60,
|
|
lastWeek: 60 * 60,
|
|
thisMonth: 10 * 60,
|
|
lastMonth: 2 * 60 * 60,
|
|
last7days: 5 * 60,
|
|
last30days: 15 * 60,
|
|
custom: 15 * 60,
|
|
};
|
|
return TTL_MAP[timeRange] || this.DEFAULT_TTL;
|
|
}
|
|
|
|
async getEventData(type, params) {
|
|
if (!this.isConnected) return null;
|
|
try {
|
|
const baseKey = this._getCacheKey('events', params);
|
|
return await this.get(`${baseKey}:${type}`);
|
|
} 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);
|
|
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);
|
|
}
|
|
}
|
|
}
|