chore: build
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Backend - Build & Lint / build (push) Failing after 25m18s
Frontend Web - Build & Lint / build (push) Failing after 9m58s

This commit is contained in:
Xor290
2026-08-06 12:06:05 +02:00
parent c034088bee
commit 22a8d5026c
174 changed files with 30315 additions and 16120 deletions
+19 -71
View File
@@ -22,9 +22,6 @@ type BasketsRequest struct {
Quantity float64 `json:"quantity"`
}
// ============================================
// ✅ SÉCURISÉ: AddProductsBasket
// ============================================
// POST /api/v1/panier/add
func AddProductsBasket(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
@@ -90,7 +87,6 @@ func GetAllBaskets(c *gin.Context) {
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")
@@ -100,7 +96,6 @@ func GetAllBaskets(c *gin.Context) {
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)
@@ -111,10 +106,8 @@ func GetAllBaskets(c *gin.Context) {
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)
@@ -130,7 +123,7 @@ func GetAllBaskets(c *gin.Context) {
var totalAmount float64
for _, item := range baskets {
totalAmount += item.Price // price = prix total de la ligne (cumul des ajouts)
totalAmount += item.Price
}
log.Printf("✅ [GET_PANIER] Panier %s: %d articles, total=%.2f€", username, len(baskets), totalAmount)
@@ -156,7 +149,6 @@ func DeleteProductFromBasket(c *gin.Context) {
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")
@@ -168,7 +160,6 @@ 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
itemUsername, err := database.GetBasketItemOwner(req.ID)
if err != nil {
@@ -187,7 +178,6 @@ func DeleteProductFromBasket(c *gin.Context) {
return
}
// Supprimer l'article
err = database.DeleteProductFromBasket(req.ID)
if err != nil {
utils.ServerErr(c, "Erreur lors de la suppression", err)
@@ -205,7 +195,6 @@ func DeleteProductFromBasket(c *gin.Context) {
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")
@@ -262,8 +251,8 @@ func ValidateBasket(c *gin.Context) {
var req struct {
DeliveryAddress string `json:"delivery_address" binding:"required"`
UseReferralBalance bool `json:"use_referral_balance"`
PaymentMethod string `json:"payment_method"` // "cash" (défaut) ou "crypto"
PayCurrency string `json:"pay_currency"` // ex: "btc", "eth", "ltc" (requis si crypto)
PaymentMethod string `json:"payment_method"`
PayCurrency string `json:"pay_currency"`
}
if err := c.ShouldBindJSON(&req); err != nil || req.DeliveryAddress == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Adresse de livraison requise"})
@@ -277,7 +266,6 @@ func ValidateBasket(c *gin.Context) {
}
req.DeliveryAddress = cmd.DeliveryAddress
// Vérifier que le client a lié son compte Telegram (seulement si les notifications sont activées)
if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() {
if _, linked, err := database.GetClientTelegramChatID(usernameStr); err != nil || !linked {
c.JSON(http.StatusForbidden, gin.H{"error": "Vous devez lier votre compte Telegram avant de commander"})
@@ -287,9 +275,6 @@ func ValidateBasket(c *gin.Context) {
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 {
utils.ServerErr(c, "Impossible de récupérer le panier", err)
@@ -304,9 +289,6 @@ func ValidateBasket(c *gin.Context) {
log.Printf("🛒 [CHECKOUT] Panier: %d articles", len(items))
// ============================================
// 1️⃣b Calculer le total et vérifier la présence d'au moins un article payant
// ============================================
var cartTotal float64
for _, item := range items {
if price, ok := item["price"].(float64); ok {
@@ -314,7 +296,6 @@ func ValidateBasket(c *gin.Context) {
}
}
// Détecter si le panier contient un article récompense (prix 0)
hasRewardItem := false
for _, item := range items {
if price, ok := item["price"].(float64); ok && price == 0 {
@@ -322,17 +303,13 @@ func ValidateBasket(c *gin.Context) {
break
}
}
// Si récompense présente mais aucun produit payant → refuser
if hasRewardItem && cartTotal <= 0 {
log.Printf("❌ [CHECKOUT] Panier contient uniquement des récompenses pour %s", usernameStr)
c.JSON(http.StatusBadRequest, gin.H{"error": "Vous devez commander au moins un produit de la boutique pour bénéficier de votre récompense"})
return
}
// Récupérer les paramètres globaux (zones + parrainage)
appSettings, _ := database.GetSettings()
// Récupérer le solde parrainage disponible (seulement si le système est activé)
var referralBalance float64
if req.UseReferralBalance && appSettings.ReferralEnabled {
referralBalance, _ = database.GetClientReferralBalance(usernameStr)
@@ -366,8 +343,6 @@ func ValidateBasket(c *gin.Context) {
return
}
// Règle parrainage : après déduction du crédit, le client doit toujours payer au minimum le seuil de zone.
// Ex : zone 50€, crédit 50€ → panier doit être >= 100€
var referralUsed float64
if req.UseReferralBalance && referralBalance > 0 {
effectivePayment := cartTotal - referralBalance
@@ -399,7 +374,6 @@ func ValidateBasket(c *gin.Context) {
log.Printf("✅ [CHECKOUT] Crédit parrainage -%.2f€ débité pour %s", referralUsed, usernameStr)
}
// Vérifier que tous les produits du panier ont encore un prix actif
unavailable, err := database.GetUnavailableBasketItems(usernameStr)
if err != nil {
utils.ServerErr(c, "Erreur vérification produits", err)
@@ -414,11 +388,11 @@ func ValidateBasket(c *gin.Context) {
return
}
// Vérification option crypto
isCrypto := req.PaymentMethod == "crypto"
if isCrypto {
np, npOk := c.MustGet("nowpayments").(*services.NowPaymentsClient)
if !npOk || np == nil {
npRaw, npExists := c.Get("nowpayments")
np, npOk := npRaw.(*services.NowPaymentsClient)
if !npExists || !npOk || np == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Paiement crypto non disponible"})
return
}
@@ -434,6 +408,10 @@ func ValidateBasket(c *gin.Context) {
_ = database.CreditClientReferral(usernameStr, referralUsed)
}
log.Printf("❌ [CHECKOUT] Erreur création commande: %v", err)
if strings.Contains(err.Error(), "stock insuffisant") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Désolé ! Le stock ou le produit n'est plus disponible, repasse commande"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création commande"})
return
}
@@ -447,7 +425,6 @@ func ValidateBasket(c *gin.Context) {
log.Printf("✅ [CHECKOUT] Commande %d créée", commandID)
// Pour le paiement crypto, le panier/stock sera décrémenté à la confirmation du webhook NowPayments.
if isCrypto {
np := c.MustGet("nowpayments").(*services.NowPaymentsClient)
ipnURL := fmt.Sprintf("%s/api/v1/webhooks/nowpayments", getBaseURL(c))
@@ -460,7 +437,6 @@ func ValidateBasket(c *gin.Context) {
}
payResp, err := np.CreatePayment(payReq)
if err != nil {
// Annuler la commande et restaurer le panier / parrainage
_ = database.CancelCryptoCommand(commandID)
if referralUsed > 0 {
_ = database.CreditClientReferral(usernameStr, referralUsed)
@@ -470,14 +446,21 @@ func ValidateBasket(c *gin.Context) {
return
}
// Passer la commande en 'pending_payment' (attente confirmation)
if _, err := database.DB.Exec(`UPDATE commandes SET status = 'pending_payment', payment_method = 'crypto', updated_at = NOW() WHERE id = $1`, commandID); err != nil {
log.Printf("⚠️ [CHECKOUT] Erreur mise à jour statut pending_payment: %v", err)
}
priceAmt, _ := payResp.PriceAmount.Float64()
payAmt, _ := payResp.PayAmount.Float64()
_, _ = database.CreateCryptoPayment(commandID, payResp.PaymentID.String(), payResp.Status, payResp.PriceCurrency, payResp.PayCurrency, payResp.PayAddress, priceAmt, payAmt)
if _, err := database.CreateCryptoPayment(commandID, payResp.PaymentID.String(), payResp.Status, payResp.PriceCurrency, payResp.PayCurrency, payResp.PayAddress, priceAmt, payAmt); err != nil {
log.Printf("❌ [CHECKOUT] Erreur enregistrement paiement crypto (commande %d, nowpayment %s): %v", commandID, payResp.PaymentID.String(), err)
_ = database.CancelCryptoCommand(commandID)
if referralUsed > 0 {
_ = database.CreditClientReferral(usernameStr, referralUsed)
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur interne lors de l'enregistrement du paiement"})
return
}
log.Printf("✅ [CHECKOUT] Commande %d en attente paiement crypto (%s)", commandID, req.PayCurrency)
c.JSON(http.StatusCreated, gin.H{
@@ -495,32 +478,8 @@ func ValidateBasket(c *gin.Context) {
return
}
// Notifier immédiatement tous les admins et agents cabine
go database.NotifyAllAdminCabine(commandID, usernameStr, req.DeliveryAddress)
// ============================================
// 3️⃣ Décrémenter le stock et vider le panier
// ============================================
err = database.ClearBasketOnCheckout(usernameStr)
if err != nil {
// Stock insuffisant au moment du checkout (concurrent) → annuler la commande
if strings.Contains(err.Error(), "stock insuffisant") {
_ = database.CancelCryptoCommand(commandID)
if referralUsed > 0 {
_ = database.CreditClientReferral(usernameStr, referralUsed)
}
log.Printf("❌ [CHECKOUT] Stock insuffisant au moment de la validation: %v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Désolé ! Tu as trop attendu pour passer commande! Le stock ou le produit n'est plus disponible, repasse commande"})
return
}
utils.ServerErr(c, "Impossible de valider le panier", err)
return
}
log.Printf("🧹 [CHECKOUT] Stock décrémenté et panier vidé")
// ============================================
// 4️⃣ Auto-assignation livreur (optionnel)
// ============================================
var assigned bool
var assignInfo gin.H
@@ -540,7 +499,6 @@ func ValidateBasket(c *gin.Context) {
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{
@@ -558,7 +516,6 @@ func ValidateBasket(c *gin.Context) {
log.Printf("⏱️ [CHECKOUT] ETA calculé: %d min, distance: %.2f km", travelTime, distance)
// Assigner la commande au livreur
err = database.AssignCommandToDeliverymanQueueWithCoords(
commandID,
nearest.Username,
@@ -571,13 +528,10 @@ func ValidateBasket(c *gin.Context) {
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)
}
// Notifier le livreur de la nouvelle commande
notifMsg := fmt.Sprintf("Nouvelle commande #%d assignée - Livraison dans ~%d min (%.2f km)", commandID, travelTime, distance)
if referralUsed > 0 {
notifMsg += fmt.Sprintf(" | Parrainage client: -%.2f€", referralUsed)
@@ -585,8 +539,6 @@ func ValidateBasket(c *gin.Context) {
if notifErr := database.NotifyLivreur(nearest.Username, commandID, "new_assignment", notifMsg); notifErr != nil {
log.Printf("⚠️ [CHECKOUT] Erreur notification livreur: %v", notifErr)
}
// Notifier le client
clientOrderID := database.GetClientOrderID(commandID)
clientMsg := fmt.Sprintf("Ta commande #%d est prise en compte ! Merci de rester branché et vigilant sur les notifs à venir.", clientOrderID)
database.NotifyClient(usernameStr, commandID, "assigned", clientMsg)
@@ -609,9 +561,6 @@ func ValidateBasket(c *gin.Context) {
log.Printf("⚠️ [CHECKOUT] Erreur géocodage: %v", err)
}
// ============================================
// 5️⃣ Réponse
// ============================================
newBalance, _ := database.GetClientReferralBalance(usernameStr)
resp := gin.H{
"success": true,
@@ -638,7 +587,6 @@ func ValidateBasket(c *gin.Context) {
c.JSON(http.StatusCreated, resp)
}
// getBaseURL construit l'URL de base depuis la requête en cours
func getBaseURL(c *gin.Context) string {
scheme := "https"
if c.Request.TLS == nil && c.GetHeader("X-Forwarded-Proto") != "https" {