package db import ( "fmt" "log" "strings" "time" ) // ============================================ // VALIDATION HELPERS // ============================================ func validateCommandID(commandID int) error { if commandID <= 0 { return fmt.Errorf("ID commande invalide: %d", commandID) } if commandID > 2147483647 { return fmt.Errorf("ID commande trop grand") } return nil } func validateItemID(itemID int) error { if itemID <= 0 { return fmt.Errorf("ID item invalide: %d", itemID) } if itemID > 2147483647 { return fmt.Errorf("ID item trop grand") } return nil } func validateQuantite(quantite float64) error { if quantite <= 0 { return fmt.Errorf("quantité doit être > 0") } if quantite > 10000 { return fmt.Errorf("quantité trop élevée (max 10000)") } return nil } func validatePrix(prix float64) error { if prix <= 0 { return fmt.Errorf("prix doit être > 0") } if prix > 100000 { return fmt.Errorf("prix trop élevé (max 100000)") } return nil } func validateUsername(username string) error { if len(username) == 0 { return fmt.Errorf("username vide") } if len(username) > 100 { return fmt.Errorf("username trop long (max 100)") } // Sanitize if strings.Contains(username, "..") || strings.Contains(username, "/") { return fmt.Errorf("username invalide") } return nil } func validateDeliveryAddress(address string) error { if len(address) == 0 { return fmt.Errorf("adresse vide") } if len(address) > 500 { return fmt.Errorf("adresse trop longue (max 500)") } return nil } func validateItemStatus(status string) error { validStatuses := []string{"pending", "assigned", "en_route", "livre", "approved", "cancelled"} status = strings.ToLower(strings.TrimSpace(status)) for _, valid := range validStatuses { if status == valid { return nil } } return fmt.Errorf("statut invalide: %s", status) } // ============================================ // INSERT COMMAND ITEM - VERSION SÉCURISÉE // ============================================ func (d *Database) InsertCommandItemWithClientInfo( commandID int, produit string, productID int, quantite float64, prix float64, clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress string, ) error { log.Printf("📝 [InsertCommandItemWithClientInfo] START - commandID=%d, produit=%s", commandID, produit) // ✅ VALIDATION COMPLÈTE if err := validateCommandID(commandID); err != nil { return err } if err := validateProductID(productID); err != nil { return err } if err := validateQuantite(quantite); err != nil { return err } if err := validatePrix(prix); err != nil { return err } if err := validateUsername(clientUsername); err != nil { return err } if err := validateDeliveryAddress(deliveryAddress); err != nil { return err } // ✅ SANITIZE STRINGS produit = strings.TrimSpace(produit) if len(produit) > 200 { produit = produit[:200] } clientNom = strings.TrimSpace(clientNom) if len(clientNom) > 100 { clientNom = clientNom[:100] } clientPrenom = strings.TrimSpace(clientPrenom) if len(clientPrenom) > 100 { clientPrenom = clientPrenom[:100] } clientTelephone = strings.TrimSpace(clientTelephone) if len(clientTelephone) > 20 { clientTelephone = clientTelephone[:20] } // ✅ VÉRIFIER QUE LA COMMANDE EXISTE var exists bool if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM commandes WHERE id = ?)`, commandID).Scan(&exists).Error; err != nil { log.Printf("❌ Erreur vérification commande: %v", err) return fmt.Errorf("erreur vérification commande: %w", err) } if !exists { return fmt.Errorf("commande %d n'existe pas", commandID) } // ✅ INSERT err := d.GDB.Exec(` INSERT INTO command_items ( command_id, produit, product_id, quantite, prix, client_username, client_nom, client_prenom, client_telephone, delivery_address, status, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, commandID, produit, productID, quantite, prix, clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress, ).Error if err != nil { log.Printf("❌ Erreur INSERT command_items: %v", err) return fmt.Errorf("erreur insertion item: %w", err) } log.Printf("✅ Item inséré avec infos client: %s %s", clientNom, clientPrenom) return nil } // ============================================ // GET COMMAND ITEMS - VERSION SÉCURISÉE + FIX NULL // ============================================ func (d *Database) GetCommandItems(commandID int) ([]map[string]interface{}, error) { log.Printf("📦 [GetCommandItems] START - commandID=%d", commandID) // ✅ VALIDATION if err := validateCommandID(commandID); err != nil { log.Printf("❌ [GetCommandItems] %v", err) return nil, err } var rows []struct { ID int `gorm:"column:id"` CommandID int `gorm:"column:command_id"` Produit string `gorm:"column:produit"` ProductID *int64 `gorm:"column:product_id"` Quantite float64 `gorm:"column:quantite"` Prix float64 `gorm:"column:prix"` ClientUsername string `gorm:"column:client_username"` ClientNom string `gorm:"column:client_nom"` ClientPrenom string `gorm:"column:client_prenom"` ClientTelephone string `gorm:"column:client_telephone"` DeliveryAddress *string `gorm:"column:delivery_address"` Status *string `gorm:"column:status"` CreatedAt time.Time `gorm:"column:created_at"` UpdatedAt time.Time `gorm:"column:updated_at"` CommandStatus *string `gorm:"column:command_status"` CommandAddress *string `gorm:"column:command_address"` TotalPrix float64 `gorm:"column:total_prix"` ReferralUsed float64 `gorm:"column:referral_used"` LivreurAssign *string `gorm:"column:livreur_assign"` CommandCreatedAt *time.Time `gorm:"column:command_created_at"` Category string `gorm:"column:category"` ClientOrderNumber int `gorm:"column:client_order_number"` } err := d.GDB.Raw(` SELECT ci.id, ci.command_id, ci.produit, ci.product_id, ci.quantite, ci.prix, ci.client_username, ci.client_nom, ci.client_prenom, ci.client_telephone, ci.delivery_address, ci.status, ci.created_at, ci.updated_at, c.status as command_status, c.adresse as command_address, c.total_prix, c.referral_used, c.livreur_assign, c.created_at as command_created_at, p.category, c.client_order_id as client_order_number FROM command_items ci LEFT JOIN commandes c ON ci.command_id = c.id LEFT JOIN products p ON ci.product_id = p.id WHERE ci.command_id = ? ORDER BY ci.id ASC`, commandID).Scan(&rows).Error if err != nil { log.Printf("❌ Erreur query: %v", err) return nil, fmt.Errorf("erreur récupération items: %w", err) } items := make([]map[string]interface{}, 0, len(rows)) for _, row := range rows { productIDValue := 0 if row.ProductID != nil { productIDValue = int(*row.ProductID) } var commandCreatedAt interface{} if row.CommandCreatedAt != nil { commandCreatedAt = *row.CommandCreatedAt } item := map[string]interface{}{ "id": row.ID, "command_id": row.CommandID, "produit": row.Produit, "product_id": productIDValue, "quantite": row.Quantite, "prix": row.Prix, "client_username": row.ClientUsername, "client_nom": row.ClientNom, "client_prenom": row.ClientPrenom, "client_telephone": row.ClientTelephone, "delivery_address": ptrStr(row.DeliveryAddress), "status": ptrStr(row.Status), "created_at": row.CreatedAt, "updated_at": row.UpdatedAt, // Infos commande "command_status": ptrStr(row.CommandStatus), "command_address": ptrStr(row.CommandAddress), "total_prix": row.TotalPrix, "referral_used": row.ReferralUsed, "livreur_assign": ptrStr(row.LivreurAssign), "command_created_at": commandCreatedAt, "category": row.Category, "client_order_number": row.ClientOrderNumber, } items = append(items, item) } log.Printf("✅ %d items récupérés avec infos client et catégories", len(items)) return items, nil } // ptrStr retourne la valeur d'un *string ou "" si nil func ptrStr(s *string) string { if s == nil { return "" } return *s } func (d *Database) GetCommandItemsByUsername(username string) ([]map[string]interface{}, error) { if err := validateUsername(username); err != nil { log.Printf("❌ [GetCommandItemsByUsername] %v", err) return nil, err } var rows []struct { ID int `gorm:"column:id"` CommandID int `gorm:"column:command_id"` Produit string `gorm:"column:produit"` ProductID *int64 `gorm:"column:product_id"` Quantite float64 `gorm:"column:quantite"` Prix float64 `gorm:"column:prix"` ClientUsername string `gorm:"column:client_username"` ClientNom string `gorm:"column:client_nom"` ClientPrenom string `gorm:"column:client_prenom"` ClientTelephone string `gorm:"column:client_telephone"` DeliveryAddress *string `gorm:"column:delivery_address"` Status *string `gorm:"column:status"` CreatedAt time.Time `gorm:"column:created_at"` UpdatedAt time.Time `gorm:"column:updated_at"` CommandStatus *string `gorm:"column:command_status"` CommandAddress *string `gorm:"column:command_address"` TotalPrix float64 `gorm:"column:total_prix"` LivreurAssign *string `gorm:"column:livreur_assign"` CommandCreatedAt *time.Time `gorm:"column:command_created_at"` } err := d.GDB.Raw(` SELECT ci.id, ci.command_id, ci.produit, ci.product_id, ci.quantite, ci.prix, ci.client_username, ci.client_nom, ci.client_prenom, ci.client_telephone, ci.delivery_address, ci.status, ci.created_at, ci.updated_at, c.status as command_status, c.adresse as command_address, c.total_prix, c.livreur_assign, c.created_at as command_created_at FROM command_items ci LEFT JOIN commandes c ON ci.command_id = c.id WHERE ci.client_username = ? ORDER BY ci.command_id DESC, ci.id ASC`, username).Scan(&rows).Error if err != nil { log.Printf("❌ Erreur query: %v", err) return nil, fmt.Errorf("erreur récupération items: %w", err) } items := make([]map[string]interface{}, 0, len(rows)) for _, row := range rows { productIDValue := 0 if row.ProductID != nil { productIDValue = int(*row.ProductID) } var commandCreatedAt interface{} if row.CommandCreatedAt != nil { commandCreatedAt = *row.CommandCreatedAt } item := map[string]interface{}{ "id": row.ID, "command_id": row.CommandID, "produit": row.Produit, "product_id": productIDValue, "quantite": row.Quantite, "prix": row.Prix, "client_username": row.ClientUsername, "client_nom": row.ClientNom, "client_prenom": row.ClientPrenom, "client_telephone": row.ClientTelephone, "delivery_address": ptrStr(row.DeliveryAddress), "status": ptrStr(row.Status), "created_at": row.CreatedAt, "updated_at": row.UpdatedAt, "command_status": ptrStr(row.CommandStatus), "command_address": ptrStr(row.CommandAddress), "total_prix": row.TotalPrix, "livreur_assign": ptrStr(row.LivreurAssign), "command_created_at": commandCreatedAt, } items = append(items, item) } log.Printf("✅ %d items récupérés pour l'utilisateur %s", len(items), username) return items, nil } func (d *Database) DeleteCommandItem(commandID, itemID int) error { log.Printf("🗑️ [DeleteCommandItem] START - commandID=%d, itemID=%d", commandID, itemID) if err := validateCommandID(commandID); err != nil { return err } if err := validateItemID(itemID); err != nil { return err } // Récupérer le prix et la quantité avant suppression pour mettre à jour le total var result struct { Prix float64 `gorm:"column:prix"` Quantite float64 `gorm:"column:quantite"` } if err := d.GDB.Raw(`SELECT prix, quantite FROM command_items WHERE id = ? AND command_id = ?`, itemID, commandID).Scan(&result).Error; err != nil { return fmt.Errorf("erreur vérification item: %w", err) } if result.Prix == 0 && result.Quantite == 0 { return fmt.Errorf("item %d non trouvé dans la commande %d", itemID, commandID) } // Supprimer l'item if err := d.GDB.Exec(`DELETE FROM command_items WHERE id = ?`, itemID).Error; err != nil { log.Printf("❌ Erreur DELETE command_items: %v", err) return fmt.Errorf("erreur suppression item: %w", err) } // Recalculer le total de la commande if err := d.GDB.Exec( `UPDATE commandes SET total_prix = GREATEST(0, total_prix - ?) WHERE id = ?`, result.Prix*result.Quantite, commandID, ).Error; err != nil { log.Printf("⚠️ [DeleteCommandItem] Erreur maj total commande: %v", err) } return nil } func (d *Database) UpdateCommandItemStatus(itemID int, status string) error { if err := validateItemID(itemID); err != nil { log.Printf("❌ [UpdateCommandItemStatus] %v", err) return err } if err := validateItemStatus(status); err != nil { log.Printf("❌ [UpdateCommandItemStatus] %v", err) return err } var exists bool if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM command_items WHERE id = ?)`, itemID).Scan(&exists).Error; err != nil { log.Printf("❌ Erreur vérification: %v", err) return fmt.Errorf("erreur vérification item: %w", err) } if !exists { log.Printf("❌ Item %d non trouvé", itemID) return fmt.Errorf("item %d non trouvé", itemID) } result := d.GDB.Exec(` UPDATE command_items SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, status, itemID) if result.Error != nil { log.Printf("❌ Erreur UPDATE: %v", result.Error) return fmt.Errorf("erreur mise à jour statut: %w", result.Error) } if result.RowsAffected == 0 { log.Printf("❌ Item %d non trouvé", itemID) return fmt.Errorf("item non trouvé") } return nil }