package database import ( "context" "encoding/json" "log" "net" "sync" "time" "xboard-go/internal/config" "github.com/redis/go-redis/v9" ) var Redis *redis.Client type memoryEntry struct { Value []byte ExpiresAt time.Time } type memoryCache struct { mu sync.RWMutex items map[string]memoryEntry } var fallbackCache = &memoryCache{ items: make(map[string]memoryEntry), } func InitCache() { if config.AppConfig.RedisHost == "" { if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "warn") { log.Printf("Redis host not configured, using in-memory cache") } return } addr := net.JoinHostPort(config.AppConfig.RedisHost, config.AppConfig.RedisPort) client := redis.NewClient(&redis.Options{ Addr: addr, Password: config.AppConfig.RedisPass, DB: config.AppConfig.RedisDB, }) // Fast timeout for initial connectivity check to avoid blocking startup ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() if err := client.Ping(ctx).Err(); err != nil { if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "warn") { log.Printf("Redis unavailable at %s, falling back to in-memory: %v", addr, err) } return } Redis = client if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") { log.Printf("Redis connection established at %s", addr) } } func CacheSet(key string, value any, ttl time.Duration) error { payload, err := json.Marshal(value) if err != nil { return err } if Redis != nil { if err := Redis.Set(context.Background(), key, payload, ttl).Err(); err == nil { return nil } } fallbackCache.Set(key, payload, ttl) return nil } func CacheDelete(key string) error { if Redis != nil { if err := Redis.Del(context.Background(), key).Err(); err == nil { fallbackCache.Delete(key) return nil } } fallbackCache.Delete(key) return nil } func CacheGetJSON[T any](key string) (T, bool) { var zero T var payload []byte if Redis != nil { value, err := Redis.Get(context.Background(), key).Bytes() if err == nil { payload = value } } if len(payload) == 0 { value, ok := fallbackCache.Get(key) if !ok { return zero, false } payload = value } var result T if err := json.Unmarshal(payload, &result); err != nil { return zero, false } return result, true } func (m *memoryCache) Set(key string, value []byte, ttl time.Duration) { m.mu.Lock() defer m.mu.Unlock() entry := memoryEntry{ Value: value, } if ttl > 0 { entry.ExpiresAt = time.Now().Add(ttl) } m.items[key] = entry } func (m *memoryCache) Get(key string) ([]byte, bool) { m.mu.RLock() entry, ok := m.items[key] m.mu.RUnlock() if !ok { return nil, false } if !entry.ExpiresAt.IsZero() && time.Now().After(entry.ExpiresAt) { m.Delete(key) return nil, false } return entry.Value, true } func (m *memoryCache) Delete(key string) { m.mu.Lock() defer m.mu.Unlock() delete(m.items, key) }