106 lines
3.0 KiB
Go
106 lines
3.0 KiB
Go
package services
|
|
|
|
import (
|
|
"fmt"
|
|
"gestion/utils"
|
|
"io"
|
|
"mime/multipart"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Storage abstrait l'emplacement de stockage des médias produits (local ou S3),
|
|
// pour que tous les points d'upload/suppression respectent le même driver.
|
|
type Storage interface {
|
|
// Upload sauvegarde le fichier et renvoie l'URL à persister en DB (models.Media.URL)
|
|
// et la clé interne (vide pour local, clé S3 sinon — models.Media.Key).
|
|
Upload(fileHeader *multipart.FileHeader, folder, fileName string) (url string, key string, err error)
|
|
// Delete supprime le fichier. url et key sont ceux stockés en DB pour ce média :
|
|
// chaque implémentation ignore celui qui ne la concerne pas.
|
|
Delete(url string, key string) error
|
|
}
|
|
|
|
// LocalStorage stocke les fichiers sur le disque local, sous baseDir (ex: "uploads").
|
|
type LocalStorage struct {
|
|
baseDir string
|
|
}
|
|
|
|
func NewLocalStorage(baseDir string) *LocalStorage {
|
|
return &LocalStorage{baseDir: baseDir}
|
|
}
|
|
|
|
func (s *LocalStorage) Upload(fileHeader *multipart.FileHeader, folder, fileName string) (url string, key string, err error) {
|
|
destFolder := filepath.Join(s.baseDir, folder)
|
|
if err := os.MkdirAll(destFolder, 0750); err != nil {
|
|
return "", "", fmt.Errorf("erreur création dossier: %w", err)
|
|
}
|
|
|
|
filePath := filepath.Join(destFolder, fileName)
|
|
safeFilePath, err := utils.SanitizeFilePath(filePath)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("chemin invalide: %w", err)
|
|
}
|
|
|
|
src, err := fileHeader.Open()
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("erreur ouverture fichier: %w", err)
|
|
}
|
|
defer src.Close()
|
|
|
|
dst, err := os.OpenFile(safeFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("erreur création fichier: %w", err)
|
|
}
|
|
defer dst.Close()
|
|
|
|
if _, err := io.Copy(dst, src); err != nil {
|
|
os.Remove(safeFilePath)
|
|
return "", "", fmt.Errorf("erreur écriture fichier: %w", err)
|
|
}
|
|
|
|
return "/" + filepath.ToSlash(safeFilePath), "", nil
|
|
}
|
|
|
|
func (s *LocalStorage) Delete(url string, key string) error {
|
|
filePath := ""
|
|
if len(url) > 0 && url[0] == '/' {
|
|
filePath = url[1:]
|
|
} else {
|
|
filePath = url
|
|
}
|
|
|
|
safeFilePath, err := utils.SanitizeFilePath(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("chemin invalide: %w", err)
|
|
}
|
|
|
|
if err := os.Remove(safeFilePath); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("erreur suppression: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// S3Storage adapte le S3Service existant (RustFS) à l'interface Storage.
|
|
type S3Storage struct {
|
|
s3 *S3Service
|
|
}
|
|
|
|
func NewS3Storage(s3 *S3Service) *S3Storage {
|
|
return &S3Storage{s3: s3}
|
|
}
|
|
|
|
func (s *S3Storage) Upload(fileHeader *multipart.FileHeader, folder, fileName string) (url string, key string, err error) {
|
|
key, err = s.s3.UploadFileWithName(fileHeader, folder, fileName)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
return "/media/" + key, key, nil
|
|
}
|
|
|
|
func (s *S3Storage) Delete(url string, key string) error {
|
|
if key == "" {
|
|
return fmt.Errorf("clé S3 manquante pour suppression")
|
|
}
|
|
return s.s3.DeleteFile(key)
|
|
}
|