feat: fully integrate DragonflyDB with application

- Add Redis client initialization with DragonflyDB connection
- Update session middleware to use DragonflyDB with fallback to memory
- Update cache middleware to use DragonflyDB for persistent caching
- Add proper error handling and connection timeouts
- Implement session storage in DragonflyDB with 24-hour expiration
- Add cache invalidation middleware for DragonflyDB
- Maintain backward compatibility with in-memory fallbacks
This commit is contained in:
Tomas Dvorak
2026-03-03 12:46:18 +01:00
parent dee7011192
commit f3a835caa2
2 changed files with 147 additions and 9 deletions
+58 -2
View File
@@ -7,8 +7,10 @@ import (
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/trackeep/backend/config" "github.com/trackeep/backend/config"
"github.com/trackeep/backend/handlers" "github.com/trackeep/backend/handlers"
@@ -49,6 +51,40 @@ func initializeSecuritySecrets() error {
return nil return nil
} }
// initializeDragonflyDB initializes DragonflyDB (Redis-compatible) connection
func initializeDragonflyDB() *redis.Client {
dragonflyAddr := os.Getenv("DRAGONFLY_ADDR")
dragonflyPassword := os.Getenv("DRAGONFLY_PASSWORD")
if dragonflyAddr == "" {
log.Println("DRAGONFLY_ADDR not set, using default: localhost:6379")
dragonflyAddr = "localhost:6379"
}
rdb := redis.NewClient(&redis.Options{
Addr: dragonflyAddr,
Password: dragonflyPassword,
DialTimeout: 5 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
PoolSize: 20,
})
// Test connection
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := rdb.Ping(ctx).Result()
if err != nil {
log.Printf("Warning: Failed to connect to DragonflyDB at %s: %v", dragonflyAddr, err)
log.Println("Falling back to in-memory cache and sessions")
return nil
}
log.Printf("Successfully connected to DragonflyDB at %s", dragonflyAddr)
return rdb
}
func main() { func main() {
os.Setenv("APP_VERSION", "1.0.0") os.Setenv("APP_VERSION", "1.0.0")
@@ -85,10 +121,28 @@ func main() {
log.Fatal("Failed to initialize security secrets:", err) log.Fatal("Failed to initialize security secrets:", err)
} }
// Initialize session store // Initialize DragonflyDB
middleware.InitSessionStore() dragonflyClient := initializeDragonflyDB()
// Initialize session store with DragonflyDB
middleware.InitSessionStore(dragonflyClient)
log.Println("Session store initialized successfully") log.Println("Session store initialized successfully")
// Initialize cache middleware with DragonflyDB
var cacheConfig middleware.CacheConfig
if dragonflyClient != nil {
cacheConfig = middleware.CacheConfig{
Duration: 5 * time.Minute,
KeyPrefix: "trackeep:",
Enabled: true,
RedisClient: dragonflyClient,
}
log.Println("DragonflyDB cache middleware initialized")
} else {
cacheConfig = middleware.DefaultCacheConfig()
log.Println("Using in-memory cache fallback")
}
// Seed demo data in background // Seed demo data in background
// go func() { // go func() {
// SeedData() // SeedData()
@@ -105,6 +159,8 @@ func main() {
// Middleware // Middleware
r.Use(gin.Logger()) r.Use(gin.Logger())
r.Use(gin.Recovery()) r.Use(gin.Recovery())
r.Use(middleware.CacheMiddleware(cacheConfig)) // Add DragonflyDB cache middleware
r.Use(middleware.CacheInvalidationMiddleware(dragonflyClient)) // Add cache invalidation
r.Use(middleware.SessionMiddleware()) // Add session middleware r.Use(middleware.SessionMiddleware()) // Add session middleware
r.Use(middleware.AuditMiddleware()) r.Use(middleware.AuditMiddleware())
r.Use(middleware.InputValidationMiddleware()) r.Use(middleware.InputValidationMiddleware())
+86 -4
View File
@@ -1,12 +1,15 @@
package middleware package middleware
import ( import (
"context"
"encoding/json"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/trackeep/backend/models" "github.com/trackeep/backend/models"
) )
@@ -34,12 +37,14 @@ type SessionStore interface {
// RedisSessionStore implements SessionStore using Redis (or fallback to memory) // RedisSessionStore implements SessionStore using Redis (or fallback to memory)
type RedisSessionStore struct { type RedisSessionStore struct {
redisClient *redis.Client
sessions map[string]*SessionData // Fallback in-memory store sessions map[string]*SessionData // Fallback in-memory store
} }
// NewSessionStore creates a new session store // NewSessionStore creates a new session store
func NewSessionStore() SessionStore { func NewSessionStore(redisClient *redis.Client) SessionStore {
return &RedisSessionStore{ return &RedisSessionStore{
redisClient: redisClient,
sessions: make(map[string]*SessionData), sessions: make(map[string]*SessionData),
} }
} }
@@ -48,32 +53,109 @@ func NewSessionStore() SessionStore {
func (r *RedisSessionStore) CreateSession(sessionData *SessionData) error { func (r *RedisSessionStore) CreateSession(sessionData *SessionData) error {
sessionData.CreatedAt = time.Now() sessionData.CreatedAt = time.Now()
sessionData.LastActive = time.Now() sessionData.LastActive = time.Now()
// Try Redis first
if r.redisClient != nil {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
sessionJSON, err := json.Marshal(sessionData)
if err != nil {
return fmt.Errorf("failed to marshal session data: %w", err)
}
// Store in Redis with 24 hour expiration
err = r.redisClient.Set(ctx, "session:"+sessionData.SessionID, sessionJSON, 24*time.Hour).Err()
if err == nil {
return nil
}
// Fall back to memory if Redis fails
}
// Fallback to in-memory storage
r.sessions[sessionData.SessionID] = sessionData r.sessions[sessionData.SessionID] = sessionData
return nil return nil
} }
// GetSession retrieves a session by ID // GetSession retrieves a session by ID
func (r *RedisSessionStore) GetSession(sessionID string) (*SessionData, error) { func (r *RedisSessionStore) GetSession(sessionID string) (*SessionData, error) {
// Try Redis first
if r.redisClient != nil {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
sessionJSON, err := r.redisClient.Get(ctx, "session:"+sessionID).Result()
if err == nil {
var sessionData SessionData
if err := json.Unmarshal([]byte(sessionJSON), &sessionData); err == nil {
// Update last active time
sessionData.LastActive = time.Now()
// Update in Redis
updatedJSON, _ := json.Marshal(sessionData)
r.redisClient.Set(ctx, "session:"+sessionID, updatedJSON, 24*time.Hour)
return &sessionData, nil
}
}
// Fall back to memory if Redis fails
}
// Fallback to in-memory storage
if session, exists := r.sessions[sessionID]; exists { if session, exists := r.sessions[sessionID]; exists {
// Update last active time // Update last active time
session.LastActive = time.Now() session.LastActive = time.Now()
return session, nil return session, nil
} }
return nil, fmt.Errorf("session not found") return nil, fmt.Errorf("session not found")
} }
// UpdateSession updates an existing session // UpdateSession updates an existing session
func (r *RedisSessionStore) UpdateSession(sessionID string, sessionData *SessionData) error { func (r *RedisSessionStore) UpdateSession(sessionID string, sessionData *SessionData) error {
if _, exists := r.sessions[sessionID]; exists {
sessionData.LastActive = time.Now() sessionData.LastActive = time.Now()
// Try Redis first
if r.redisClient != nil {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
sessionJSON, err := json.Marshal(sessionData)
if err != nil {
return fmt.Errorf("failed to marshal session data: %w", err)
}
err = r.redisClient.Set(ctx, "session:"+sessionID, sessionJSON, 24*time.Hour).Err()
if err == nil {
return nil
}
// Fall back to memory if Redis fails
}
// Fallback to in-memory storage
if _, exists := r.sessions[sessionID]; exists {
r.sessions[sessionID] = sessionData r.sessions[sessionID] = sessionData
return nil return nil
} }
return fmt.Errorf("session not found") return fmt.Errorf("session not found")
} }
// DeleteSession removes a session // DeleteSession removes a session
func (r *RedisSessionStore) DeleteSession(sessionID string) error { func (r *RedisSessionStore) DeleteSession(sessionID string) error {
// Try Redis first
if r.redisClient != nil {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := r.redisClient.Del(ctx, "session:"+sessionID).Err()
if err == nil {
// Also remove from memory fallback
delete(r.sessions, sessionID)
return nil
}
// Fall back to memory if Redis fails
}
// Fallback to in-memory storage
delete(r.sessions, sessionID) delete(r.sessions, sessionID)
return nil return nil
} }
@@ -93,8 +175,8 @@ func (r *RedisSessionStore) CleanupExpiredSessions() error {
var sessionStore SessionStore var sessionStore SessionStore
// InitSessionStore initializes the session store // InitSessionStore initializes the session store
func InitSessionStore() { func InitSessionStore(redisClient *redis.Client) {
sessionStore = NewSessionStore() sessionStore = NewSessionStore(redisClient)
// Start cleanup goroutine // Start cleanup goroutine
go func() { go func() {