404 lines
12 KiB
Go
404 lines
12 KiB
Go
package db
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"gestion/models"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
// ============================================
|
|
// SESSION REDIS - GESTION UTILISATEUR
|
|
// ============================================
|
|
|
|
// SessionData représente une session client en Redis
|
|
type SessionData struct {
|
|
ClientID int `json:"client_id"`
|
|
Username string `json:"username"`
|
|
SessionID string `json:"session_id"`
|
|
Role string `json:"role"`
|
|
CreatedAt int64 `json:"created_at"`
|
|
LastActivity int64 `json:"last_activity"`
|
|
ExpiresAt int64 `json:"expires_at"`
|
|
BasketVersion int `json:"basket_version"`
|
|
PointsCache int `json:"points_cache"`
|
|
PenaltyCache float64 `json:"penalty_cache"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
}
|
|
|
|
// ============================================
|
|
// CRÉER UNE SESSION CLIENT
|
|
// ============================================
|
|
|
|
// CreateClientSession crée une session Redis pour un client authentifié
|
|
// Appelé depuis handlers/auth.go après LoginClient réussi
|
|
//
|
|
// Exemple d'utilisation:
|
|
//
|
|
// sessionID := uuid.New().String()
|
|
// database.CreateClientSession(clientID, username, sessionID)
|
|
func (d *Database) CreateClientSession(clientID int, username string, sessionID string) error {
|
|
log.Printf("📝 [SESSION] Création session pour client: %s (ID: %d)", username, clientID)
|
|
|
|
sessionKey := fmt.Sprintf("session:client:%d", clientID)
|
|
now := time.Now().Unix()
|
|
expiresAt := now + (5 * 3600) // 5 heures
|
|
|
|
sessionData := SessionData{
|
|
ClientID: clientID,
|
|
Username: username,
|
|
SessionID: sessionID,
|
|
Role: "client",
|
|
CreatedAt: now,
|
|
LastActivity: now,
|
|
ExpiresAt: expiresAt,
|
|
BasketVersion: 0,
|
|
PointsCache: 0,
|
|
PenaltyCache: 0,
|
|
}
|
|
|
|
// Sérialiser et sauvegarder
|
|
sessionJSON, err := json.Marshal(sessionData)
|
|
if err != nil {
|
|
log.Printf("❌ [SESSION] Erreur sérialisation: %v", err)
|
|
return fmt.Errorf("erreur sérialisation session: %w", err)
|
|
}
|
|
|
|
ttl := time.Duration(expiresAt-now) * time.Second
|
|
if err := Redis.Set(RedisCtx, sessionKey, sessionJSON, ttl).Err(); err != nil {
|
|
log.Printf("❌ [SESSION] Erreur sauvegarde Redis: %v", err)
|
|
return fmt.Errorf("erreur sauvegarde session Redis: %w", err)
|
|
}
|
|
|
|
// Ajouter à l'index des sessions actives
|
|
if err := Redis.SAdd(RedisCtx, "session:active:clients", clientID).Err(); err != nil {
|
|
log.Printf("⚠️ [SESSION] Erreur ajout index: %v", err)
|
|
}
|
|
|
|
// Charger les infos du client (points, penalty) dans le cache
|
|
if client, err := d.GetClientByUsername(username); err == nil {
|
|
sessionData.PenaltyCache = float64(client.Amende)
|
|
sessionJSON, _ := json.Marshal(sessionData)
|
|
Redis.Set(RedisCtx, sessionKey, sessionJSON, ttl)
|
|
}
|
|
|
|
log.Printf("✅ [SESSION] Session créée pour %s - TTL: 5h", username)
|
|
return nil
|
|
}
|
|
|
|
// ============================================
|
|
// RÉCUPÉRER UNE SESSION CLIENT
|
|
// ============================================
|
|
|
|
// GetClientSession récupère la session Redis d'un client
|
|
// Retourne nil si session expirée ou inexistante
|
|
func (d *Database) GetClientSession(clientID int) (*SessionData, error) {
|
|
sessionKey := fmt.Sprintf("session:client:%d", clientID)
|
|
|
|
data, err := Redis.Get(RedisCtx, sessionKey).Result()
|
|
if err != nil {
|
|
log.Printf("⚠️ [SESSION] Pas de session trouvée pour client %d", clientID)
|
|
return nil, fmt.Errorf("session non trouvée")
|
|
}
|
|
|
|
var session SessionData
|
|
if err := json.Unmarshal([]byte(data), &session); err != nil {
|
|
log.Printf("❌ [SESSION] Erreur désérialisation: %v", err)
|
|
return nil, fmt.Errorf("erreur désérialisation: %w", err)
|
|
}
|
|
|
|
// Vérifier si session expirée
|
|
if time.Now().Unix() > session.ExpiresAt {
|
|
log.Printf("⚠️ [SESSION] Session expirée pour client %d", clientID)
|
|
Redis.Del(RedisCtx, sessionKey)
|
|
return nil, fmt.Errorf("session expirée")
|
|
}
|
|
|
|
return &session, nil
|
|
}
|
|
|
|
// ============================================
|
|
// METTRE À JOUR L'ACTIVITÉ DE SESSION
|
|
// ============================================
|
|
|
|
// RefreshSessionTimeout prolonge la durée de vie de la session
|
|
// Appelé régulièrement par SessionMiddleware (chaque requête client)
|
|
func (d *Database) RefreshSessionTimeout(clientID int) error {
|
|
sessionKey := fmt.Sprintf("session:client:%d", clientID)
|
|
|
|
// Récupérer la session
|
|
data, err := Redis.Get(RedisCtx, sessionKey).Result()
|
|
if err != nil {
|
|
return fmt.Errorf("session non trouvée")
|
|
}
|
|
|
|
var session SessionData
|
|
if err := json.Unmarshal([]byte(data), &session); err != nil {
|
|
return fmt.Errorf("erreur désérialisation: %w", err)
|
|
}
|
|
|
|
// Mettre à jour lastActivity et expiresAt
|
|
now := time.Now().Unix()
|
|
session.LastActivity = now
|
|
session.ExpiresAt = now + (5 * 3600) // Prolonger de 5 heures
|
|
|
|
// Resauvegarder
|
|
sessionJSON, _ := json.Marshal(session)
|
|
ttl := time.Duration(session.ExpiresAt-now) * time.Second
|
|
|
|
if err := Redis.Set(RedisCtx, sessionKey, sessionJSON, ttl).Err(); err != nil {
|
|
log.Printf("⚠️ [SESSION] Erreur refresh: %v", err)
|
|
return nil // Pas critique
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ============================================
|
|
// INVALIDER UNE SESSION (LOGOUT)
|
|
// ============================================
|
|
|
|
// InvalidateSession supprime la session Redis (logout)
|
|
// Appelé depuis handlers/auth.go dans LogoutClient
|
|
func (d *Database) InvalidateSession(clientID int) error {
|
|
sessionKey := fmt.Sprintf("session:client:%d", clientID)
|
|
basketKey := fmt.Sprintf("session:basket:%d", clientID)
|
|
|
|
// Supprimer la session
|
|
if err := Redis.Del(RedisCtx, sessionKey).Err(); err != nil {
|
|
log.Printf("⚠️ [SESSION] Erreur suppression: %v", err)
|
|
}
|
|
|
|
// Vider le panier Redis
|
|
if err := Redis.Del(RedisCtx, basketKey).Err(); err != nil {
|
|
log.Printf("⚠️ [SESSION] Erreur suppression panier: %v", err)
|
|
}
|
|
|
|
// Retirer de l'index
|
|
if err := Redis.SRem(RedisCtx, "session:active:clients", clientID).Err(); err != nil {
|
|
log.Printf("⚠️ [SESSION] Erreur retrait index: %v", err)
|
|
}
|
|
|
|
log.Printf("✅ [SESSION] Session invalidée pour client %d", clientID)
|
|
return nil
|
|
}
|
|
|
|
// ============================================
|
|
// PANIER EN CACHE REDIS
|
|
// ============================================
|
|
|
|
// BasketItemCache représente un item du panier en cache
|
|
type BasketItemCache struct {
|
|
ID int `json:"id"`
|
|
ProductID int `json:"product_id"`
|
|
ProductName string `json:"product_name"`
|
|
Quantity int `json:"quantity"`
|
|
Price float64 `json:"price"`
|
|
Category string `json:"category"`
|
|
AddedAt int64 `json:"added_at"`
|
|
}
|
|
|
|
// GetSessionBasket récupère le panier en cache Redis
|
|
// Retourne les items du panier avec total
|
|
func (d *Database) GetSessionBasket(clientID int) ([]BasketItemCache, float64, error) {
|
|
basketKey := fmt.Sprintf("session:basket:%d", clientID)
|
|
|
|
// Récupérer tous les items du panier
|
|
items, err := Redis.HGetAll(RedisCtx, basketKey).Result()
|
|
if err != nil {
|
|
log.Printf("⚠️ [BASKET] Pas de panier en cache pour client %d", clientID)
|
|
return []BasketItemCache{}, 0, nil
|
|
}
|
|
|
|
var basketItems []BasketItemCache
|
|
var totalPrice float64
|
|
|
|
for _, itemJSON := range items {
|
|
var item BasketItemCache
|
|
if err := json.Unmarshal([]byte(itemJSON), &item); err != nil {
|
|
log.Printf("⚠️ [BASKET] Erreur parsing item: %v", err)
|
|
continue
|
|
}
|
|
basketItems = append(basketItems, item)
|
|
totalPrice += item.Price * float64(item.Quantity)
|
|
}
|
|
|
|
return basketItems, totalPrice, nil
|
|
}
|
|
|
|
// UpdateSessionBasket met à jour le panier en cache Redis
|
|
// Appelé après ajout/modification d'un produit au panier
|
|
func (d *Database) UpdateSessionBasket(clientID int, basketItems []BasketItemCache) error {
|
|
basketKey := fmt.Sprintf("session:basket:%d", clientID)
|
|
|
|
// Vider le panier existant
|
|
Redis.Del(RedisCtx, basketKey)
|
|
|
|
// Ajouter tous les items
|
|
for _, item := range basketItems {
|
|
itemJSON, _ := json.Marshal(item)
|
|
if err := Redis.HSet(RedisCtx, basketKey, item.ProductID, itemJSON).Err(); err != nil {
|
|
log.Printf("⚠️ [BASKET] Erreur ajout item: %v", err)
|
|
}
|
|
}
|
|
|
|
// TTL: 24 heures
|
|
if err := Redis.Expire(RedisCtx, basketKey, 24*time.Hour).Err(); err != nil {
|
|
log.Printf("⚠️ [BASKET] Erreur TTL: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ClearSessionBasket vide le panier en cache Redis
|
|
// Appelé après validation de commande (checkout)
|
|
func (d *Database) ClearSessionBasket(clientID int) error {
|
|
basketKey := fmt.Sprintf("session:basket:%d", clientID)
|
|
if err := Redis.Del(RedisCtx, basketKey).Err(); err != nil {
|
|
log.Printf("⚠️ [BASKET] Erreur clear: %v", err)
|
|
return nil
|
|
}
|
|
log.Printf("✅ [BASKET] Panier vidé pour client %d", clientID)
|
|
return nil
|
|
}
|
|
|
|
// ============================================
|
|
// UTILITAIRES SESSION
|
|
// ============================================
|
|
|
|
// GetAllActiveSessions récupère toutes les sessions actives
|
|
// Utile pour admin/stats
|
|
func (d *Database) GetAllActiveSessions() ([]SessionData, error) {
|
|
clientIDs, err := Redis.SMembers(RedisCtx, "session:active:clients").Result()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("erreur récupération sessions: %w", err)
|
|
}
|
|
|
|
var sessions []SessionData
|
|
for _, clientIDStr := range clientIDs {
|
|
var clientID int
|
|
if _, err := fmt.Sscanf(clientIDStr, "%d", &clientID); err != nil {
|
|
continue
|
|
}
|
|
|
|
if session, err := d.GetClientSession(clientID); err == nil {
|
|
sessions = append(sessions, *session)
|
|
}
|
|
}
|
|
|
|
return sessions, nil
|
|
}
|
|
|
|
// GetSessionCount retourne le nombre de sessions actives
|
|
func (d *Database) GetSessionCount() (int64, error) {
|
|
count, err := Redis.SCard(RedisCtx, "session:active:clients").Result()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("erreur comptage sessions: %w", err)
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// ============================================
|
|
// CACHE PROFIL CLIENT
|
|
// ============================================
|
|
|
|
// CacheClientProfile met en cache les infos du client (pour 1h)
|
|
func (d *Database) CacheClientProfile(client interface{}) error {
|
|
// Récupérer le client depuis DB si c'est un username
|
|
var clientData *models.Client
|
|
|
|
// Si c'est un username string
|
|
if username, ok := client.(string); ok {
|
|
var err error
|
|
clientData, err = d.GetClientByUsername(username)
|
|
if err != nil {
|
|
return fmt.Errorf("client non trouvé: %w", err)
|
|
}
|
|
} else {
|
|
// Si c'est déjà un *models.Client
|
|
clientData = client.(*models.Client)
|
|
}
|
|
|
|
cacheKey := fmt.Sprintf("cache:client:profile:%d", clientData.ID)
|
|
|
|
// Sérialiser
|
|
profileJSON, err := json.Marshal(clientData)
|
|
if err != nil {
|
|
return fmt.Errorf("erreur sérialisation: %w", err)
|
|
}
|
|
|
|
// Sauvegarder avec TTL 1h
|
|
if err := Redis.Set(RedisCtx, cacheKey, profileJSON, 1*time.Hour).Err(); err != nil {
|
|
return fmt.Errorf("erreur cache: %w", err)
|
|
}
|
|
|
|
log.Printf("✅ [CACHE] Profil client %d mis en cache (1h)", clientData.ID)
|
|
return nil
|
|
}
|
|
|
|
// GetCachedClientProfile récupère le profil en cache
|
|
func (d *Database) GetCachedClientProfile(clientID int) (*models.Client, error) {
|
|
cacheKey := fmt.Sprintf("cache:client:profile:%d", clientID)
|
|
|
|
data, err := Redis.Get(RedisCtx, cacheKey).Result()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cache miss")
|
|
}
|
|
|
|
var client models.Client
|
|
if err := json.Unmarshal([]byte(data), &client); err != nil {
|
|
return nil, fmt.Errorf("erreur désérialisation: %w", err)
|
|
}
|
|
|
|
return &client, nil
|
|
}
|
|
|
|
// InvalidateClientCache invalide le cache du client
|
|
func (d *Database) InvalidateClientCache(clientID int) error {
|
|
cacheKey := fmt.Sprintf("cache:client:profile:%d", clientID)
|
|
if err := Redis.Del(RedisCtx, cacheKey).Err(); err != nil {
|
|
return fmt.Errorf("erreur invalidation: %w", err)
|
|
}
|
|
log.Printf("✅ [CACHE] Profil client %d invalidé", clientID)
|
|
return nil
|
|
}
|
|
|
|
// ============================================
|
|
// COMMANDES EN CACHE (POUR TRACKING)
|
|
// ============================================
|
|
|
|
// CacheCommandInfo met en cache les infos d'une commande
|
|
func (d *Database) CacheCommandInfo(commandID int, command map[string]interface{}) error {
|
|
cacheKey := fmt.Sprintf("cache:command:%d", commandID)
|
|
|
|
commandJSON, err := json.Marshal(command)
|
|
if err != nil {
|
|
return fmt.Errorf("erreur sérialisation: %w", err)
|
|
}
|
|
|
|
// TTL: 4 heures
|
|
if err := Redis.Set(RedisCtx, cacheKey, commandJSON, 4*time.Hour).Err(); err != nil {
|
|
return fmt.Errorf("erreur cache: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetCachedCommand récupère une commande en cache
|
|
func (d *Database) GetCachedCommand(commandID int) (map[string]interface{}, error) {
|
|
cacheKey := fmt.Sprintf("cache:command:%d", commandID)
|
|
|
|
data, err := Redis.Get(RedisCtx, cacheKey).Result()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cache miss")
|
|
}
|
|
|
|
var command map[string]interface{}
|
|
if err := json.Unmarshal([]byte(data), &command); err != nil {
|
|
return nil, fmt.Errorf("erreur désérialisation: %w", err)
|
|
}
|
|
|
|
return command, nil
|
|
}
|