package admin import ( "crypto/sha256" "encoding/hex" "encoding/json" "strings" "sync" "time" "golang.org/x/sync/singleflight" ) type snapshotCacheEntry struct { ETag string Payload any ExpiresAt time.Time } type snapshotCache struct { mu sync.RWMutex ttl time.Duration items map[string]snapshotCacheEntry sf singleflight.Group } type snapshotCacheLoadResult struct { Entry snapshotCacheEntry Hit bool } func newSnapshotCache(ttl time.Duration) *snapshotCache { if ttl <= 0 { ttl = 30 * time.Second } return &snapshotCache{ ttl: ttl, items: make(map[string]snapshotCacheEntry), } } func (c *snapshotCache) Get(key string) (snapshotCacheEntry, bool) { if c == nil || key == "" { return snapshotCacheEntry{}, false } now := time.Now() c.mu.RLock() entry, ok := c.items[key] c.mu.RUnlock() if !ok { return snapshotCacheEntry{}, false } if now.After(entry.ExpiresAt) { c.mu.Lock() delete(c.items, key) c.mu.Unlock() return snapshotCacheEntry{}, false } return entry, true } func (c *snapshotCache) Set(key string, payload any) snapshotCacheEntry { if c == nil { return snapshotCacheEntry{} } entry := snapshotCacheEntry{ ETag: buildETagFromAny(payload), Payload: payload, ExpiresAt: time.Now().Add(c.ttl), } if key == "" { return entry } c.mu.Lock() c.items[key] = entry c.mu.Unlock() return entry } func (c *snapshotCache) GetOrLoad(key string, load func() (any, error)) (snapshotCacheEntry, bool, error) { if load == nil { return snapshotCacheEntry{}, false, nil } if entry, ok := c.Get(key); ok { return entry, true, nil } if c == nil || key == "" { payload, err := load() if err != nil { return snapshotCacheEntry{}, false, err } return c.Set(key, payload), false, nil } value, err, _ := c.sf.Do(key, func() (any, error) { if entry, ok := c.Get(key); ok { return snapshotCacheLoadResult{Entry: entry, Hit: true}, nil } payload, err := load() if err != nil { return nil, err } return snapshotCacheLoadResult{Entry: c.Set(key, payload), Hit: false}, nil }) if err != nil { return snapshotCacheEntry{}, false, err } result, ok := value.(snapshotCacheLoadResult) if !ok { return snapshotCacheEntry{}, false, nil } return result.Entry, result.Hit, nil } func buildETagFromAny(payload any) string { raw, err := json.Marshal(payload) if err != nil { return "" } sum := sha256.Sum256(raw) return "\"" + hex.EncodeToString(sum[:]) + "\"" } func parseBoolQueryWithDefault(raw string, def bool) bool { value := strings.TrimSpace(strings.ToLower(raw)) if value == "" { return def } switch value { case "1", "true", "yes", "on": return true case "0", "false", "no", "off": return false default: return def } }