From a276e1126f605994f275528034f1ab8435dff7d2 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Wed, 22 Apr 2026 19:06:35 +0200 Subject: [PATCH] chore: refacto --- backend/gestion/db/db_basket.go | 12 +++ backend/gestion/db/redis_eta_management.go | 7 +- backend/gestion/db/redis_queue_clean_up.go | 5 -- backend/gestion/handlers/commands.go | 82 ++++--------------- backend/gestion/handlers/panier.go | 11 +-- .../gestion/handlers/validation_deleviry.go | 36 ++++++-- backend/gestion/workers/cron_auto_assign.go | 1 - 7 files changed, 64 insertions(+), 90 deletions(-) diff --git a/backend/gestion/db/db_basket.go b/backend/gestion/db/db_basket.go index 0fcbd33f..a6587614 100644 --- a/backend/gestion/db/db_basket.go +++ b/backend/gestion/db/db_basket.go @@ -347,6 +347,18 @@ func (d *Database) CheckBasketReservations(username string) (bool, error) { return result.Count > 0, nil } +func (d *Database) GetBasketItemOwner(basketID int) (string, error) { + var username string + err := d.GDB.Raw(`SELECT username FROM baskets WHERE id = ?`, basketID).Scan(&username).Error + if err != nil { + return "", err + } + if username == "" { + return "", fmt.Errorf("article non trouvé") + } + return username, nil +} + func (d *Database) GetBasketItems(username string) ([]map[string]any, error) { var items []map[string]any if err := d.GDB.Raw(`SELECT product_id, quantity::float8 as quantity, price::float8 as price FROM baskets WHERE username = ?`, diff --git a/backend/gestion/db/redis_eta_management.go b/backend/gestion/db/redis_eta_management.go index 6569d2ea..15672ce6 100644 --- a/backend/gestion/db/redis_eta_management.go +++ b/backend/gestion/db/redis_eta_management.go @@ -134,8 +134,11 @@ func (d *Database) CalculateETAForDeliveryman(deliveryman string, destLat, destL Longitude: destLng, } - distance := services.CalculateDistance(from, to) - eta := services.CalculateETA(distance) + eta, _, err2 := services.CalculateETAWithTomTom(from, to) + if err2 != nil { + distance := services.CalculateDistance(from, to) + eta = services.CalculateETA(distance) + } return eta } diff --git a/backend/gestion/db/redis_queue_clean_up.go b/backend/gestion/db/redis_queue_clean_up.go index 2382858d..17e70239 100644 --- a/backend/gestion/db/redis_queue_clean_up.go +++ b/backend/gestion/db/redis_queue_clean_up.go @@ -9,10 +9,6 @@ import ( "time" ) -// ============================================ -// NETTOYAGE DES COMMANDES INVALIDES -// ============================================ - // 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...") @@ -42,7 +38,6 @@ func (d *Database) CleanupInvalidQueueCommands() (int, error) { continue } - // ✅ VALIDATION DES CHAMPS REQUIS isValid := true reasons := []string{} diff --git a/backend/gestion/handlers/commands.go b/backend/gestion/handlers/commands.go index 23927419..bcbe99b8 100644 --- a/backend/gestion/handlers/commands.go +++ b/backend/gestion/handlers/commands.go @@ -6,6 +6,7 @@ import ( "gestion/utils" "log" "net/http" + "slices" "strconv" "strings" "sync" @@ -68,7 +69,6 @@ func validateAddress(address string) error { return nil } -// UpdateCommandAddress met à jour l'adresse de livraison d'une commande func UpdateCommandAddress(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -78,14 +78,12 @@ func UpdateCommandAddress(c *gin.Context) { return } - // ✅ Récupération sécurisée du username adminUsername, err := safeGetUsername(c) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } - // ✅ Rate limiting rateLimitKey := fmt.Sprintf("update_addr:%s", adminUsername) if !checkRateLimit(rateLimitKey) { log.Printf("⚠️ [UPD_ADDR] Rate limit dépassé pour %s", adminUsername) @@ -184,7 +182,6 @@ func ProposeAddressChange(c *gin.Context) { return } - // Récupérer la commande pour vérifier statut et obtenir username client command, err := database.GetCommandByID(commandID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) @@ -202,7 +199,6 @@ func ProposeAddressChange(c *gin.Context) { return } - // Notifier le client clientUsername, _ := command["username"].(string) if clientUsername != "" { msg := fmt.Sprintf("📍 Une nouvelle adresse de livraison vous est proposée pour votre commande #%d : %s. Veuillez l'accepter ou la refuser dans le suivi de commande.", database.GetClientOrderID(commandID), req.ProposedAddress) @@ -287,23 +283,16 @@ func GetAllCommands(c *gin.Context) { } } - log.Printf("🔍 [GET_CMDS] Filtre - status=[%s], username=[%s]", status, username) - commands, err := database.GetAllCommands(status, username) if err != nil { log.Printf("❌ [GET_CMDS] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la récupération des commandes", + "error": "Erreur lors de la récupération des commandes", }) return } - - // ✅ MODIFICATION: Pour le count, utiliser len(commands) au lieu de GetCommandCount() - // Cela permet d'avoir le vrai nombre de commandes retournées countCommand := len(commands) - log.Printf("✅ [GET_CMDS] Trouvées: %d commandes", countCommand) - c.JSON(http.StatusOK, gin.H{ "success": true, "commands": commands, @@ -312,7 +301,6 @@ func GetAllCommands(c *gin.Context) { } // GetCommandByID récupère une commande complète avec logs -// GET /api/v1/commands/:id func GetCommandByID(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -338,17 +326,14 @@ func GetCommandByID(c *gin.Context) { return } - // ✅ CRITIQUE: Vérification de propriété cmdUsername, _ := command["username"].(string) - // Seuls l'admin, la cabine ou le propriétaire peuvent voir la commande if userRole != "admin" && userRole != "cabine" && cmdUsername != username { log.Printf("❌ [GET_CMD] Accès refusé - User %s tente d'accéder à cmd de %s", username, cmdUsername) c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"}) return } - // ✅ Logs uniquement pour admin/cabine if userRole == "admin" || userRole == "cabine" { logs, err := database.GetCommandLogs(commandID) if err == nil { @@ -373,7 +358,6 @@ func ApproveDelivery(c *gin.Context) { return } - // ✅ Rate limiting rateLimitKey := fmt.Sprintf("approve:%s", username) if !checkRateLimit(rateLimitKey) { c.JSON(http.StatusTooManyRequests, gin.H{"error": "Trop de requêtes"}) @@ -387,13 +371,9 @@ func ApproveDelivery(c *gin.Context) { } log.Printf("✅ [APPROVE] Client %s approuve cmd %d", username, commandID) - - // ✅ TRANSACTION ATOMIQUE dans la DB pour éviter race condition - // Cette fonction doit être créée dans le fichier db totalPoints, pointCategory, err := database.ApproveDeliveryAtomic(commandID, username) if err != nil { log.Printf("❌ [APPROVE] Erreur: %v", err) - // ❌ Ne pas exposer les détails de l'erreur c.JSON(http.StatusBadRequest, gin.H{ "error": "Impossible d'approuver la livraison", }) @@ -536,15 +516,7 @@ func ValidateDelivery(c *gin.Context) { currentStatus, _ := command["status"].(string) validStatuses := []string{"assigned", "en_route", "pending", "livre"} - isValid := false - for _, s := range validStatuses { - if currentStatus == s { - isValid = true - break - } - } - - if !isValid { + if !slices.Contains(validStatuses, currentStatus) { failed = append(failed, gin.H{ "command_id": commandID, "error": "Statut invalide pour validation", @@ -600,7 +572,7 @@ func GetAvailableDeliveryPersons(c *gin.Context) { if err != nil { log.Printf("❌ [GET_LIVREURS] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la récupération des livreurs", + "error": "Erreur lors de la récupération des livreurs", }) return } @@ -646,7 +618,7 @@ func AssignDeliveryPerson(c *gin.Context) { } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "error": "Données invalides", + "error": "Données invalides", }) return } @@ -659,7 +631,7 @@ func AssignDeliveryPerson(c *gin.Context) { if err := database.AssignDeliveryPerson(commandID, livreurUsername); err != nil { log.Printf("❌ [ASSIGN] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur assignation livreur", + "error": "Erreur assignation livreur", }) return } @@ -697,7 +669,7 @@ func GetClientCommandsHistory(c *gin.Context) { if err != nil { log.Printf("❌ [HISTORY] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur récupération historique", + "error": "Erreur récupération historique", }) return } @@ -811,18 +783,12 @@ func NotifyClientToDescend(c *gin.Context) { }) } -// ============================================ -// DÉTAILS COMMANDES & ITEMS -// ============================================ - // ShowItems affiche les items d'une commande (Admin/Cabine) -// GET /api/v1/admin/commands/:id/items func ShowItems(c *gin.Context) { database := c.MustGet("database").(*db.Database) userRole := c.GetString("role") - // ✅ SÉCURITÉ: Admin ou Cabine seulement if userRole != "admin" && userRole != "cabine" { log.Printf("❌ [ITEMS] Accès refusé - role=%s", userRole) c.JSON(http.StatusForbidden, gin.H{ @@ -851,7 +817,7 @@ func ShowItems(c *gin.Context) { if err != nil { log.Printf("❌ [ITEMS] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la récupération des items", + "error": "Erreur lors de la récupération des items", }) return } @@ -860,7 +826,7 @@ func ShowItems(c *gin.Context) { log.Printf("⚠️ [ITEMS] Aucun item trouvé") c.JSON(http.StatusOK, gin.H{ "success": true, - "items": []map[string]interface{}{}, + "items": []map[string]any{}, "count": 0, }) return @@ -868,7 +834,7 @@ func ShowItems(c *gin.Context) { log.Printf("✅ [ITEMS] %d items récupérés", len(items)) - commandInfo := map[string]interface{}{ + commandInfo := map[string]any{ "id": items[0]["command_id"], "status": items[0]["command_status"], "address": items[0]["command_address"], @@ -878,7 +844,7 @@ func ShowItems(c *gin.Context) { "created_at": items[0]["command_created_at"], } - clientInfo := map[string]interface{}{ + clientInfo := map[string]any{ "username": items[0]["client_username"], "nom": items[0]["client_nom"], "prenom": items[0]["client_prenom"], @@ -895,11 +861,9 @@ func ShowItems(c *gin.Context) { } // GetCommandItemsWithDetails récupère les items enrichis -// GET /api/v1/commands/:id/items/detailed func GetCommandItemsWithDetails(c *gin.Context) { database := c.MustGet("database").(*db.Database) - // 🔐 Auth obligatoire username, exists := c.Get("username") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) @@ -919,7 +883,6 @@ func GetCommandItemsWithDetails(c *gin.Context) { log.Printf("📦 [ITEMS_DETAILED] User=%s Role=%s Cmd=%d", usernameStr, userRole, commandID) - // 🔒 ÉTAPE 1 — Vérifier l'accès à la commande allowed, err := database.CanUserAccessCommand(commandID, usernameStr, userRole) if err != nil { log.Printf("❌ [ITEMS_DETAILED] Erreur vérif accès: %v", err) @@ -937,12 +900,11 @@ func GetCommandItemsWithDetails(c *gin.Context) { return } - // 🔓 ÉTAPE 2 — Accès autorisé, récupérer les items items, err := database.GetCommandItems(commandID) if err != nil { log.Printf("❌ [ITEMS_DETAILED] Erreur DB: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur récupération items", + "error": "Erreur récupération items", }) return } @@ -954,7 +916,7 @@ func GetCommandItemsWithDetails(c *gin.Context) { return } - commandInfo := map[string]interface{}{ + commandInfo := map[string]any{ "id": items[0]["command_id"], "command_status": items[0]["command_status"], "command_address": items[0]["command_address"], @@ -979,8 +941,6 @@ func GetCommandItemsWithDetails(c *gin.Context) { }) } -// UpdateItemStatus met à jour le statut d'un item -// PUT /api/v1/admin/items/:item_id/status func UpdateItemStatus(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -998,7 +958,7 @@ func UpdateItemStatus(c *gin.Context) { if err := c.ShouldBindJSON(&req); err != nil { log.Printf("❌ [UPD_ITEM] Erreur JSON: %v", err) c.JSON(http.StatusBadRequest, gin.H{ - "error": "Status requis", + "error": "Status requis", }) return } @@ -1006,15 +966,7 @@ func UpdateItemStatus(c *gin.Context) { log.Printf("📝 [UPD_ITEM] Mise à jour: item=%d, status=%s", itemID, req.Status) validStatuses := []string{"pending", "preparing", "delivered"} - isValid := false - for _, vs := range validStatuses { - if req.Status == vs { - isValid = true - break - } - } - - if !isValid { + if !slices.Contains(validStatuses, req.Status) { log.Printf("❌ [UPD_ITEM] Statut invalide: %s", req.Status) c.JSON(http.StatusBadRequest, gin.H{ "error": "Statut invalide", @@ -1028,7 +980,7 @@ func UpdateItemStatus(c *gin.Context) { if err != nil { log.Printf("❌ [UPD_ITEM] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la mise à jour", + "error": "Erreur lors de la mise à jour", }) return } @@ -1043,7 +995,6 @@ func UpdateItemStatus(c *gin.Context) { }) } -// DeleteCommandItem supprime un item d'une commande // DELETE /api/v2/admin/protected/orders/:id/items/:item_id func DeleteCommandItem(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -1092,7 +1043,6 @@ func DeleteCommandItem(c *gin.Context) { }) } -// UpdateCommandStatusAdmin met à jour le statut d'une commande (Admin) // PUT /api/v2/admin/protected/orders/:id/status func UpdateCommandStatusAdmin(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/handlers/panier.go b/backend/gestion/handlers/panier.go index 69124d43..c11b5f64 100644 --- a/backend/gestion/handlers/panier.go +++ b/backend/gestion/handlers/panier.go @@ -179,11 +179,7 @@ func DeleteProductFromBasket(c *gin.Context) { log.Printf("🗑️ [DEL_PANIER] Suppression article: id=%d, client=%s", req.ID, authUsernameStr) // ✅ SÉCURITÉ 2: Vérifier que l'article appartient à ce client - var itemUsername string - err := database.QueryRow( - "SELECT username FROM baskets WHERE id = $1", - req.ID, - ).Scan(&itemUsername) + itemUsername, err := database.GetBasketItemOwner(req.ID) if err != nil { log.Printf("❌ [DEL_PANIER] Article non trouvé: id=%d", req.ID) @@ -260,12 +256,7 @@ func ClearBasket(c *gin.Context) { }) } -// ============================================ -// ✅ SÉCURISÉ: ValidateBasket - CHECKOUT FINAL -// ============================================ // POST /api/v1/checkout -// Crée la commande depuis le panier et le vide -// ⚠️ SEUL ENDPOINT DE CRÉATION DE COMMANDE (CreateCommandFromBasket supprimé) func ValidateBasket(c *gin.Context) { database := c.MustGet("database").(*db.Database) geoService := c.MustGet("geoService").(*services.GeoService) diff --git a/backend/gestion/handlers/validation_deleviry.go b/backend/gestion/handlers/validation_deleviry.go index 3b88f48f..006d8639 100644 --- a/backend/gestion/handlers/validation_deleviry.go +++ b/backend/gestion/handlers/validation_deleviry.go @@ -9,7 +9,6 @@ import ( "net/http" "strconv" - "github.com/gin-gonic/gin" ) @@ -56,7 +55,7 @@ func ValidateDeliveryByLivreur(c *gin.Context) { if err := c.ShouldBindJSON(&req); err != nil { log.Printf("❌ [VALIDATE_LIVREUR] Erreur JSON: %v", err) c.JSON(http.StatusBadRequest, gin.H{ - "error": "Coordonnées GPS requises", + "error": "Coordonnées GPS requises", }) return } @@ -98,7 +97,7 @@ func ValidateDeliveryByLivreur(c *gin.Context) { if err := database.UpdateCommandStatus(commandID, "livre"); err != nil { log.Printf("❌ [VALIDATE_LIVREUR] Erreur update statut: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur validation", + "error": "Erreur validation", }) return } @@ -297,7 +296,7 @@ func StartDelivery(c *gin.Context) { if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "error": "Coordonnées GPS requises", + "error": "Coordonnées GPS requises", }) return } @@ -333,7 +332,7 @@ func StartDelivery(c *gin.Context) { // Mettre à jour le statut en "en_route" if err := database.UpdateCommandStatus(commandID, "en_route"); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur mise à jour statut", + "error": "Erreur mise à jour statut", }) return } @@ -351,7 +350,32 @@ func StartDelivery(c *gin.Context) { // Notifier le client if clientUsername, _ := command["username"].(string); clientUsername != "" { - msg := fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route.", database.GetClientOrderID(commandID)) + var msg string + etaMinutes := 0 + if req.Latitude != 0 && req.Longitude != 0 { + destLat, _ := command["dest_latitude"].(float64) + destLon, _ := command["dest_longitude"].(float64) + if destLat != 0 && destLon != 0 { + etaMinutes = database.CalculateETAForDeliveryman(usernameStr, destLat, destLon) + } + } + if etaMinutes > 0 { + var etaStr string + if etaMinutes >= 60 { + h := etaMinutes / 60 + m := etaMinutes % 60 + if m > 0 { + etaStr = fmt.Sprintf("%dh%02d", h, m) + } else { + etaStr = fmt.Sprintf("%dh", h) + } + } else { + etaStr = fmt.Sprintf("%d min", etaMinutes) + } + msg = fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route (~%s)", database.GetClientOrderID(commandID), etaStr) + } else { + msg = fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route.", database.GetClientOrderID(commandID)) + } database.NotifyClient(clientUsername, commandID, "en_route", msg) } diff --git a/backend/gestion/workers/cron_auto_assign.go b/backend/gestion/workers/cron_auto_assign.go index e8c5e04e..846afb06 100644 --- a/backend/gestion/workers/cron_auto_assign.go +++ b/backend/gestion/workers/cron_auto_assign.go @@ -17,7 +17,6 @@ func StartAutoAssignmentCron(database *db.Database, geoService *services.GeoServ log.Println("⏰ [CRON] Auto-Assignment Worker démarré (1 min) avec système de priorisation") - // Exécution immédiate au démarrage go processAutoAssignmentWithPriority(database, geoService) for range ticker.C {