// Package redis provides Redis client for the KnowFoolery application. package redis import ( "context" "errors" "fmt" "time" redisv9 "github.com/redis/go-redis/v9" "knowfoolery/backend/shared/infra/utils/envutil" ) // Config holds the configuration for the Redis client. type Config struct { Host string Port int Password string DB int PoolSize int MinIdleConns int DialTimeout time.Duration ReadTimeout time.Duration WriteTimeout time.Duration } // DefaultConfig returns a default configuration for development. func DefaultConfig() Config { return Config{ Host: "localhost", Port: 6379, Password: "", DB: 0, PoolSize: 10, MinIdleConns: 5, DialTimeout: 5 * time.Second, ReadTimeout: 3 * time.Second, WriteTimeout: 3 * time.Second, } } // Addr returns the Redis server address. func (c Config) Addr() string { return fmt.Sprintf("%s:%d", c.Host, c.Port) } // Client wraps Redis operations. // This is a placeholder that should be replaced with an actual Redis client. type Client struct { config Config client *redisv9.Client } // NewClient creates a new Redis client. func NewClient(config Config) (*Client, error) { if err := validateConfig(config); err != nil { return nil, err } client := redisv9.NewClient(&redisv9.Options{ Addr: config.Addr(), Password: config.Password, DB: config.DB, PoolSize: config.PoolSize, MinIdleConns: config.MinIdleConns, DialTimeout: config.DialTimeout, ReadTimeout: config.ReadTimeout, WriteTimeout: config.WriteTimeout, }) return &Client{ config: config, client: client, }, nil } // Close closes the Redis connection. func (c *Client) Close() error { return c.client.Close() } // Ping checks if the Redis connection is alive. func (c *Client) Ping(ctx context.Context) error { return c.client.Ping(ctx).Err() } // HealthCheck performs a health check on Redis. func (c *Client) HealthCheck(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() return c.Ping(ctx) } // Set stores a key-value pair with expiration. func (c *Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error { return c.client.Set(ctx, key, value, expiration).Err() } // Get retrieves a value by key. func (c *Client) Get(ctx context.Context, key string) (string, error) { return c.client.Get(ctx, key).Result() } // Delete removes a key. func (c *Client) Delete(ctx context.Context, keys ...string) error { return c.client.Del(ctx, keys...).Err() } // Exists checks if a key exists. func (c *Client) Exists(ctx context.Context, key string) (bool, error) { n, err := c.client.Exists(ctx, key).Result() if err != nil { return false, err } return n > 0, nil } // Incr increments a counter. func (c *Client) Incr(ctx context.Context, key string) (int64, error) { return c.client.Incr(ctx, key).Result() } // Expire sets expiration on a key. func (c *Client) Expire(ctx context.Context, key string, expiration time.Duration) error { return c.client.Expire(ctx, key, expiration).Err() } // ConfigFromEnv creates a Config from environment variables. func ConfigFromEnv() Config { cfg := DefaultConfig() cfg.Host = envutil.String("REDIS_HOST", cfg.Host) cfg.Port = envutil.Int("REDIS_PORT", cfg.Port) cfg.Password = envutil.String("REDIS_PASSWORD", cfg.Password) cfg.DB = envutil.Int("REDIS_DB", cfg.DB) cfg.PoolSize = envutil.Int("REDIS_POOL_SIZE", cfg.PoolSize) cfg.MinIdleConns = envutil.Int("REDIS_MIN_IDLE_CONNS", cfg.MinIdleConns) cfg.DialTimeout = envutil.Duration("REDIS_DIAL_TIMEOUT", cfg.DialTimeout) cfg.ReadTimeout = envutil.Duration("REDIS_READ_TIMEOUT", cfg.ReadTimeout) cfg.WriteTimeout = envutil.Duration("REDIS_WRITE_TIMEOUT", cfg.WriteTimeout) return cfg } func validateConfig(c Config) error { switch { case c.Host == "": return errors.New("redis host is required") case c.Port <= 0: return errors.New("redis port must be greater than 0") case c.PoolSize <= 0: return errors.New("redis pool size must be greater than 0") case c.MinIdleConns < 0: return errors.New("redis min idle conns must be >= 0") case c.DialTimeout <= 0: return errors.New("redis dial timeout must be greater than 0") case c.ReadTimeout <= 0: return errors.New("redis read timeout must be greater than 0") case c.WriteTimeout <= 0: return errors.New("redis write timeout must be greater than 0") } return nil }