Onboarding Manager and Router Manager refactored, analytics and background jobs added, database setup updated, environment setup updated, frontend updated, backend updated. Critical onboarding database migration implemented.
206 lines
5.6 KiB
TypeScript
206 lines
5.6 KiB
TypeScript
/**
|
|
* Analytics Cache Service
|
|
*
|
|
* Provides caching for analytics API calls to reduce redundant requests
|
|
* and improve performance while managing cache invalidation.
|
|
*/
|
|
|
|
interface CacheEntry<T> {
|
|
data: T;
|
|
timestamp: number;
|
|
ttl: number; // Time to live in milliseconds
|
|
}
|
|
|
|
interface AnalyticsCacheConfig {
|
|
defaultTTL: number; // Default TTL in milliseconds
|
|
maxCacheSize: number; // Maximum number of entries to cache
|
|
databaseDataTTL?: number; // Special TTL for database-stored data (longer)
|
|
}
|
|
|
|
class AnalyticsCacheService {
|
|
private cache = new Map<string, CacheEntry<any>>();
|
|
private config: AnalyticsCacheConfig;
|
|
|
|
constructor(config: Partial<AnalyticsCacheConfig> = {}) {
|
|
this.config = {
|
|
defaultTTL: 5 * 60 * 1000, // 5 minutes
|
|
maxCacheSize: 100,
|
|
databaseDataTTL: 2 * 60 * 60 * 1000, // 2 hours for database data
|
|
...config
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate cache key from parameters
|
|
*/
|
|
private generateKey(endpoint: string, params?: Record<string, any>): string {
|
|
const sortedParams = params ? Object.keys(params)
|
|
.sort()
|
|
.reduce((result, key) => {
|
|
result[key] = params[key];
|
|
return result;
|
|
}, {} as Record<string, any>) : {};
|
|
|
|
return `${endpoint}:${JSON.stringify(sortedParams)}`;
|
|
}
|
|
|
|
/**
|
|
* Get cached data if valid
|
|
*/
|
|
get<T>(endpoint: string, params?: Record<string, any>): T | null {
|
|
const key = this.generateKey(endpoint, params);
|
|
const entry = this.cache.get(key);
|
|
|
|
if (!entry) {
|
|
return null;
|
|
}
|
|
|
|
// Check if cache entry is still valid
|
|
const now = Date.now();
|
|
if (now - entry.timestamp > entry.ttl) {
|
|
this.cache.delete(key);
|
|
return null;
|
|
}
|
|
|
|
console.log(`📦 Analytics Cache HIT: ${key}`);
|
|
return entry.data;
|
|
}
|
|
|
|
/**
|
|
* Set cache entry
|
|
*/
|
|
set<T>(endpoint: string, params: Record<string, any> | undefined, data: T, ttl?: number): void {
|
|
const key = this.generateKey(endpoint, params);
|
|
const now = Date.now();
|
|
|
|
// Remove oldest entries if cache is full
|
|
if (this.cache.size >= this.config.maxCacheSize) {
|
|
this.evictOldest();
|
|
}
|
|
|
|
this.cache.set(key, {
|
|
data,
|
|
timestamp: now,
|
|
ttl: ttl || this.config.defaultTTL
|
|
});
|
|
|
|
console.log(`💾 Analytics Cache SET: ${key} (TTL: ${ttl || this.config.defaultTTL}ms)`);
|
|
}
|
|
|
|
/**
|
|
* Set cache entry with database TTL (longer cache for DB-stored data)
|
|
*/
|
|
setDatabaseData<T>(endpoint: string, params: Record<string, any> | undefined, data: T): void {
|
|
const ttl = this.config.databaseDataTTL || this.config.defaultTTL;
|
|
this.set(endpoint, params, data, ttl);
|
|
console.log(`🗄️ Analytics Cache SET (DB): ${this.generateKey(endpoint, params)} (TTL: ${ttl}ms)`);
|
|
}
|
|
|
|
/**
|
|
* Invalidate cache entries matching pattern
|
|
*/
|
|
invalidate(pattern?: string): void {
|
|
if (!pattern) {
|
|
this.cache.clear();
|
|
console.log('🗑️ Analytics Cache CLEARED: All entries');
|
|
return;
|
|
}
|
|
|
|
const keysToDelete: string[] = [];
|
|
for (const key of this.cache.keys()) {
|
|
if (key.includes(pattern)) {
|
|
keysToDelete.push(key);
|
|
}
|
|
}
|
|
|
|
keysToDelete.forEach(key => this.cache.delete(key));
|
|
console.log(`🗑️ Analytics Cache INVALIDATED: ${keysToDelete.length} entries matching "${pattern}"`);
|
|
}
|
|
|
|
/**
|
|
* Invalidate entries older than specified time
|
|
*/
|
|
invalidateOlderThan(olderThanMs: number): void {
|
|
const now = Date.now();
|
|
const keysToDelete: string[] = [];
|
|
|
|
for (const [key, entry] of this.cache.entries()) {
|
|
if (now - entry.timestamp > olderThanMs) {
|
|
keysToDelete.push(key);
|
|
}
|
|
}
|
|
|
|
keysToDelete.forEach(key => this.cache.delete(key));
|
|
console.log(`🗑️ Analytics Cache INVALIDATED: ${keysToDelete.length} entries older than ${olderThanMs}ms`);
|
|
}
|
|
|
|
/**
|
|
* Get cache statistics
|
|
*/
|
|
getStats(): { size: number; keys: string[]; oldestEntry?: number; newestEntry?: number } {
|
|
const keys = Array.from(this.cache.keys());
|
|
const timestamps = Array.from(this.cache.values()).map(entry => entry.timestamp);
|
|
|
|
return {
|
|
size: this.cache.size,
|
|
keys,
|
|
oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : undefined,
|
|
newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : undefined
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Remove oldest cache entry
|
|
*/
|
|
private evictOldest(): void {
|
|
let oldestKey = '';
|
|
let oldestTime = Date.now();
|
|
|
|
for (const [key, entry] of this.cache.entries()) {
|
|
if (entry.timestamp < oldestTime) {
|
|
oldestTime = entry.timestamp;
|
|
oldestKey = key;
|
|
}
|
|
}
|
|
|
|
if (oldestKey) {
|
|
this.cache.delete(oldestKey);
|
|
console.log(`🗑️ Analytics Cache EVICTED: ${oldestKey}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean up expired entries
|
|
*/
|
|
cleanup(): void {
|
|
const now = Date.now();
|
|
const keysToDelete: string[] = [];
|
|
|
|
for (const [key, entry] of this.cache.entries()) {
|
|
if (now - entry.timestamp > entry.ttl) {
|
|
keysToDelete.push(key);
|
|
}
|
|
}
|
|
|
|
keysToDelete.forEach(key => this.cache.delete(key));
|
|
|
|
if (keysToDelete.length > 0) {
|
|
console.log(`🧹 Analytics Cache CLEANUP: Removed ${keysToDelete.length} expired entries`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create singleton instance
|
|
export const analyticsCache = new AnalyticsCacheService({
|
|
defaultTTL: 60 * 60 * 1000, // 60 minutes for analytics data (since it's stored in DB)
|
|
maxCacheSize: 100, // Increased cache size since we're keeping data longer
|
|
databaseDataTTL: 2 * 60 * 60 * 1000 // 2 hours for database-stored data
|
|
});
|
|
|
|
// Cleanup expired entries every 5 minutes (since we have longer TTL)
|
|
setInterval(() => {
|
|
analyticsCache.cleanup();
|
|
}, 5 * 60 * 1000);
|
|
|
|
export default analyticsCache;
|