Files
Xor290 22a8d5026c
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Backend - Build & Lint / build (push) Failing after 25m18s
Frontend Web - Build & Lint / build (push) Failing after 9m58s
chore: build
2026-08-06 12:06:05 +02:00

315 lines
9.2 KiB
Go

package db
import (
"fmt"
"gestion/models"
"log"
"slices"
"strings"
"time"
)
type MediaInterface interface {
GetProductID() int
GetType() string
GetURL() string
SetID(int)
}
// 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))
if slices.Contains(validTypes, mediaType) {
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)")
}
if strings.Contains(url, "..") || strings.Contains(url, "...") || strings.Contains(url, "..//") {
return fmt.Errorf("path traversal détecté dans l'URL")
}
if !strings.HasPrefix(url, "/uploads/") && !strings.HasPrefix(url, "/media/") {
return fmt.Errorf("URL doit commencer par /uploads/ ou /media/")
}
dangerousChars := []string{"<", ">", "\"", "'", ";", "|", "&", "$", "`", "\\"}
for _, char := range dangerousChars {
if strings.Contains(url, char) {
return fmt.Errorf("caractères interdits dans l'URL")
}
}
return nil
}
func (d *Database) CreateMedia(media any) error {
log.Printf("🔒 [CreateMedia] START - Type: %T", media)
m, ok := media.(MediaInterface)
if !ok {
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")
}
productID := m.GetProductID()
mediaType := m.GetType()
mediaURL := m.GetURL()
mediaKey := ""
if mediaPtr, isPtr := media.(*models.Media); isPtr {
mediaKey = mediaPtr.Key
}
if err := validateProductID(productID); err != nil {
log.Printf("❌ [CreateMedia] %v", err)
return err
}
if err := validateMediaType(mediaType); err != nil {
log.Printf("❌ [CreateMedia] %v", err)
return err
}
if err := validateMediaURL(mediaURL); err != nil {
log.Printf("❌ [CreateMedia] %v", err)
return err
}
if err := d.InsertMedia(m, productID, mediaURL, mediaType, mediaKey); err != nil {
log.Printf("❌ [InsertMedia] %v", err)
return err
}
return nil
}
func (d *Database) InsertMedia(m any, productID int, mediaURL any, mediaType string, key string) error {
var exists bool
if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM products WHERE id = ?)`, productID).Scan(&exists).Error; 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)
}
var result struct {
ID int `gorm:"column:id"`
}
err := d.GDB.Raw(`
INSERT INTO media (product_id, url, type, key, created_at)
VALUES (?, ?, ?, ?, ?) RETURNING id`,
productID, mediaURL, mediaType, key, time.Now(),
).Scan(&result).Error
if err != nil {
log.Printf("❌ [CreateMedia] Erreur INSERT: %v", err)
return fmt.Errorf("erreur création média: %w", err)
}
if mi, ok := m.(MediaInterface); ok {
mi.SetID(result.ID)
}
log.Printf("✅ [CreateMedia] Média créé: ID=%d, Type=%s", result.ID, mediaType)
return nil
}
func (d *Database) GetMediaByID(mediaID int) (*models.Media, error) {
log.Printf("🔍 [GetMediaByID] START - ID=%d", mediaID)
if err := validateMediaID(mediaID); err != nil {
log.Printf("❌ [GetMediaByID] %v", err)
return nil, err
}
var media models.Media
err := d.GDB.Raw(`
SELECT id, product_id, url, type, key, created_at
FROM media WHERE id = ?`, mediaID).Scan(&media).Error
if err != nil {
log.Printf("❌ [GetMediaByID] Erreur query: %v", err)
return nil, fmt.Errorf("erreur récupération média: %w", err)
}
if media.ID == 0 {
log.Printf("❌ [GetMediaByID] Média %d non trouvé", mediaID)
return nil, fmt.Errorf("média non trouvé")
}
log.Printf("✅ [GetMediaByID] Média trouvé: Type=%s", media.Type)
return &media, nil
}
// GetMediaBatch charge les médias de plusieurs produits en une seule requête.
func (d *Database) GetMediaBatch(productIDs []int) map[int][]models.Media {
result := make(map[int][]models.Media, len(productIDs))
if len(productIDs) == 0 {
return result
}
var mediaList []models.Media
d.GDB.Raw(`SELECT id, product_id, url, type, key, created_at FROM media WHERE product_id IN ? ORDER BY product_id ASC, id ASC`, productIDs).Scan(&mediaList)
for _, m := range mediaList {
result[m.ProductID] = append(result[m.ProductID], m)
}
return result
}
func (d *Database) GetMediaByProductID(productID int) ([]models.Media, error) {
log.Printf("🖼️ [GetMediaByProductID] START - ProductID=%d", productID)
if err := validateProductID(productID); err != nil {
log.Printf("❌ [GetMediaByProductID] %v", err)
return nil, err
}
var mediaList []models.Media
err := d.GDB.Raw(`
SELECT id, product_id, url, type, key, created_at
FROM media WHERE product_id = ?
ORDER BY id ASC`, productID).Scan(&mediaList).Error
if err != nil {
log.Printf("❌ [GetMediaByProductID] Erreur query: %v", err)
return nil, fmt.Errorf("erreur récupération médias: %w", err)
}
log.Printf("✅ [GetMediaByProductID] %d médias trouvés", len(mediaList))
return mediaList, nil
}
func (d *Database) UpdateMedia(media *models.Media) error {
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
}
if err := d.CheckMediaExists(media); err != nil {
log.Printf("❌ [UpdateMedia] %v", err)
return err
}
result := d.GDB.Exec(`UPDATE media SET url = ?, type = ? WHERE id = ?`, media.URL, media.Type, media.ID)
if result.Error != nil {
log.Printf("❌ [UpdateMedia] Erreur UPDATE: %v", result.Error)
return fmt.Errorf("erreur mise à jour média: %w", result.Error)
}
if result.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
}
func (d *Database) CheckMediaExists(media *models.Media) error {
var exists bool
if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM media WHERE id = ?)`, media.ID).Scan(&exists).Error; 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)
}
return nil
}
func (d *Database) DeleteMedia(mediaID int) error {
if err := validateMediaID(mediaID); err != nil {
log.Printf("❌ [DeleteMedia] %v", err)
return err
}
media := models.Media{ID: mediaID}
if err := d.CheckMediaExists(&media); err != nil {
log.Printf("❌ [DeleteMedia] %v", err)
return err
}
result := d.GDB.Exec(`DELETE FROM media WHERE id = ?`, mediaID)
if result.Error != nil {
log.Printf("❌ [DeleteMedia] Erreur DELETE: %v", result.Error)
return fmt.Errorf("erreur suppression média: %w", result.Error)
}
if result.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
}
func (d *Database) DeleteMediaByProductID(productID int) error {
log.Printf("🗑️ [DeleteMediaByProductID] START - ProductID=%d", productID)
if err := validateProductID(productID); err != nil {
log.Printf("❌ [DeleteMediaByProductID] %v", err)
return err
}
var exists bool
if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM products WHERE id = ?)`, productID).Scan(&exists).Error; 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)
}
result := d.GDB.Exec(`DELETE FROM media WHERE product_id = ?`, productID)
if result.Error != nil {
log.Printf("❌ [DeleteMediaByProductID] Erreur DELETE: %v", result.Error)
return fmt.Errorf("erreur suppression médias: %w", result.Error)
}
log.Printf("✅ [DeleteMediaByProductID] %d média(s) supprimé(s) pour produit %d",
result.RowsAffected, productID)
return nil
}