This commit is contained in:
@@ -294,6 +294,11 @@ func InitDB() *Database {
|
|||||||
log.Fatalf("❌ Erreur migration contacts: %v", err)
|
log.Fatalf("❌ Erreur migration contacts: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migration: clé RustFS pour les médias (stockage objet)
|
||||||
|
if _, err = database.Exec(`ALTER TABLE media ADD COLUMN IF NOT EXISTS key TEXT NOT NULL DEFAULT ''`); err != nil {
|
||||||
|
log.Fatalf("❌ Erreur migration media.key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Lancer le nettoyage périodique des tokens expirés
|
// Lancer le nettoyage périodique des tokens expirés
|
||||||
go database.cleanExpiredTokensPeriodically()
|
go database.cleanExpiredTokensPeriodically()
|
||||||
|
|
||||||
@@ -399,6 +404,7 @@ func (db *Database) createTables() error {
|
|||||||
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
|
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL,
|
||||||
type VARCHAR(50) NOT NULL,
|
type VARCHAR(50) NOT NULL,
|
||||||
|
key TEXT NOT NULL DEFAULT '',
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);`,
|
);`,
|
||||||
|
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ func validateMediaURL(url string) error {
|
|||||||
if strings.Contains(url, "..") || strings.Contains(url, "...") || strings.Contains(url, "..//") {
|
if strings.Contains(url, "..") || strings.Contains(url, "...") || strings.Contains(url, "..//") {
|
||||||
return fmt.Errorf("path traversal détecté dans l'URL")
|
return fmt.Errorf("path traversal détecté dans l'URL")
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(url, "/uploads/") {
|
if !strings.HasPrefix(url, "/uploads/") && !strings.HasPrefix(url, "/media/") {
|
||||||
return fmt.Errorf("URL doit commencer par /uploads/")
|
return fmt.Errorf("URL doit commencer par /uploads/ ou /media/")
|
||||||
}
|
}
|
||||||
dangerousChars := []string{"<", ">", "\"", "'", ";", "|", "&", "$", "`", "\\"}
|
dangerousChars := []string{"<", ">", "\"", "'", ";", "|", "&", "$", "`", "\\"}
|
||||||
for _, char := range dangerousChars {
|
for _, char := range dangerousChars {
|
||||||
@@ -93,6 +93,11 @@ func (d *Database) CreateMedia(media any) error {
|
|||||||
mediaType := m.GetType()
|
mediaType := m.GetType()
|
||||||
mediaURL := m.GetURL()
|
mediaURL := m.GetURL()
|
||||||
|
|
||||||
|
mediaKey := ""
|
||||||
|
if mediaPtr, isPtr := media.(*models.Media); isPtr {
|
||||||
|
mediaKey = mediaPtr.Key
|
||||||
|
}
|
||||||
|
|
||||||
if err := validateProductID(productID); err != nil {
|
if err := validateProductID(productID); err != nil {
|
||||||
log.Printf("❌ [CreateMedia] %v", err)
|
log.Printf("❌ [CreateMedia] %v", err)
|
||||||
return err
|
return err
|
||||||
@@ -106,14 +111,14 @@ func (d *Database) CreateMedia(media any) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.InsertMedia(m, productID, mediaURL, mediaType); err != nil {
|
if err := d.InsertMedia(m, productID, mediaURL, mediaType, mediaKey); err != nil {
|
||||||
log.Printf("❌ [InsertMedia] %v", err)
|
log.Printf("❌ [InsertMedia] %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) InsertMedia(m any, productID int, mediaURL any, mediaType string) error {
|
func (d *Database) InsertMedia(m any, productID int, mediaURL any, mediaType string, key string) error {
|
||||||
var exists bool
|
var exists bool
|
||||||
if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM products WHERE id = ?)`, productID).Scan(&exists).Error; err != nil {
|
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)
|
log.Printf("❌ [CreateMedia] Erreur vérification produit: %v", err)
|
||||||
@@ -128,9 +133,9 @@ func (d *Database) InsertMedia(m any, productID int, mediaURL any, mediaType str
|
|||||||
ID int `gorm:"column:id"`
|
ID int `gorm:"column:id"`
|
||||||
}
|
}
|
||||||
err := d.GDB.Raw(`
|
err := d.GDB.Raw(`
|
||||||
INSERT INTO media (product_id, url, type, created_at)
|
INSERT INTO media (product_id, url, type, key, created_at)
|
||||||
VALUES (?, ?, ?, ?) RETURNING id`,
|
VALUES (?, ?, ?, ?, ?) RETURNING id`,
|
||||||
productID, mediaURL, mediaType, time.Now(),
|
productID, mediaURL, mediaType, key, time.Now(),
|
||||||
).Scan(&result).Error
|
).Scan(&result).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [CreateMedia] Erreur INSERT: %v", err)
|
log.Printf("❌ [CreateMedia] Erreur INSERT: %v", err)
|
||||||
@@ -154,7 +159,7 @@ func (d *Database) GetMediaByID(mediaID int) (*models.Media, error) {
|
|||||||
|
|
||||||
var media models.Media
|
var media models.Media
|
||||||
err := d.GDB.Raw(`
|
err := d.GDB.Raw(`
|
||||||
SELECT id, product_id, url, type, created_at
|
SELECT id, product_id, url, type, key, created_at
|
||||||
FROM media WHERE id = ?`, mediaID).Scan(&media).Error
|
FROM media WHERE id = ?`, mediaID).Scan(&media).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [GetMediaByID] Erreur query: %v", err)
|
log.Printf("❌ [GetMediaByID] Erreur query: %v", err)
|
||||||
@@ -179,7 +184,7 @@ func (d *Database) GetMediaByProductID(productID int) ([]models.Media, error) {
|
|||||||
|
|
||||||
var mediaList []models.Media
|
var mediaList []models.Media
|
||||||
err := d.GDB.Raw(`
|
err := d.GDB.Raw(`
|
||||||
SELECT id, product_id, url, type, created_at
|
SELECT id, product_id, url, type, key, created_at
|
||||||
FROM media WHERE product_id = ?
|
FROM media WHERE product_id = ?
|
||||||
ORDER BY id ASC`, productID).Scan(&mediaList).Error
|
ORDER BY id ASC`, productID).Scan(&mediaList).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -914,7 +914,7 @@ func ServeMedia(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, contentType, err := s3Service.GetFile(key)
|
body, contentType, err := s3Service.GetFile(c.Request.Context(), key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Média non trouvé"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Média non trouvé"})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func main() {
|
|||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
//r.Static("/uploads", "./uploads")
|
r.Static("/uploads", "./uploads")
|
||||||
|
|
||||||
routes.SetupRoutes(r, database, geoService, s3Service)
|
routes.SetupRoutes(r, database, geoService, s3Service)
|
||||||
|
|
||||||
|
|||||||
@@ -102,10 +102,8 @@ func (s *S3Service) UploadFileWithName(fileHeader *multipart.FileHeader, folder,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFile récupère un objet depuis RustFS (stream + content-type) pour le proxy.
|
// GetFile récupère un objet depuis RustFS (stream + content-type) pour le proxy.
|
||||||
func (s *S3Service) GetFile(key string) (io.ReadCloser, string, error) {
|
// Le contexte doit rester actif pendant toute la lecture du body par l'appelant.
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
func (s *S3Service) GetFile(ctx context.Context, key string) (io.ReadCloser, string, error) {
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
out, err := s.client.GetObject(ctx, &s3.GetObjectInput{
|
out, err := s.client.GetObject(ctx, &s3.GetObjectInput{
|
||||||
Bucket: aws.String(s.bucketName),
|
Bucket: aws.String(s.bucketName),
|
||||||
Key: aws.String(key),
|
Key: aws.String(key),
|
||||||
|
|||||||
Reference in New Issue
Block a user