From dce417c2095a40e7327a44f34b4f87942bc608ca Mon Sep 17 00:00:00 2001 From: Xor290 Date: Thu, 14 May 2026 19:33:39 +0200 Subject: [PATCH] chore: refacto --- backend/gestion/handlers/auth.go | 84 +-- backend/gestion/handlers/cabine.go | 512 +----------------- backend/gestion/handlers/cancel_command.go | 13 - backend/gestion/handlers/commands.go | 11 - backend/gestion/handlers/delivery_admin.go | 41 +- backend/gestion/handlers/eta.go | 5 - backend/gestion/handlers/geoloca.go | 22 - backend/gestion/handlers/gps.go | 51 -- backend/gestion/handlers/history.go | 26 +- backend/gestion/handlers/panier.go | 24 - backend/gestion/handlers/product.go | 39 -- backend/gestion/handlers/redis_services.go | 239 +------- backend/gestion/handlers/stats.go | 36 +- backend/gestion/handlers/traffic.go | 2 +- backend/gestion/handlers/update_profile.go | 12 - .../gestion/handlers/validation_deleviry.go | 256 --------- .../gestion/middleware/session_middleware.go | 103 +--- backend/gestion/models/stats.go | 21 + backend/gestion/routes/routes.go | 1 - 19 files changed, 63 insertions(+), 1435 deletions(-) create mode 100644 backend/gestion/models/stats.go diff --git a/backend/gestion/handlers/auth.go b/backend/gestion/handlers/auth.go index 3bba4bb9..aa53c5c7 100644 --- a/backend/gestion/handlers/auth.go +++ b/backend/gestion/handlers/auth.go @@ -412,9 +412,9 @@ func GetClient2FAStatus(c *gin.Context) { settings, _ := database.GetSettings() c.JSON(http.StatusOK, gin.H{ - "two_fa_enabled": client.TwoFAEnabled, - "telegram_linked": tgLinked, - "admin_2fa_enabled": settings.Telegram2FAEnabled, + "two_fa_enabled": client.TwoFAEnabled, + "telegram_linked": tgLinked, + "admin_2fa_enabled": settings.Telegram2FAEnabled, }) } @@ -520,60 +520,6 @@ func LogoutClient(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Déconnexion réussie"}) } -// RegisterAdmin crée un nouvel utilisateur admin/cabine/livreur -func RegisterAdmin(c *gin.Context) { - var req models.RegisterAdminRequest - if err := c.ShouldBindJSON(&req); err != nil { - log.Printf("❌ [REGISTER_ADMIN] Erreur binding: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) - return - } - - database := c.MustGet("database").(*db.Database) - - if existingUser, _ := database.GetUserByUsername(req.Username); existingUser != nil { - log.Printf("❌ [REGISTER_ADMIN] Username déjà utilisé: %s", req.Username) - c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"}) - return - } - - hashed, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) - user := &models.User{ - Username: req.Username, - Password: string(hashed), - Role: req.Role, - } - - if err := database.CreateUser(user); err != nil { - log.Printf("❌ [REGISTER_ADMIN] Erreur création: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création utilisateur"}) - return - } - - token, err := generateAdminToken(user) - if err != nil { - log.Printf("❌ [REGISTER_ADMIN] Erreur génération token: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur génération token"}) - return - } - - expiresAt := time.Now().Add(adminTokenDuration) - if err := database.SaveToken(user.ID, user.Role, token, expiresAt); err != nil { - log.Printf("❌ [REGISTER_ADMIN] Erreur SaveToken: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur enregistrement token"}) - return - } - - user.Password = "" - - c.JSON(http.StatusCreated, models.LoginResponse{ - AccessToken: token, - TokenType: "Bearer", - ExpiresIn: int(adminTokenDuration.Seconds()), - User: user, - }) -} - // LoginAdmin authentifie un admin/cabine/livreur func LoginAdmin(c *gin.Context) { var req models.LoginRequest @@ -707,30 +653,6 @@ func GetCurrentAdmin(c *gin.Context) { }) } -// HealthCheck vérifie la santé de l'API -// GET /api/v1/health -func HealthCheck(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - if err := database.DB.Ping(); err != nil { - log.Printf("⚠️ [HEALTH] Database down: %v", err) - c.JSON(http.StatusServiceUnavailable, gin.H{ - "status": "unhealthy", - "database": "disconnected", - "timestamp": time.Now().Unix(), - }) - return - } - - log.Printf("✅ [HEALTH] API healthy") - - c.JSON(http.StatusOK, gin.H{ - "status": "healthy", - "database": "connected", - "timestamp": time.Now().Unix(), - "version": "2.0.0", - }) -} - // GetAllUsers récupère tous les utilisateurs (Admin only) func GetAllUsers(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/handlers/cabine.go b/backend/gestion/handlers/cabine.go index 6a57d4ac..76ccab24 100644 --- a/backend/gestion/handlers/cabine.go +++ b/backend/gestion/handlers/cabine.go @@ -1,244 +1,13 @@ -// ============================================ -// handlers/cabine_handlers.go - COMPLET -// INCLUT: SetCommandDestinationCoordinates -// ============================================ - package handlers import ( - "encoding/json" - "fmt" "gestion/db" - "gestion/utils" - "log" "net/http" - "slices" "strconv" - "time" "github.com/gin-gonic/gin" ) -// ============================================ -// 0️⃣ FONCTION ADMIN: SET DESTINATION COORDINATES -// ============================================ - -// SetCommandDestinationCoordinates stocke les coordonnées destination en Redis -func SetCommandDestinationCoordinates(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - userRole := c.GetString("role") - if userRole != "admin" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux administrateurs"}) - return - } - - adminUsername := c.GetString("username") - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - var req struct { - Latitude float64 `json:"latitude" binding:"required"` - Longitude float64 `json:"longitude" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Latitude et longitude requises", - }) - return - } - - // Validation des coordonnées GPS - if req.Latitude < -90 || req.Latitude > 90 { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Latitude invalide (doit être entre -90 et 90)", - "value": req.Latitude, - }) - return - } - - if req.Longitude < -180 || req.Longitude > 180 { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Longitude invalide (doit être entre -180 et 180)", - "value": req.Longitude, - }) - return - } - - if !utils.CheckCommand(commandID, database) { - c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) - } - - destCacheKey := fmt.Sprintf("command:destination:%d", commandID) - coordsJSON, _ := json.Marshal(map[string]float64{ - "lat": req.Latitude, - "lon": req.Longitude, - }) - - ttlSeconds := 24 * 60 * 60 // 24 heures - err = db.Redis.Set(db.RedisCtx, destCacheKey, coordsJSON, time.Duration(ttlSeconds)*time.Second).Err() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur stockage Redis", - }) - return - } - - // Ajouter un log - database.AddCommandLog(commandID, "destination_set", - fmt.Sprintf("Coordonnées destination définies par admin %s: (%.6f, %.6f) via Redis", - adminUsername, req.Latitude, req.Longitude), - adminUsername) - - log.Printf("✅ [ADMIN %s] Coordonnées destination définies pour CMD %d: (%.6f, %.6f) en Redis", - adminUsername, commandID, req.Latitude, req.Longitude) - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Coordonnées définies avec succès en Redis", - "command_id": commandID, - }) -} - -// ============================================ -// 1. CLIENT PROFILE -// ============================================ - -func GetClientProfile(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - username := c.Param("username") - - if username == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"}) - return - } - - client, err := database.GetClientByUsername(username) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"}) - return - } - - client.Password = "" - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "client": gin.H{ - "id": client.ID, - "username": client.Username, - "command": client.Command, - "amende": client.Amende, - "points_extra": client.PointsExtra, - "created_at": client.CreatedAt, - }, - }) -} - -func GetClientFullHistory(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - username := c.Param("username") - - if username == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"}) - return - } - - client, err := database.GetClientByUsername(username) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"}) - return - } - - commands, err := database.GetAllCommands("", username) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur récupération historique"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "client": gin.H{ - "username": client.Username, - "total_commands": client.Command, - "amende": client.Amende, - "points_extra": client.PointsExtra, - }, - "commands": commands, - "count": len(commands), - }) -} - -// ============================================ -// 2. UPDATE ADDRESS -// ============================================ - -func UpdateCommandAddressCabine(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - var req struct { - DeliveryAddress string `json:"delivery_address" binding:"required"` - Reason string `json:"reason"` - } - if err := c.ShouldBindJSON(&req); err != nil || req.DeliveryAddress == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Adresse de livraison requise"}) - return - } - - command, err := database.GetCommandByID(commandID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) - return - } - - status, _ := command["status"].(string) - - allowedStatuses := []string{"pending", "", "assigned"} - if !slices.Contains(allowedStatuses, status) { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Impossible de modifier l'adresse d'une commande en cours ou terminée", - "current_status": status, - "allowed_statuses": allowedStatuses, - }) - return - } - - if err := database.UpdateCommandAddress(commandID, req.DeliveryAddress); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la mise à jour de l'adresse", - }) - return - } - - cabineUsername, _ := c.Get("username") - message := fmt.Sprintf("Adresse modifiée par cabine: %s", req.DeliveryAddress) - if req.Reason != "" { - message += fmt.Sprintf(" (Raison: %s)", req.Reason) - } - database.AddCommandLog(commandID, "address_updated", message, cabineUsername.(string)) - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Adresse de livraison mise à jour", - "command_id": commandID, - "delivery_address": req.DeliveryAddress, - }) -} - -// ============================================ -// 3. LIVREUR POSITION -// ============================================ - func GetLivreurPosition(c *gin.Context) { database := c.MustGet("database").(*db.Database) livreurUsername := c.Param("username") @@ -273,108 +42,6 @@ func GetLivreurPosition(c *gin.Context) { }) } -func GetDeliveryTrackingClient(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - username, exists := c.Get("username") - if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) - return - } - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - command, err := database.GetCommandByID(commandID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) - return - } - - if command["username"].(string) != username.(string) { - c.JSON(http.StatusForbidden, gin.H{"error": "Cette commande ne vous appartient pas"}) - return - } - - livreurAssign, _ := command["livreur_assign"].(string) - logs, _ := database.GetCommandLogs(commandID) - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "command_id": commandID, - "status": command["status"], - "livreur": livreurAssign, - "address": command["adresse"], - "logs": logs, - "message": "Suivi en cours - ETA disponible via /api/v1/orders/:id/eta", - }) -} - -// ============================================ -// 5. DELIVERY TRACKING ADMIN (AVEC GPS) -// ============================================ - -func GetDeliveryTracking(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - userRole := c.GetString("role") - if userRole != "admin" && userRole != "cabine" { - c.JSON(http.StatusForbidden, gin.H{ - "error": "Accès refusé - Réservé aux administrateurs et cabines", - }) - return - } - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - command, err := database.GetCommandByID(commandID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) - return - } - - livreurAssign, _ := command["livreur_assign"].(string) - if livreurAssign == "" { - c.JSON(http.StatusOK, gin.H{ - "success": true, - "command": command, - "status": "Aucun livreur assigné", - }) - return - } - - position, err := database.GetLivreurPosition(livreurAssign) - logs, _ := database.GetCommandLogs(commandID) - - response := gin.H{ - "success": true, - "command": command, - "livreur": livreurAssign, - "logs": logs, - } - - status, _ := command["status"].(string) - if err != nil && (status == "livre" || status == "approved") { - response["livreur_position"] = nil - response["position_status"] = "Livraison terminée - Position non suivie" - } else if err != nil { - response["livreur_position"] = nil - response["position_status"] = "Position non disponible (GPS peut-être désactivé)" - } else { - response["livreur_position"] = position - response["position_status"] = "Position en temps réel" - } - - c.JSON(http.StatusOK, response) -} - func GetDeliveryIssues(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -390,7 +57,7 @@ func GetDeliveryIssues(c *gin.Context) { issues, err := database.GetDeliveryIssues(status) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur récupération problèmes", + "error": "Erreur récupération problèmes", }) return } @@ -426,7 +93,7 @@ func CreateDeliveryIssue(c *gin.Context) { ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur création problème", + "error": "Erreur création problème", }) return } @@ -462,7 +129,7 @@ func UpdateDeliveryIssue(c *gin.Context) { err = database.UpdateDeliveryIssue(issueID, req.Status, req.Resolution, cabineUsername.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur mise à jour", + "error": "Erreur mise à jour", }) return } @@ -473,53 +140,6 @@ func UpdateDeliveryIssue(c *gin.Context) { }) } -func AddDeliverySupport(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID commande invalide"}) - return - } - - var req struct { - Message string `json:"message"` - } - - c.ShouldBindJSON(&req) - - if req.Message == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Message requis", - "example": gin.H{ - "message": "Votre message de support ici", - }, - }) - return - } - - cabineUsername, _ := c.Get("username") - - err = database.AddCommandLog( - commandID, - "note", - fmt.Sprintf("Note cabine: %s", req.Message), - cabineUsername.(string), - ) - - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur ajout support", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Support ajouté", - }) -} - func GetCommandLogs(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -532,7 +152,7 @@ func GetCommandLogs(c *gin.Context) { logs, err := database.GetCommandLogs(commandID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur récupération logs", + "error": "Erreur récupération logs", }) return } @@ -543,127 +163,3 @@ func GetCommandLogs(c *gin.Context) { "count": len(logs), }) } - -// ============================================ -// 7. FORCE VALIDATE DELIVERY -// ============================================ - -func ForceValidateDelivery(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - userRole := c.GetString("role") - if userRole != "admin" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé - Admin seulement"}) - return - } - - adminUsername, _ := c.Get("username") - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - var req struct { - Reason string `json:"reason" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Raison requise pour validation forcée", - "example": gin.H{ - "reason": "Client confirmé par téléphone", - }, - }) - return - } - - if req.Reason == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Veuillez fournir une raison pour la validation forcée", - }) - return - } - - command, err := database.GetCommandByID(commandID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Commande non trouvée", - "command_id": commandID, - }) - return - } - - status, ok := command["status"].(string) - if !ok { - c.JSON(http.StatusBadRequest, gin.H{"error": "Statut de commande invalide"}) - return - } - - if status == "livre" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Cette commande a déjà été validée", - "current_status": status, - }) - return - } - - validStatuses := []string{"assigned", "en_route", "pending", "priority"} - if !slices.Contains(validStatuses, status) { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Commande ne peut pas être validée de force dans ce statut", - "current_status": status, - "valid_statuses": validStatuses, - }) - return - } - - err = database.UpdateCommandStatus(commandID, "livre") - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la validation forcée", - }) - return - } - - clientUsername, _ := command["username"].(string) - livreurAssign, _ := command["livreur_assign"].(string) - - if clientUsername != "" { - clientMsg := fmt.Sprintf("Ta commande #%d a bien été livrée ! Bonne dégustation l'ami et à bientôt 😊\n\n⚠️ VALIDE LA RÉCEPTION DE TA COMMANDE DANS LA RUBRIQUE SUIVI POUR RÉCUPÉRER TES POINTS DE FIDÉLITÉ ⚠️", database.GetClientOrderID(commandID)) - database.NotifyClient(clientUsername, commandID, "livre", clientMsg) - } - - if err := database.IncrementClientCommandCount(clientUsername); err != nil { - log.Printf("⚠️ Erreur compteur commandes: %v", err) - } - - if err := database.AddClientPointsByCategory(clientUsername, 10, ""); err != nil { - log.Printf("⚠️ Erreur ajout points: %v", err) - } - - if livreurAssign != "" { - err := database.CompleteDeliveryAndProcessNext(livreurAssign, commandID) - if err != nil { - log.Printf("⚠️ Erreur optimisation: %v", err) - } - } - - message := fmt.Sprintf("VALIDATION FORCÉE par admin %s - Raison: %s", adminUsername.(string), req.Reason) - database.AddCommandLog(commandID, "livre", message, adminUsername.(string)) - - log.Printf("🔴 Commande %d validée de force par %s - Raison: %s", commandID, adminUsername.(string), req.Reason) - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Livraison validée de force (sans vérification GPS)", - "command_id": commandID, - "validation_type": "forced", - "reason": req.Reason, - "validated_by": adminUsername.(string), - "new_status": "livre", - "points_awarded": 10, - "queue_optimized": livreurAssign != "", - }) -} diff --git a/backend/gestion/handlers/cancel_command.go b/backend/gestion/handlers/cancel_command.go index d0b7b840..ee437f8f 100644 --- a/backend/gestion/handlers/cancel_command.go +++ b/backend/gestion/handlers/cancel_command.go @@ -1,9 +1,3 @@ -// ============================================ -// handlers/cancel_command_handler.go -// ANNULATION DE COMMANDES AVEC SANCTIONS ÉVOLUTIVES -// VERSION SÉCURISÉE - FIX ETA CHECK -// ============================================ - package handlers import ( @@ -218,9 +212,6 @@ func CancelCommandByClient(c *gin.Context) { return } - // ============================================ - // SUCCÈS - // ============================================ log.Printf("✅ [CANCEL_CLIENT] Commande %d annulée", commandID) response := gin.H{ @@ -242,10 +233,6 @@ func CancelCommandByClient(c *gin.Context) { c.JSON(http.StatusOK, response) } -// ============================================ -// HISTORIQUE DES ANNULATIONS - VERSION SÉCURISÉE -// ============================================ - func GetMyCancellationHistory(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/handlers/commands.go b/backend/gestion/handlers/commands.go index 68a23b1d..56174424 100644 --- a/backend/gestion/handlers/commands.go +++ b/backend/gestion/handlers/commands.go @@ -595,10 +595,6 @@ func ValidateDelivery(c *gin.Context) { }) } -// ============================================ -// GESTION ADMIN -// ============================================ - // GetAvailableDeliveryPersons récupère les livreurs disponibles // GET /api/v1/admin/delivery-persons/available func GetAvailableDeliveryPersons(c *gin.Context) { @@ -778,13 +774,6 @@ func GetClientCommandsHistory(c *gin.Context) { c.JSON(http.StatusOK, resp) } -// ============================================ -// NOTIFICATIONS CLIENT -// ============================================ - -// NotifyClientToDescend envoie une notification push au client pour descendre récupérer sa commande -// POST /api/v2/admin/protected/orders/:id/notify-client -// POST /api/v1/cabine/commands/:id/notify-client func NotifyClientToDescend(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/handlers/delivery_admin.go b/backend/gestion/handlers/delivery_admin.go index d026df9f..a905908a 100644 --- a/backend/gestion/handlers/delivery_admin.go +++ b/backend/gestion/handlers/delivery_admin.go @@ -11,6 +11,7 @@ import ( "gestion/utils" "log" "net/http" + "slices" "strconv" "time" @@ -39,7 +40,7 @@ func GetDeliveryPersonDetails(c *gin.Context) { if err != nil { log.Printf("❌ [GET_DELIVERY_DETAILS] Livreur non trouvé: %v", err) c.JSON(http.StatusNotFound, gin.H{ - "error": "Livreur non trouvé", + "error": "Livreur non trouvé", }) return } @@ -116,22 +117,14 @@ func UpdateDeliveryPersonStatusAdmin(c *gin.Context) { if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "error": "Statut requis", + "error": "Statut requis", }) return } - // Valider le statut validStatuses := []string{"available", "busy", "offline"} - isValid := false - for _, vs := range validStatuses { - if req.Status == vs { - isValid = true - break - } - } - if !isValid { + if !slices.Contains(validStatuses, req.Status) { c.JSON(http.StatusBadRequest, gin.H{ "error": "Statut invalide", "valid_statuses": validStatuses, @@ -160,7 +153,7 @@ func UpdateDeliveryPersonStatusAdmin(c *gin.Context) { if err != nil { log.Printf("❌ [UPDATE_DELIVERY_STATUS] Erreur mise à jour: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur mise à jour statut", + "error": "Erreur mise à jour statut", }) return } @@ -321,7 +314,7 @@ func GetDeliveryPersonHistory(c *gin.Context) { if err != nil { log.Printf("❌ [GET_DELIVERY_HISTORY] Erreur récupération: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur récupération historique", + "error": "Erreur récupération historique", }) return } @@ -368,7 +361,7 @@ func UpdateDeliveryPersonLocationAdmin(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 } @@ -415,7 +408,7 @@ func UpdateDeliveryPersonLocationAdmin(c *gin.Context) { if err != nil { log.Printf("❌ [UPDATE_DELIVERY_LOCATION] Erreur mise à jour: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur mise à jour position", + "error": "Erreur mise à jour position", }) return } @@ -437,12 +430,6 @@ func UpdateDeliveryPersonLocationAdmin(c *gin.Context) { }) } -// ============================================ -// 🗑️ REMOVE COMMAND FROM QUEUE -// ============================================ - -// RemoveCommandFromQueue retire une commande de la queue d'un livreur -// DELETE /api/v2/admin/protected/delivery-persons/:username/queue/:command_id func RemoveCommandFromQueue(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -474,9 +461,6 @@ func RemoveCommandFromQueue(c *gin.Context) { log.Printf("🗑️ [REMOVE_FROM_QUEUE] Suppression: cmd %d de la queue de %s", commandID, username) - // ============================================ - // Vérifier que le livreur existe - // ============================================ livreur, err := database.GetUserByUsername(username) if err != nil { log.Printf("❌ [REMOVE_FROM_QUEUE] Livreur non trouvé") @@ -491,9 +475,6 @@ func RemoveCommandFromQueue(c *gin.Context) { return } - // ============================================ - // Vérifier que la commande existe - // ============================================ command, err := database.GetCommandByID(commandID) if err != nil { log.Printf("❌ [REMOVE_FROM_QUEUE] Commande non trouvée") @@ -501,19 +482,15 @@ func RemoveCommandFromQueue(c *gin.Context) { return } - // ============================================ - // Retirer de la queue - // ============================================ err = database.RemoveCommandFromDeliverymanQueue(username, commandID) if err != nil { log.Printf("❌ [REMOVE_FROM_QUEUE] Erreur suppression: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur suppression de la queue", + "error": "Erreur suppression de la queue", }) return } - // Optionnel: Réassigner la commande en "pending" currentStatus, _ := command["status"].(string) if currentStatus == "assigned" || currentStatus == "en_route" { err = database.UpdateCommandStatus(commandID, "pending") diff --git a/backend/gestion/handlers/eta.go b/backend/gestion/handlers/eta.go index e1565463..96ed5ba2 100644 --- a/backend/gestion/handlers/eta.go +++ b/backend/gestion/handlers/eta.go @@ -1,8 +1,3 @@ -// ============================================ -// handlers/eta_handler_corrected.go -// CORRECTION: ETA visible UNIQUEMENT après en_route -// ============================================ - package handlers import ( diff --git a/backend/gestion/handlers/geoloca.go b/backend/gestion/handlers/geoloca.go index b79ad92e..70245fe5 100644 --- a/backend/gestion/handlers/geoloca.go +++ b/backend/gestion/handlers/geoloca.go @@ -13,11 +13,6 @@ import ( "github.com/gin-gonic/gin" ) -// ============================================ -// GÉOCODAGE D'ADRESSES -// ============================================ -// geo_handlers.go - func GeocodeAddress(c *gin.Context) { geoService := c.MustGet("geoService").(*services.GeoService) @@ -323,9 +318,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) { log.Printf("📍 Adresse géocodée: %s -> (%.6f, %.6f)", address, location.Latitude, location.Longitude) - // ============================================ - // 🔹 SAUVEGARDER LES COORDONNÉES DANS LE CACHE REDIS - // ============================================ destCacheKey := fmt.Sprintf("command:destination:%d", commandID) coordsJSON, _ := json.Marshal(map[string]float64{ "lat": location.Latitude, @@ -555,10 +547,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) { }) } -// ============================================ -// ASSIGNATION EN MASSE (TOUTES LES COMMANDES PENDING) -// ============================================ - // AutoAssignAllPendingCommands assigne toutes les commandes en attente // POST /api/v2/admin/protected/commands/auto-assign-all func AutoAssignAllPendingCommands(c *gin.Context) { @@ -716,10 +704,6 @@ func AutoAssignAllPendingCommands(c *gin.Context) { }) } -// ============================================ -// RÉCUPÉRER L'ÉTAT DES QUEUES DES LIVREURS -// ============================================ - // GetAllDeliveryQueues retourne l'état de toutes les queues des livreurs // GET /api/v2/admin/protected/delivery/queues func GetAllDeliveryQueues(c *gin.Context) { @@ -769,12 +753,6 @@ func GetAllDeliveryQueues(c *gin.Context) { }) } -// ============================================ -// RÉCUPÉRER LA QUEUE D'UN LIVREUR SPÉCIFIQUE -// ============================================ - -// GetDeliverymanQueue retourne la queue d'un livreur spécifique -// GET /api/v2/admin/protected/delivery/:username/queue func GetDeliverymanQueue(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/handlers/gps.go b/backend/gestion/handlers/gps.go index 21d8343d..4897de64 100644 --- a/backend/gestion/handlers/gps.go +++ b/backend/gestion/handlers/gps.go @@ -13,7 +13,6 @@ import ( "net/url" "strconv" - "github.com/gin-gonic/gin" ) @@ -79,56 +78,6 @@ func GetDeliveryPersonMapLinks(c *gin.Context) { }) } -// GetCommandNavigationLinks génère les liens de navigation pour une commande -// GET /api/v2/admin/protected/commands/:id/navigation-links -func GetCommandNavigationLinks(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - userRole := c.GetString("role") - if userRole != "admin" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"}) - return - } - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - // Récupérer la commande - command, err := database.GetCommandByID(commandID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) - return - } - - // Vérifier qu'un livreur est assigné - livreurAssign, ok := command["livreur_assign"].(string) - if !ok || livreurAssign == "" { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Aucun livreur assigné à cette commande", - }) - return - } - - // Générer les liens de navigation - links, err := database.GenerateMapLinksForCommand(commandID, livreurAssign) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur génération des liens", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "command_id": commandID, - "deliveryman": livreurAssign, - "navigation_links": links, - }) -} - // GetLivreurNavLink retourne le lien Waze App pour une livraison assignée au livreur connecté // GET /api/v1/livreur/deliveries/:id/nav-link func GetLivreurNavLink(c *gin.Context) { diff --git a/backend/gestion/handlers/history.go b/backend/gestion/handlers/history.go index d05ab4ce..8fbc858b 100644 --- a/backend/gestion/handlers/history.go +++ b/backend/gestion/handlers/history.go @@ -1,8 +1,3 @@ -// ============================================ -// handlers/history_handlers.go -// ============================================ -// Gestion de l'historique des commandes terminées - package handlers import ( @@ -12,14 +7,9 @@ import ( "net/http" "strconv" - "github.com/gin-gonic/gin" ) -// GetMyCompletedOrders récupère l'historique des commandes terminées du client -// GET /api/v1/my-commands/history -// ✅ Authentification requise (ClientMiddleware) -// ✅ Retourne uniquement les commandes avec status = "approved" func GetMyCompletedOrders(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -41,7 +31,7 @@ func GetMyCompletedOrders(c *gin.Context) { if err != nil { log.Printf("❌ [HISTORY] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la récupération de l'historique", + "error": "Erreur lors de la récupération de l'historique", }) return } @@ -96,8 +86,6 @@ func GetMyCompletedOrders(c *gin.Context) { // GetMyCompletedOrdersWithItems récupère l'historique avec les détails des items // GET /api/v1/my-commands/history/detailed -// ✅ Authentification requise (ClientMiddleware) -// ✅ Retourne les commandes approved avec tous les items func GetMyCompletedOrdersWithItems(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -119,13 +107,13 @@ func GetMyCompletedOrdersWithItems(c *gin.Context) { if err != nil { log.Printf("❌ [HISTORY_DETAILED] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la récupération de l'historique", + "error": "Erreur lors de la récupération de l'historique", }) return } // ✅ Enrichir chaque commande avec ses items - var enrichedCommands []map[string]interface{} + var enrichedCommands []map[string]any for _, command := range commands { commandID, _ := strconv.Atoi(fmt.Sprintf("%v", command["id"])) @@ -137,11 +125,11 @@ func GetMyCompletedOrdersWithItems(c *gin.Context) { items, err := database.GetCommandItems(commandID) if err != nil { log.Printf("⚠️ [HISTORY_DETAILED] Erreur items pour cmd %d: %v", commandID, err) - items = []map[string]interface{}{} + items = []map[string]any{} } // Ajouter les items à la commande - enrichedCommand := make(map[string]interface{}) + enrichedCommand := make(map[string]any) for k, v := range command { enrichedCommand[k] = v } @@ -177,10 +165,6 @@ func GetMyCompletedOrdersWithItems(c *gin.Context) { c.JSON(http.StatusOK, response) } -// GetOrderHistory récupère l'historique d'une commande spécifique avec logs -// GET /api/v1/commands/:id/history -// ✅ Authentification requise -// ✅ Vérifie que la commande appartient au client func GetOrderHistory(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/handlers/panier.go b/backend/gestion/handlers/panier.go index 3b1d7930..8892eec9 100644 --- a/backend/gestion/handlers/panier.go +++ b/backend/gestion/handlers/panier.go @@ -1,7 +1,3 @@ -// ============================================ -// handlers/basket_handlers_CORRIGES.go -// ============================================ - package handlers import ( @@ -78,10 +74,6 @@ func AddProductsBasket(c *gin.Context) { }) } -// ============================================ -// ============================================ -// GET /api/v1/panier/:username -// Récupère le panier du client authentifié func GetAllBaskets(c *gin.Context) { database := c.MustGet("database").(*db.Database) username := c.Param("username") @@ -148,11 +140,6 @@ func GetAllBaskets(c *gin.Context) { }) } -// ============================================ -// ✅ SÉCURISÉ: DeleteProductFromBasket -// ============================================ -// DELETE /api/v1/panier/remove -// Supprime un produit du panier func DeleteProductFromBasket(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -212,11 +199,6 @@ func DeleteProductFromBasket(c *gin.Context) { }) } -// ============================================ -// ✅ SÉCURISÉ: ClearBasket -// ============================================ -// DELETE /api/v1/panier/clear -// Vide le panier du client func ClearBasket(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -391,9 +373,6 @@ func ValidateBasket(c *gin.Context) { log.Printf("✅ [CHECKOUT] Zone OK: %s, total=%.2f€ >= %.2f€, crédit parrainage utilisé: %.2f€", zoneResult.ZoneName, cartTotal, zoneResult.MinAmount, referralUsed) - // ============================================ - // 2️⃣ Débiter le parrainage AVANT la commande (évite double-spend) - // ============================================ if referralUsed > 0 { if err := database.DebitReferralBalance(usernameStr, referralUsed); err != nil { log.Printf("❌ [CHECKOUT] Solde parrainage insuffisant pour %s: %v", usernameStr, err) @@ -451,9 +430,6 @@ func ValidateBasket(c *gin.Context) { log.Printf("✅ [CHECKOUT] Commande %d créée", commandID) - // ============================================ - // PAIEMENT CRYPTO - créer le paiement NowPayments - // ============================================ if isCrypto { np := c.MustGet("nowpayments").(*services.NowPaymentsClient) ipnURL := fmt.Sprintf("%s/api/v1/webhooks/nowpayments", getBaseURL(c)) diff --git a/backend/gestion/handlers/product.go b/backend/gestion/handlers/product.go index 0bee9888..ea504405 100644 --- a/backend/gestion/handlers/product.go +++ b/backend/gestion/handlers/product.go @@ -17,10 +17,6 @@ import ( "github.com/gin-gonic/gin" ) -// ============================================ -// CONFIGURATION & LIMITES -// ============================================ - const ( MaxFileSize = 10 * 1024 * 1024 // 10MB par fichier MaxTotalUploadSize = 50 * 1024 * 1024 // 50MB total @@ -30,7 +26,6 @@ const ( MaxProductsPerUser = 100 // Limite pour éviter spam ) -// ✅ MIME types autorisés (vérification réelle du contenu) var allowedMimeTypes = map[string]bool{ "image/jpeg": true, "image/png": true, @@ -41,28 +36,6 @@ var allowedMimeTypes = map[string]bool{ "video/quicktime": true, } -// ============================================ -// MIDDLEWARE D'AUTHORIZATION -// ============================================ - -func RequireAdminOrCabine() gin.HandlerFunc { - return func(c *gin.Context) { - role := c.GetString("role") - if role != "admin" && role != "cabine" { - c.JSON(http.StatusForbidden, gin.H{ - "error": "Accès refusé - Admin ou Cabine requis", - }) - c.Abort() - return - } - c.Next() - } -} - -// ============================================ -// HELPERS DE VALIDATION -// ============================================ - func validateProductName(name string) error { if len(name) == 0 { return fmt.Errorf("nom requis") @@ -187,10 +160,6 @@ func sanitizeFilePath(path string) (string, error) { return cleaned, nil } -// ============================================ -// CREATE PRODUCT - VERSION SÉCURISÉE -// ============================================ - func CreateProduct(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -475,10 +444,6 @@ func CreateProduct(c *gin.Context) { }) } -// ============================================ -// GET ENDPOINTS - SÉCURISÉS (lecture publique OK) -// ============================================ - func GetAllProducts(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -567,10 +532,6 @@ func GetProductByID(c *gin.Context) { }) } -// ============================================ -// UPDATE PRODUCT - VERSION SÉCURISÉE -// ============================================ - func UpdateProduct(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/handlers/redis_services.go b/backend/gestion/handlers/redis_services.go index 687837c7..6ded3e41 100644 --- a/backend/gestion/handlers/redis_services.go +++ b/backend/gestion/handlers/redis_services.go @@ -1,8 +1,3 @@ -// ============================================ -// handlers/redis_handlers.go - VERSION FINALE -// UTILISE UNIQUEMENT LES MÉTHODES DB PostgreSQL -// ============================================ - package handlers import ( @@ -13,6 +8,7 @@ import ( "gestion/utils" "log" "net/http" + "slices" "strconv" "strings" "time" @@ -20,10 +16,6 @@ import ( "github.com/gin-gonic/gin" ) -// ============================================ -// GESTION DE LA FILE DE COMMANDES -// ============================================ - func validatePenaltyPoints(points int) error { if points <= 0 { return fmt.Errorf("points invalides: %d (doit être > 0)", points) @@ -47,72 +39,6 @@ func sanitizeReason(reason string) string { return strings.TrimSpace(reason) } -// GetCommandQueue récupère toutes les commandes en attente dans la file Redis -// GET /api/v2/admin/protected/queue/pending -func GetCommandQueue(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - userRole := c.GetString("role") - if userRole != "admin" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"}) - return - } - - nextCommand, err := database.GetNextCommandInQueue() - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Aucune commande en attente", - "queue": []interface{}{}, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "next_command": nextCommand, - }) -} - -// AutoAssignNextCommand assigne automatiquement la prochaine commande en file -// POST /api/v2/admin/protected/queue/auto-assign -func AutoAssignNextCommand(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - userRole := c.GetString("role") - if userRole != "admin" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"}) - return - } - - nextCommand, err := database.GetNextCommandInQueue() - if err != nil { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Aucune commande en attente", - }) - return - } - - err = database.AutoAssignCommand(nextCommand.CommandID) - if err != nil { - utils.ServerErr(c, "Erreur lors de l'assignation automatique", err) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Commande assignée automatiquement", - "command_id": nextCommand.CommandID, - }) -} - -// ============================================ -// GESTION DES LIVREURS - LOCALISATION -// ============================================ - -// UpdateLivreurLocation met à jour la position GPS du livreur -// POST /api/v1/livreur/location/update -// Body: {"latitude": 48.8566, "longitude": 2.3522} func UpdateLivreurLocation(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -171,7 +97,7 @@ func UpdateLivreurLocation(c *gin.Context) { usernameStr, req.Latitude, req.Longitude) // ✅ Recalculer l'ETA en temps réel si livreur en_route - go refreshETAForActivDelivery(database, usernameStr, req.Latitude, req.Longitude) + go refreshETAForActivDelivery(usernameStr, req.Latitude, req.Longitude) // ✅ 2. Vérifier/Initialiser le statut du livreur statusKey := fmt.Sprintf("delivery:status:%s", usernameStr) @@ -290,14 +216,6 @@ func GetDeliveryPersonLocation(c *gin.Context) { }) } -// ============================================ -// LOCALISATION DU LIVREUR POUR UNE COMMANDE -// ============================================ - -// GetDeliverymanLocationForCommand récupère la position GPS du livreur assigné à une commande -// GET /api/v2/admin/protected/commands/:id/deliveryman/location (ADMIN) -// GET /api/v1/cabine/commands/:id/deliveryman/location (CABINE) -// Accessible uniquement par les admins et la cabine func GetDeliverymanLocationForCommand(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -460,13 +378,6 @@ func GetDeliverymanLocationForCommand(c *gin.Context) { }) } -// ============================================ -// GESTION DES LIVREURS - STATUT -// ============================================ - -// UpdateDeliveryPersonStatus met à jour le statut de disponibilité du livreur -// POST /api/v1/livreur/status -// Body: {"status": "available" | "busy" | "offline"} func UpdateDeliveryPersonStatus(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -490,18 +401,9 @@ func UpdateDeliveryPersonStatus(c *gin.Context) { utils.BindErr(c, err) return } - - // Validation du statut validStatuses := []string{"available", "busy", "offline"} - isValid := false - for _, s := range validStatuses { - if req.Status == s { - isValid = true - break - } - } - if !isValid { + if !slices.Contains(validStatuses, req.Status) { c.JSON(http.StatusBadRequest, gin.H{ "error": "Statut invalide", "valid_statuses": validStatuses, @@ -509,7 +411,6 @@ func UpdateDeliveryPersonStatus(c *gin.Context) { }) return } - usernameStr := username.(string) err := database.SetDeliveryPersonStatus(usernameStr, req.Status, 0) @@ -604,118 +505,6 @@ func GetMyQueue(c *gin.Context) { }) } -// GetAvailableDeliveryPersonsRealtime récupère les livreurs disponibles depuis Redis -// GET /api/v2/admin/protected/delivery/available-realtime -func GetAvailableDeliveryPersonsRealtime(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - userRole := c.GetString("role") - if userRole != "admin" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"}) - return - } - - livreurs, err := database.GetAvailableDeliveryPersonsRedis() - if err != nil { - utils.ServerErr(c, "Erreur lors de la récupération des livreurs", err) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "livreurs": livreurs, - "count": len(livreurs), - }) -} - -// ============================================ -// GESTION ETA (Estimated Time of Arrival) -// ============================================ - -// SetCommandETAHandler permet au livreur de définir l'ETA d'une livraison -// POST /api/v1/livreur/deliveries/:id/set-eta -// Body: {"eta_minutes": 25} -func SetCommandETAHandler(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - username, exists := c.Get("username") - if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) - return - } - - userRole := c.GetString("role") - if userRole != "livreur" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"}) - return - } - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - var req struct { - ETAMinutes int `json:"eta_minutes" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - utils.BindErr(c, err) - return - } - - // Validation de l'ETA - if req.ETAMinutes < 1 || req.ETAMinutes > 120 { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "L'ETA doit être entre 1 et 120 minutes", - }) - return - } - - usernameStr := username.(string) - - // Vérifier que la commande existe et est assignée au livreur - command, err := database.GetCommandByID(commandID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Commande non trouvée", - }) - return - } - - livreurAssign, ok := command["livreur_assign"].(string) - if !ok || livreurAssign != usernameStr { - c.JSON(http.StatusForbidden, gin.H{ - "error": "Cette commande ne vous est pas assignée", - }) - return - } - - // Mettre à jour l'ETA dans Redis - err = database.SetCommandETA(commandID, req.ETAMinutes) - if err != nil { - utils.ServerErr(c, "Erreur lors de la mise à jour de l'ETA", err) - return - } - - log.Printf("⏱️ ETA défini pour commande %d par %s: %d minutes", commandID, usernameStr, req.ETAMinutes) - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "ETA mis à jour avec succès", - "command_id": commandID, - "eta_minutes": req.ETAMinutes, - }) -} - -// ============================================ -// PÉNALITÉS - UTILISE PostgreSQL -// ============================================ - -// ApplyClientPenalty applique une pénalité à un client (Admin seulement) -// POST /api/v2/admin/protected/penalty -// Body: {"username": "john", "points": 50, "reason": "Retard paiement"} func ApplyClientPenalty(c *gin.Context) { database := c.MustGet("database").(*db.Database) @@ -993,9 +782,6 @@ func ResetClientPenaltiesAdmin(c *gin.Context) { }) } -// AddClientPointsAdmin ajoute des points à un client dans un pool donné (Admin/Cabine) -// POST /api/v2/admin/protected/client/:username/points/add -// Body: {"pool_key": "pool_0", "points": 10} func AddClientPointsAdmin(c *gin.Context) { userRole := c.GetString("role") if userRole != "admin" && userRole != "cabine" { @@ -1040,7 +826,7 @@ func AddClientPointsAdmin(c *gin.Context) { } if !poolExists { c.JSON(http.StatusBadRequest, gin.H{ - "error": "Pool de points invalide", + "error": "Pool de points invalide", "pools_valides": func() []string { keys := make([]string, 0, len(settings.PointsPools)) for _, p := range settings.PointsPools { @@ -1073,9 +859,6 @@ func AddClientPointsAdmin(c *gin.Context) { }) } -// SubtractClientPointsAdmin retire des points à un client (plancher à 0) -// POST /api/v2/admin/protected/client/:username/points/subtract -// Body: {"pool_key": "pool_0", "points": 10} func SubtractClientPointsAdmin(c *gin.Context) { userRole := c.GetString("role") if userRole != "admin" && userRole != "cabine" { @@ -1164,12 +947,6 @@ func SubtractClientPointsAdmin(c *gin.Context) { }) } -// ============================================ -// STATISTIQUES TEMPS RÉEL -// ============================================ - -// GetRealtimeStats récupère les statistiques en temps réel -// GET /api/v2/admin/protected/stats/realtime func GetRealtimeStats(c *gin.Context) { userRole := c.GetString("role") if userRole != "admin" { @@ -1200,13 +977,7 @@ func GetRealtimeStats(c *gin.Context) { }) } -// ============================================ -// RECALCUL ETA EN TEMPS RÉEL (appelé à chaque update GPS) -// ============================================ - -// refreshETAForActivDelivery recalcule l'ETA depuis la position actuelle du livreur. -// Appelé en goroutine à chaque mise à jour GPS (toutes les ~15s). -func refreshETAForActivDelivery(database *db.Database, username string, lat, lon float64) { +func refreshETAForActivDelivery(username string, lat, lon float64) { // 1. Récupérer le statut actuel du livreur statusKey := fmt.Sprintf("delivery:status:%s", username) statusData, err := db.Redis.Get(db.RedisCtx, statusKey).Result() diff --git a/backend/gestion/handlers/stats.go b/backend/gestion/handlers/stats.go index a476c533..585b40b0 100644 --- a/backend/gestion/handlers/stats.go +++ b/backend/gestion/handlers/stats.go @@ -2,30 +2,12 @@ package handlers import ( "gestion/db" + "gestion/models" "net/http" - "time" "github.com/gin-gonic/gin" ) -type weekdayRow struct { - DOW int `gorm:"column:dow"` - Count int `gorm:"column:count"` -} - -type dayRow struct { - Day time.Time `gorm:"column:day"` - Count int `gorm:"column:count"` -} - -type productRow struct { - ProductID int `gorm:"column:product_id"` - Name string `gorm:"column:name"` - Quantity float64 `gorm:"column:total_quantity"` - OrderCount int `gorm:"column:order_count"` - Revenue float64 `gorm:"column:revenue"` -} - var weekdayNames = []string{"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"} // GetAdminStats returns aggregated order & product statistics for the admin dashboard. @@ -34,7 +16,7 @@ func GetAdminStats(c *gin.Context) { gdb := database.GDB // ── Commandes par jour de la semaine (all time, non annulées) ────────────── - var wdRows []weekdayRow + var wdRows []models.WeekdayRow gdb.Raw(` SELECT EXTRACT(DOW FROM created_at)::int AS dow, COUNT(*) AS count FROM commandes @@ -59,7 +41,7 @@ func GetAdminStats(c *gin.Context) { } // ── Commandes par jour sur 30 jours ─────────────────────────────────────── - var dayRows []dayRow + var dayRows []models.DayRow gdb.Raw(` SELECT DATE(created_at) AS day, COUNT(*) AS count FROM commandes @@ -79,7 +61,7 @@ func GetAdminStats(c *gin.Context) { } // ── Top produits (quantité vendue, commandes terminées) ─────────────────── - var prodRows []productRow + var prodRows []models.ProductRow gdb.Raw(` SELECT ci.product_id, @@ -137,11 +119,11 @@ func GetAdminStats(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "summary": gin.H{ - "total_orders": totalOrders, - "total_revenue": totalRevenue, - "peak_weekday": peakWeekday, - "top_product": topProductName, - "avg_per_day": avgPerDay, + "total_orders": totalOrders, + "total_revenue": totalRevenue, + "peak_weekday": peakWeekday, + "top_product": topProductName, + "avg_per_day": avgPerDay, }, "by_weekday": byWeekday, "by_day_30": byDay, diff --git a/backend/gestion/handlers/traffic.go b/backend/gestion/handlers/traffic.go index 40f65650..92d88281 100644 --- a/backend/gestion/handlers/traffic.go +++ b/backend/gestion/handlers/traffic.go @@ -10,7 +10,7 @@ import ( ) // getFloatFromMap récupère un float64 depuis une map avec différents types -func getFloatFromMap(m map[string]interface{}, key string) (float64, bool) { +func getFloatFromMap(m map[string]any, key string) (float64, bool) { value, exists := m[key] if !exists || value == nil { return 0, false diff --git a/backend/gestion/handlers/update_profile.go b/backend/gestion/handlers/update_profile.go index 7465f1d8..337b6fba 100644 --- a/backend/gestion/handlers/update_profile.go +++ b/backend/gestion/handlers/update_profile.go @@ -169,10 +169,6 @@ func GetMyProfile(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true, "client": sanitizeClient(client)}) } -// ============================================ -// MODIFICATION PROFIL CLIENT (PAR ADMIN) -// ============================================ - // UpdateClientByAdmin permet à un admin de modifier n'importe quel profil client // PUT /api/v2/admin/protected/clients/:id func UpdateClientByAdmin(c *gin.Context) { @@ -349,10 +345,6 @@ func UpdateClientByAdmin(c *gin.Context) { }) } -// ============================================ -// MODIFICATION PROFIL USER (PAR ADMIN) -// ============================================ - // UpdateUserByAdmin permet à un admin de modifier n'importe quel profil user // PUT /api/v2/admin/protected/users/:id func UpdateUserByAdmin(c *gin.Context) { @@ -468,10 +460,6 @@ func UpdateUserByAdmin(c *gin.Context) { }) } -// ============================================ -// UTILITAIRES -// ============================================ - func sanitizeClient(client *models.Client) gin.H { return gin.H{ "id": client.ID, diff --git a/backend/gestion/handlers/validation_deleviry.go b/backend/gestion/handlers/validation_deleviry.go index 8aee1110..9d8abfbb 100644 --- a/backend/gestion/handlers/validation_deleviry.go +++ b/backend/gestion/handlers/validation_deleviry.go @@ -1,10 +1,8 @@ package handlers import ( - "encoding/json" "fmt" "gestion/db" - "gestion/services" "log" "net/http" "strconv" @@ -24,249 +22,6 @@ const ( MAX_DELIVERY_VALIDATION_DISTANCE_KM = 0.1 // 100 mètres = 0.1 km ) -// ============================================ -// 1️⃣ VALIDATION LIVRAISON PAR LE LIVREUR (AVEC VÉRIFICATION GPS) -// ============================================ - -func ValidateDeliveryByLivreur(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - // ✅ SÉCURITÉ: Livreur seulement - username, exists := c.Get("username") - if !exists || c.GetString("role") != "livreur" { - log.Printf("❌ [VALIDATE_LIVREUR] Accès refusé") - c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"}) - return - } - - usernameStr := username.(string) - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - var req struct { - Latitude float64 `json:"latitude" binding:"required"` - Longitude float64 `json:"longitude" binding:"required"` - } - - 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", - }) - return - } - - log.Printf("📍 [VALIDATE_LIVREUR] Livreur %s valide cmd %d avec GPS: (%.6f, %.6f)", - usernameStr, commandID, req.Latitude, req.Longitude) - - // ✅ ÉTAPE 1: Récupérer la commande - command, err := database.GetCommandByID(commandID) - if err != nil { - log.Printf("❌ [VALIDATE_LIVREUR] Commande non trouvée") - c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) - return - } - - // ✅ ÉTAPE 2: VÉRIFIER PROPRIÉTÉ - livreurAssign, _ := command["livreur_assign"].(string) - if livreurAssign != usernameStr { - log.Printf("❌ [VALIDATE_LIVREUR] ⚠️ TENTATIVE D'ACCÈS NON AUTORISÉ!") - c.JSON(http.StatusForbidden, gin.H{ - "error": "Cette commande ne vous est pas assignée", - }) - return - } - - // ÉTAPE 3: Coordonnées GPS reçues et valides - log.Printf("📍 [VALIDATE_LIVREUR] GPS reçu: (%.6f, %.6f)", req.Latitude, req.Longitude) - - // ÉTAPE 4: Sauvegarder les coordonnées du livreur - _, err = database.Exec( - "UPDATE commandes SET livreur_latitude = $1, livreur_longitude = $2 WHERE id = $3", - req.Latitude, req.Longitude, commandID, - ) - if err != nil { - log.Printf("⚠️ [VALIDATE_LIVREUR] Erreur sauvegarde GPS: %v", err) - } - - // ✅ ÉTAPE 5: Marquer la livraison comme "livre" - 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", - }) - return - } - - // ✅ ÉTAPE 6: Ajouter un log - database.AddCommandLog(commandID, "livre", - fmt.Sprintf("Livraison confirmée par livreur - GPS: (%.6f, %.6f)", req.Latitude, req.Longitude), - usernameStr) - - // ✅ ÉTAPE 7: Optimiser la queue - log.Printf("📦 [VALIDATE_LIVREUR] Optimisation queue de %s...", usernameStr) - err = database.CompleteDeliveryAndProcessNext(usernameStr, commandID) - if err != nil { - log.Printf("⚠️ [VALIDATE_LIVREUR] Erreur optimisation: %v", err) - } - - log.Printf("✅ [VALIDATE_LIVREUR] Commande %d validée et marquée 'livre'", commandID) - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Livraison validée avec succès", - "command_id": commandID, - "new_status": "livre", - "gps_verified": true, - }) -} - -// ============================================ -// 2️⃣ VÉRIFIER SI LE LIVREUR PEUT VALIDER (SANS VALIDER) -// ============================================ - -// CheckDeliveryValidationEligibility vérifie si le livreur peut valider une livraison -// GET /api/v1/deliveries/:id/can-validate -func CheckDeliveryValidationEligibility(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - username, exists := c.Get("username") - if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) - return - } - - userRole := c.GetString("role") - if userRole != "livreur" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"}) - return - } - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) - return - } - - command, err := database.GetCommandByID(commandID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) - return - } - - // Vérifier l'assignation - livreurAssign, _ := command["livreur_assign"].(string) - if livreurAssign != username.(string) { - c.JSON(http.StatusOK, gin.H{ - "can_validate": false, - "reason": "Commande non assignée à vous", - }) - return - } - - // Récupérer la position du livreur - livreurLat, livreurLon, err := database.GetDeliveryPersonLocation(username.(string)) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "can_validate": false, - "reason": "Position GPS non disponible", - "action": "Mettez à jour votre position GPS", - }) - return - } - - // Récupérer les coordonnées de destination (même priorité que ValidateDeliveryByLivreur) - var destLat, destLon float64 - var coordsSource string - - // ✅ PRIORITÉ 1: Cache Redis - destCacheKey := fmt.Sprintf("command:destination:%d", commandID) - destData, redisErr := db.Redis.Get(db.RedisCtx, destCacheKey).Result() - if redisErr == nil && destData != "" { - var coords struct { - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` - } - if err := json.Unmarshal([]byte(destData), &coords); err == nil && coords.Lat != 0 && coords.Lon != 0 { - destLat = coords.Lat - destLon = coords.Lon - coordsSource = "REDIS" - log.Printf("📍 [CAN-VALIDATE] Coords depuis Redis: (%.6f, %.6f)", destLat, destLon) - } - } - - // ✅ PRIORITÉ 2: DB - if coordsSource == "" { - if dLat, ok := getFloatFromMap(command, "dest_latitude"); ok && dLat != 0 { - destLat = dLat - } - if dLon, ok := getFloatFromMap(command, "dest_longitude"); ok && dLon != 0 { - destLon = dLon - } - if destLat != 0 && destLon != 0 { - coordsSource = "DB" - } - } - - // ✅ PRIORITÉ 3: Géocodage - if coordsSource == "" { - geoService := c.MustGet("geoService").(*services.GeoService) - address, _ := command["adresse"].(string) - if address != "" && address != "Adresse non spécifiée" { - location, err := geoService.GeocodeAddress(address) - if err == nil { - destLat = location.Latitude - destLon = location.Longitude - coordsSource = "GEOCODING" - } - } - } - - if destLat == 0 || destLon == 0 { - c.JSON(http.StatusOK, gin.H{ - "can_validate": false, - "reason": "Coordonnées de destination non disponibles", - }) - return - } - - // Calculer la distance - distance := services.CalculateDistance( - services.Coordinates{Latitude: livreurLat, Longitude: livreurLon}, - services.Coordinates{Latitude: destLat, Longitude: destLon}, - ) - - distanceMeters := distance * 1000 - canValidate := distance <= MAX_DELIVERY_VALIDATION_DISTANCE_KM - - c.JSON(http.StatusOK, gin.H{ - "can_validate": canValidate, - "your_position": gin.H{ - "latitude": livreurLat, - "longitude": livreurLon, - }, - "destination": gin.H{ - "latitude": destLat, - "longitude": destLon, - "address": command["adresse"], - "source": coordsSource, - }, - "distance_meters": int(distanceMeters), - "max_allowed_meters": MAX_DELIVERY_VALIDATION_DISTANCE_METERS, - "remaining_meters": maxInt(0, int(distanceMeters)-MAX_DELIVERY_VALIDATION_DISTANCE_METERS), - "message": func() string { - if canValidate { - return "Vous pouvez valider cette livraison" - } - return fmt.Sprintf("Rapprochez-vous de %.0f mètres pour valider", distanceMeters-float64(MAX_DELIVERY_VALIDATION_DISTANCE_METERS)) - }(), - }) -} - // ============================================ // 3️⃣ DÉMARRER UNE LIVRAISON (PASSER EN IN_ROUTE) // ============================================ @@ -395,14 +150,3 @@ func StartDelivery(c *gin.Context) { "status": "en_route", }) } - -// ============================================ -// HELPERS -// ============================================ - -func maxInt(a, b int) int { - if a > b { - return a - } - return b -} diff --git a/backend/gestion/middleware/session_middleware.go b/backend/gestion/middleware/session_middleware.go index 0fbc8685..180523cb 100644 --- a/backend/gestion/middleware/session_middleware.go +++ b/backend/gestion/middleware/session_middleware.go @@ -54,7 +54,7 @@ var ( // ============================================ // validateClientToken valide un token client -func validateClientToken(tokenString string, database *db.Database) (*ClientClaims, error) { +func validateClientToken(tokenString string) (*ClientClaims, error) { tokenString = strings.TrimSpace(tokenString) if tokenString == "" { return nil, fmt.Errorf("token vide") @@ -103,7 +103,7 @@ func validateClientToken(tokenString string, database *db.Database) (*ClientClai } // validateAdminToken valide un token admin -func validateAdminToken(tokenString string, database *db.Database) (*AdminClaims, error) { +func validateAdminToken(tokenString string) (*AdminClaims, error) { tokenString = strings.TrimSpace(tokenString) if tokenString == "" { return nil, fmt.Errorf("token vide") @@ -161,7 +161,7 @@ func ClientMiddleware(c *gin.Context) { tokenStr := strings.TrimPrefix(authHeader, "Bearer ") database := c.MustGet("database").(*db.Database) - claims, err := validateClientToken(tokenStr, database) + claims, err := validateClientToken(tokenStr) if err != nil { log.Printf("❌ [CLIENT-MWARE] Token invalide: %v", err) c.JSON(http.StatusUnauthorized, gin.H{"error": "Token invalide"}) @@ -205,7 +205,7 @@ func AdminMiddleware(c *gin.Context) { tokenStr := strings.TrimPrefix(authHeader, "Bearer ") database := c.MustGet("database").(*db.Database) - claims, err := validateAdminToken(tokenStr, database) + claims, err := validateAdminToken(tokenStr) if err != nil { log.Printf("❌ [ADMIN-MWARE] Token invalide: %v", err) c.JSON(http.StatusUnauthorized, gin.H{"error": "Token admin invalide"}) @@ -258,7 +258,7 @@ func CabineMiddleware(c *gin.Context) { tokenStr := strings.TrimPrefix(authHeader, "Bearer ") database := c.MustGet("database").(*db.Database) - claims, err := validateAdminToken(tokenStr, database) + claims, err := validateAdminToken(tokenStr) if err != nil { log.Printf("❌ [CABINE-MWARE] Token invalide: %v", err) c.JSON(http.StatusUnauthorized, gin.H{"error": "Token invalide"}) @@ -312,7 +312,7 @@ func LivreurMiddleware(c *gin.Context) { tokenStr := strings.TrimPrefix(authHeader, "Bearer ") database := c.MustGet("database").(*db.Database) - claims, err := validateAdminToken(tokenStr, database) + claims, err := validateAdminToken(tokenStr) if err != nil { log.Printf("❌ [LIVREUR-MWARE] Token invalide: %v", err) c.JSON(http.StatusUnauthorized, gin.H{"error": "Token invalide"}) @@ -507,94 +507,3 @@ func LoginRateLimitMiddleware(c *gin.Context) { c.Header("X-RateLimit-Remaining", strconv.FormatInt(10-count, 10)) c.Next() } - -// ============================================ -// HELPER MIDDLEWARE -// ============================================ - -// VerifyAuthHeader vérifie que le header Authorization est valide -func VerifyAuthHeader(c *gin.Context) { - authHeader := c.GetHeader("Authorization") - - if authHeader == "" { - log.Printf("❌ [AUTH-HEADER] Authorization header manquant") - c.JSON(http.StatusUnauthorized, gin.H{ - "error": "Authorization header manquant", - "hint": "Utilisez: Authorization: Bearer ", - }) - c.Abort() - return - } - - // Vérifier le format "Bearer " - parts := strings.Split(authHeader, " ") - if len(parts) != 2 || parts[0] != "Bearer" { - log.Printf("❌ [AUTH-HEADER] Format invalide: %s", authHeader) - c.JSON(http.StatusUnauthorized, gin.H{ - "error": "Format Authorization invalide", - "hint": "Utilisez: Authorization: Bearer ", - }) - c.Abort() - return - } - - log.Printf("✅ [AUTH-HEADER] Format valide") - c.Next() -} - -// SessionErrorRecovery récupère les erreurs de session -func SessionErrorRecovery(c *gin.Context) { - defer func() { - if err := recover(); err != nil { - log.Printf("❌ [SESSION-ERROR] Erreur système: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur serveur - Session compromise", - }) - } - }() - - c.Next() - - if len(c.Errors) > 0 { - log.Printf("⚠️ [SESSION] Erreur handler: %v", c.Errors) - } -} - -// LogSessionMiddleware log toutes les infos de session -func LogSessionMiddleware(c *gin.Context) { - username, _ := c.Get("username") - clientID, _ := c.Get("client_id") - sessionID, _ := c.Get("session_id") - - log.Printf("📊 [SESSION-LOG] %s %s | user=%v | client_id=%v | session=%v", - c.Request.Method, c.Request.URL.Path, username, clientID, sessionID) - - c.Next() - - log.Printf("📊 [SESSION-LOG] Response: %d", c.Writer.Status()) -} - -// LoadClientContext charge les infos du client en contexte -func LoadClientContext(c *gin.Context, database *db.Database) (*db.SessionData, error) { - clientID, ok := c.Get("client_id") - if !ok { - return nil, fmt.Errorf("client_id manquant du contexte") - } - - clientIDInt := clientID.(int) - - // Récupérer la session - session, err := database.GetClientSession(clientIDInt) - if err != nil { - return nil, err - } - - return session, nil -} - -func DatabaseMiddleware(db *db.Database) gin.HandlerFunc { - return func(c *gin.Context) { - c.Set("database", db) - c.Next() - } -} diff --git a/backend/gestion/models/stats.go b/backend/gestion/models/stats.go new file mode 100644 index 00000000..57da85d6 --- /dev/null +++ b/backend/gestion/models/stats.go @@ -0,0 +1,21 @@ +package models + +import "time" + +type WeekdayRow struct { + DOW int `gorm:"column:dow"` + Count int `gorm:"column:count"` +} + +type DayRow struct { + Day time.Time `gorm:"column:day"` + Count int `gorm:"column:count"` +} + +type ProductRow struct { + ProductID int `gorm:"column:product_id"` + Name string `gorm:"column:name"` + Quantity float64 `gorm:"column:total_quantity"` + OrderCount int `gorm:"column:order_count"` + Revenue float64 `gorm:"column:revenue"` +} diff --git a/backend/gestion/routes/routes.go b/backend/gestion/routes/routes.go index 08d422ea..bc7f789d 100644 --- a/backend/gestion/routes/routes.go +++ b/backend/gestion/routes/routes.go @@ -139,7 +139,6 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services // ============================================ adminAuthGroupV2 := router.Group("/api/v2/admin/auth") { - //adminAuthGroupV2.POST("/register", handlers.RegisterAdmin) adminAuthGroupV2.POST("/login", middleware.LoginRateLimitMiddleware, handlers.LoginAdmin) adminAuthGroupV2.POST("/logout", handlers.LogoutAdmin) }