package services import ( "context" "encoding/json" "fmt" "sync" "time" ) // CacheService provides a simple in-memory cache with TTL type CacheService struct { mu sync.RWMutex items map[string]*cacheItem } type cacheItem struct { Value interface{} Expiration time.Time } var ( defaultCache *CacheService cacheInitOnce sync.Once ) // GetCacheService returns singleton cache instance func GetCacheService() *CacheService { cacheInitOnce.Do(func() { defaultCache = &CacheService{ items: make(map[string]*cacheItem), } // Start cleanup goroutine go defaultCache.startCleanup() }) return defaultCache } // Get retrieves value from cache func (cs *CacheService) Get(key string, dest interface{}) error { cs.mu.RLock() item, exists := cs.items[key] cs.mu.RUnlock() if !exists { return fmt.Errorf("key not found") } if time.Now().After(item.Expiration) { cs.Delete(key) return fmt.Errorf("key expired") } // Type assertion or JSON marshal/unmarshal switch v := item.Value.(type) { case []byte: return json.Unmarshal(v, dest) default: // Marshal and unmarshal for type conversion data, err := json.Marshal(item.Value) if err != nil { return err } return json.Unmarshal(data, dest) } } // Set stores value in cache with TTL func (cs *CacheService) Set(key string, value interface{}, ttl time.Duration) error { cs.mu.Lock() defer cs.mu.Unlock() cs.items[key] = &cacheItem{ Value: value, Expiration: time.Now().Add(ttl), } return nil } // Delete removes item from cache func (cs *CacheService) Delete(key string) { cs.mu.Lock() defer cs.mu.Unlock() delete(cs.items, key) } // Clear removes all items from cache func (cs *CacheService) Clear() { cs.mu.Lock() defer cs.mu.Unlock() cs.items = make(map[string]*cacheItem) } // Has checks if key exists and is not expired func (cs *CacheService) Has(key string) bool { cs.mu.RLock() item, exists := cs.items[key] cs.mu.RUnlock() if !exists { return false } if time.Now().After(item.Expiration) { cs.Delete(key) return false } return true } // GetOrSet retrieves from cache or executes function and caches result func (cs *CacheService) GetOrSet(key string, dest interface{}, ttl time.Duration, fn func() (interface{}, error)) error { // Try to get from cache err := cs.Get(key, dest) if err == nil { return nil } // Execute function value, err := fn() if err != nil { return err } // Store in cache if err := cs.Set(key, value, ttl); err != nil { return err } // Marshal and unmarshal for type conversion data, err := json.Marshal(value) if err != nil { return err } return json.Unmarshal(data, dest) } // startCleanup periodically removes expired items func (cs *CacheService) startCleanup() { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for range ticker.C { cs.cleanup() } } func (cs *CacheService) cleanup() { cs.mu.Lock() defer cs.mu.Unlock() now := time.Now() for key, item := range cs.items { if now.After(item.Expiration) { delete(cs.items, key) } } } // Stats returns cache statistics func (cs *CacheService) Stats() map[string]interface{} { cs.mu.RLock() defer cs.mu.RUnlock() expired := 0 now := time.Now() for _, item := range cs.items { if now.After(item.Expiration) { expired++ } } return map[string]interface{}{ "total_items": len(cs.items), "expired_items": expired, "active_items": len(cs.items) - expired, } } // CacheKey generates consistent cache keys func CacheKey(parts ...string) string { return "cache:" + joinStrings(parts, ":") } func joinStrings(parts []string, sep string) string { if len(parts) == 0 { return "" } result := parts[0] for i := 1; i < len(parts); i++ { result += sep + parts[i] } return result } // WithCache is a decorator for caching function results func WithCache(key string, ttl time.Duration, fn func() (interface{}, error)) (interface{}, error) { cache := GetCacheService() // Try cache first var result interface{} if err := cache.Get(key, &result); err == nil { return result, nil } // Execute function result, err := fn() if err != nil { return nil, err } // Cache result cache.Set(key, result, ttl) return result, nil } // InvalidateCachePattern removes all keys matching pattern func (cs *CacheService) InvalidateCachePattern(pattern string) { cs.mu.Lock() defer cs.mu.Unlock() for key := range cs.items { if matchesPattern(key, pattern) { delete(cs.items, key) } } } func matchesPattern(key, pattern string) bool { // Simple pattern matching - supports * wildcard if pattern == "*" { return true } // Check if pattern contains wildcard if len(pattern) > 0 && pattern[len(pattern)-1] == '*' { prefix := pattern[:len(pattern)-1] return len(key) >= len(prefix) && key[:len(prefix)] == prefix } return key == pattern } // WarmupCache preloads frequently accessed data func WarmupCache(ctx context.Context) error { _ = GetCacheService() // Available for warmup logic // Example: preload settings // This should be called on application startup // Add your warmup logic here // Example: // cache := GetCacheService() // settings, err := fetchSettings() // if err == nil { // cache.Set("settings:main", settings, 1*time.Hour) // } return nil }