chore: refacto
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user