package licensing import ( "sync" "time" "github.com/google/uuid" ) // CacheEntry holds cached license validation data type CacheEntry struct { Response *ValidationResponse ExpiresAt time.Time } // LicenseCache provides in-memory caching for license validations type LicenseCache struct { mu sync.RWMutex entries map[string]*CacheEntry ttl time.Duration } // NewLicenseCache creates a new license cache with specified TTL func NewLicenseCache(ttl time.Duration) *LicenseCache { cache := &LicenseCache{ entries: make(map[string]*CacheEntry), ttl: ttl, } // Start cleanup goroutine go cache.cleanup() return cache } // Get retrieves cached validation response if available and not expired func (c *LicenseCache) Get(deploymentID uuid.UUID, feature string) *ValidationResponse { c.mu.RLock() defer c.mu.RUnlock() key := c.cacheKey(deploymentID, feature) entry, exists := c.entries[key] if !exists || time.Now().After(entry.ExpiresAt) { return nil } return entry.Response } // Set stores validation response in cache with TTL func (c *LicenseCache) Set(deploymentID uuid.UUID, feature string, response *ValidationResponse) { c.mu.Lock() defer c.mu.Unlock() key := c.cacheKey(deploymentID, feature) c.entries[key] = &CacheEntry{ Response: response, ExpiresAt: time.Now().Add(c.ttl), } } // Invalidate removes specific cache entry func (c *LicenseCache) Invalidate(deploymentID uuid.UUID, feature string) { c.mu.Lock() defer c.mu.Unlock() key := c.cacheKey(deploymentID, feature) delete(c.entries, key) } // InvalidateAll removes all cached entries for a deployment func (c *LicenseCache) InvalidateAll(deploymentID uuid.UUID) { c.mu.Lock() defer c.mu.Unlock() prefix := deploymentID.String() + ":" for key := range c.entries { if len(key) > len(prefix) && key[:len(prefix)] == prefix { delete(c.entries, key) } } } // Clear removes all cached entries func (c *LicenseCache) Clear() { c.mu.Lock() defer c.mu.Unlock() c.entries = make(map[string]*CacheEntry) } // Stats returns cache statistics func (c *LicenseCache) Stats() map[string]interface{} { c.mu.RLock() defer c.mu.RUnlock() totalEntries := len(c.entries) expiredEntries := 0 now := time.Now() for _, entry := range c.entries { if now.After(entry.ExpiresAt) { expiredEntries++ } } return map[string]interface{}{ "total_entries": totalEntries, "expired_entries": expiredEntries, "active_entries": totalEntries - expiredEntries, "ttl_seconds": int(c.ttl.Seconds()), } } // cacheKey generates cache key from deployment ID and feature func (c *LicenseCache) cacheKey(deploymentID uuid.UUID, feature string) string { return deploymentID.String() + ":" + feature } // cleanup removes expired entries periodically func (c *LicenseCache) cleanup() { ticker := time.NewTicker(c.ttl / 2) // Clean up twice as often as TTL defer ticker.Stop() for range ticker.C { c.mu.Lock() now := time.Now() for key, entry := range c.entries { if now.After(entry.ExpiresAt) { delete(c.entries, key) } } c.mu.Unlock() } }