This commit is contained in:
@@ -10,8 +10,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -142,20 +140,6 @@ func validateFileMimeType(fileHeader *multipart.FileHeader) (string, error) {
|
|||||||
return mimeType, nil
|
return mimeType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizeFilePath(path string) (string, error) {
|
|
||||||
cleaned := filepath.Clean(path)
|
|
||||||
|
|
||||||
if strings.Contains(cleaned, "..") {
|
|
||||||
return "", fmt.Errorf("path traversal détecté")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(cleaned, "uploads/") && !strings.HasPrefix(cleaned, "uploads\\") {
|
|
||||||
return "", fmt.Errorf("chemin invalide")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleaned, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateProduct(c *gin.Context) {
|
func CreateProduct(c *gin.Context) {
|
||||||
database := c.MustGet("database").(*db.Database)
|
database := c.MustGet("database").(*db.Database)
|
||||||
|
|
||||||
@@ -324,12 +308,13 @@ func CreateProduct(c *gin.Context) {
|
|||||||
|
|
||||||
cleanProductName := cleanFileName(product.Name)
|
cleanProductName := cleanFileName(product.Name)
|
||||||
uploadedMedia := []models.Media{}
|
uploadedMedia := []models.Media{}
|
||||||
savedFiles := []string{}
|
savedFiles := []models.Media{}
|
||||||
|
storage := c.MustGet("storage").(services.Storage)
|
||||||
var totalSize int64 = 0
|
var totalSize int64 = 0
|
||||||
|
|
||||||
for i, fileHeader := range files {
|
for i, fileHeader := range files {
|
||||||
if fileHeader.Size > MaxFileSize {
|
if fileHeader.Size > MaxFileSize {
|
||||||
rollbackFiles(savedFiles)
|
rollbackFiles(storage, savedFiles)
|
||||||
database.DeleteProduct(product.ID)
|
database.DeleteProduct(product.ID)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Fichier %s trop volumineux (max %dMB)", fileHeader.Filename, MaxFileSize/(1024*1024)),
|
"error": fmt.Sprintf("Fichier %s trop volumineux (max %dMB)", fileHeader.Filename, MaxFileSize/(1024*1024)),
|
||||||
@@ -339,7 +324,7 @@ func CreateProduct(c *gin.Context) {
|
|||||||
|
|
||||||
totalSize += fileHeader.Size
|
totalSize += fileHeader.Size
|
||||||
if totalSize > MaxTotalUploadSize {
|
if totalSize > MaxTotalUploadSize {
|
||||||
rollbackFiles(savedFiles)
|
rollbackFiles(storage, savedFiles)
|
||||||
database.DeleteProduct(product.ID)
|
database.DeleteProduct(product.ID)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Taille totale dépassée (max %dMB)", MaxTotalUploadSize/(1024*1024)),
|
"error": fmt.Sprintf("Taille totale dépassée (max %dMB)", MaxTotalUploadSize/(1024*1024)),
|
||||||
@@ -352,7 +337,7 @@ func CreateProduct(c *gin.Context) {
|
|||||||
mimeType, err := validateFileMimeType(fileHeader)
|
mimeType, err := validateFileMimeType(fileHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [CreateProduct] Type MIME invalide: %v", err)
|
log.Printf("❌ [CreateProduct] Type MIME invalide: %v", err)
|
||||||
rollbackFiles(savedFiles)
|
rollbackFiles(storage, savedFiles)
|
||||||
database.DeleteProduct(product.ID)
|
database.DeleteProduct(product.ID)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Type de fichier non autorisé"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Type de fichier non autorisé"})
|
||||||
return
|
return
|
||||||
@@ -364,7 +349,7 @@ func CreateProduct(c *gin.Context) {
|
|||||||
} else if strings.HasPrefix(mimeType, "video/") {
|
} else if strings.HasPrefix(mimeType, "video/") {
|
||||||
mediaType = "video"
|
mediaType = "video"
|
||||||
} else {
|
} else {
|
||||||
rollbackFiles(savedFiles)
|
rollbackFiles(storage, savedFiles)
|
||||||
database.DeleteProduct(product.ID)
|
database.DeleteProduct(product.ID)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Type de média non supporté"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Type de média non supporté"})
|
||||||
return
|
return
|
||||||
@@ -372,46 +357,27 @@ func CreateProduct(c *gin.Context) {
|
|||||||
|
|
||||||
uniqueFileName := utils.GenerateUniqueFileName(cleanProductName, fileHeader.Filename)
|
uniqueFileName := utils.GenerateUniqueFileName(cleanProductName, fileHeader.Filename)
|
||||||
|
|
||||||
destFolder := filepath.Join("uploads", mediaType+"s")
|
mediaURL, mediaKey, err := storage.Upload(fileHeader, mediaType+"s", uniqueFileName)
|
||||||
if err := os.MkdirAll(destFolder, 0750); err != nil {
|
|
||||||
log.Printf("❌ [CreateProduct] Erreur création dossier: %v", err)
|
|
||||||
rollbackFiles(savedFiles)
|
|
||||||
database.DeleteProduct(product.ID)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur système"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := filepath.Join(destFolder, uniqueFileName)
|
|
||||||
|
|
||||||
safeFilePath, err := sanitizeFilePath(filePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [CreateProduct] Path traversal détecté: %v", err)
|
|
||||||
rollbackFiles(savedFiles)
|
|
||||||
database.DeleteProduct(product.ID)
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Chemin invalide"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.SaveUploadedFile(fileHeader, safeFilePath); err != nil {
|
|
||||||
log.Printf("❌ [CreateProduct] Erreur sauvegarde: %v", err)
|
log.Printf("❌ [CreateProduct] Erreur sauvegarde: %v", err)
|
||||||
rollbackFiles(savedFiles)
|
rollbackFiles(storage, savedFiles)
|
||||||
database.DeleteProduct(product.ID)
|
database.DeleteProduct(product.ID)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur sauvegarde fichier"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur sauvegarde fichier"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
savedFiles = append(savedFiles, safeFilePath)
|
savedFiles = append(savedFiles, models.Media{URL: mediaURL, Key: mediaKey})
|
||||||
|
|
||||||
mediaURL := "/" + filepath.ToSlash(safeFilePath)
|
|
||||||
media := models.Media{
|
media := models.Media{
|
||||||
ProductID: product.ID,
|
ProductID: product.ID,
|
||||||
Type: mediaType,
|
Type: mediaType,
|
||||||
URL: mediaURL,
|
URL: mediaURL,
|
||||||
|
Key: mediaKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.CreateMedia(&media); err != nil {
|
if err := database.CreateMedia(&media); err != nil {
|
||||||
log.Printf("❌ [CreateProduct] Erreur DB média: %v", err)
|
log.Printf("❌ [CreateProduct] Erreur DB média: %v", err)
|
||||||
rollbackFiles(savedFiles)
|
rollbackFiles(storage, savedFiles)
|
||||||
database.DeleteProduct(product.ID)
|
database.DeleteProduct(product.ID)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création média"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création média"})
|
||||||
return
|
return
|
||||||
@@ -736,6 +702,13 @@ func DeleteMedia(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
log.Printf("✅ [DeleteMedia] Fichier supprimé sur RustFS: %s", media.Key)
|
log.Printf("✅ [DeleteMedia] Fichier supprimé sur RustFS: %s", media.Key)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
localStorage := services.NewLocalStorage("uploads")
|
||||||
|
if err := localStorage.Delete(media.URL, ""); err != nil {
|
||||||
|
log.Printf("⚠️ [DeleteMedia] Fichier local non supprimé (%s): %v", media.URL, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("✅ [DeleteMedia] Fichier local supprimé: %s", media.URL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@@ -746,7 +719,6 @@ func DeleteMedia(c *gin.Context) {
|
|||||||
|
|
||||||
func UploadMedia(c *gin.Context) {
|
func UploadMedia(c *gin.Context) {
|
||||||
database := c.MustGet("database").(*db.Database)
|
database := c.MustGet("database").(*db.Database)
|
||||||
s3Service := c.MustGet("s3Service").(*services.S3Service)
|
|
||||||
|
|
||||||
username, err := safeGetUsername(c)
|
username, err := safeGetUsername(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -816,27 +788,28 @@ func UploadMedia(c *gin.Context) {
|
|||||||
cleanProductName := cleanFileName(productName)
|
cleanProductName := cleanFileName(productName)
|
||||||
uniqueFileName := utils.GenerateUniqueFileName(cleanProductName, file.Filename)
|
uniqueFileName := utils.GenerateUniqueFileName(cleanProductName, file.Filename)
|
||||||
|
|
||||||
|
storage := c.MustGet("storage").(services.Storage)
|
||||||
folder := fileType + "s"
|
folder := fileType + "s"
|
||||||
key, err := s3Service.UploadFileWithName(file, folder, uniqueFileName)
|
mediaURL, mediaKey, err := storage.Upload(file, folder, uniqueFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [UploadMedia] Erreur upload RustFS: %v", err)
|
log.Printf("❌ [UploadMedia] Erreur upload: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur upload fichier"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur upload fichier"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("✅ [UploadMedia] Fichier uploadé sur RustFS: %s", key)
|
log.Printf("✅ [UploadMedia] Fichier uploadé: %s", mediaURL)
|
||||||
|
|
||||||
media := models.Media{
|
media := models.Media{
|
||||||
ProductID: productID,
|
ProductID: productID,
|
||||||
Type: fileType,
|
Type: fileType,
|
||||||
URL: "/media/" + key,
|
URL: mediaURL,
|
||||||
Key: key,
|
Key: mediaKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = database.CreateMedia(&media)
|
err = database.CreateMedia(&media)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if delErr := s3Service.DeleteFile(key); delErr != nil {
|
if delErr := storage.Delete(mediaURL, mediaKey); delErr != nil {
|
||||||
log.Printf("⚠️ [UploadMedia] Échec rollback RustFS (clé: %s): %v", key, delErr)
|
log.Printf("⚠️ [UploadMedia] Échec rollback (%s): %v", mediaURL, delErr)
|
||||||
}
|
}
|
||||||
log.Printf("❌ [UploadMedia] Erreur DB: %v", err)
|
log.Printf("❌ [UploadMedia] Erreur DB: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création média"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création média"})
|
||||||
@@ -948,17 +921,17 @@ func DeleteProduct(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s3Service := c.MustGet("s3Service").(*services.S3Service)
|
||||||
|
localStorage := services.NewLocalStorage("uploads")
|
||||||
for _, media := range mediaList {
|
for _, media := range mediaList {
|
||||||
filePath := strings.TrimPrefix(media.URL, "/")
|
if media.Key != "" {
|
||||||
|
if err := s3Service.DeleteFile(media.Key); err != nil {
|
||||||
safeFilePath, err := sanitizeFilePath(filePath)
|
log.Printf("⚠️ [DeleteProduct] Fichier non supprimé sur RustFS (clé: %s): %v", media.Key, err)
|
||||||
if err != nil {
|
}
|
||||||
log.Printf("⚠️ [DeleteProduct] Path invalide: %v", err)
|
} else {
|
||||||
continue
|
if err := localStorage.Delete(media.URL, ""); err != nil {
|
||||||
}
|
log.Printf("⚠️ [DeleteProduct] Erreur suppression locale: %v", err)
|
||||||
|
}
|
||||||
if err := os.Remove(safeFilePath); err != nil && !os.IsNotExist(err) {
|
|
||||||
log.Printf("⚠️ [DeleteProduct] Erreur suppression: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,13 +951,11 @@ func DeleteProduct(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func rollbackFiles(files []string) {
|
func rollbackFiles(storage services.Storage, files []models.Media) {
|
||||||
for _, file := range files {
|
for _, f := range files {
|
||||||
safeFilePath, err := sanitizeFilePath(file)
|
if err := storage.Delete(f.URL, f.Key); err != nil {
|
||||||
if err != nil {
|
log.Printf("⚠️ [rollbackFiles] Erreur suppression %s: %v", f.URL, err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
os.Remove(safeFilePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,16 @@ func main() {
|
|||||||
log.Fatalf("erreur init S3: %v", err)
|
log.Fatalf("erreur init S3: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var storage services.Storage
|
||||||
|
switch os.Getenv("STORAGE_DRIVER") {
|
||||||
|
case "s3":
|
||||||
|
storage = services.NewS3Storage(s3Service)
|
||||||
|
log.Println("✅ Storage driver: s3 (RustFS)")
|
||||||
|
default:
|
||||||
|
storage = services.NewLocalStorage("uploads")
|
||||||
|
log.Println("✅ Storage driver: local")
|
||||||
|
}
|
||||||
|
|
||||||
log.Println("")
|
log.Println("")
|
||||||
log.Println("🧹 Démarrage du nettoyage des commandes invalides...")
|
log.Println("🧹 Démarrage du nettoyage des commandes invalides...")
|
||||||
removed, err := database.CleanupInvalidQueueCommands()
|
removed, err := database.CleanupInvalidQueueCommands()
|
||||||
@@ -167,6 +177,7 @@ func main() {
|
|||||||
c.Set("database", database)
|
c.Set("database", database)
|
||||||
c.Set("geoService", geoService)
|
c.Set("geoService", geoService)
|
||||||
c.Set("s3Service", s3Service)
|
c.Set("s3Service", s3Service)
|
||||||
|
c.Set("storage", storage)
|
||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateUniqueFileName(productName string, originalFileName string) string {
|
func GenerateUniqueFileName(productName string, originalFileName string) string {
|
||||||
@@ -13,3 +14,19 @@ func GenerateUniqueFileName(productName string, originalFileName string) string
|
|||||||
ext := filepath.Ext(originalFileName)
|
ext := filepath.Ext(originalFileName)
|
||||||
return fmt.Sprintf("%s_%s%s", productName, randomString, ext)
|
return fmt.Sprintf("%s_%s%s", productName, randomString, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanitizeFilePath valide qu'un chemin de fichier local reste bien dans le
|
||||||
|
// dossier uploads/ et ne contient pas de tentative de path traversal.
|
||||||
|
func SanitizeFilePath(path string) (string, error) {
|
||||||
|
cleaned := filepath.Clean(path)
|
||||||
|
|
||||||
|
if strings.Contains(cleaned, "..") {
|
||||||
|
return "", fmt.Errorf("path traversal détecté")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(cleaned, "uploads/") && !strings.HasPrefix(cleaned, "uploads\\") {
|
||||||
|
return "", fmt.Errorf("chemin invalide")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleaned, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,3 +24,11 @@ BACKEND_LINK_SECRET=change_me_internal_secret
|
|||||||
API_PORT=8080
|
API_PORT=8080
|
||||||
FRONTEND_PORT=5173
|
FRONTEND_PORT=5173
|
||||||
GIN_MODE=release
|
GIN_MODE=release
|
||||||
|
# STORAGE_DRIVER=local (defaut, stockage disque via le volume backend_uploads) ou s3 (RustFS)
|
||||||
|
STORAGE_DRIVER=local
|
||||||
|
# Requis uniquement si STORAGE_DRIVER=s3
|
||||||
|
S3_REGION=us-east-1
|
||||||
|
S3_BUCKET=
|
||||||
|
S3_ENDPOINT=
|
||||||
|
RUSTFS_ACCESS_KEY=
|
||||||
|
RUSTFS_SECRET_KEY=
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ services:
|
|||||||
- LBTELEGRAM_BOT1_USERNAME=${LBTELEGRAM_BOT1_USERNAME:-GetRezStealer_bot}
|
- LBTELEGRAM_BOT1_USERNAME=${LBTELEGRAM_BOT1_USERNAME:-GetRezStealer_bot}
|
||||||
- LBTELEGRAM_BOT2_USERNAME=${LBTELEGRAM_BOT2_USERNAME:-rezDJDFJSFUltraFast_bot}
|
- LBTELEGRAM_BOT2_USERNAME=${LBTELEGRAM_BOT2_USERNAME:-rezDJDFJSFUltraFast_bot}
|
||||||
- BACKEND_LINK_SECRET=${BACKEND_LINK_SECRET:-change_me_internal_secret}
|
- BACKEND_LINK_SECRET=${BACKEND_LINK_SECRET:-change_me_internal_secret}
|
||||||
|
- STORAGE_DRIVER=${STORAGE_DRIVER:-local}
|
||||||
|
- S3_REGION=${S3_REGION:-}
|
||||||
|
- S3_BUCKET=${S3_BUCKET:-}
|
||||||
|
- S3_ENDPOINT=${S3_ENDPOINT:-}
|
||||||
|
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-}
|
||||||
|
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-}
|
||||||
volumes:
|
volumes:
|
||||||
- backend_uploads:/app/uploads
|
- backend_uploads:/app/uploads
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
Reference in New Issue
Block a user