Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/guides/github-mcp-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,4 @@ If you prefer not to use environment variables, you can directly configure the t




183 changes: 183 additions & 0 deletions lib/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import Redis from 'ioredis'

// Redis configuration
const redisConfig = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
lazyConnect: true,
// Connection pool settings
family: 4,
keepAlive: 30000, // keepAlive expects number (milliseconds), not boolean
// Timeouts
connectTimeout: 10000,
commandTimeout: 5000,
}

// Create Redis client instance
export const redis = new Redis(redisConfig)

// Redis client with error handling
redis.on('connect', () => {
console.log('βœ… Redis connected successfully')
})

redis.on('error', (error) => {
console.error('❌ Redis connection error:', error)
})

redis.on('close', () => {
console.log('πŸ”Œ Redis connection closed')
})

// Cache key generators
export const CacheKeys = {
property: (id: string) => `property:${id}`,
properties: (filters: string) => `properties:${filters}`,
agency: (id: string) => `agency:${id}`,
agencies: () => 'agencies:all',
userReservations: (userId: string) => `user:${userId}:reservations`,
propertyReservations: (propertyId: string) => `property:${propertyId}:reservations`,
}

// Cache TTL constants (in seconds)
export const CacheTTL = {
PROPERTY: 300, // 5 minutes
PROPERTIES_LIST: 180, // 3 minutes
AGENCY: 600, // 10 minutes
AGENCIES_LIST: 300, // 5 minutes
USER_RESERVATIONS: 120, // 2 minutes
PROPERTY_RESERVATIONS: 60, // 1 minute
} as const

// Cache utility functions
export class CacheManager {
/**
* Get cached data with JSON parsing
*/
static async get<T>(key: string): Promise<T | null> {
try {
const cached = await redis.get(key)
return cached ? JSON.parse(cached) : null
} catch (error) {
console.error(`Cache get error for key ${key}:`, error)
return null
}
}

/**
* Set cache data with JSON stringification and TTL
*/
static async set<T>(key: string, data: T, ttl: number): Promise<boolean> {
try {
await redis.setex(key, ttl, JSON.stringify(data))
return true
} catch (error) {
console.error(`Cache set error for key ${key}:`, error)
return false
}
}

/**
* Delete cache entry
*/
static async del(key: string): Promise<boolean> {
try {
await redis.del(key)
return true
} catch (error) {
console.error(`Cache delete error for key ${key}:`, error)
return false
}
}

/**
* Delete multiple cache entries by pattern
*/
static async delPattern(pattern: string): Promise<number> {
try {
const keys = await redis.keys(pattern)
if (keys.length === 0) return 0

const deleted = await redis.del(...keys)
return deleted
} catch (error) {
console.error(`Cache delete pattern error for pattern ${pattern}:`, error)
return 0
}
}

/**
* Check if Redis is connected
*/
static async isConnected(): Promise<boolean> {
try {
await redis.ping()
return true
} catch (error) {
console.error('Redis health check failed:', error)
return false
}
}

/**
* Get cache statistics
*/
static async getStats(): Promise<{
connected: boolean
memoryUsage?: string
keyCount?: number
}> {
try {
const connected = await this.isConnected()
if (!connected) return { connected: false }

const info = await redis.info('memory')
const dbSize = await redis.dbsize()

// Parse memory usage from Redis info
const memoryMatch = info.match(/used_memory_human:(.+)/)
const memoryUsage = memoryMatch ? memoryMatch[1].trim() : 'unknown'

return {
connected: true,
memoryUsage,
keyCount: dbSize
}
} catch (error) {
console.error('Error getting cache stats:', error)
return { connected: false }
}
}
}

// Cache decorator for methods
export function Cached(keyGenerator: (...args: unknown[]) => string, ttl: number) {
return function (_target: unknown, _propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value

descriptor.value = async function (...args: unknown[]) {
const cacheKey = keyGenerator(...args)

// Try to get from cache first
const cached = await CacheManager.get(cacheKey)
if (cached !== null) {
return cached
}

// Execute original method
const result = await method.apply(this as unknown, args)

// Cache the result
if (result !== null && result !== undefined) {
await CacheManager.set(cacheKey, result, ttl)
}

return result
}
}
}

export default redis
Loading
Loading