This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user