package cache import ( "context" "encoding/json" "sync" "sync/atomic" "time" domain "knowfoolery/backend/services/question-bank-service/internal/domain/question" sharedredis "knowfoolery/backend/shared/infra/database/redis" ) // RandomQuestionCache provides cache behavior for random question retrieval. type RandomQuestionCache struct { client *sharedredis.Client version atomic.Uint64 localMu sync.RWMutex local map[string]cacheEntry } type cacheEntry struct { question domain.Question expiresAt time.Time } // NewRandomQuestionCache creates a new cache adapter. func NewRandomQuestionCache(client *sharedredis.Client) *RandomQuestionCache { c := &RandomQuestionCache{ client: client, local: make(map[string]cacheEntry), } c.version.Store(1) return c } // Get returns a cached question if present. func (c *RandomQuestionCache) Get(ctx context.Context, key string) (*domain.Question, bool) { nsKey := c.namespacedKey(key) if c.client != nil { value, err := c.client.Get(ctx, nsKey) if err == nil { var q domain.Question if json.Unmarshal([]byte(value), &q) == nil { return &q, true } } } c.localMu.RLock() entry, ok := c.local[nsKey] c.localMu.RUnlock() if !ok || time.Now().After(entry.expiresAt) { return nil, false } q := entry.question return &q, true } // Set stores a question in cache. func (c *RandomQuestionCache) Set(ctx context.Context, key string, q *domain.Question, ttl time.Duration) { nsKey := c.namespacedKey(key) payload, err := json.Marshal(q) if err == nil && c.client != nil { _ = c.client.Set(ctx, nsKey, string(payload), ttl) } c.localMu.Lock() c.local[nsKey] = cacheEntry{question: *q, expiresAt: time.Now().Add(ttl)} c.localMu.Unlock() } // Invalidate bumps cache namespace version to invalidate current keys. func (c *RandomQuestionCache) Invalidate(ctx context.Context) { _ = ctx c.version.Add(1) } func (c *RandomQuestionCache) namespacedKey(key string) string { return "v" + itoa(c.version.Load()) + ":" + key } func itoa(i uint64) string { if i == 0 { return "0" } buf := [20]byte{} pos := len(buf) for i > 0 { pos-- buf[pos] = byte('0' + i%10) i /= 10 } return string(buf[pos:]) }