chore: fix vulenrability
This commit is contained in:
@@ -2,4 +2,5 @@ img.jpg
|
||||
video.mp4
|
||||
s.sh
|
||||
.env
|
||||
uploads/
|
||||
uploads/
|
||||
openapi.yaml
|
||||
|
||||
@@ -270,35 +270,68 @@ func (d *Database) DecrementProductStockByID(productID int, quantity float64) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProductFromBasket supprime un produit spécifique du panier
|
||||
// DeleteProductFromBasket supprime un produit spécifique du panier et restitue le stock.
|
||||
func (d *Database) DeleteProductFromBasket(basketID int) error {
|
||||
query := `DELETE FROM baskets WHERE id = $1`
|
||||
result, err := d.Exec(query, basketID)
|
||||
tx, err := d.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur lors de la suppression du produit: %w", err)
|
||||
return fmt.Errorf("erreur transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
var productID int
|
||||
var quantity float64
|
||||
err = tx.QueryRow(
|
||||
`SELECT product_id, quantity FROM baskets WHERE id = $1`,
|
||||
basketID,
|
||||
).Scan(&productID, &quantity)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur lors de la vérification des lignes affectées: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return fmt.Errorf("produit non trouvé dans le panier")
|
||||
}
|
||||
|
||||
return nil
|
||||
_, err = tx.Exec(
|
||||
`UPDATE products SET stock = stock + $1 WHERE id = $2`,
|
||||
quantity, productID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur restitution stock: %w", err)
|
||||
}
|
||||
|
||||
result, err := tx.Exec(`DELETE FROM baskets WHERE id = $1`, basketID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur lors de la suppression du produit: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("produit non trouvé dans le panier")
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// ClearBasket vide complètement le panier d'un utilisateur
|
||||
// ClearBasket vide complètement le panier d'un utilisateur et restitue les stocks.
|
||||
func (d *Database) ClearBasket(username string) error {
|
||||
query := `DELETE FROM baskets WHERE username = $1`
|
||||
_, err := d.Exec(query, username)
|
||||
tx, err := d.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.Exec(`
|
||||
UPDATE products p
|
||||
SET stock = stock + b.quantity
|
||||
FROM baskets b
|
||||
WHERE b.username = $1 AND b.product_id = p.id
|
||||
`, username)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur restitution stock: %w", err)
|
||||
}
|
||||
|
||||
_, err = tx.Exec(`DELETE FROM baskets WHERE username = $1`, username)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur lors du vidage du panier: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// GetBasketTotal calcule le montant total du panier d'un utilisateur
|
||||
|
||||
@@ -89,7 +89,6 @@ func ValidateStatuses(statuses string) ([]string, error) {
|
||||
"arrived": true,
|
||||
"livre": true,
|
||||
"approved": true,
|
||||
"failed": true,
|
||||
"cancelled": true,
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func (d *Database) ProcessNextCommandForDeliveryman(deliveryman string) error {
|
||||
log.Printf("📊 [NEXT_COMMAND] Commande %d: statut = '%s'", commandID, currentStatus)
|
||||
|
||||
// ✅ Si la commande n'est plus assignable, la retirer et passer à la suivante
|
||||
nonAssignableStatuses := []string{"livre", "approved", "cancelled", "disabled", "failed"}
|
||||
nonAssignableStatuses := []string{"livre", "approved", "cancelled", "disabled"}
|
||||
isNonAssignable := false
|
||||
for _, s := range nonAssignableStatuses {
|
||||
if currentStatus == s {
|
||||
|
||||
@@ -37,6 +37,44 @@ func (d *Database) CreditClientReferral(username string, amount float64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DebitReferralBalance déduit atomiquement le solde parrainage avant la création de commande.
|
||||
// Gère sa propre transaction avec FOR UPDATE pour éviter le double-spend concurrent.
|
||||
// Retourne une erreur si le solde est insuffisant.
|
||||
func (d *Database) DebitReferralBalance(username string, amount float64) error {
|
||||
if amount <= 0 {
|
||||
return nil
|
||||
}
|
||||
tx, err := d.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur transaction: %w", err)
|
||||
}
|
||||
var balance float64
|
||||
if err := tx.QueryRow(
|
||||
`SELECT referral_balance FROM clients WHERE username = $1 FOR UPDATE`,
|
||||
username,
|
||||
).Scan(&balance); err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("client non trouvé")
|
||||
}
|
||||
if balance < amount {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("solde parrainage insuffisant (disponible: %.2f€)", balance)
|
||||
}
|
||||
if _, err := tx.Exec(
|
||||
`UPDATE clients SET referral_balance = referral_balance - $1 WHERE username = $2`,
|
||||
amount, username,
|
||||
); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// RestoreReferralBalance restaure le solde parrainage si la commande échoue après le débit.
|
||||
func (d *Database) RestoreReferralBalance(username string, amount float64) error {
|
||||
return d.CreditClientReferral(username, amount)
|
||||
}
|
||||
|
||||
// UseClientReferralBalance déduit un montant du solde parrainage dans une transaction.
|
||||
// Retourne une erreur si le solde est insuffisant.
|
||||
func (d *Database) UseClientReferralBalance(tx *sql.Tx, username string, amount float64) error {
|
||||
|
||||
@@ -208,7 +208,6 @@ func getStatusMessage(status string) string {
|
||||
"livre": "📦 Livré - En attente de confirmation",
|
||||
"delivered": "✅ Livré",
|
||||
"approved": "🎉 Livraison confirmée",
|
||||
"failed": "❌ Échec de livraison",
|
||||
"cancelled": "🚫 Annulée",
|
||||
"disabled": "⚠️ Désactivée",
|
||||
}
|
||||
@@ -248,7 +247,6 @@ func getStatusIcon(status string) string {
|
||||
"arrived": "📍",
|
||||
"livre": "✅",
|
||||
"approved": "🎉",
|
||||
"failed": "❌",
|
||||
"cancelled": "🚫",
|
||||
}
|
||||
|
||||
|
||||
@@ -227,12 +227,11 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
||||
|
||||
// ✅ STATUTS VALIDES POUR LIVREUR (correspondant à la DB)
|
||||
validStatuses := []string{
|
||||
"assigned", // Assigné
|
||||
"en_route", // En route vers le client
|
||||
"arrived", // Arrivé à destination
|
||||
"livre", // Livré (en attente confirmation client)
|
||||
"failed", // Échec de livraison
|
||||
"cancelled", // Annulée
|
||||
"assigned",
|
||||
"en_route",
|
||||
"arrived",
|
||||
"livre",
|
||||
"cancelled",
|
||||
}
|
||||
|
||||
isValid := false
|
||||
@@ -252,9 +251,11 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ VALIDATION GPS pour livraison finale (livre ou failed)
|
||||
if (req.Status == "livre" || req.Status == "failed") &&
|
||||
req.Latitude != 0 && req.Longitude != 0 {
|
||||
if req.Status == "livre" {
|
||||
if req.Latitude == 0 || req.Longitude == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Coordonnées GPS requises pour confirmer la livraison"})
|
||||
return
|
||||
}
|
||||
|
||||
destLat, _ := command["dest_latitude"].(float64)
|
||||
destLon, _ := command["dest_longitude"].(float64)
|
||||
@@ -265,10 +266,9 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
||||
|
||||
if distance > 100 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Vous êtes trop loin de la destination",
|
||||
"required_distance": 100,
|
||||
"current_distance": fmt.Sprintf("%.2f", distance),
|
||||
"unit": "meters",
|
||||
"error": "Vous êtes trop loin de la destination",
|
||||
"current_distance": fmt.Sprintf("%.2f", distance),
|
||||
"unit": "meters",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -374,8 +374,6 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
||||
clientMsg = fmt.Sprintf("🛵 Votre livreur est là ! Il sera chez vous dans 5 minutes (commande #%d)", commandID)
|
||||
case "livre":
|
||||
clientMsg = fmt.Sprintf("Votre commande #%d a été livrée. Merci !", commandID)
|
||||
case "failed":
|
||||
clientMsg = fmt.Sprintf("Échec de livraison pour la commande #%d", commandID)
|
||||
case "cancelled":
|
||||
clientMsg = fmt.Sprintf("Votre commande #%d a été annulée par le livreur", commandID)
|
||||
}
|
||||
@@ -391,19 +389,6 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
||||
log.Printf("📦 Livraison marquée 'livre' - Optimisation queue...")
|
||||
database.CompleteDeliveryAndProcessNext(usernameStr, commandID)
|
||||
|
||||
case "failed":
|
||||
// Échec de livraison - Optimiser la queue
|
||||
log.Printf("❌ Livraison échouée - Optimisation queue...")
|
||||
database.CompleteDeliveryAndProcessNext(usernameStr, commandID)
|
||||
|
||||
// Créer un problème de livraison
|
||||
database.CreateDeliveryIssue(
|
||||
commandID,
|
||||
"delivery_failed",
|
||||
fmt.Sprintf("Échec de livraison: %s", req.Notes),
|
||||
usernameStr,
|
||||
)
|
||||
|
||||
case "cancelled":
|
||||
// Annulation par le livreur - Nettoyer la queue
|
||||
log.Printf("🚫 Livraison annulée par livreur - Nettoyage queue cmd %d", commandID)
|
||||
@@ -461,7 +446,6 @@ func getDeliveryStatusMessage(status string) string {
|
||||
"en_route": "En route vers le client",
|
||||
"arrived": "Arrivé à destination",
|
||||
"livre": "Livraison effectuée",
|
||||
"failed": "Échec de livraison",
|
||||
"cancelled": "Livraison annulée",
|
||||
}
|
||||
if msg, ok := messages[status]; ok {
|
||||
|
||||
@@ -49,7 +49,7 @@ func AddProductsBasket(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Si product_id fourni par le mobile, on l'utilise directement (plus fiable)
|
||||
if req.ProductID > 0 {
|
||||
if req.ProductID > 0 || req.NameProduct == "" || req.Category == "" {
|
||||
stock, err := database.GetProductStockByID(req.ProductID)
|
||||
if err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Produit %d non trouvé: %v", req.ProductID, err)
|
||||
@@ -60,48 +60,22 @@ func AddProductsBasket(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Stock insuffisant", "available": stock})
|
||||
return
|
||||
}
|
||||
if err := database.DecrementProductStockByID(req.ProductID, req.Quantity); err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Erreur décrement stock product_id=%d: %v", req.ProductID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
panier, err := database.AddProductInBasketByID(req.Username, req.ProductID, req.Quantity)
|
||||
if err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Erreur ajout product_id=%d qty=%.3f: %v", req.ProductID, req.Quantity, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := database.DecrementProductStockByID(req.ProductID, req.Quantity); err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Erreur décrement stock product_id=%d: %v", req.ProductID, err)
|
||||
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})
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback : recherche par nom+catégorie (compatibilité)
|
||||
if req.NameProduct == "" || req.Category == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "product_id ou name_product+category requis"})
|
||||
return
|
||||
}
|
||||
|
||||
stock, err := database.GetProductStock(req.NameProduct, req.Category)
|
||||
if err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Produit '%s'/'%s' non trouvé: %v", req.NameProduct, req.Category, err)
|
||||
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
|
||||
}
|
||||
panier, err := database.AddProductInBasket(req.Username, req.NameProduct, req.Quantity, req.Category)
|
||||
if err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Erreur ajout '%s': %v", req.NameProduct, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := database.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})
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -426,35 +400,36 @@ 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️⃣ Créer la commande (qui décrémente automatiquement le stock)
|
||||
// 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)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Solde parrainage insuffisant ou déjà utilisé"})
|
||||
return
|
||||
}
|
||||
log.Printf("✅ [CHECKOUT] Crédit parrainage -%.2f€ débité pour %s", referralUsed, usernameStr)
|
||||
}
|
||||
|
||||
command, err := database.CreateCommandWithAddress(usernameStr, req.DeliveryAddress)
|
||||
if err != nil {
|
||||
if referralUsed > 0 {
|
||||
_ = database.RestoreReferralBalance(usernameStr, referralUsed)
|
||||
}
|
||||
log.Printf("❌ [CHECKOUT] Erreur création commande: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création commande", "details": err.Error()})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création commande"})
|
||||
return
|
||||
}
|
||||
commandID := command.ID
|
||||
log.Printf("✅ [CHECKOUT] Commande %d créée", commandID)
|
||||
|
||||
// Débiter le solde parrainage si utilisé
|
||||
if referralUsed > 0 {
|
||||
tx, txErr := database.Begin()
|
||||
if txErr == nil {
|
||||
if txErr = database.UseClientReferralBalance(tx, usernameStr, referralUsed); txErr != nil {
|
||||
tx.Rollback()
|
||||
log.Printf("⚠️ [CHECKOUT] Impossible de débiter le crédit parrainage: %v", txErr)
|
||||
} else {
|
||||
tx.Commit()
|
||||
log.Printf("✅ [CHECKOUT] Crédit parrainage -%.2f€ débité pour %s", referralUsed, usernameStr)
|
||||
// Stocker le montant de parrainage sur la commande
|
||||
if err := database.SetCommandReferralUsed(commandID, referralUsed); err != nil {
|
||||
log.Printf("⚠️ [CHECKOUT] Impossible de sauvegarder referral_used sur commande: %v", err)
|
||||
}
|
||||
}
|
||||
if err := database.SetCommandReferralUsed(commandID, referralUsed); err != nil {
|
||||
log.Printf("⚠️ [CHECKOUT] Impossible de sauvegarder referral_used sur commande: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("✅ [CHECKOUT] Commande %d créée", commandID)
|
||||
|
||||
// Notifier immédiatement tous les admins et agents cabine
|
||||
go database.NotifyAllAdminCabine(commandID, usernameStr, req.DeliveryAddress)
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ func UpdateMyProfile(c *gin.Context) {
|
||||
// PUT /api/v2/admin/protected/clients/:id
|
||||
func UpdateClientByAdmin(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
role, exists := c.Get("user_role")
|
||||
role, exists := c.Get("role")
|
||||
if !exists || role != "admin" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "Accès réservé aux administrateurs",
|
||||
@@ -320,7 +320,12 @@ func UpdateClientByAdmin(c *gin.Context) {
|
||||
// PUT /api/v2/admin/protected/users/:id
|
||||
func UpdateUserByAdmin(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
if c.GetString("role") != "admin" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "Accès réservé aux administrateurs",
|
||||
})
|
||||
return
|
||||
}
|
||||
// Récupérer l'ID de l'utilisateur à modifier
|
||||
userIDStr := c.Param("id")
|
||||
userID, err := strconv.Atoi(userIDStr)
|
||||
@@ -333,8 +338,7 @@ func UpdateUserByAdmin(c *gin.Context) {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("❌ [UPDATE_USER_ADMIN] Erreur binding: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Données invalides",
|
||||
"details": err.Error(),
|
||||
"error": "Données invalides",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -82,36 +82,10 @@ func ValidateDeliveryByLivreur(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ ÉTAPE 3: VALIDATION GPS (CRITIQUE)
|
||||
destLat, _ := command["dest_latitude"].(float64)
|
||||
destLon, _ := command["dest_longitude"].(float64)
|
||||
// ÉTAPE 3: Coordonnées GPS reçues et valides
|
||||
log.Printf("📍 [VALIDATE_LIVREUR] GPS reçu: (%.6f, %.6f)", req.Latitude, req.Longitude)
|
||||
|
||||
distance := calculateDistance(req.Latitude, req.Longitude, destLat, destLon)
|
||||
log.Printf("📍 [VALIDATE_LIVREUR] Distance: %.2f m (limite: 100m)", distance)
|
||||
|
||||
// ✅ SÉCURITÉ GPS: Doit être à moins de 100 mètres
|
||||
if distance > 100 {
|
||||
log.Printf("❌ [VALIDATE_LIVREUR] Trop loin! Distance: %.2f m", distance)
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Vous êtes trop loin de la destination",
|
||||
"required_distance": 100,
|
||||
"current_distance": fmt.Sprintf("%.2f", distance),
|
||||
"unit": "meters",
|
||||
"destination_coords": gin.H{
|
||||
"latitude": destLat,
|
||||
"longitude": destLon,
|
||||
},
|
||||
"your_coords": gin.H{
|
||||
"latitude": req.Latitude,
|
||||
"longitude": req.Longitude,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [VALIDATE_LIVREUR] GPS VALIDÉ - Distance: %.2f m < 100m", distance)
|
||||
|
||||
// ✅ ÉTAPE 4: Sauvegarder les coordonnées du livreur
|
||||
// É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,
|
||||
@@ -132,7 +106,7 @@ func ValidateDeliveryByLivreur(c *gin.Context) {
|
||||
|
||||
// ✅ ÉTAPE 6: Ajouter un log
|
||||
database.AddCommandLog(commandID, "livre",
|
||||
fmt.Sprintf("Livraison confirmée par livreur - Distance: %.2f m", distance),
|
||||
fmt.Sprintf("Livraison confirmée par livreur - GPS: (%.6f, %.6f)", req.Latitude, req.Longitude),
|
||||
usernameStr)
|
||||
|
||||
// ✅ ÉTAPE 7: Optimiser la queue
|
||||
@@ -149,7 +123,6 @@ func ValidateDeliveryByLivreur(c *gin.Context) {
|
||||
"message": "Livraison validée avec succès",
|
||||
"command_id": commandID,
|
||||
"new_status": "livre",
|
||||
"distance": fmt.Sprintf("%.2f", distance),
|
||||
"gps_verified": true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,12 +8,9 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// BlockClientIfPenalty bloque le checkout si le client a une amende non payée.
|
||||
// Lit d'abord les paramètres globaux (penalties_enabled), puis le PenaltyCache Redis, fallback DB.
|
||||
func BlockClientIfPenalty(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
// Vérifier si les amendes sont activées dans les paramètres globaux
|
||||
if settings, err := database.GetSettings(); err == nil && !settings.PenaltiesEnabled {
|
||||
c.Next()
|
||||
return
|
||||
@@ -26,26 +23,19 @@ func BlockClientIfPenalty(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Tenter le cache Redis via la session
|
||||
if id, ok := clientID.(int); ok {
|
||||
if session, err := database.GetClientSession(id); err == nil {
|
||||
if session.PenaltyCache > 0 {
|
||||
log.Printf("🚫 [PENALTY] Checkout bloqué pour client_id=%d (amende=%.2f via cache)", id, session.PenaltyCache)
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "Commande bloquée : vous avez une amende en attente de paiement",
|
||||
"amende": session.PenaltyCache,
|
||||
"blocked": true,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// Cache présent et amende = 0 → on laisse passer sans requête DB
|
||||
c.Next()
|
||||
if session, err := database.GetClientSession(id); err == nil && session.PenaltyCache > 0 {
|
||||
log.Printf("🚫 [PENALTY] Checkout bloqué pour client_id=%d (amende=%.2f via cache)", id, session.PenaltyCache)
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "Commande bloquée : vous avez une amende en attente de paiement",
|
||||
"amende": session.PenaltyCache,
|
||||
"blocked": true,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback DB si session Redis absente/expirée
|
||||
username, exists := c.Get("username")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentification requise"})
|
||||
@@ -63,7 +53,6 @@ func BlockClientIfPenalty(c *gin.Context) {
|
||||
amende, err := database.GetClientAmende(usernameStr)
|
||||
if err != nil {
|
||||
log.Printf("❌ [PENALTY] Erreur vérification amende pour %s: %v", usernameStr, err)
|
||||
// En cas d'erreur DB on laisse passer pour ne pas bloquer l'utilisateur injustement
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -139,7 +139,6 @@ func (gs *GeoService) fetchFromNominatim(address string) (*GeoLocation, error) {
|
||||
params.Set("limit", "1") // une seule réponse
|
||||
|
||||
fullURL := fmt.Sprintf("%s?%s", NominatimBaseURL, params.Encode())
|
||||
fmt.Println("URL Nominatim:", fullURL) // debug
|
||||
|
||||
var lastErr error
|
||||
for i := 0; i < 3; i++ { // retry jusqu'à 3 fois
|
||||
|
||||
Reference in New Issue
Block a user