421 lines
11 KiB
Go
421 lines
11 KiB
Go
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
|
|
}
|