diff --git a/backend/gestion/db/db_product_price.go b/backend/gestion/db/db_product_price.go index b8ef59d2..5643184f 100644 --- a/backend/gestion/db/db_product_price.go +++ b/backend/gestion/db/db_product_price.go @@ -13,37 +13,6 @@ func (d *Database) GetProductPrices(productID int) ([]models.ProductPrice, error return prices, nil } -func (d *Database) CreateProductPrice(productID int, quantity float64, price float64) error { - p := models.ProductPrice{ProductID: productID, Quantity: quantity, Price: price} - if err := d.GDB.Create(&p).Error; err != nil { - return fmt.Errorf("erreur création prix: %w", err) - } - return nil -} - -func (d *Database) UpdateProductPrice(priceID int, quantity float64, price float64) error { - result := d.GDB.Model(&models.ProductPrice{}).Where("id = ?", priceID). - Updates(map[string]any{"quantity": quantity, "price": price}) - if result.Error != nil { - return fmt.Errorf("erreur mise à jour prix: %w", result.Error) - } - if result.RowsAffected == 0 { - return fmt.Errorf("prix introuvable") - } - return nil -} - -func (d *Database) DeleteProductPrice(priceID int) error { - result := d.GDB.Delete(&models.ProductPrice{}, priceID) - if result.Error != nil { - return fmt.Errorf("erreur suppression prix: %w", result.Error) - } - if result.RowsAffected == 0 { - return fmt.Errorf("prix introuvable") - } - return nil -} - func (d *Database) AddActivePrice(priceID int) error { result := d.GDB.Model(&models.ProductPrice{}). Where("id = ?", priceID). diff --git a/backend/gestion/db/db_referral.go b/backend/gestion/db/db_referral.go index 5a276d20..fb5eec09 100644 --- a/backend/gestion/db/db_referral.go +++ b/backend/gestion/db/db_referral.go @@ -58,17 +58,3 @@ func (d *Database) ResetClientReferralBalance(username string) error { } return nil } - -func (d *Database) UseClientReferralBalance(tx *gorm.DB, username string, amount float64) error { - if amount <= 0 { - return nil - } - var balance float64 - if err := tx.Raw(`SELECT referral_balance FROM clients WHERE username = ? FOR UPDATE`, username).Scan(&balance).Error; err != nil { - return fmt.Errorf("client non trouvé") - } - if balance < amount { - return fmt.Errorf("solde parrainage insuffisant (disponible: %.2f€)", balance) - } - return tx.Exec(`UPDATE clients SET referral_balance = referral_balance - ? WHERE username = ?`, amount, username).Error -} diff --git a/backend/gestion/db/redis_deliveryman_assignment.go b/backend/gestion/db/redis_deliveryman_assignment.go index e9d1b308..9c513522 100644 --- a/backend/gestion/db/redis_deliveryman_assignment.go +++ b/backend/gestion/db/redis_deliveryman_assignment.go @@ -67,57 +67,6 @@ func (d *Database) FindLeastLoadedDeliveryman() (string, error) { return leastLoaded, nil } -// FindAvailableOrLeastLoadedDeliveryman trouve un livreur disponible ou le moins chargé -func (d *Database) FindAvailableOrLeastLoadedDeliveryman() (string, string, int, error) { - keys, err := Redis.Keys(RedisCtx, "delivery:status:*").Result() - if err != nil || len(keys) == 0 { - return "", "", 0, fmt.Errorf("aucun livreur trouvé") - } - - var bestDeliveryman string - var bestStatus string - bestQueueSize := int64(MAX_COMMANDS_PER_DELIVERYMAN + 1) - - for _, key := range keys { - username := key[len("delivery:status:"):] - - data, err := Redis.Get(RedisCtx, key).Result() - if err != nil { - continue - } - - var status models.DeliveryPersonStatus - json.Unmarshal([]byte(data), &status) - - if status.Status == "offline" { - continue - } - - if !d.CanDeliverymanAcceptCommands(username) { - continue - } - - queueKey := fmt.Sprintf("queue:deliveryman:%s", username) - queueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result() - - if status.Status == "available" && queueSize == 0 { - return username, "available", 0, nil - } - - if queueSize < bestQueueSize { - bestQueueSize = queueSize - bestDeliveryman = username - bestStatus = status.Status - } - } - - if bestDeliveryman == "" { - return "", "", 0, fmt.Errorf("tous les livreurs sont au maximum de leur capacité") - } - - return bestDeliveryman, bestStatus, int(bestQueueSize), nil -} - // GetLeastLoadedDeliverymanForced retourne le livreur avec le moins de commandes (SANS limite) func (d *Database) GetLeastLoadedDeliverymanForced() (string, int64, error) { activeUsernames, err := d.GetAllActiveDeliverymenUsernames() diff --git a/backend/gestion/db/redis_eta_management.go b/backend/gestion/db/redis_eta_management.go index 15672ce6..fc76f158 100644 --- a/backend/gestion/db/redis_eta_management.go +++ b/backend/gestion/db/redis_eta_management.go @@ -156,10 +156,6 @@ func (d *Database) CalculateETABetweenPoints(lat1, lng1, lat2, lng2 float64) int return services.CalculateETA(distance) } -func (d *Database) GetDeliverymanQueueStats(deliveryman string) (map[string]any, error) { - return d.GetDeliverymanQueueInfo(deliveryman) -} - func (d *Database) SetCommandETAWithDetails(commandID, totalETA, queuePosition int) error { key := fmt.Sprintf("command:eta:%d", commandID) diff --git a/backend/gestion/db/redis_queue_assignment.go b/backend/gestion/db/redis_queue_assignment.go index 8ace1e37..60f26b77 100644 --- a/backend/gestion/db/redis_queue_assignment.go +++ b/backend/gestion/db/redis_queue_assignment.go @@ -87,35 +87,6 @@ func (d *Database) AssignCommandToDeliverymanQueue(commandID int, deliveryman st return nil } -// AssignCommandToDeliverymanQueueUnlimited assigne sans limite (pour un seul livreur) -func (d *Database) AssignCommandToDeliverymanQueueUnlimited(deliveryman string, queueItem models.CommandQueue) error { - queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) - currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result() - - travelTime := d.CalculateETAForDeliveryman(deliveryman, queueItem.Lat, queueItem.Lng) - - queueItem.EstimatedETA = travelTime - - err := d.AddToDeliverymanQueue(deliveryman, queueItem) - if err != nil { - return fmt.Errorf("erreur ajout à la queue: %w", err) - } - - d.UpdateCommandStatus(queueItem.CommandID, "assigned") - d.AssignDeliveryPerson(queueItem.CommandID, deliveryman) - d.SetCommandETAWithDetails(queueItem.CommandID, travelTime, int(currentQueueSize)+1) - - d.AddCommandLog(queueItem.CommandID, "queued", - fmt.Sprintf("Assigné au seul livreur actif %s (position: %d, ETA trajet: %d min)", - deliveryman, currentQueueSize+1, travelTime), - "system") - - log.Printf("✅ Commande %d -> Queue %s (SANS LIMITE - pos: %d, ETA trajet: %d min)", - queueItem.CommandID, deliveryman, currentQueueSize+1, travelTime) - - return nil -} - // AssignCommandToDeliverymanQueueWithCoords assigne une commande avec les coordonnées GPS func (d *Database) AssignCommandToDeliverymanQueueWithCoords(commandID int, deliveryman string, estimatedTravelTime int, lat, lng float64, address string) error { command, err := d.GetCommandByID(commandID) diff --git a/backend/gestion/db/redis_queue_clean_up.go b/backend/gestion/db/redis_queue_clean_up.go index 918c51fa..9b5fb81f 100644 --- a/backend/gestion/db/redis_queue_clean_up.go +++ b/backend/gestion/db/redis_queue_clean_up.go @@ -203,64 +203,3 @@ func (d *Database) StartQueueCleanupScheduler() { } }() } - -// ============================================ -// RAPPORT DE VALIDATION -// ============================================ - -// GetQueueValidationReport génère un rapport de validation sans supprimer -func (d *Database) GetQueueValidationReport() (map[string]any, error) { - keys, err := Redis.Keys(RedisCtx, "queue:pending:*").Result() - if err != nil { - return nil, err - } - - report := map[string]any{ - "total_commands": len(keys), - "valid_commands": 0, - "invalid_commands": 0, - "invalid_details": []map[string]any{}, - "validation_results": []string{}, - } - - for _, key := range keys { - data, err := Redis.Get(RedisCtx, key).Result() - if err != nil { - continue - } - - var queueItem models.CommandQueue - if err := json.Unmarshal([]byte(data), &queueItem); err != nil { - report["invalid_commands"] = report["invalid_commands"].(int) + 1 - continue - } - - // Validation - issues := []string{} - if queueItem.Username == "" { - issues = append(issues, "username vide") - } - if queueItem.Address == "" { - issues = append(issues, "adresse vide") - } - if queueItem.Lat == 0 || queueItem.Lng == 0 { - issues = append(issues, "GPS manquant") - } - if queueItem.CreatedAt.IsZero() { - issues = append(issues, "date invalide") - } - - if len(issues) > 0 { - report["invalid_commands"] = report["invalid_commands"].(int) + 1 - report["invalid_details"] = append(report["invalid_details"].([]map[string]any), map[string]any{ - "command_id": queueItem.CommandID, - "issues": issues, - "data": queueItem, - }) - } else { - report["valid_commands"] = report["valid_commands"].(int) + 1 - } - } - - return report, nil -} diff --git a/backend/gestion/db/redis_queue_management.go b/backend/gestion/db/redis_queue_management.go index 427b1a9b..ae5097ce 100644 --- a/backend/gestion/db/redis_queue_management.go +++ b/backend/gestion/db/redis_queue_management.go @@ -86,40 +86,6 @@ func (d *Database) CanDeliverymanAcceptCommands(deliveryman string) bool { return true } -// GetAvailableDeliveryPersonsForAssignment récupère UNIQUEMENT les livreurs pouvant accepter -func (d *Database) GetAvailableDeliveryPersonsForAssignment() ([]models.DeliveryPersonStatus, error) { - keys, err := Redis.Keys(RedisCtx, "delivery:status:*").Result() - if err != nil { - return nil, err - } - - var available []models.DeliveryPersonStatus - - for _, key := range keys { - data, err := Redis.Get(RedisCtx, key).Result() - if err != nil { - continue - } - - var status models.DeliveryPersonStatus - if err := json.Unmarshal([]byte(data), &status); err != nil { - continue - } - - if d.CanDeliverymanAcceptCommands(status.Username) { - available = append(available, status) - } - } - - log.Printf("📊 [AVAILABLE] %d livreur(s) disponible(s) pour assignation", len(available)) - - return available, nil -} - -// ============================================ -// 🔄 FONCTIONS MODIFIÉES AVEC AUTO-STATUS -// ============================================ - // AddToDeliverymanQueue - VERSION MISE À JOUR avec auto-update du statut func (d *Database) AddToDeliverymanQueue(deliveryman string, queueItem models.CommandQueue) error { data, err := json.Marshal(queueItem) @@ -150,111 +116,6 @@ func (d *Database) AddToDeliverymanQueue(deliveryman string, queueItem models.Co return nil } -// AddCommandToQueue ajoute une commande à la file d'attente Redis (version simple) -func (d *Database) AddCommandToQueue(commandID int) error { - if err := d.ValidateCommandBeforeQueue(commandID); err != nil { - log.Printf("❌ [QUEUE] Commande %d REFUSÉE: %v", commandID, err) - return fmt.Errorf("validation échouée: %w", err) - } - - command, err := d.GetCommandByID(commandID) - if err != nil { - return fmt.Errorf("commande introuvable: %w", err) - } - var lat, lng float64 - if command["dest_latitude"] != nil { - if latVal, ok := command["dest_latitude"].(float64); ok { - lat = latVal - } - } - if command["dest_longitude"] != nil { - if lngVal, ok := command["dest_longitude"].(float64); ok { - lng = lngVal - } - } - - var totalPrice float64 - if tp, ok := command["total_prix"].(float64); ok { - totalPrice = tp - } - - var address string - if addr, ok := command["delivery_address"].(string); ok { - address = addr - } - - queueItem := models.CommandQueue{ - CommandID: commandID, - Username: command["username"].(string), - TotalPrice: totalPrice, - Address: address, - Lat: lat, - Lng: lng, - CreatedAt: time.Now(), - EstimatedETA: 0, - } - - return d.AddToGeneralQueue(queueItem) -} - -// AddCommandToSmartQueue - Ajoute une commande avec attribution au livreur le moins chargé -func (d *Database) AddCommandToSmartQueue(commandID int, address string) error { - if err := d.ValidateCommandBeforeQueue(commandID); err != nil { - log.Printf("❌ [QUEUE] Commande %d REFUSÉE: %v", commandID, err) - return fmt.Errorf("validation échouée: %w", err) - } - command, err := d.GetCommandByID(commandID) - if err != nil { - return fmt.Errorf("commande introuvable: %w", err) - } - var lat, lng float64 - if command["dest_latitude"] != nil { - if latVal, ok := command["dest_latitude"].(float64); ok { - lat = latVal - } - } - if command["dest_longitude"] != nil { - if lngVal, ok := command["dest_longitude"].(float64); ok { - lng = lngVal - } - } - - var totalPrice float64 - if tp, ok := command["total_prix"].(float64); ok { - totalPrice = tp - } - - queueItem := models.CommandQueue{ - CommandID: commandID, - Username: command["username"].(string), - TotalPrice: totalPrice, - Address: address, - Lat: lat, - Lng: lng, - CreatedAt: time.Now(), - EstimatedETA: 0, - } - - // ✅ MODIFIÉ: Utiliser FindLeastLoadedDeliveryman qui respecte maintenant le statut - assignedDeliveryman, err := d.FindLeastLoadedDeliveryman() - if err != nil { - log.Printf("⚠️ Aucun livreur trouvé, ajout à la queue générale") - return d.AddToGeneralQueue(queueItem) - } - - err = d.AddToDeliverymanQueue(assignedDeliveryman, queueItem) - if err != nil { - return fmt.Errorf("erreur ajout à la queue du livreur: %w", err) - } - - log.Printf("📋 Commande %d assignée à la queue de %s", commandID, assignedDeliveryman) - - d.PublishCommandEvent(commandID, "queued", - fmt.Sprintf("En attente dans la queue de %s", assignedDeliveryman)) - - return nil -} - // AddToGeneralQueue ajoute une commande à la queue générale (fallback) func (d *Database) AddToGeneralQueue(queueItem models.CommandQueue) error { data, err := json.Marshal(queueItem) @@ -355,109 +216,6 @@ func (d *Database) GetNextCommandInQueue() (*models.CommandQueue, error) { return &queue, nil } -// GetLastCommandInQueue récupère la dernière commande dans la queue d'un livreur -func (d *Database) GetLastCommandInQueue(deliveryman string) (*models.CommandQueue, error) { - queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) - - // Récupérer la dernière commande (index -1) - commandIDs, err := Redis.ZRange(RedisCtx, queueKey, -1, -1).Result() - if err != nil || len(commandIDs) == 0 { - return nil, fmt.Errorf("queue vide") - } - - commandID := extractCommandID(commandIDs[0]) - if commandID <= 0 { - return nil, fmt.Errorf("ID invalide") - } - - commandKey := fmt.Sprintf("queue:pending:%d", commandID) - data, err := Redis.Get(RedisCtx, commandKey).Result() - if err != nil { - return nil, err - } - - var queueItem models.CommandQueue - if err := json.Unmarshal([]byte(data), &queueItem); err != nil { - return nil, err - } - - return &queueItem, nil -} - -// GetCommandQueuePosition récupère la position d'une commande dans la queue -func (d *Database) GetCommandQueuePosition(commandID int) (int, error) { - commandIDStr := strconv.Itoa(commandID) - - // Chercher d'abord dans les queues des livreurs - keys, _ := Redis.Keys(RedisCtx, "queue:deliveryman:*").Result() - - for _, queueKey := range keys { - // Éviter les clés de compteur - if len(queueKey) > 6 && queueKey[len(queueKey)-6:] == ":count" { - continue - } - - rank, err := Redis.ZRank(RedisCtx, queueKey, commandIDStr).Result() - if err == nil { - return int(rank) + 1, nil - } - } - - // Chercher dans la queue générale - rank, err := Redis.ZRank(RedisCtx, "queue:pending:sorted", commandIDStr).Result() - if err == nil { - return int(rank) + 1, nil - } - - return 0, fmt.Errorf("commande non trouvée dans les queues") -} - -func (d *Database) ClearDeliverymanQueue(deliveryman string) error { - queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) - - // Récupérer toutes les commandes - commandIDs, _ := Redis.ZRange(RedisCtx, queueKey, 0, -1).Result() - - // Redistribuer chaque commande - for _, cmdIDStr := range commandIDs { - commandID := extractCommandID(cmdIDStr) - if commandID <= 0 { - continue - } - - commandKey := fmt.Sprintf("queue:pending:%d", commandID) - data, err := Redis.Get(RedisCtx, commandKey).Result() - if err != nil { - continue - } - - var queueItem models.CommandQueue - if err := json.Unmarshal([]byte(data), &queueItem); err != nil { - continue - } - - newDeliveryman, err := d.FindLeastLoadedDeliveryman() - if err != nil { - d.AddToGeneralQueue(queueItem) - continue - } - - if newDeliveryman != deliveryman { - d.AddToDeliverymanQueue(newDeliveryman, queueItem) - log.Printf("🔄 Commande %d réassignée de %s à %s", - commandID, deliveryman, newDeliveryman) - } - } - - // Vider la queue - Redis.Del(RedisCtx, queueKey) - Redis.Del(RedisCtx, fmt.Sprintf("queue:deliveryman:%s:count", deliveryman)) - - go d.UpdateDeliverymanStatusBasedOnQueue(deliveryman) - - return nil -} - func (d *Database) SetDeliveryPersonStatus(username, status string, commandID int) error { key := fmt.Sprintf("delivery:status:%s", username) @@ -543,46 +301,6 @@ func (d *Database) SyncAllDeliverymanStatuses() error { }) } -// GetDeliverymanCapacityReport génère un rapport détaillé -func (d *Database) GetDeliverymanCapacityReport() (map[string]any, error) { - report := map[string]any{ - "total_deliverymen": 0, - "available": 0, - "busy_full": 0, - "busy_delivering": 0, - "offline": 0, - "details": []map[string]any{}, - } - - err := d.iterDeliveryStatuses(func(s models.DeliveryPersonStatus) { - queueKey := fmt.Sprintf("queue:deliveryman:%s", s.Username) - queueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result() - canAccept := d.CanDeliverymanAcceptCommands(s.Username) - - report["total_deliverymen"] = report["total_deliverymen"].(int) + 1 - switch { - case s.Status == "offline": - report["offline"] = report["offline"].(int) + 1 - case s.Status == "busy" && queueSize >= MAX_COMMANDS_PER_DELIVERYMAN: - report["busy_full"] = report["busy_full"].(int) + 1 - case s.Status == "busy": - report["busy_delivering"] = report["busy_delivering"].(int) + 1 - case canAccept: - report["available"] = report["available"].(int) + 1 - } - - report["details"] = append(report["details"].([]map[string]any), map[string]any{ - "username": s.Username, - "status": s.Status, - "queue_size": queueSize, - "capacity": fmt.Sprintf("%d/10", queueSize), - "can_accept": canAccept, - "current_order": s.CurrentCommand, - }) - }) - return report, err -} - // iterDeliveryStatuses itère sur tous les statuts Redis des livreurs et appelle fn pour chacun. func (d *Database) iterDeliveryStatuses(fn func(models.DeliveryPersonStatus)) error { keys, err := Redis.Keys(RedisCtx, "delivery:status:*").Result() diff --git a/backend/gestion/db/redis_queue_optimization.go b/backend/gestion/db/redis_queue_optimization.go index 7d6b1231..750f4475 100644 --- a/backend/gestion/db/redis_queue_optimization.go +++ b/backend/gestion/db/redis_queue_optimization.go @@ -288,10 +288,6 @@ func (d *Database) RecalculateQueueETAs(deliveryman string) error { return nil } -func (d *Database) UpdateQueueETAsAfterCompletion(deliveryman string) error { - return d.RecalculateQueueETAs(deliveryman) -} - // FindNearestCommandInQueue trouve la commande la plus proche du livreur func (d *Database) FindNearestCommandInQueue(deliveryman string) (*models.CommandQueue, int, error) { livreurLat, livreurLng, err := d.GetDeliveryPersonLocation(deliveryman) diff --git a/backend/gestion/db/redis_session.go b/backend/gestion/db/redis_session.go index bb484301..78cdb951 100644 --- a/backend/gestion/db/redis_session.go +++ b/backend/gestion/db/redis_session.go @@ -3,7 +3,6 @@ package db import ( "encoding/json" "fmt" - "gestion/models" "log" "time" ) @@ -183,221 +182,3 @@ func (d *Database) InvalidateSession(clientID int) error { 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 -}