251 lines
6.3 KiB
Go
251 lines
6.3 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/kms/api-key-service/internal/config"
|
|
"github.com/kms/api-key-service/internal/errors"
|
|
)
|
|
|
|
// CacheProvider defines the interface for cache operations
|
|
type CacheProvider interface {
|
|
// Get retrieves a value from cache
|
|
Get(ctx context.Context, key string) ([]byte, error)
|
|
|
|
// Set stores a value in cache with TTL
|
|
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
|
|
|
|
// Delete removes a value from cache
|
|
Delete(ctx context.Context, key string) error
|
|
|
|
// Exists checks if a key exists in cache
|
|
Exists(ctx context.Context, key string) (bool, error)
|
|
|
|
// Clear removes all cached values (use with caution)
|
|
Clear(ctx context.Context) error
|
|
|
|
// Close closes the cache connection
|
|
Close() error
|
|
}
|
|
|
|
// MemoryCache implements CacheProvider using in-memory storage
|
|
type MemoryCache struct {
|
|
data map[string]cacheItem
|
|
config config.ConfigProvider
|
|
logger *zap.Logger
|
|
}
|
|
|
|
type cacheItem struct {
|
|
Value []byte
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
// NewMemoryCache creates a new in-memory cache
|
|
func NewMemoryCache(config config.ConfigProvider, logger *zap.Logger) CacheProvider {
|
|
cache := &MemoryCache{
|
|
data: make(map[string]cacheItem),
|
|
config: config,
|
|
logger: logger,
|
|
}
|
|
|
|
// Start cleanup goroutine
|
|
go cache.cleanup()
|
|
|
|
return cache
|
|
}
|
|
|
|
// Get retrieves a value from memory cache
|
|
func (m *MemoryCache) Get(ctx context.Context, key string) ([]byte, error) {
|
|
m.logger.Debug("Getting value from memory cache", zap.String("key", key))
|
|
|
|
item, exists := m.data[key]
|
|
if !exists {
|
|
return nil, errors.NewNotFoundError("cache key")
|
|
}
|
|
|
|
// Check if expired
|
|
if time.Now().After(item.ExpiresAt) {
|
|
delete(m.data, key)
|
|
return nil, errors.NewNotFoundError("cache key")
|
|
}
|
|
|
|
return item.Value, nil
|
|
}
|
|
|
|
// Set stores a value in memory cache
|
|
func (m *MemoryCache) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
|
|
m.logger.Debug("Setting value in memory cache",
|
|
zap.String("key", key),
|
|
zap.Duration("ttl", ttl))
|
|
|
|
m.data[key] = cacheItem{
|
|
Value: value,
|
|
ExpiresAt: time.Now().Add(ttl),
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete removes a value from memory cache
|
|
func (m *MemoryCache) Delete(ctx context.Context, key string) error {
|
|
m.logger.Debug("Deleting value from memory cache", zap.String("key", key))
|
|
|
|
delete(m.data, key)
|
|
return nil
|
|
}
|
|
|
|
// Exists checks if a key exists in memory cache
|
|
func (m *MemoryCache) Exists(ctx context.Context, key string) (bool, error) {
|
|
item, exists := m.data[key]
|
|
if !exists {
|
|
return false, nil
|
|
}
|
|
|
|
// Check if expired
|
|
if time.Now().After(item.ExpiresAt) {
|
|
delete(m.data, key)
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Clear removes all values from memory cache
|
|
func (m *MemoryCache) Clear(ctx context.Context) error {
|
|
m.logger.Debug("Clearing memory cache")
|
|
|
|
m.data = make(map[string]cacheItem)
|
|
return nil
|
|
}
|
|
|
|
// Close closes the memory cache (no-op for memory cache)
|
|
func (m *MemoryCache) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// cleanup removes expired items from memory cache
|
|
func (m *MemoryCache) cleanup() {
|
|
ticker := time.NewTicker(5 * time.Minute) // Cleanup every 5 minutes
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
now := time.Now()
|
|
for key, item := range m.data {
|
|
if now.After(item.ExpiresAt) {
|
|
delete(m.data, key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// CacheManager provides high-level caching operations with JSON serialization
|
|
type CacheManager struct {
|
|
provider CacheProvider
|
|
config config.ConfigProvider
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewCacheManager creates a new cache manager
|
|
func NewCacheManager(config config.ConfigProvider, logger *zap.Logger) *CacheManager {
|
|
var provider CacheProvider
|
|
|
|
// For now, we'll use memory cache. In production, this could be Redis
|
|
provider = NewMemoryCache(config, logger)
|
|
|
|
return &CacheManager{
|
|
provider: provider,
|
|
config: config,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// GetJSON retrieves and unmarshals a JSON value from cache
|
|
func (c *CacheManager) GetJSON(ctx context.Context, key string, dest interface{}) error {
|
|
c.logger.Debug("Getting JSON from cache", zap.String("key", key))
|
|
|
|
data, err := c.provider.Get(ctx, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := json.Unmarshal(data, dest); err != nil {
|
|
c.logger.Error("Failed to unmarshal cached JSON", zap.Error(err))
|
|
return errors.NewInternalError("Failed to unmarshal cached data").WithInternal(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetJSON marshals and stores a JSON value in cache
|
|
func (c *CacheManager) SetJSON(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
|
|
c.logger.Debug("Setting JSON in cache",
|
|
zap.String("key", key),
|
|
zap.Duration("ttl", ttl))
|
|
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
c.logger.Error("Failed to marshal JSON for cache", zap.Error(err))
|
|
return errors.NewInternalError("Failed to marshal data for cache").WithInternal(err)
|
|
}
|
|
|
|
return c.provider.Set(ctx, key, data, ttl)
|
|
}
|
|
|
|
// Get retrieves raw bytes from cache
|
|
func (c *CacheManager) Get(ctx context.Context, key string) ([]byte, error) {
|
|
return c.provider.Get(ctx, key)
|
|
}
|
|
|
|
// Set stores raw bytes in cache
|
|
func (c *CacheManager) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
|
|
return c.provider.Set(ctx, key, value, ttl)
|
|
}
|
|
|
|
// Delete removes a value from cache
|
|
func (c *CacheManager) Delete(ctx context.Context, key string) error {
|
|
return c.provider.Delete(ctx, key)
|
|
}
|
|
|
|
// Exists checks if a key exists in cache
|
|
func (c *CacheManager) Exists(ctx context.Context, key string) (bool, error) {
|
|
return c.provider.Exists(ctx, key)
|
|
}
|
|
|
|
// Clear removes all cached values
|
|
func (c *CacheManager) Clear(ctx context.Context) error {
|
|
return c.provider.Clear(ctx)
|
|
}
|
|
|
|
// Close closes the cache connection
|
|
func (c *CacheManager) Close() error {
|
|
return c.provider.Close()
|
|
}
|
|
|
|
// GetDefaultTTL returns the default TTL from config
|
|
func (c *CacheManager) GetDefaultTTL() time.Duration {
|
|
return c.config.GetDuration("CACHE_TTL")
|
|
}
|
|
|
|
// IsEnabled returns whether caching is enabled
|
|
func (c *CacheManager) IsEnabled() bool {
|
|
return c.config.GetBool("CACHE_ENABLED")
|
|
}
|
|
|
|
// CacheKey generates a cache key with prefix
|
|
func CacheKey(prefix, key string) string {
|
|
return prefix + ":" + key
|
|
}
|
|
|
|
// Common cache key prefixes
|
|
const (
|
|
KeyPrefixPermission = "perm"
|
|
KeyPrefixApplication = "app"
|
|
KeyPrefixToken = "token"
|
|
KeyPrefixUserClaims = "user_claims"
|
|
KeyPrefixTokenRevoked = "token_revoked"
|
|
)
|