744 lines
20 KiB
TypeScript
744 lines
20 KiB
TypeScript
import Redis from 'ioredis';
|
|
import CryptoJS from 'crypto-js';
|
|
|
|
// Initialize Redis client
|
|
let redisClient: Redis | null = null;
|
|
let isConnecting = false;
|
|
let connectionAttempts = 0;
|
|
const MAX_RECONNECT_ATTEMPTS = 5;
|
|
|
|
/**
|
|
* Get a Redis client instance (singleton pattern) with improved connection management
|
|
*/
|
|
export function getRedisClient(): Redis {
|
|
if (redisClient && redisClient.status === 'ready') {
|
|
return redisClient;
|
|
}
|
|
|
|
if (isConnecting) {
|
|
// If we're already trying to connect, return the existing client
|
|
// This prevents multiple simultaneous connection attempts
|
|
if (redisClient) return redisClient;
|
|
|
|
// This is a fallback in case we're connecting but don't have a client yet
|
|
console.warn('Redis connection in progress, creating temporary client');
|
|
}
|
|
|
|
if (!redisClient) {
|
|
isConnecting = true;
|
|
connectionAttempts = 0;
|
|
|
|
// Set Redis connection parameters from environment variables only
|
|
const redisOptions = {
|
|
host: process.env.REDIS_HOST,
|
|
port: process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined,
|
|
password: process.env.REDIS_PASSWORD,
|
|
retryStrategy: (times: number) => {
|
|
connectionAttempts = times;
|
|
if (times > MAX_RECONNECT_ATTEMPTS) {
|
|
console.error(`Redis connection failed after ${times} attempts, giving up`);
|
|
return null; // Stop trying to reconnect
|
|
}
|
|
const delay = Math.min(times * 100, 5000);
|
|
console.log(`Redis reconnect attempt ${times}, retrying in ${delay}ms`);
|
|
return delay;
|
|
},
|
|
maxRetriesPerRequest: 5,
|
|
enableOfflineQueue: true,
|
|
connectTimeout: 10000, // 10 seconds
|
|
disconnectTimeout: 2000, // 2 seconds
|
|
keepAlive: 10000, // 10 seconds
|
|
keyPrefix: '' // No prefix to keep keys clean
|
|
};
|
|
|
|
console.log('Connecting to Redis using environment variables');
|
|
redisClient = new Redis(redisOptions);
|
|
|
|
redisClient.on('error', (err) => {
|
|
console.error('Redis connection error:', err);
|
|
|
|
// Only set to null if we've exceeded max attempts
|
|
if (connectionAttempts > MAX_RECONNECT_ATTEMPTS) {
|
|
console.error('Redis connection failed permanently, will create new client on next request');
|
|
redisClient = null;
|
|
isConnecting = false;
|
|
}
|
|
});
|
|
|
|
redisClient.on('connect', () => {
|
|
console.log('Successfully connected to Redis');
|
|
isConnecting = false;
|
|
connectionAttempts = 0;
|
|
});
|
|
|
|
redisClient.on('reconnecting', () => {
|
|
console.log('Reconnecting to Redis...');
|
|
isConnecting = true;
|
|
});
|
|
|
|
redisClient.on('ready', () => {
|
|
console.log('Redis connection warmed up');
|
|
isConnecting = false;
|
|
});
|
|
|
|
redisClient.on('end', () => {
|
|
console.log('Redis connection ended');
|
|
// Don't set to null here - let the error handler decide
|
|
});
|
|
}
|
|
|
|
return redisClient;
|
|
}
|
|
|
|
/**
|
|
* Close Redis connection (useful for serverless environments)
|
|
*/
|
|
export async function closeRedisConnection(): Promise<void> {
|
|
if (redisClient) {
|
|
await redisClient.quit();
|
|
redisClient = null;
|
|
}
|
|
}
|
|
|
|
// Encryption key from environment variable or fallback
|
|
const getEncryptionKey = () => {
|
|
return process.env.REDIS_ENCRYPTION_KEY || 'default-encryption-key-change-in-production';
|
|
};
|
|
|
|
/**
|
|
* Encrypt sensitive data before storing in Redis
|
|
*/
|
|
export function encryptData(data: string): string {
|
|
return CryptoJS.AES.encrypt(data, getEncryptionKey()).toString();
|
|
}
|
|
|
|
/**
|
|
* Decrypt sensitive data retrieved from Redis
|
|
*/
|
|
export function decryptData(encryptedData: string): string {
|
|
const bytes = CryptoJS.AES.decrypt(encryptedData, getEncryptionKey());
|
|
return bytes.toString(CryptoJS.enc.Utf8);
|
|
}
|
|
|
|
// Cache key definitions
|
|
export const KEYS = {
|
|
CREDENTIALS: (userId: string, accountId: string) => `email:credentials:${userId}:${accountId}`,
|
|
SESSION: (userId: string) => `email:session:${userId}`,
|
|
EMAIL_LIST: (userId: string, accountId: string, folder: string, page: number, perPage: number) =>
|
|
`email:list:${userId}:${accountId}:${folder}:${page}:${perPage}`,
|
|
EMAIL_CONTENT: (userId: string, accountId: string, emailId: string) =>
|
|
`email:content:${userId}:${accountId}:${emailId}`,
|
|
// New widget cache keys
|
|
CALENDAR: (userId: string) => `widget:calendar:${userId}`,
|
|
NEWS: (limit = '100') => `widget:news:${limit}`, // Include limit in cache key
|
|
TASKS: (userId: string) => `widget:tasks:${userId}`,
|
|
MESSAGES: (userId: string) => `widget:messages:${userId}`
|
|
};
|
|
|
|
// TTL constants in seconds
|
|
export const TTL = {
|
|
CREDENTIALS: 60 * 60 * 24, // 24 hours
|
|
SESSION: 60 * 60 * 4, // 4 hours (increased from 30 minutes)
|
|
EMAIL_LIST: 60 * 5, // 5 minutes
|
|
EMAIL_CONTENT: 60 * 15, // 15 minutes
|
|
// New widget cache TTLs
|
|
CALENDAR: 60 * 10, // 10 minutes for calendar events
|
|
NEWS: 60 * 15, // 15 minutes for news
|
|
TASKS: 60 * 10, // 10 minutes for tasks
|
|
MESSAGES: 60 * 2 // 2 minutes for messages (more frequent updates)
|
|
};
|
|
|
|
interface EmailCredentials {
|
|
email: string;
|
|
password?: string;
|
|
host: string;
|
|
port: number;
|
|
secure?: boolean;
|
|
encryptedPassword?: string;
|
|
smtp_host?: string;
|
|
smtp_port?: number;
|
|
smtp_secure?: boolean;
|
|
display_name?: string;
|
|
color?: string;
|
|
useOAuth?: boolean;
|
|
accessToken?: string;
|
|
refreshToken?: string;
|
|
tokenExpiry?: number;
|
|
}
|
|
|
|
interface ImapSessionData {
|
|
connectionId?: string;
|
|
lastActive: number;
|
|
mailboxes?: string[];
|
|
lastVisit?: number;
|
|
defaultAccountId?: string;
|
|
}
|
|
|
|
/**
|
|
* Cache email credentials in Redis
|
|
*/
|
|
export async function cacheEmailCredentials(
|
|
userId: string,
|
|
accountId: string,
|
|
credentials: EmailCredentials
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.CREDENTIALS(userId, accountId);
|
|
|
|
// Validate credentials before caching
|
|
if (!credentials.email || !credentials.host || (!credentials.password && !credentials.useOAuth)) {
|
|
console.error(`Cannot cache incomplete credentials for user ${userId}`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log(`Caching credentials for user ${userId}`);
|
|
|
|
// Create a copy without the password to store
|
|
const secureCredentials: EmailCredentials = {
|
|
email: credentials.email,
|
|
host: credentials.host,
|
|
port: credentials.port,
|
|
secure: credentials.secure ?? true,
|
|
// Include the extended fields
|
|
...(credentials.smtp_host && { smtp_host: credentials.smtp_host }),
|
|
...(credentials.smtp_port && { smtp_port: credentials.smtp_port }),
|
|
...(credentials.smtp_secure !== undefined && { smtp_secure: credentials.smtp_secure }),
|
|
...(credentials.display_name && { display_name: credentials.display_name }),
|
|
...(credentials.color && { color: credentials.color }),
|
|
// Include OAuth fields
|
|
...(credentials.useOAuth !== undefined && { useOAuth: credentials.useOAuth }),
|
|
...(credentials.accessToken && { accessToken: credentials.accessToken }),
|
|
...(credentials.refreshToken && { refreshToken: credentials.refreshToken }),
|
|
...(credentials.tokenExpiry && { tokenExpiry: credentials.tokenExpiry })
|
|
};
|
|
|
|
// Encrypt password if provided
|
|
if (credentials.password) {
|
|
try {
|
|
const encrypted = encryptData(credentials.password);
|
|
console.log(`Successfully encrypted password for user ${userId}`);
|
|
secureCredentials.encryptedPassword = encrypted;
|
|
} catch (encryptError) {
|
|
console.error(`Failed to encrypt password for user ${userId}:`, encryptError);
|
|
// Continue anyway since we might have OAuth tokens
|
|
}
|
|
}
|
|
|
|
await redis.set(key, JSON.stringify(secureCredentials), 'EX', TTL.CREDENTIALS);
|
|
console.log(`Credentials cached for user ${userId}`);
|
|
} catch (error) {
|
|
console.error(`Error caching credentials for user ${userId}:`, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get email credentials from Redis
|
|
*/
|
|
export async function getEmailCredentials(
|
|
userId: string,
|
|
accountId: string
|
|
): Promise<EmailCredentials | null> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.CREDENTIALS(userId, accountId);
|
|
|
|
try {
|
|
const credStr = await redis.get(key);
|
|
|
|
if (!credStr) {
|
|
return null;
|
|
}
|
|
|
|
const creds = JSON.parse(credStr) as EmailCredentials;
|
|
|
|
let password: string | undefined;
|
|
|
|
// Handle OAuth accounts (they might not have a password)
|
|
if (creds.encryptedPassword) {
|
|
try {
|
|
// Decrypt the password
|
|
password = decryptData(creds.encryptedPassword);
|
|
} catch (decryptError) {
|
|
console.error(`Failed to decrypt password for user ${userId}:`, decryptError);
|
|
// For OAuth accounts, we can continue without a password
|
|
if (!creds.useOAuth) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the full credentials with decrypted password if available
|
|
const result: EmailCredentials = {
|
|
email: creds.email,
|
|
host: creds.host,
|
|
port: creds.port,
|
|
secure: creds.secure ?? true,
|
|
...(password && { password }),
|
|
...(creds.smtp_host && { smtp_host: creds.smtp_host }),
|
|
...(creds.smtp_port && { smtp_port: creds.smtp_port }),
|
|
...(creds.smtp_secure !== undefined && { smtp_secure: creds.smtp_secure }),
|
|
...(creds.display_name && { display_name: creds.display_name }),
|
|
...(creds.color && { color: creds.color }),
|
|
// Include OAuth fields
|
|
...(creds.useOAuth !== undefined && { useOAuth: creds.useOAuth }),
|
|
...(creds.accessToken && { accessToken: creds.accessToken }),
|
|
...(creds.refreshToken && { refreshToken: creds.refreshToken }),
|
|
...(creds.tokenExpiry && { tokenExpiry: creds.tokenExpiry })
|
|
};
|
|
|
|
return result;
|
|
} catch (error) {
|
|
console.error(`Error getting credentials for user ${userId}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache IMAP session data for quick reconnection
|
|
*/
|
|
export async function cacheImapSession(
|
|
userId: string,
|
|
sessionData: ImapSessionData
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.SESSION(userId);
|
|
|
|
// Always update the lastActive timestamp
|
|
sessionData.lastActive = Date.now();
|
|
|
|
await redis.set(key, JSON.stringify(sessionData), 'EX', TTL.SESSION);
|
|
}
|
|
|
|
/**
|
|
* Get cached IMAP session data
|
|
*/
|
|
export async function getCachedImapSession(
|
|
userId: string
|
|
): Promise<ImapSessionData | null> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.SESSION(userId);
|
|
|
|
const cachedData = await redis.get(key);
|
|
if (!cachedData) return null;
|
|
|
|
return JSON.parse(cachedData) as ImapSessionData;
|
|
}
|
|
|
|
/**
|
|
* Cache email list in Redis
|
|
*/
|
|
export async function cacheEmailList(
|
|
userId: string,
|
|
accountId: string,
|
|
folder: string,
|
|
page: number,
|
|
perPage: number,
|
|
data: any
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.EMAIL_LIST(userId, accountId, folder, page, perPage);
|
|
|
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.EMAIL_LIST);
|
|
}
|
|
|
|
/**
|
|
* Get cached email list from Redis
|
|
*/
|
|
export async function getCachedEmailList(
|
|
userId: string,
|
|
accountId: string,
|
|
folder: string,
|
|
page: number,
|
|
perPage: number
|
|
): Promise<any | null> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.EMAIL_LIST(userId, accountId, folder, page, perPage);
|
|
|
|
const cachedData = await redis.get(key);
|
|
if (!cachedData) return null;
|
|
|
|
return JSON.parse(cachedData);
|
|
}
|
|
|
|
/**
|
|
* Cache email content in Redis
|
|
*/
|
|
export async function cacheEmailContent(
|
|
userId: string,
|
|
accountId: string,
|
|
emailId: string,
|
|
data: any
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.EMAIL_CONTENT(userId, accountId, emailId);
|
|
|
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.EMAIL_CONTENT);
|
|
}
|
|
|
|
/**
|
|
* Get cached email content from Redis
|
|
*/
|
|
export async function getCachedEmailContent(
|
|
userId: string,
|
|
accountId: string,
|
|
emailId: string
|
|
): Promise<any | null> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.EMAIL_CONTENT(userId, accountId, emailId);
|
|
|
|
const cachedData = await redis.get(key);
|
|
if (!cachedData) return null;
|
|
|
|
return JSON.parse(cachedData);
|
|
}
|
|
|
|
/**
|
|
* Invalidate all email caches for a folder
|
|
*/
|
|
export async function invalidateFolderCache(
|
|
userId: string,
|
|
accountId: string,
|
|
folder: string
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const pattern = `email:list:${userId}:${accountId}:${folder}:*`;
|
|
|
|
// Use SCAN to find and delete keys matching the pattern
|
|
let cursor = '0';
|
|
do {
|
|
const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
|
|
cursor = nextCursor;
|
|
|
|
if (keys.length > 0) {
|
|
await redis.del(...keys);
|
|
}
|
|
} while (cursor !== '0');
|
|
}
|
|
|
|
/**
|
|
* Invalidate email content cache
|
|
*/
|
|
export async function invalidateEmailContentCache(
|
|
userId: string,
|
|
accountId: string,
|
|
emailId: string
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.EMAIL_CONTENT(userId, accountId, emailId);
|
|
|
|
await redis.del(key);
|
|
}
|
|
|
|
/**
|
|
* Warm up Redis connection to avoid cold starts
|
|
*/
|
|
export async function warmupRedisCache(): Promise<boolean> {
|
|
try {
|
|
// Ping Redis to establish connection early
|
|
const redis = getRedisClient();
|
|
await redis.ping();
|
|
console.log('Redis connection warmed up');
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error warming up Redis:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get Redis connection status
|
|
*/
|
|
export async function getRedisStatus(): Promise<{
|
|
status: 'connected' | 'error';
|
|
ping?: string;
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const redis = getRedisClient();
|
|
const pong = await redis.ping();
|
|
return {
|
|
status: 'connected',
|
|
ping: pong
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
status: 'error',
|
|
error: error instanceof Error ? error.message : String(error)
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate all user email caches (email lists and content)
|
|
*/
|
|
export async function invalidateUserEmailCache(
|
|
userId: string
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
|
|
// Patterns to delete
|
|
const patterns = [
|
|
`email:list:${userId}:*`,
|
|
`email:content:${userId}:*`
|
|
];
|
|
|
|
for (const pattern of patterns) {
|
|
let cursor = '0';
|
|
do {
|
|
const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
|
|
cursor = nextCursor;
|
|
|
|
if (keys.length > 0) {
|
|
await redis.del(...keys);
|
|
}
|
|
} while (cursor !== '0');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached email credentials from Redis
|
|
* @deprecated Use getEmailCredentials instead
|
|
*/
|
|
export async function getCachedEmailCredentials(
|
|
userId: string,
|
|
accountId: string
|
|
): Promise<EmailCredentials | null> {
|
|
return getEmailCredentials(userId, accountId);
|
|
}
|
|
|
|
/**
|
|
* Cache calendar data for a user
|
|
*/
|
|
export async function cacheCalendarData(
|
|
userId: string,
|
|
data: any
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.CALENDAR(userId);
|
|
|
|
try {
|
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.CALENDAR);
|
|
console.log(`Calendar data cached for user ${userId}`);
|
|
} catch (error) {
|
|
console.error(`Error caching calendar data for user ${userId}:`, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached calendar data for a user
|
|
*/
|
|
export async function getCachedCalendarData(
|
|
userId: string
|
|
): Promise<any | null> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.CALENDAR(userId);
|
|
|
|
try {
|
|
const cachedData = await redis.get(key);
|
|
if (!cachedData) {
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(cachedData);
|
|
} catch (error) {
|
|
console.error(`Error getting cached calendar data for user ${userId}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate calendar cache for a user
|
|
*/
|
|
export async function invalidateCalendarCache(
|
|
userId: string
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.CALENDAR(userId);
|
|
|
|
try {
|
|
await redis.del(key);
|
|
console.log(`Calendar cache invalidated for user ${userId}`);
|
|
} catch (error) {
|
|
console.error(`Error invalidating calendar cache for user ${userId}:`, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache news data (global, not user-specific)
|
|
*/
|
|
export async function cacheNewsData(
|
|
data: any,
|
|
limit = '100'
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.NEWS(limit);
|
|
|
|
try {
|
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.NEWS);
|
|
console.log(`News data cached successfully (${data.length} articles, limit=${limit})`);
|
|
} catch (error) {
|
|
console.error('Error caching news data:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached news data
|
|
*/
|
|
export async function getCachedNewsData(limit = '100'): Promise<any | null> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.NEWS(limit);
|
|
|
|
try {
|
|
const cachedData = await redis.get(key);
|
|
if (!cachedData) {
|
|
return null;
|
|
}
|
|
|
|
const parsedData = JSON.parse(cachedData);
|
|
console.log(`Retrieved ${parsedData.length} articles from cache with limit=${limit}`);
|
|
return parsedData;
|
|
} catch (error) {
|
|
console.error('Error getting cached news data:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate news cache
|
|
*/
|
|
export async function invalidateNewsCache(limit?: string): Promise<void> {
|
|
const redis = getRedisClient();
|
|
|
|
try {
|
|
if (limit) {
|
|
// Invalidate specific limit cache
|
|
const key = KEYS.NEWS(limit);
|
|
await redis.del(key);
|
|
console.log(`News cache invalidated for limit=${limit}`);
|
|
} else {
|
|
// Try to invalidate for some common limits
|
|
const limits = ['5', '50', '100', '200'];
|
|
for (const lim of limits) {
|
|
const key = KEYS.NEWS(lim);
|
|
await redis.del(key);
|
|
}
|
|
console.log('All news caches invalidated');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error invalidating news cache:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache tasks data for a user
|
|
*/
|
|
export async function cacheTasksData(
|
|
userId: string,
|
|
data: any
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.TASKS(userId);
|
|
|
|
try {
|
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.TASKS);
|
|
console.log(`Tasks data cached for user ${userId}`);
|
|
} catch (error) {
|
|
console.error(`Error caching tasks data for user ${userId}:`, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached tasks data for a user
|
|
*/
|
|
export async function getCachedTasksData(
|
|
userId: string
|
|
): Promise<any | null> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.TASKS(userId);
|
|
|
|
try {
|
|
const cachedData = await redis.get(key);
|
|
if (!cachedData) {
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(cachedData);
|
|
} catch (error) {
|
|
console.error(`Error getting cached tasks data for user ${userId}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate tasks cache for a user
|
|
*/
|
|
export async function invalidateTasksCache(
|
|
userId: string
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.TASKS(userId);
|
|
|
|
try {
|
|
await redis.del(key);
|
|
console.log(`Tasks cache invalidated for user ${userId}`);
|
|
} catch (error) {
|
|
console.error(`Error invalidating tasks cache for user ${userId}:`, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache messages data for a user
|
|
*/
|
|
export async function cacheMessagesData(
|
|
userId: string,
|
|
data: any
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.MESSAGES(userId);
|
|
|
|
try {
|
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.MESSAGES);
|
|
console.log(`Messages data cached for user ${userId}`);
|
|
} catch (error) {
|
|
console.error(`Error caching messages data for user ${userId}:`, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached messages data for a user
|
|
*/
|
|
export async function getCachedMessagesData(
|
|
userId: string
|
|
): Promise<any | null> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.MESSAGES(userId);
|
|
|
|
try {
|
|
const cachedData = await redis.get(key);
|
|
if (!cachedData) {
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(cachedData);
|
|
} catch (error) {
|
|
console.error(`Error getting cached messages data for user ${userId}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate messages cache for a user
|
|
*/
|
|
export async function invalidateMessagesCache(
|
|
userId: string
|
|
): Promise<void> {
|
|
const redis = getRedisClient();
|
|
const key = KEYS.MESSAGES(userId);
|
|
|
|
try {
|
|
await redis.del(key);
|
|
console.log(`Messages cache invalidated for user ${userId}`);
|
|
} catch (error) {
|
|
console.error(`Error invalidating messages cache for user ${userId}:`, error);
|
|
}
|
|
}
|