import { Redis } from "@upstash/redis" // Helper to handle potential connection string mismatch const getRedisConfig = () => { let url = process.env.UPSTASH_REDIS_REST_URL || "" let token = process.env.UPSTASH_REDIS_REST_TOKEN || "" // If URL is a connection string (rediss://), parse it to get REST URL and token if (url.startsWith("rediss://") || url.startsWith("redis://")) { try { // Format: rediss://user:password@host:port const clean = url.replace(/^rediss?:\/\//, "") const [auth, hostPort] = clean.split("@") if (auth && hostPort) { const [_, password] = auth.split(":") const [host] = hostPort.split(":") if (host) { url = `https://${host}` } if (password) { token = password } } } catch (e) { console.warn("Failed to parse Redis connection string, falling back to original values") } } return { url, token } } const config = getRedisConfig() // Initialize Redis client with Upstash credentials const redis = new Redis({ url: config.url, token: config.token, }) // Cache key prefixes export const CACHE_KEYS = { SESSIONS: (userToken: string) => `sessions:user:${hashToken(userToken)}`, SESSION_MESSAGES: (sessionId: string) => `session:${sessionId}:messages`, // Admin cache keys ADMIN_STATS: "admin:stats", ADMIN_HEALTH: "admin:health", ADMIN_DATABASES: "admin:databases", ADMIN_DATABASE_STORAGE: "admin:database-storage", ADMIN_DOCUMENTS: (queryHash: string) => `admin:documents:${queryHash}`, ADMIN_CONFIG: "admin:config", ADMIN_ANALYTICS: "admin:analytics", ADMIN_USERS: (page: number) => `admin:users:${page}`, } // Default TTL values (in seconds) export const CACHE_TTL = { SESSIONS: 300, // 5 minutes SESSION_MESSAGES: 600, // 10 minutes // Admin TTLs (shorter for frequently changing data) ADMIN_STATS: 60, // 1 minute ADMIN_HEALTH: 60, // 1 minute ADMIN_DATABASES: 120, // 2 minutes ADMIN_DATABASE_STORAGE: 120, // 2 minutes ADMIN_DOCUMENTS: 120, // 2 minutes ADMIN_CONFIG: 300, // 5 minutes ADMIN_ANALYTICS: 120, // 2 minutes ADMIN_USERS: 120, // 2 minutes } // Simple hash function for user tokens function hashToken(token: string): string { // Use a simple hash to avoid storing sensitive tokens in Redis keys let hash = 0 for (let i = 0; i < token.length; i++) { const char = token.charCodeAt(i) hash = (hash << 5) - hash + char hash = hash & hash // Convert to 32bit integer } return Math.abs(hash).toString(36) } /** * Get cached data from Redis */ export async function getCached(key: string): Promise { try { const data = await redis.get(key) return data } catch (error) { console.error("Redis get error:", error) return null } } /** * Set cached data in Redis with optional TTL */ export async function setCached( key: string, value: unknown, ttl: number = 600 ): Promise { try { await redis.set(key, JSON.stringify(value), { ex: ttl }) } catch (error) { console.error("Redis set error:", error) } } /** * Delete a specific cache key */ export async function deleteCacheKey(key: string): Promise { try { await redis.del(key) } catch (error) { console.error("Redis delete error:", error) } } /** * Invalidate cache keys matching a pattern */ export async function invalidateCache(pattern: string): Promise { try { // Upstash Redis supports SCAN command let cursor: number | string = 0 do { const result: [number | string, string[]] = await redis.scan(cursor, { match: pattern, count: 100, }) cursor = result[0] const keys = result[1] if (keys.length > 0) { await redis.del(...keys) } } while (cursor !== 0 && cursor !== "0") } catch (error) { console.error("Redis invalidate error:", error) } } /** * Get user token hash for cache keys */ export function getUserTokenHash(token: string | null): string { return token ? hashToken(token) : "anonymous" } /** * Generate a simple hash for query strings */ export function hashQueryString(query: string): string { return hashToken(query) }