1package redis
2
3import (
4 "context"
5 "time"
6
7 "github.com/charmbracelet/soft-serve/server/cache"
8 "github.com/redis/go-redis/v9"
9)
10
11// Cache is a Redis cache.
12type Cache struct {
13 client *redis.Client
14}
15
16// NewCache returns a new Redis cache.
17// It converts non-string types to JSON before storing/retrieving them.
18func NewCache(ctx context.Context, _ ...cache.Option) (cache.Cache, error) {
19 cfg, err := NewConfig("")
20 if err != nil {
21 return nil, err
22 }
23
24 client := redis.NewClient(&redis.Options{
25 Addr: cfg.Addr,
26 Username: cfg.Username,
27 Password: cfg.Password,
28 DB: cfg.DB,
29 })
30
31 return &Cache{
32 client: client,
33 }, client.Ping(ctx).Err()
34}
35
36type option struct {
37 cache.Item
38 ttl time.Duration
39}
40
41func (*option) item() {}
42
43// WithTTL sets the TTL for the cache item.
44func WithTTL(ttl time.Duration) cache.ItemOption {
45 return func(io cache.Item) {
46 i := io.(*option)
47 i.ttl = ttl
48 }
49}
50
51// Contains implements cache.Cache.
52func (r *Cache) Contains(ctx context.Context, key string) bool {
53 return r.client.Exists(ctx, key).Val() == 1
54}
55
56// Delete implements cache.Cache.
57func (r *Cache) Delete(ctx context.Context, key string) {
58 r.client.Del(ctx, key)
59}
60
61// Get implements cache.Cache.
62func (r *Cache) Get(ctx context.Context, key string) (value any, ok bool) {
63 val := r.client.Get(ctx, key)
64 if val.Err() != nil {
65 return nil, false
66 }
67
68 return val.Val(), true
69}
70
71// Keys implements cache.Cache.
72func (r *Cache) Keys(ctx context.Context) []string {
73 return r.client.Keys(ctx, "*").Val()
74}
75
76// Len implements cache.Cache.
77func (r *Cache) Len(ctx context.Context) int64 {
78 return r.client.DBSize(ctx).Val()
79}
80
81// Set implements cache.Cache.
82func (r *Cache) Set(ctx context.Context, key string, val any, opts ...cache.ItemOption) {
83 var opt option
84 for _, o := range opts {
85 o(&opt)
86 }
87 r.client.Set(ctx, key, val, opt.ttl)
88}