Files
projet_gestion_commande/backend/gestion/handlers/panier.go
T
2026-02-24 18:26:20 +01:00

465 lines
14 KiB
Go

// ============================================
// handlers/basket_handlers_CORRIGES.go
// ============================================
package handlers
import (
"gestion/db"
"gestion/models"
"gestion/services"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
type BasketsRequest struct {
Username string `json:"username"`
NameProduct string `json:"name_product"`
Category string `json:"category"`
Quantity float64 `json:"quantity"`
}
// ============================================
// ✅ SÉCURISÉ: AddProductsBasket
// ============================================
// POST /api/v1/panier/add
func AddProductsBasket(c *gin.Context) {
db := c.MustGet("database").(*db.Database)
// Liaison JSON
var req BasketsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Requête invalide", "details": err.Error()})
return
}
// Récupérer le username depuis JWT ou contexte
username, ok := c.Get("username")
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentification requise"})
return
}
req.Username = username.(string)
// Validation des champs
if req.NameProduct == "" || req.Category == "" || req.Quantity <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "Champs invalides"})
return
}
// Vérifier le stock
stock, err := db.GetProductStock(req.NameProduct, req.Category)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Produit non trouvé"})
return
}
if stock < req.Quantity {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Stock insuffisant",
"available": stock,
})
return
}
// Ajouter au panier (ou mettre à jour si déjà présent)
panier, err := db.AddProductInBasket(req.Username, req.NameProduct, req.Quantity, req.Category)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier"})
return
}
// Décrémenter le stock
if err := db.DecrementProductStock(req.NameProduct, req.Category, req.Quantity); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de réserver le stock"})
return
}
c.JSON(http.StatusCreated, gin.H{
"success": true,
"message": "Produit ajouté au panier avec succès",
"panier": panier,
})
}
// ============================================
// ============================================
// 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")
log.Printf("📦 [GET_PANIER] Requête pour: %s", username)
if username == "" {
log.Printf("❌ [GET_PANIER] Username manquant")
c.JSON(http.StatusBadRequest, gin.H{"error": "Le paramètre 'username' est requis"})
return
}
// ✅ SÉCURITÉ 1: Récupérer username depuis le contexte (du JWT validé)
authUsername, hasAuth := c.Get("username")
if !hasAuth {
log.Printf("❌ [GET_PANIER] Username manquant dans JWT")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentification requise"})
return
}
authUsernameStr := authUsername.(string)
// ✅ SÉCURITÉ 2: Vérifier que c'est bien l'utilisateur de la session
if username != authUsernameStr {
log.Printf("❌ [GET_PANIER] ⚠️ TENTATIVE D'ACCÈS AU PANIER NON AUTORISÉE!")
log.Printf(" Username du JWT: %s", authUsernameStr)
log.Printf(" Username demandé: %s", username)
c.JSON(http.StatusForbidden, gin.H{
"error": "Vous ne pouvez accéder qu'à votre panier",
})
return
}
// ✅ SÉCURITÉ 3: Forcer l'utilisation du username du JWT
username = authUsernameStr
// ✅ SÉCURITÉ 4: Vérifier que c'est un CLIENT
_, err := database.GetClientByUsername(username)
if err != nil {
log.Printf("❌ [GET_PANIER] Client inexistant: %s", username)
c.JSON(http.StatusNotFound, gin.H{"error": "Utilisateur inexistant"})
return
}
baskets, err := database.GetAllProductsInBasket(username)
if err != nil {
log.Printf("❌ [GET_PANIER] Erreur récupération: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Erreur lors de la récupération du panier",
"details": err.Error(),
})
return
}
var totalAmount float64
for _, item := range baskets {
totalAmount += item.Price * item.Quantity
}
log.Printf("✅ [GET_PANIER] Panier %s: %d articles, total=%.2f€", username, len(baskets), totalAmount)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Panier récupéré avec succès",
"panier": baskets,
"count": len(baskets),
"total_amount": totalAmount,
})
}
// ============================================
// ✅ SÉCURISÉ: DeleteProductFromBasket
// ============================================
// DELETE /api/v1/panier/remove
// Supprime un produit du panier
func DeleteProductFromBasket(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
var req struct {
ID int `json:"id" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
log.Printf("❌ [DEL_PANIER] Erreur JSON: %v", err)
c.JSON(http.StatusBadRequest, gin.H{
"error": "Données requises manquantes",
"details": err.Error(),
})
return
}
// ✅ SÉCURITÉ 1: Récupérer username depuis le contexte (du JWT validé)
authUsername, hasAuth := c.Get("username")
if !hasAuth {
log.Printf("❌ [DEL_PANIER] Username manquant dans JWT")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentification requise"})
return
}
authUsernameStr := authUsername.(string)
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)
if err != nil {
log.Printf("❌ [DEL_PANIER] Article non trouvé: id=%d", req.ID)
c.JSON(http.StatusNotFound, gin.H{"error": "Article non trouvé"})
return
}
if itemUsername != authUsernameStr {
log.Printf("❌ [DEL_PANIER] ⚠️ TENTATIVE DE SUPPRESSION NON AUTORISÉE!")
log.Printf(" Client JWT: %s", authUsernameStr)
log.Printf(" Propriétaire article: %s", itemUsername)
c.JSON(http.StatusForbidden, gin.H{
"error": "Vous ne pouvez supprimer que vos articles",
})
return
}
// Supprimer l'article
err = database.DeleteProductFromBasket(req.ID)
if err != nil {
log.Printf("❌ [DEL_PANIER] Erreur suppression: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Erreur lors de la suppression",
"details": err.Error(),
})
return
}
log.Printf("✅ [DEL_PANIER] Article %d supprimé", req.ID)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Produit supprimé du panier avec succès",
"item_id": req.ID,
"stock_released": true,
})
}
// ============================================
// ✅ SÉCURISÉ: ClearBasket
// ============================================
// DELETE /api/v1/panier/clear
// Vide le panier du client
func ClearBasket(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
// ✅ SÉCURITÉ 1: Récupérer username depuis le contexte (du JWT validé)
authUsername, hasAuth := c.Get("username")
if !hasAuth {
log.Printf("❌ [CLEAR_PANIER] Username manquant dans JWT")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentification requise"})
return
}
authUsernameStr := authUsername.(string)
log.Printf("🧹 [CLEAR_PANIER] Vider panier de: %s", authUsernameStr)
baskets, err := database.GetAllProductsInBasket(authUsernameStr)
if err != nil {
log.Printf("❌ [CLEAR_PANIER] Erreur récupération: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Erreur lors du vidage du panier",
})
return
}
err = database.ClearBasket(authUsernameStr)
if err != nil {
log.Printf("❌ [CLEAR_PANIER] Erreur vidage: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Erreur lors du vidage du panier",
"details": err.Error(),
})
return
}
log.Printf("✅ [CLEAR_PANIER] Panier %s vidé: %d articles supprimés", authUsernameStr, len(baskets))
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Panier vidé avec succès",
"stock_released": len(baskets),
})
}
// ============================================
// ✅ 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)
username, exists := c.Get("username")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Utilisateur non authentifié"})
return
}
usernameStr := username.(string)
var req struct {
DeliveryAddress string `json:"delivery_address" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil || req.DeliveryAddress == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Adresse de livraison requise"})
return
}
cmd := &models.Command{DeliveryAddress: req.DeliveryAddress}
if err := database.CheckAddress(cmd); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(), "corrected_address": cmd.DeliveryAddress})
return
}
req.DeliveryAddress = cmd.DeliveryAddress
log.Printf("🛒 [CHECKOUT] Début checkout pour: %s", usernameStr)
// ============================================
// 1️⃣ Vérifier que le panier n'est pas vide
// ============================================
items, err := database.GetBasketItems(usernameStr)
if err != nil {
log.Printf("❌ [CHECKOUT] Erreur récupération panier: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de récupérer le panier", "details": err.Error()})
return
}
if len(items) == 0 {
log.Printf("❌ [CHECKOUT] Panier vide")
c.JSON(http.StatusBadRequest, gin.H{"error": "Panier vide"})
return
}
log.Printf("🛒 [CHECKOUT] Panier: %d articles", len(items))
// ============================================
// 2️⃣ Créer la commande (qui décrémente automatiquement le stock)
// ============================================
command, err := database.CreateCommandWithAddress(usernameStr, req.DeliveryAddress)
if err != nil {
log.Printf("❌ [CHECKOUT] Erreur création commande: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création commande", "details": err.Error()})
return
}
commandID := command.ID
log.Printf("✅ [CHECKOUT] Commande %d créée", commandID)
// ============================================
// 3️⃣ Vider le panier
// ============================================
err = database.ClearBasket(usernameStr)
if err != nil {
log.Printf("❌ [CHECKOUT] Erreur vidage panier: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de vider le panier", "details": err.Error()})
return
}
log.Printf("🧹 [CHECKOUT] Panier vidé")
// ============================================
// 4️⃣ Auto-assignation livreur (optionnel)
// ============================================
var assigned bool
var assignInfo gin.H
location, err := geoService.GeocodeAddress(req.DeliveryAddress)
if err == nil {
log.Printf("📍 [CHECKOUT] Adresse géocodée: %.6f,%.6f", location.Latitude, location.Longitude)
activeLivreurs, err := database.GetAllActiveDeliveryPersons()
if err == nil && len(activeLivreurs) > 0 {
log.Printf("🚚 [CHECKOUT] %d livreurs actifs disponibles", len(activeLivreurs))
usernames := make([]string, len(activeLivreurs))
for i, livreur := range activeLivreurs {
usernames[i] = livreur.Username
}
nearest, err := geoService.FindNearestDeliveryPersonFast(services.Coordinates{
Latitude: location.Latitude,
Longitude: location.Longitude,
}, usernames)
if err == nil {
log.Printf("👤 [CHECKOUT] Livreur le plus proche: %s (%.2f km)", nearest.Username, nearest.Distance)
// ✅ CORRECTION: Utiliser CalculateETAWithTomTom au lieu de GetETAWithTraffic
travelTime, distance, err := services.CalculateETAWithTomTom(
nearest.Location,
services.Coordinates{
Latitude: location.Latitude,
Longitude: location.Longitude,
},
)
if err != nil {
// Fallback sur ETA simple
travelTime = nearest.EstimatedTime
distance = nearest.Distance
log.Printf("⚠️ [CHECKOUT] Fallback ETA: %d min", travelTime)
}
log.Printf("⏱️ [CHECKOUT] ETA calculé: %d min, distance: %.2f km", travelTime, distance)
// Assigner la commande au livreur
err = database.AssignCommandToDeliverymanQueueWithCoords(
commandID,
nearest.Username,
travelTime,
location.Latitude,
location.Longitude,
req.DeliveryAddress,
)
if err != nil {
log.Printf("⚠️ [CHECKOUT] Erreur assignation: %v", err)
} else {
// Mettre à jour le statut du livreur
err = database.SetDeliveryPersonStatus(nearest.Username, "busy", commandID)
if err != nil {
log.Printf("⚠️ [CHECKOUT] Erreur mise à jour statut livreur: %v", err)
}
assigned = true
assignInfo = gin.H{
"username": nearest.Username,
"distance_km": distance,
"travel_time": travelTime,
}
log.Printf("✅ [CHECKOUT] Commande assignée à %s", nearest.Username)
}
} else {
log.Printf("⚠️ [CHECKOUT] Aucun livreur trouvé: %v", err)
}
} else {
log.Printf("⚠️ [CHECKOUT] Aucun livreur actif disponible")
}
} else {
log.Printf("⚠️ [CHECKOUT] Erreur géocodage: %v", err)
}
// ============================================
// 5️⃣ Réponse
// ============================================
resp := gin.H{
"success": true,
"command_id": commandID,
"delivery_address": req.DeliveryAddress,
"status": "pending",
}
if assigned {
resp["message"] = "Commande créée et livreur assigné automatiquement"
resp["auto_assigned"] = true
resp["assigned_to"] = assignInfo
resp["status"] = "assigned"
log.Printf("✅ [CHECKOUT] Réponse 200 - Commande %d assignée", commandID)
} else {
resp["message"] = "Commande créée - En attente d'assignation"
resp["auto_assigned"] = false
log.Printf("✅ [CHECKOUT] Réponse 200 - Commande %d en attente", commandID)
}
c.JSON(http.StatusOK, resp)
}