package cache import ( "context" "encoding/json" "fmt" "time" "github.com/redis/go-redis/v9" ) // RedisClient wraps go-redis with BOC-specific operations type RedisClient struct { client *redis.Client ctx context.Context } // NewRedisClient creates a new Redis client func NewRedisClient(addr string) (*RedisClient, error) { client := redis.NewClient(&redis.Options{ Addr: addr, Password: "", // no password DB: 0, // default DB PoolSize: 10, }) ctx := context.Background() if err := client.Ping(ctx).Err(); err != nil { return nil, fmt.Errorf("redis ping failed: %w", err) } return &RedisClient{ client: client, ctx: ctx, }, nil } // Close closes the Redis connection func (r *RedisClient) Close() error { return r.client.Close() } // Get retrieves a value from cache func (r *RedisClient) Get(key string, dest interface{}) error { data, err := r.client.Get(r.ctx, key).Bytes() if err == redis.Nil { return fmt.Errorf("cache miss") } if err != nil { return err } return json.Unmarshal(data, dest) } // Set stores a value in cache with TTL func (r *RedisClient) Set(key string, value interface{}, ttl time.Duration) error { data, err := json.Marshal(value) if err != nil { return err } return r.client.Set(r.ctx, key, data, ttl).Err() } // Delete removes a key from cache func (r *RedisClient) Delete(key string) error { return r.client.Del(r.ctx, key).Err() } // DeletePattern removes keys matching a pattern func (r *RedisClient) DeletePattern(pattern string) error { keys, err := r.client.Keys(r.ctx, pattern).Result() if err != nil { return err } if len(keys) > 0 { return r.client.Del(r.ctx, keys...).Err() } return nil } // Exists checks if a key exists func (r *RedisClient) Exists(key string) bool { n, err := r.client.Exists(r.ctx, key).Result() return err == nil && n > 0 } // Increment atomically increments a counter func (r *RedisClient) Increment(key string) (int64, error) { return r.client.Incr(r.ctx, key).Result() } // Expire sets a TTL on a key func (r *RedisClient) Expire(key string, ttl time.Duration) error { return r.client.Expire(r.ctx, key, ttl).Err() } // Cache analytics result func (r *RedisClient) CacheAnalytics(tenantID, metric, period string, data interface{}) error { key := fmt.Sprintf("analytics:%s:%s:%s", tenantID, metric, period) return r.Set(key, data, 5*time.Minute) } // GetCachedAnalytics retrieves cached analytics func (r *RedisClient) GetCachedAnalytics(tenantID, metric, period string, dest interface{}) error { key := fmt.Sprintf("analytics:%s:%s:%s", tenantID, metric, period) return r.Get(key, dest) } // Cache dashboard data func (r *RedisClient) CacheDashboard(tenantID string, data interface{}) error { key := fmt.Sprintf("dashboard:%s", tenantID) return r.Set(key, data, 1*time.Minute) } // GetCachedDashboard retrieves cached dashboard func (r *RedisClient) GetCachedDashboard(tenantID string, dest interface{}) error { key := fmt.Sprintf("dashboard:%s", tenantID) return r.Get(key, dest) } // Rate limiting func (r *RedisClient) RateLimit(key string, maxRequests int, window time.Duration) (bool, error) { pipe := r.client.Pipeline() now := time.Now().Unix() windowStart := now - int64(window.Seconds()) // Remove old entries pipe.ZRemRangeByScore(r.ctx, key, "0", fmt.Sprintf("%d", windowStart)) // Count current entries pipe.ZCard(r.ctx, key) // Add current request pipe.ZAdd(r.ctx, key, redis.Z{Score: float64(now), Member: now}) // Set expiry on the key pipe.Expire(r.ctx, key, window) cmders, err := pipe.Exec(r.ctx) if err != nil { return false, err } // cmders[1] is ZCard result count := cmders[1].(*redis.IntCmd).Val() return count <= int64(maxRequests), nil } // Session management func (r *RedisClient) SetSession(sessionID string, data map[string]interface{}, ttl time.Duration) error { key := fmt.Sprintf("session:%s", sessionID) return r.Set(key, data, ttl) } func (r *RedisClient) GetSession(sessionID string) (map[string]interface{}, error) { key := fmt.Sprintf("session:%s", sessionID) var data map[string]interface{} err := r.Get(key, &data) return data, err } func (r *RedisClient) DeleteSession(sessionID string) error { key := fmt.Sprintf("session:%s", sessionID) return r.Delete(key) } // Pub/Sub for real-time events func (r *RedisClient) Publish(channel string, message interface{}) error { data, err := json.Marshal(message) if err != nil { return err } return r.client.Publish(r.ctx, channel, data).Err() } func (r *RedisClient) Subscribe(channel string) *redis.PubSub { return r.client.Subscribe(r.ctx, channel) }