package cache import ( "context" "encoding/json" "fmt" "time" "github.com/redis/go-redis/v9" ) type Store struct { rdb *redis.Client prefix string } func NewStore(rdb *redis.Client, prefix string) *Store { return &Store{rdb: rdb, prefix: prefix} } func (s *Store) key(tenantID, key string) string { if tenantID == "" { return fmt.Sprintf("%s:%s", s.prefix, key) } return fmt.Sprintf("%s:%s:%s", s.prefix, tenantID, key) } func (s *Store) Get(ctx context.Context, tenantID, key string, dest interface{}) error { val, err := s.rdb.Get(ctx, s.key(tenantID, key)).Result() if err != nil { return err } return json.Unmarshal([]byte(val), dest) } func (s *Store) Set(ctx context.Context, tenantID, key string, value interface{}, ttl time.Duration) error { data, err := json.Marshal(value) if err != nil { return fmt.Errorf("cache marshal: %w", err) } return s.rdb.Set(ctx, s.key(tenantID, key), data, ttl).Err() } func (s *Store) Delete(ctx context.Context, tenantID, key string) error { return s.rdb.Del(ctx, s.key(tenantID, key)).Err() } func (s *Store) DeletePattern(ctx context.Context, tenantID, pattern string) error { fullPattern := s.key(tenantID, pattern) keys, err := s.rdb.Keys(ctx, fullPattern).Result() if err != nil { return err } if len(keys) == 0 { return nil } return s.rdb.Del(ctx, keys...).Err() } func (s *Store) GetOrSet(ctx context.Context, tenantID, key string, dest interface{}, ttl time.Duration, fetch func() (interface{}, error)) error { err := s.Get(ctx, tenantID, key, dest) if err == nil { return nil } if err != redis.Nil { return err } val, err := fetch() if err != nil { return err } data, err := json.Marshal(val) if err != nil { return fmt.Errorf("cache marshal: %w", err) } if err := s.rdb.Set(ctx, s.key(tenantID, key), data, ttl).Err(); err != nil { return err } return json.Unmarshal(data, dest) } func (s *Store) Exists(ctx context.Context, tenantID, key string) (bool, error) { n, err := s.rdb.Exists(ctx, s.key(tenantID, key)).Result() return n > 0, err } func (s *Store) Expire(ctx context.Context, tenantID, key string, ttl time.Duration) error { return s.rdb.Expire(ctx, s.key(tenantID, key), ttl).Err() } func (s *Store) Incr(ctx context.Context, tenantID, key string) (int64, error) { return s.rdb.Incr(ctx, s.key(tenantID, key)).Result() } func (s *Store) Client() *redis.Client { return s.rdb }