mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-04 04:22:57 +00:00
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:
+59
-3
@@ -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,7 +159,9 @@ func main() {
|
|||||||
// Middleware
|
// Middleware
|
||||||
r.Use(gin.Logger())
|
r.Use(gin.Logger())
|
||||||
r.Use(gin.Recovery())
|
r.Use(gin.Recovery())
|
||||||
r.Use(middleware.SessionMiddleware()) // Add session middleware
|
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.AuditMiddleware())
|
r.Use(middleware.AuditMiddleware())
|
||||||
r.Use(middleware.InputValidationMiddleware())
|
r.Use(middleware.InputValidationMiddleware())
|
||||||
|
|
||||||
|
|||||||
@@ -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,13 +37,15 @@ 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 {
|
||||||
sessions map[string]*SessionData // Fallback in-memory store
|
redisClient *redis.Client
|
||||||
|
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{
|
||||||
sessions: make(map[string]*SessionData),
|
redisClient: redisClient,
|
||||||
|
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 {
|
||||||
|
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 {
|
if _, exists := r.sessions[sessionID]; exists {
|
||||||
sessionData.LastActive = time.Now()
|
|
||||||
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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user