package db import ( "database/sql" "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 checkQuery := `SELECT EXISTS(SELECT 1 FROM commandes WHERE id = $1)` err := d.QueryRow(checkQuery, commandID).Scan(&exists) if 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 query := `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 ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)` _, err = d.Exec(query, commandID, produit, productID, quantite, prix, clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress, ) 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 } query := ` 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, COALESCE(p.category, 'weed_hash') as category 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 = $1 ORDER BY ci.id ASC` rows, err := d.Query(query, commandID) if err != nil { log.Printf("❌ Erreur query: %v", err) return nil, fmt.Errorf("erreur récupération items: %w", err) } defer rows.Close() var items []map[string]interface{} for rows.Next() { var id, commandID int var quantite float64 var productID sql.NullInt64 // ✅ FIX: Utiliser NullInt64 pour gérer NULL var produit, clientUsername, clientNom, clientPrenom, clientTelephone string var deliveryAddress, status sql.NullString var prix, totalPrix float64 var createdAt, updatedAt, commandCreatedAt time.Time var commandStatus, commandAddress, livreurAssign sql.NullString var category string // ✅ FIX: Utiliser &productID (sql.NullInt64) err := rows.Scan( &id, &commandID, &produit, &productID, &quantite, &prix, &clientUsername, &clientNom, &clientPrenom, &clientTelephone, &deliveryAddress, &status, &createdAt, &updatedAt, &commandStatus, &commandAddress, &totalPrix, &livreurAssign, &commandCreatedAt, &category, ) if err != nil { log.Printf("❌ Erreur scan: %v", err) return nil, fmt.Errorf("erreur scan: %w", err) } // ✅ CONVERTIR sql.NullInt64 en int (0 si NULL) productIDValue := 0 if productID.Valid { productIDValue = int(productID.Int64) } item := map[string]interface{}{ "id": id, "command_id": commandID, "produit": produit, "product_id": productIDValue, // ✅ FIX: Utiliser la valeur convertie "quantite": quantite, "prix": prix, "client_username": clientUsername, "client_nom": clientNom, "client_prenom": clientPrenom, "client_telephone": clientTelephone, "delivery_address": deliveryAddress.String, "status": status.String, "created_at": createdAt, "updated_at": updatedAt, // Infos commande "command_status": commandStatus.String, "command_address": commandAddress.String, "total_prix": totalPrix, "livreur_assign": livreurAssign.String, "command_created_at": commandCreatedAt, "category": category, } items = append(items, item) } if err = rows.Err(); err != nil { log.Printf("❌ Erreur itération: %v", err) return nil, fmt.Errorf("erreur itération: %w", err) } log.Printf("✅ %d items récupérés avec infos client et catégories", len(items)) return items, nil } // ============================================ // GET COMMAND ITEMS BY USERNAME - VERSION SÉCURISÉE + FIX NULL // ============================================ func (d *Database) GetCommandItemsByUsername(username string) ([]map[string]interface{}, error) { log.Printf("📦 [GetCommandItemsByUsername] START - username=%s", username) // ✅ VALIDATION if err := validateUsername(username); err != nil { log.Printf("❌ [GetCommandItemsByUsername] %v", err) return nil, err } query := ` 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 = $1 ORDER BY ci.command_id DESC, ci.id ASC` rows, err := d.Query(query, username) if err != nil { log.Printf("❌ Erreur query: %v", err) return nil, fmt.Errorf("erreur récupération items: %w", err) } defer rows.Close() var items []map[string]interface{} for rows.Next() { var id, commandID int var quantite float64 var productID sql.NullInt64 // ✅ FIX: NullInt64 var produit, clientUsername, clientNom, clientPrenom, clientTelephone string var deliveryAddress, status sql.NullString var prix, totalPrix float64 var createdAt, updatedAt, commandCreatedAt time.Time var commandStatus, commandAddress, livreurAssign sql.NullString err := rows.Scan( &id, &commandID, &produit, &productID, &quantite, &prix, &clientUsername, &clientNom, &clientPrenom, &clientTelephone, &deliveryAddress, &status, &createdAt, &updatedAt, &commandStatus, &commandAddress, &totalPrix, &livreurAssign, &commandCreatedAt, ) if err != nil { log.Printf("❌ Erreur scan: %v", err) return nil, fmt.Errorf("erreur scan: %w", err) } // ✅ CONVERTIR NullInt64 productIDValue := 0 if productID.Valid { productIDValue = int(productID.Int64) } item := map[string]interface{}{ "id": id, "command_id": commandID, "produit": produit, "product_id": productIDValue, // ✅ FIX "quantite": quantite, "prix": prix, "client_username": clientUsername, "client_nom": clientNom, "client_prenom": clientPrenom, "client_telephone": clientTelephone, "delivery_address": deliveryAddress.String, "status": status.String, "created_at": createdAt, "updated_at": updatedAt, // Infos commande "command_status": commandStatus.String, "command_address": commandAddress.String, "total_prix": totalPrix, "livreur_assign": livreurAssign.String, "command_created_at": commandCreatedAt, } items = append(items, item) } if err = rows.Err(); err != nil { log.Printf("❌ Erreur itération: %v", err) return nil, fmt.Errorf("erreur itération: %w", err) } log.Printf("✅ %d items récupérés pour l'utilisateur %s", len(items), username) return items, nil } // ============================================ // UPDATE COMMAND ITEM STATUS - VERSION SÉCURISÉE // ============================================ func (d *Database) UpdateCommandItemStatus(itemID int, status string) error { log.Printf("📝 [UpdateCommandItemStatus] START - itemID=%d, status=%s", itemID, status) // ✅ VALIDATION 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 } // ✅ VÉRIFIER QUE L'ITEM EXISTE var exists bool checkQuery := `SELECT EXISTS(SELECT 1 FROM command_items WHERE id = $1)` err := d.QueryRow(checkQuery, itemID).Scan(&exists) if 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) } // ✅ UPDATE query := `UPDATE command_items SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2` result, err := d.Exec(query, status, itemID) if err != nil { log.Printf("❌ Erreur UPDATE: %v", err) return fmt.Errorf("erreur mise à jour statut: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { log.Printf("❌ Erreur RowsAffected: %v", err) return fmt.Errorf("erreur vérification: %w", err) } if rowsAffected == 0 { log.Printf("❌ Item %d non trouvé", itemID) return fmt.Errorf("item non trouvé") } log.Printf("✅ Statut item %d mis à jour: %s", itemID, status) return nil }