package cache import ( "crypto/sha256" "encoding/hex" "sync" "time" ) // CacheEntry represents a cached value with expiration type CacheEntry struct { Value interface{} ExpiresAt time.Time CreatedAt time.Time } // IsExpired checks if the cache entry has expired func (e *CacheEntry) IsExpired() bool { return time.Now().After(e.ExpiresAt) } // Cache provides an in-memory cache with TTL support type Cache struct { entries map[string]*CacheEntry mu sync.RWMutex ttl time.Duration } // NewCache creates a new cache with a default TTL func NewCache(defaultTTL time.Duration) *Cache { c := &Cache{ entries: make(map[string]*CacheEntry), ttl: defaultTTL, } // Start background cleanup goroutine go c.cleanup() return c } // Get retrieves a value from the cache func (c *Cache) Get(key string) (interface{}, bool) { c.mu.RLock() defer c.mu.RUnlock() entry, exists := c.entries[key] if !exists { return nil, false } if entry.IsExpired() { // Don't delete here, let cleanup handle it return nil, false } return entry.Value, true } // Set stores a value in the cache with the default TTL func (c *Cache) Set(key string, value interface{}) { c.SetWithTTL(key, value, c.ttl) } // SetWithTTL stores a value in the cache with a custom TTL func (c *Cache) SetWithTTL(key string, value interface{}, ttl time.Duration) { c.mu.Lock() defer c.mu.Unlock() c.entries[key] = &CacheEntry{ Value: value, ExpiresAt: time.Now().Add(ttl), CreatedAt: time.Now(), } } // Delete removes a value from the cache func (c *Cache) Delete(key string) { c.mu.Lock() defer c.mu.Unlock() delete(c.entries, key) } // Clear removes all entries from the cache func (c *Cache) Clear() { c.mu.Lock() defer c.mu.Unlock() c.entries = make(map[string]*CacheEntry) } // cleanup periodically removes expired entries func (c *Cache) cleanup() { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() for range ticker.C { c.mu.Lock() for key, entry := range c.entries { if entry.IsExpired() { delete(c.entries, key) } } c.mu.Unlock() } } // Stats returns cache statistics func (c *Cache) Stats() map[string]interface{} { c.mu.RLock() defer c.mu.RUnlock() total := len(c.entries) expired := 0 for _, entry := range c.entries { if entry.IsExpired() { expired++ } } return map[string]interface{}{ "total_entries": total, "active_entries": total - expired, "expired_entries": expired, "default_ttl_seconds": int(c.ttl.Seconds()), } } // GenerateKey generates a cache key from a string func GenerateKey(prefix string, parts ...string) string { key := prefix for _, part := range parts { key += ":" + part } // Hash long keys to keep them manageable if len(key) > 200 { hash := sha256.Sum256([]byte(key)) return prefix + ":" + hex.EncodeToString(hash[:]) } return key }