package main import ( "context" "errors" "fmt" "mime" "mime/multipart" "net/http" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/spf13/viper" ) func NewMediaClient(endpoint, accessKeyId, secretAccessKey string, useSSL bool) (*MediaClient, error) { if accessKeyId == "" { return nil, errors.New("accessKeyId was found empty, but cannot be") } if secretAccessKey == "" { return nil, errors.New("secretAccessKey was found empty, but cannot be") } var mediaClient MediaClient minioClient, err := minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(accessKeyId, secretAccessKey, ""), Secure: useSSL, }) if err != nil { return &mediaClient, err } mediaClient.MinioClient = *minioClient return &mediaClient, nil } func NewMediaClientFromViper() (*MediaClient, error) { var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, err } mediaClient, err := NewMediaClient(cfg.Server.Documents.Endpoint, cfg.Server.Documents.AccessKeyId, cfg.Server.Documents.SecretAccessKey, cfg.Server.Documents.UseSSL) if err != nil { return mediaClient, err } return mediaClient, nil } type MediaClient struct { MinioClient minio.Client } func (m *MediaClient) Seed() ([]string, error) { var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, err } var new_buckets []string for bucket := range cfg.Server.Documents.Buckets { exists, err := m.MinioClient.BucketExists(context.Background(), bucket) if err != nil { return new_buckets, err } if exists { continue } if err = m.MinioClient.MakeBucket(context.Background(), bucket, minio.MakeBucketOptions{}); err != nil { return new_buckets, err } new_buckets = append(new_buckets, bucket) } return new_buckets, nil } func (m *MediaClient) UploadFormFiles(bucketName string, fileHeaders []*multipart.FileHeader) (statusCode int, result string) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() ok, err := m.MinioClient.BucketExists(ctx, bucketName) if err != nil { return http.StatusInternalServerError, fmt.Sprintf("Erreur lors de vérification d'existence de bucket '%s': %s", bucketName, err) } if !ok { return http.StatusBadRequest, fmt.Sprintf("Bucket '%s' n'existe pas", bucketName) } switch count := len(fileHeaders); count { case 0: return http.StatusBadRequest, "Veuillez sélectionner au moins 1 document à téléverser" case 1: result = "Téléversement de 1 fichier\n" default: result = fmt.Sprintf("Téléversement de %d fichiers\n", count) } var allowedMediaTypes = []string{"application/pdf", "text/markdown", "text/plain"} var fileNames []string for _, fileHeader := range fileHeaders { fileNames = append(fileNames, fileHeader.Filename) } var validFileHeaders []*multipart.FileHeader for i, fileHeader := range fileHeaders { // Check for conflicting file names in upload for j, fileName := range fileNames { if fileName == fileHeader.Filename && i != j { return http.StatusBadRequest, fmt.Sprintf("Doublon de nom de fichier '%s' trouvé, les noms de fichiers doivent être uniques", fileName) } } // Check media type mediaType, _, err := mime.ParseMediaType(fileHeader.Header.Get("Content-Type")) if err != nil { return http.StatusBadRequest, fmt.Sprintf("Impossible de déterminer le type de fichier pour %d '%s'.\nPlus de détails: %s", i, fileHeader.Filename, err.Error()) } var isAllowedMediaType bool for _, allowedMediaType := range allowedMediaTypes { if allowedMediaType == mediaType { isAllowedMediaType = true } } if !isAllowedMediaType { return http.StatusUnsupportedMediaType, fmt.Sprintf("Type de fichier interdit '%s' pour '%s'.\nTypes de fichiers permis: %s", mediaType, fileHeader.Filename, allowedMediaTypes) } // Check for conflicting fileNames with existing files objectInfo, err := m.MinioClient.StatObject(ctx, bucketName, fileHeader.Filename, minio.StatObjectOptions{}) if err == nil && objectInfo.Key == fileHeader.Filename { return http.StatusConflict, fmt.Sprintf("Un document au nom '%s' de catégorie '%s' existe déjà et ne peut pas être inséré de cette façon.", fileHeader.Filename, bucketName) } switch msg := err.Error(); msg { case "The specified key does not exist.": default: return http.StatusInternalServerError, fmt.Sprintf("Erreur inattendue lors de vérification de conflit de nom de fichier avec la base de données: %s", err) } validFileHeaders = append(validFileHeaders, fileHeader) } if len(validFileHeaders) == 0 { return http.StatusOK, "Aucun fichier valide envoyé au serveur, rien à faire." } for i, fileHeader := range validFileHeaders { mediaType, _, err := mime.ParseMediaType(fileHeader.Header.Get("Content-Type")) if err != nil { return http.StatusBadRequest, fmt.Sprintf("Impossible de déterminer le type de fichier pour %d '%s'.\nPlus de détails: %s", i, fileHeader.Filename, err.Error()) } // Get file content fileContent, err := fileHeader.Open() if err != nil { return http.StatusBadRequest, fmt.Sprintf("Impossible de lire le contenu de '%s': %s", fileHeader.Filename, err) } defer fileContent.Close() // Upload file info, err := m.MinioClient.PutObject(ctx, bucketName, fileHeader.Filename, fileContent, fileHeader.Size, minio.PutObjectOptions{ ContentType: mediaType, }) if err != nil { return http.StatusInternalServerError, fmt.Sprintf("Impossible d'ajouter '%s' à la base de donnée: %s", fileHeader.Filename, err) } result = fmt.Sprintf("%sDocument %d '%s' de type '%s' et de taille '%d' téléversé à '%s' avec succès\n", result, i, info.Key, mediaType, info.Size, info.Bucket) } return http.StatusCreated, result }