package cache import ( "context" "encoding/json" "fmt" "history-api/pkg/config" "time" "github.com/redis/go-redis/v9" ) type Cache interface { Set(ctx context.Context, key string, value any, ttl time.Duration) error Get(ctx context.Context, key string, dest any) error Del(ctx context.Context, keys ...string) error DelByPattern(ctx context.Context, pattern string) error MGet(ctx context.Context, keys ...string) [][]byte MSet(ctx context.Context, pairs map[string]any, ttl time.Duration) error } type RedisClient struct { client *redis.Client } func NewRedisClient() (Cache, error) { uri, err := config.GetConfig("REDIS_CONNECTION_URI") if err != nil { return nil, err } rdb := redis.NewClient(&redis.Options{ Addr: uri, MinIdleConns: 10, DialTimeout: 5 * time.Second, ReadTimeout: 3 * time.Second, WriteTimeout: 3 * time.Second, MaxRetries: 3, MinRetryBackoff: 8 * time.Millisecond, MaxRetryBackoff: 512 * time.Millisecond, DisableIdentity: true, }) if err := rdb.Ping(context.Background()).Err(); err != nil { return nil, fmt.Errorf("could not connect to Redis: %v", err) } return &RedisClient{client: rdb}, nil } func (r *RedisClient) Del(ctx context.Context, keys ...string) error { if len(keys) == 0 { return nil } return r.client.Del(ctx, keys...).Err() } func (r *RedisClient) DelByPattern(ctx context.Context, pattern string) error { var cursor uint64 for { keys, nextCursor, err := r.client.Scan(ctx, cursor, pattern, 100).Result() if err != nil { return fmt.Errorf("error scanning keys with pattern %s: %v", pattern, err) } if len(keys) > 0 { if err := r.client.Del(ctx, keys...).Err(); err != nil { return fmt.Errorf("error deleting keys during scan: %v", err) } } cursor = nextCursor if cursor == 0 { break } } return nil } func (r *RedisClient) Set(ctx context.Context, key string, value any, ttl time.Duration) error { data, err := json.Marshal(value) if err != nil { return err } return r.client.Set(ctx, key, data, ttl).Err() } func (r *RedisClient) Get(ctx context.Context, key string, dest any) error { data, err := r.client.Get(ctx, key).Bytes() if err != nil { return err } return json.Unmarshal(data, dest) } func (r *RedisClient) MSet(ctx context.Context, pairs map[string]any, ttl time.Duration) error { pipe := r.client.Pipeline() for key, value := range pairs { data, err := json.Marshal(value) if err != nil { return fmt.Errorf("failed to marshal key %s: %v", key, err) } pipe.Set(ctx, key, data, ttl) } _, err := pipe.Exec(ctx) return err } func (r *RedisClient) MGet(ctx context.Context, keys ...string) [][]byte { res, err := r.client.MGet(ctx, keys...).Result() if err != nil { return nil } results := make([][]byte, len(res)) for i, val := range res { if val != nil { results[i] = []byte(val.(string)) } } return results } func GetMultiple[T any](ctx context.Context, c Cache, keys []string) ([]T, error) { raws := c.MGet(ctx, keys...) final := make([]T, 0) for _, b := range raws { if b == nil { continue } var item T if err := json.Unmarshal(b, &item); err == nil { final = append(final, item) } } return final, nil }