From dae547cfa959bc2f2ac75c944e026e7e5e28a3b6 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Thu, 25 Jun 2026 19:46:46 +0200 Subject: [PATCH] fix --- backend/gestion/db/db_commands.go | 2 +- backend/gestion/db/db_delivery.go | 5 +- backend/gestion/db/db_notifications.go | 59 +++++++++++++------- backend/gestion/db/redis_queue_clean_up.go | 6 +- backend/gestion/db/redis_queue_info.go | 20 ++----- backend/gestion/db/redis_queue_management.go | 20 ++++++- backend/gestion/handlers/auth.go | 8 +++ 7 files changed, 79 insertions(+), 41 deletions(-) diff --git a/backend/gestion/db/db_commands.go b/backend/gestion/db/db_commands.go index 0275fd4e..cdbf4577 100644 --- a/backend/gestion/db/db_commands.go +++ b/backend/gestion/db/db_commands.go @@ -240,7 +240,7 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (* // Stock déjà déduit à l'ajout au panier — ne pas déduire une seconde fois ici. } - if err := d.GDB.Delete(&models.Panier{}, "username = ?", username).Error; err != nil { + if err := d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil { log.Printf("⚠️ Erreur vidage panier: %v", err) } diff --git a/backend/gestion/db/db_delivery.go b/backend/gestion/db/db_delivery.go index 810b8458..fbb46a85 100644 --- a/backend/gestion/db/db_delivery.go +++ b/backend/gestion/db/db_delivery.go @@ -95,7 +95,6 @@ func (d *Database) AssignDeliveryPerson(commandID int, livreurUsername string) e }) } -// GetDeliveryPersonCommands récupère les commandes assignées à un livreur func (d *Database) GetDeliveryPersonCommands(livreurUsername string, status string) ([]map[string]any, error) { query := `SELECT id, username, status, adresse, total_prix::float8 as total_prix, livreur_assign, created_at, updated_at FROM commandes @@ -106,6 +105,10 @@ func (d *Database) GetDeliveryPersonCommands(livreurUsername string, status stri if status != "" { query += " AND status = ?" args = append(args, status) + } else { + // Ne retourner que les commandes actives — exclure l'historique terminé + // pour éviter le N+1 sur 66+ commandes qui fait timeout le mobile + query += " AND status IN ('assigned', 'en_route', 'arrived', 'livre')" } query += " ORDER BY created_at DESC" diff --git a/backend/gestion/db/db_notifications.go b/backend/gestion/db/db_notifications.go index 8bd09d3c..2048d0d9 100644 --- a/backend/gestion/db/db_notifications.go +++ b/backend/gestion/db/db_notifications.go @@ -33,12 +33,20 @@ func (d *Database) NotifyClient(username string, commandID int, notifType, messa } notifJSON, _ := json.Marshal(notification) - Redis.LPush(RedisCtx, notifKey, notifJSON) - Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour) + pipe := Redis.Pipeline() + pipe.LPush(RedisCtx, notifKey, notifJSON) + pipe.LTrim(RedisCtx, notifKey, 0, 199) + pipe.Expire(RedisCtx, notifKey, 7*24*time.Hour) + pipe.Exec(RedisCtx) //nolint if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { if chatID, ok, err := d.GetClientTelegramChatID(username); err == nil && ok { - go sendTelegramNotif(chatID, fmt.Sprintf("🔔 Notification\n\n%s", message)) + dedupKey := fmt.Sprintf("notif:dedup:%d:%s", commandID, notifType) + if set, _ := Redis.SetNX(RedisCtx, dedupKey, "1", 5*time.Minute).Result(); set { + go sendTelegramNotif(chatID, fmt.Sprintf("🔔 Notification\n\n%s", message)) + } else { + log.Printf("⚠️ [NOTIF] Doublon détecté (cmd=%d, type=%s) — Telegram ignoré", commandID, notifType) + } } } @@ -46,7 +54,6 @@ func (d *Database) NotifyClient(username string, commandID int, notifType, messa return nil } -// NotifyLivreur envoie une notification in-app (Redis) à un livreur func (d *Database) NotifyLivreur(username string, commandID int, notifType, message string) error { notifKey := fmt.Sprintf("notifications:%s", username) @@ -59,12 +66,20 @@ func (d *Database) NotifyLivreur(username string, commandID int, notifType, mess } notifJSON, _ := json.Marshal(notification) - Redis.LPush(RedisCtx, notifKey, notifJSON) - Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour) + pipe2 := Redis.Pipeline() + pipe2.LPush(RedisCtx, notifKey, notifJSON) + pipe2.LTrim(RedisCtx, notifKey, 0, 199) + pipe2.Expire(RedisCtx, notifKey, 7*24*time.Hour) + pipe2.Exec(RedisCtx) //nolint if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { if chatID, ok, err := d.GetUserTelegramChatID(username); err == nil && ok { - go sendTelegramNotif(chatID, fmt.Sprintf("🔔 Notification\n\n%s", message)) + dedupKey := fmt.Sprintf("notif:dedup:livreur:%d:%s", commandID, notifType) + if set, _ := Redis.SetNX(RedisCtx, dedupKey, "1", 5*time.Minute).Result(); set { + go sendTelegramNotif(chatID, fmt.Sprintf("🔔 Notification\n\n%s", message)) + } else { + log.Printf("⚠️ [NOTIF] Doublon livreur détecté (cmd=%d, type=%s) — Telegram ignoré", commandID, notifType) + } } } @@ -72,7 +87,6 @@ func (d *Database) NotifyLivreur(username string, commandID int, notifType, mess return nil } -// NotifyAllAdminCabine stocke une notification Redis pour tous les admins/cabines func (d *Database) NotifyAllAdminCabine(commandID int, clientUsername, deliveryAddr string) { var users []struct { Username string `gorm:"column:username"` @@ -93,24 +107,27 @@ func (d *Database) NotifyAllAdminCabine(commandID int, clientUsername, deliveryA } notifJSON, _ := json.Marshal(notification) - count := 0 + pipe := Redis.Pipeline() for _, u := range users { notifKey := fmt.Sprintf("notifications:%s", u.Username) - Redis.LPush(RedisCtx, notifKey, notifJSON) - Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour) + pipe.LPush(RedisCtx, notifKey, notifJSON) + pipe.LTrim(RedisCtx, notifKey, 0, 199) + pipe.Expire(RedisCtx, notifKey, 7*24*time.Hour) + } + pipe.Exec(RedisCtx) //nolint - if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { + count := len(users) + if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { + for _, u := range users { if chatID, ok, err := d.GetUserTelegramChatID(u.Username); err == nil && ok { capturedChatID := chatID go sendTelegramNotif(capturedChatID, fmt.Sprintf("🔔 Nouvelle commande\n\n%s", msg)) } } - count++ } log.Printf("📬 [ADMIN_NOTIF] Notif Redis (%d users) pour commande #%d", count, commandID) } -// NotifyAllAdminCabineAlert envoie une notification Redis à tous les admins/cabines lors d'une alerte func (d *Database) NotifyAllAdminCabineAlert(alertID int, livreurUsername, alertMessage string) { var users []struct { Username string `gorm:"column:username"` @@ -131,19 +148,23 @@ func (d *Database) NotifyAllAdminCabineAlert(alertID int, livreurUsername, alert } notifJSON, _ := json.Marshal(notification) - count := 0 + pipe := Redis.Pipeline() for _, u := range users { notifKey := fmt.Sprintf("notifications:%s", u.Username) - Redis.LPush(RedisCtx, notifKey, notifJSON) - Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour) + pipe.LPush(RedisCtx, notifKey, notifJSON) + pipe.LTrim(RedisCtx, notifKey, 0, 199) + pipe.Expire(RedisCtx, notifKey, 7*24*time.Hour) + } + pipe.Exec(RedisCtx) //nolint - if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { + count := len(users) + if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { + for _, u := range users { if chatID, ok, err := d.GetUserTelegramChatID(u.Username); err == nil && ok { capturedChatID := chatID go sendTelegramNotif(capturedChatID, fmt.Sprintf("🚨 Alerte livreur\n\n%s", body)) } } - count++ } log.Printf("🚨 [ALERT_NOTIF] Notif Redis (%d users) pour alerte #%d de %s", count, alertID, livreurUsername) } diff --git a/backend/gestion/db/redis_queue_clean_up.go b/backend/gestion/db/redis_queue_clean_up.go index 9b5fb81f..7d7eb702 100644 --- a/backend/gestion/db/redis_queue_clean_up.go +++ b/backend/gestion/db/redis_queue_clean_up.go @@ -9,11 +9,10 @@ import ( "time" ) -// CleanupInvalidQueueCommands supprime toutes les commandes avec des données manquantes func (d *Database) CleanupInvalidQueueCommands() (int, error) { log.Println("🧹 [CLEANUP] Démarrage du nettoyage des commandes invalides...") - keys, err := Redis.Keys(RedisCtx, "queue:pending:*").Result() + keys, err := scanRedisKeys("queue:pending:*") if err != nil { return 0, fmt.Errorf("erreur récupération des clés: %w", err) } @@ -82,7 +81,6 @@ func (d *Database) CleanupInvalidQueueCommands() (int, error) { return removedCount, nil } -// removeInvalidCommand supprime une commande invalide de toutes les queues func (d *Database) removeInvalidCommand(key string, commandID int, reason string) { commandIDStr := fmt.Sprintf("%d", commandID) @@ -94,7 +92,7 @@ func (d *Database) removeInvalidCommand(key string, commandID int, reason string Redis.ZRem(RedisCtx, "queue:priority:sorted", commandIDStr) // 3. Supprimer des queues de livreurs - livreurKeys, _ := Redis.Keys(RedisCtx, "queue:deliveryman:*").Result() + livreurKeys, _ := scanRedisKeys("queue:deliveryman:*") for _, queueKey := range livreurKeys { if len(queueKey) > 6 && queueKey[len(queueKey)-6:] == ":count" { continue diff --git a/backend/gestion/db/redis_queue_info.go b/backend/gestion/db/redis_queue_info.go index 5a27b350..dbca88a4 100644 --- a/backend/gestion/db/redis_queue_info.go +++ b/backend/gestion/db/redis_queue_info.go @@ -68,8 +68,9 @@ func (d *Database) GetAllQueuesOverview() (map[string]any, error) { overview["general_queue"] = generalQueueSize deliverymanQueues := make(map[string]any) - keys, _ := Redis.Keys(RedisCtx, "queue:deliveryman:*").Result() + keys, _ := scanRedisKeys("queue:deliveryman:*") + var totalPending int64 = generalQueueSize for _, key := range keys { if len(key) > 6 && key[len(key)-6:] == ":count" { continue @@ -83,29 +84,19 @@ func (d *Database) GetAllQueuesOverview() (map[string]any, error) { "can_accept_more": queueSize < MAX_COMMANDS_PER_DELIVERYMAN, "capacity": fmt.Sprintf("%d/%d", queueSize, MAX_COMMANDS_PER_DELIVERYMAN), } - } - - overview["deliveryman_queues"] = deliverymanQueues - var totalPending int64 = generalQueueSize - for _, key := range keys { - if len(key) > 6 && key[len(key)-6:] == ":count" { - continue - } - size, _ := Redis.ZCard(RedisCtx, key).Result() - totalPending += size + totalPending += queueSize } overview["total_pending"] = totalPending return overview, nil } -// GetQueueStats - Statistiques détaillées func (d *Database) GetQueueStats() (map[string]any, error) { normalCount, _ := Redis.ZCard(RedisCtx, "queue:pending:sorted").Result() priorityCount, _ := Redis.ZCard(RedisCtx, "queue:priority:sorted").Result() var deliverymanQueueCount int64 - keys, _ := Redis.Keys(RedisCtx, "queue:deliveryman:*").Result() + keys, _ := scanRedisKeys("queue:deliveryman:*") for _, key := range keys { if len(key) > 6 && key[len(key)-6:] == ":count" { continue @@ -117,7 +108,8 @@ func (d *Database) GetQueueStats() (map[string]any, error) { var totalWaitTime int64 var commandCount int64 - normalResults, _ := Redis.ZRangeWithScores(RedisCtx, "queue:pending:sorted", 0, -1).Result() + // Limité aux 100 premières entrées pour ne pas bloquer Redis sur une grande queue + normalResults, _ := Redis.ZRangeWithScores(RedisCtx, "queue:pending:sorted", 0, 99).Result() for _, result := range normalResults { commandID := extractCommandID(result.Member) if commandID <= 0 { diff --git a/backend/gestion/db/redis_queue_management.go b/backend/gestion/db/redis_queue_management.go index ae5097ce..b613304e 100644 --- a/backend/gestion/db/redis_queue_management.go +++ b/backend/gestion/db/redis_queue_management.go @@ -142,7 +142,6 @@ func (d *Database) AddToGeneralQueue(queueItem models.CommandQueue) error { return nil } -// RemoveCommandFromQueue - VERSION AMÉLIORÉE avec auto-update du statut func (d *Database) RemoveCommandFromQueue(commandID int) error { key := fmt.Sprintf("queue:pending:%d", commandID) commandIDStr := strconv.Itoa(commandID) @@ -153,7 +152,7 @@ func (d *Database) RemoveCommandFromQueue(commandID int) error { pipe.ZRem(RedisCtx, "queue:priority:sorted", commandIDStr) // Trouver et retirer de la queue du livreur - keys, _ := Redis.Keys(RedisCtx, "queue:deliveryman:*").Result() + keys, _ := scanRedisKeys("queue:deliveryman:*") var affectedDeliveryman string for _, queueKey := range keys { @@ -320,3 +319,20 @@ func (d *Database) iterDeliveryStatuses(fn func(models.DeliveryPersonStatus)) er } return nil } + +func scanRedisKeys(pattern string) ([]string, error) { + var all []string + cursor := uint64(0) + for { + batch, next, err := Redis.Scan(RedisCtx, cursor, pattern, 200).Result() + if err != nil { + return nil, err + } + all = append(all, batch...) + cursor = next + if cursor == 0 { + break + } + } + return all, nil +} diff --git a/backend/gestion/handlers/auth.go b/backend/gestion/handlers/auth.go index ff51915e..03eee1fc 100644 --- a/backend/gestion/handlers/auth.go +++ b/backend/gestion/handlers/auth.go @@ -838,6 +838,14 @@ func CreateUser(c *gin.Context) { return } + hashed, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) + if err != nil { + log.Printf("❌ [CREATE_USER] Erreur bcrypt: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur traitement mot de passe"}) + return + } + user.Password = string(hashed) + if err := database.CreateUser(&user); err != nil { log.Printf("❌ [CREATE_USER] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création"})