465 lines
12 KiB
Go
465 lines
12 KiB
Go
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
|
|
}
|