package db import ( "database/sql" "fmt" "gestion/models" "log" "strings" "time" ) // ============================================ // VALIDATION HELPERS // ============================================ // validateMediaID vérifie la validité d'un ID média func validateMediaID(mediaID int) error { if mediaID <= 0 { return fmt.Errorf("ID média invalide: %d", mediaID) } if mediaID > 2147483647 { // Max int32 return fmt.Errorf("ID média trop grand") } return nil } // validateProductID vérifie la validité d'un ID produit func validateProductID(productID int) error { if productID <= 0 { return fmt.Errorf("ID produit invalide: %d", productID) } if productID > 2147483647 { return fmt.Errorf("ID produit trop grand") } return nil } // validateMediaType vérifie le type de média func validateMediaType(mediaType string) error { validTypes := []string{"image", "video"} mediaType = strings.ToLower(strings.TrimSpace(mediaType)) for _, valid := range validTypes { if mediaType == valid { return nil } } return fmt.Errorf("type de média invalide: %s (autorisé: image, video)", mediaType) } // validateMediaURL vérifie la sécurité de l'URL func validateMediaURL(url string) error { if len(url) == 0 { return fmt.Errorf("URL vide") } if len(url) > 500 { return fmt.Errorf("URL trop longue (max 500 caractères)") } // ✅ PROTECTION PATH TRAVERSAL if strings.Contains(url, "..") { return fmt.Errorf("path traversal détecté dans l'URL") } // ✅ VÉRIFIER QUE L'URL COMMENCE PAR /uploads/ if !strings.HasPrefix(url, "/uploads/") { return fmt.Errorf("URL doit commencer par /uploads/") } // ✅ VÉRIFIER QU'IL N'Y A PAS DE CARACTÈRES DANGEREUX dangerousChars := []string{ "<", ">", "\"", "'", ";", "|", "&", "$", "`", "\\", } for _, char := range dangerousChars { if strings.Contains(url, char) { return fmt.Errorf("caractères interdits dans l'URL") } } return nil } // ============================================ // CREATE MEDIA - VERSION SÉCURISÉE // ============================================ func (db *Database) CreateMedia(media interface{}) error { log.Printf("🔒 [CreateMedia] START - Type: %T", media) type MediaInterface interface { GetProductID() int GetType() string GetURL() string SetID(int) } // ✅ TYPE ASSERTION SÉCURISÉE m, ok := media.(MediaInterface) if !ok { // Vérifier si c'est un pointeur vers models.Media if mediaPtr, isPtr := media.(*models.Media); isPtr { m = mediaPtr ok = true } else { log.Printf("❌ [CreateMedia] Type invalide: %T", media) return fmt.Errorf("type de média invalide: reçu %T", media) } } if !ok { return fmt.Errorf("type de média invalide") } // ✅ VALIDATION COMPLÈTE productID := m.GetProductID() mediaType := m.GetType() mediaURL := m.GetURL() log.Printf("📋 [CreateMedia] ProductID=%d, Type=%s, URL=%s", productID, mediaType, mediaURL) // Valider le product ID if err := validateProductID(productID); err != nil { log.Printf("❌ [CreateMedia] %v", err) return err } // Valider le type if err := validateMediaType(mediaType); err != nil { log.Printf("❌ [CreateMedia] %v", err) return err } // Valider l'URL if err := validateMediaURL(mediaURL); err != nil { log.Printf("❌ [CreateMedia] %v", err) return err } // ✅ VÉRIFIER QUE LE PRODUIT EXISTE var exists bool checkQuery := `SELECT EXISTS(SELECT 1 FROM products WHERE id = $1)` err := db.QueryRow(checkQuery, productID).Scan(&exists) if err != nil { log.Printf("❌ [CreateMedia] Erreur vérification produit: %v", err) return fmt.Errorf("erreur vérification produit: %w", err) } if !exists { log.Printf("❌ [CreateMedia] Produit %d n'existe pas", productID) return fmt.Errorf("produit %d n'existe pas", productID) } // ✅ INSÉRER LE MÉDIA query := `INSERT INTO media (product_id, url, type, created_at) VALUES ($1, $2, $3, $4) RETURNING id` var mediaID int now := time.Now() err = db.QueryRow(query, productID, mediaURL, mediaType, now).Scan(&mediaID) if err != nil { log.Printf("❌ [CreateMedia] Erreur INSERT: %v", err) return fmt.Errorf("erreur création média: %w", err) } m.SetID(mediaID) log.Printf("✅ [CreateMedia] Média créé: ID=%d, Type=%s", mediaID, mediaType) return nil } // ============================================ // GET MEDIA BY ID - VERSION SÉCURISÉE // ============================================ func (db *Database) GetMediaByID(mediaID int) (*models.Media, error) { log.Printf("🔍 [GetMediaByID] START - ID=%d", mediaID) // ✅ VALIDATION if err := validateMediaID(mediaID); err != nil { log.Printf("❌ [GetMediaByID] %v", err) return nil, err } var media models.Media query := `SELECT id, product_id, url, type, created_at FROM media WHERE id = $1` err := db.QueryRow(query, mediaID).Scan( &media.ID, &media.ProductID, &media.URL, &media.Type, &media.CreatedAt, ) if err == sql.ErrNoRows { log.Printf("❌ [GetMediaByID] Média %d non trouvé", mediaID) return nil, fmt.Errorf("média non trouvé") } if err != nil { log.Printf("❌ [GetMediaByID] Erreur query: %v", err) return nil, fmt.Errorf("erreur récupération média: %w", err) } log.Printf("✅ [GetMediaByID] Média trouvé: Type=%s", media.Type) return &media, nil } // ============================================ // GET MEDIA BY PRODUCT ID - VERSION SÉCURISÉE // ============================================ func (db *Database) GetMediaByProductID(productID int) ([]models.Media, error) { log.Printf("🖼️ [GetMediaByProductID] START - ProductID=%d", productID) // ✅ VALIDATION if err := validateProductID(productID); err != nil { log.Printf("❌ [GetMediaByProductID] %v", err) return nil, err } query := `SELECT id, product_id, url, type, created_at FROM media WHERE product_id = $1 ORDER BY id ASC` rows, err := db.Query(query, productID) if err != nil { log.Printf("❌ [GetMediaByProductID] Erreur query: %v", err) return nil, fmt.Errorf("erreur récupération médias: %w", err) } defer rows.Close() var mediaList []models.Media for rows.Next() { var media models.Media if err := rows.Scan( &media.ID, &media.ProductID, &media.URL, &media.Type, &media.CreatedAt, ); err != nil { log.Printf("❌ [GetMediaByProductID] Erreur scan: %v", err) return nil, fmt.Errorf("erreur scan média: %w", err) } log.Printf(" 🖼️ Media: ID=%d, Type=%s", media.ID, media.Type) mediaList = append(mediaList, media) } if err = rows.Err(); err != nil { log.Printf("❌ [GetMediaByProductID] Erreur iteration: %v", err) return nil, fmt.Errorf("erreur itération médias: %w", err) } log.Printf("✅ [GetMediaByProductID] %d médias trouvés", len(mediaList)) return mediaList, nil } // ============================================ // UPDATE MEDIA - VERSION SÉCURISÉE // ============================================ func (db *Database) UpdateMedia(media *models.Media) error { log.Printf("🔄 [UpdateMedia] START - ID=%d", media.ID) // ✅ VALIDATION if media == nil { return fmt.Errorf("média nil") } if err := validateMediaID(media.ID); err != nil { log.Printf("❌ [UpdateMedia] %v", err) return err } if err := validateMediaType(media.Type); err != nil { log.Printf("❌ [UpdateMedia] %v", err) return err } if err := validateMediaURL(media.URL); err != nil { log.Printf("❌ [UpdateMedia] %v", err) return err } // ✅ VÉRIFIER QUE LE MÉDIA EXISTE var exists bool checkQuery := `SELECT EXISTS(SELECT 1 FROM media WHERE id = $1)` err := db.QueryRow(checkQuery, media.ID).Scan(&exists) if err != nil { log.Printf("❌ [UpdateMedia] Erreur vérification: %v", err) return fmt.Errorf("erreur vérification média: %w", err) } if !exists { log.Printf("❌ [UpdateMedia] Média %d n'existe pas", media.ID) return fmt.Errorf("média %d non trouvé", media.ID) } // ✅ UPDATE query := `UPDATE media SET url = $1, type = $2 WHERE id = $3` result, err := db.Exec(query, media.URL, media.Type, media.ID) if err != nil { log.Printf("❌ [UpdateMedia] Erreur UPDATE: %v", err) return fmt.Errorf("erreur mise à jour média: %w", err) } rowsAffected, _ := result.RowsAffected() if rowsAffected == 0 { log.Printf("❌ [UpdateMedia] Aucune ligne affectée") return fmt.Errorf("média non trouvé") } log.Printf("✅ [UpdateMedia] Média %d mis à jour", media.ID) return nil } // ============================================ // DELETE MEDIA - VERSION SÉCURISÉE // ============================================ func (db *Database) DeleteMedia(mediaID int) error { log.Printf("🗑️ [DeleteMedia] START - ID=%d", mediaID) // ✅ VALIDATION if err := validateMediaID(mediaID); err != nil { log.Printf("❌ [DeleteMedia] %v", err) return err } // ✅ VÉRIFIER QUE LE MÉDIA EXISTE var exists bool checkQuery := `SELECT EXISTS(SELECT 1 FROM media WHERE id = $1)` err := db.QueryRow(checkQuery, mediaID).Scan(&exists) if err != nil { log.Printf("❌ [DeleteMedia] Erreur vérification: %v", err) return fmt.Errorf("erreur vérification média: %w", err) } if !exists { log.Printf("❌ [DeleteMedia] Média %d n'existe pas", mediaID) return fmt.Errorf("média %d non trouvé", mediaID) } // ✅ DELETE query := `DELETE FROM media WHERE id = $1` result, err := db.Exec(query, mediaID) if err != nil { log.Printf("❌ [DeleteMedia] Erreur DELETE: %v", err) return fmt.Errorf("erreur suppression média: %w", err) } rowsAffected, _ := result.RowsAffected() if rowsAffected == 0 { log.Printf("❌ [DeleteMedia] Aucune ligne affectée") return fmt.Errorf("média non trouvé") } log.Printf("✅ [DeleteMedia] Média %d supprimé", mediaID) return nil } // ============================================ // DELETE MEDIA BY PRODUCT ID - VERSION SÉCURISÉE // ============================================ func (db *Database) DeleteMediaByProductID(productID int) error { log.Printf("🗑️ [DeleteMediaByProductID] START - ProductID=%d", productID) // ✅ VALIDATION if err := validateProductID(productID); err != nil { log.Printf("❌ [DeleteMediaByProductID] %v", err) return err } // ✅ VÉRIFIER QUE LE PRODUIT EXISTE var exists bool checkQuery := `SELECT EXISTS(SELECT 1 FROM products WHERE id = $1)` err := db.QueryRow(checkQuery, productID).Scan(&exists) if err != nil { log.Printf("❌ [DeleteMediaByProductID] Erreur vérification: %v", err) return fmt.Errorf("erreur vérification produit: %w", err) } if !exists { log.Printf("❌ [DeleteMediaByProductID] Produit %d n'existe pas", productID) return fmt.Errorf("produit %d non trouvé", productID) } // ✅ DELETE query := `DELETE FROM media WHERE product_id = $1` result, err := db.Exec(query, productID) if err != nil { log.Printf("❌ [DeleteMediaByProductID] Erreur DELETE: %v", err) return fmt.Errorf("erreur suppression médias: %w", err) } rowsAffected, _ := result.RowsAffected() log.Printf("✅ [DeleteMediaByProductID] %d média(s) supprimé(s) pour produit %d", rowsAffected, productID) return nil }