chore: refacto

This commit is contained in:
2026-04-22 19:06:35 +02:00
parent 7605fce9f4
commit a276e1126f
7 changed files with 64 additions and 90 deletions
+12
View File
@@ -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 = ?`,
+5 -2
View File
@@ -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
}
@@ -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{}
+16 -66
View File
@@ -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)
+1 -10
View File
@@ -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)
}
@@ -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 {