mirror of
https://github.com/Dvorinka/SEEN.git
synced 2026-06-04 20:43:03 +00:00
small fix, don't worry about it
This commit is contained in:
+235
@@ -0,0 +1,235 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/tdvorak/seen/backend/internal/config"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Manager provides centralized cache management
|
||||
type Manager struct {
|
||||
client *redis.Client
|
||||
service *Service
|
||||
session *SessionCache
|
||||
catalog *CatalogCache
|
||||
download *DownloadCache
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
// NewManager creates a new cache manager with all cache services
|
||||
func NewManager(ctx context.Context, cfg config.CacheConfig, log *zap.Logger) (*Manager, error) {
|
||||
client, err := NewClient(ctx, cfg, log)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cache client: %w", err)
|
||||
}
|
||||
|
||||
service := NewService(client)
|
||||
namespace := "seen"
|
||||
|
||||
manager := &Manager{
|
||||
client: client,
|
||||
service: service,
|
||||
session: NewSessionCache(service, namespace),
|
||||
catalog: NewCatalogCache(service, namespace),
|
||||
download: NewDownloadCache(service, namespace),
|
||||
log: log,
|
||||
}
|
||||
|
||||
// Start background cleanup tasks
|
||||
go manager.startCleanupTasks(ctx)
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
// Client returns the underlying Redis client
|
||||
func (m *Manager) Client() *redis.Client {
|
||||
return m.client
|
||||
}
|
||||
|
||||
// Service returns the cache service
|
||||
func (m *Manager) Service() *Service {
|
||||
return m.service
|
||||
}
|
||||
|
||||
// Session returns the session cache
|
||||
func (m *Manager) Session() *SessionCache {
|
||||
return m.session
|
||||
}
|
||||
|
||||
// Catalog returns the catalog cache
|
||||
func (m *Manager) Catalog() *CatalogCache {
|
||||
return m.catalog
|
||||
}
|
||||
|
||||
// Download returns the download cache
|
||||
func (m *Manager) Download() *DownloadCache {
|
||||
return m.download
|
||||
}
|
||||
|
||||
// Ping checks if the cache is responsive
|
||||
func (m *Manager) Ping(ctx context.Context) error {
|
||||
return m.service.Ping(ctx)
|
||||
}
|
||||
|
||||
// Close closes all cache connections
|
||||
func (m *Manager) Close() error {
|
||||
m.log.Info("closing cache connections")
|
||||
return m.service.Close()
|
||||
}
|
||||
|
||||
// Stats returns cache statistics
|
||||
func (m *Manager) Stats(ctx context.Context) (map[string]interface{}, error) {
|
||||
info, err := m.client.Info(ctx, "stats", "memory", "keyspace").Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get cache stats: %w", err)
|
||||
}
|
||||
|
||||
dbSize, err := m.client.DBSize(ctx).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get db size: %w", err)
|
||||
}
|
||||
|
||||
stats := map[string]interface{}{
|
||||
"dbSize": dbSize,
|
||||
"info": info,
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// FlushAll clears all cache data (use with extreme caution!)
|
||||
func (m *Manager) FlushAll(ctx context.Context) error {
|
||||
m.log.Warn("flushing all cache data")
|
||||
return m.service.FlushDB(ctx)
|
||||
}
|
||||
|
||||
// startCleanupTasks starts background tasks for cache maintenance
|
||||
func (m *Manager) startCleanupTasks(ctx context.Context) {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.log.Info("stopping cache cleanup tasks")
|
||||
return
|
||||
case <-ticker.C:
|
||||
m.runCleanupTasks(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runCleanupTasks performs periodic cache maintenance
|
||||
func (m *Manager) runCleanupTasks(ctx context.Context) {
|
||||
// Cleanup stale download progress (older than 1 hour)
|
||||
if err := m.download.CleanupStaleProgress(ctx, 1*time.Hour); err != nil {
|
||||
m.log.Error("failed to cleanup stale download progress", zap.Error(err))
|
||||
}
|
||||
|
||||
// Log cache stats
|
||||
stats, err := m.Stats(ctx)
|
||||
if err != nil {
|
||||
m.log.Error("failed to get cache stats", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if dbSize, ok := stats["dbSize"].(int64); ok {
|
||||
m.log.Debug("cache stats", zap.Int64("keys", dbSize))
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateUser removes all cached data for a user
|
||||
func (m *Manager) InvalidateUser(ctx context.Context, userID string) error {
|
||||
m.log.Info("invalidating user cache", zap.String("userId", userID))
|
||||
|
||||
// Invalidate user data
|
||||
if err := m.session.DeleteUser(ctx, userID); err != nil {
|
||||
m.log.Error("failed to delete user cache", zap.Error(err))
|
||||
}
|
||||
|
||||
// Invalidate user sessions
|
||||
if err := m.session.InvalidateUserSessions(ctx, userID); err != nil {
|
||||
m.log.Error("failed to invalidate user sessions", zap.Error(err))
|
||||
}
|
||||
|
||||
// Invalidate catalog data
|
||||
if err := m.catalog.InvalidateUserCatalog(ctx, userID); err != nil {
|
||||
m.log.Error("failed to invalidate user catalog", zap.Error(err))
|
||||
}
|
||||
|
||||
// Invalidate download data
|
||||
if err := m.download.InvalidateUserDownloads(ctx, userID); err != nil {
|
||||
m.log.Error("failed to invalidate user downloads", zap.Error(err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WarmupCache pre-populates cache with common data
|
||||
func (m *Manager) WarmupCache(ctx context.Context) error {
|
||||
m.log.Info("warming up cache")
|
||||
|
||||
// Add warmup logic here as needed
|
||||
// For example, pre-cache popular catalog sections
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HealthCheck performs a comprehensive health check
|
||||
func (m *Manager) HealthCheck(ctx context.Context) error {
|
||||
// Check basic connectivity
|
||||
if err := m.Ping(ctx); err != nil {
|
||||
return fmt.Errorf("ping failed: %w", err)
|
||||
}
|
||||
|
||||
// Test write operation
|
||||
testKey := "health:check"
|
||||
testValue := map[string]interface{}{
|
||||
"timestamp": time.Now().UTC(),
|
||||
"test": true,
|
||||
}
|
||||
|
||||
if err := m.service.Set(ctx, testKey, testValue, 10*time.Second); err != nil {
|
||||
return fmt.Errorf("write test failed: %w", err)
|
||||
}
|
||||
|
||||
// Test read operation
|
||||
var readValue map[string]interface{}
|
||||
if err := m.service.Get(ctx, testKey, &readValue); err != nil {
|
||||
return fmt.Errorf("read test failed: %w", err)
|
||||
}
|
||||
|
||||
// Cleanup test key
|
||||
if err := m.service.Delete(ctx, testKey); err != nil {
|
||||
m.log.Warn("failed to cleanup health check key", zap.Error(err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetKeysByPattern retrieves all keys matching a pattern
|
||||
func (m *Manager) GetKeysByPattern(ctx context.Context, pattern string) ([]string, error) {
|
||||
return m.service.Keys(ctx, pattern)
|
||||
}
|
||||
|
||||
// DeleteKeysByPattern deletes all keys matching a pattern
|
||||
func (m *Manager) DeleteKeysByPattern(ctx context.Context, pattern string) error {
|
||||
keys, err := m.service.Keys(ctx, pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.log.Info("deleting keys by pattern",
|
||||
zap.String("pattern", pattern),
|
||||
zap.Int("count", len(keys)))
|
||||
|
||||
return m.service.Delete(ctx, keys...)
|
||||
}
|
||||
Reference in New Issue
Block a user