Implement multi-file upload functionality for companies and enhance file upload routes
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package filestore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxUploadSize is the maximum allowed size per file (100 MB).
|
||||
MaxUploadSize = 100 * 1024 * 1024
|
||||
// MaxBatchUploadFiles is the maximum number of files per batch request.
|
||||
MaxBatchUploadFiles = 20
|
||||
)
|
||||
|
||||
// UploadedFileResult holds metadata for a successfully stored file.
|
||||
type UploadedFileResult struct {
|
||||
FileID string `json:"file_id"`
|
||||
Filename string `json:"filename"`
|
||||
ContentType string `json:"content_type"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// FailedUploadResult describes a file that could not be uploaded.
|
||||
type FailedUploadResult struct {
|
||||
Filename string `json:"filename"`
|
||||
Error string `json:"error"`
|
||||
Code string `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
// UploadFromMultipartFile validates and stores a single multipart file.
|
||||
func UploadFromMultipartFile(service FileStoreService, file *multipart.FileHeader, opts UploadOptions) (UploadedFileResult, error) {
|
||||
if file == nil {
|
||||
return UploadedFileResult{}, NewError(ErrCodeInvalidInput, "file is required")
|
||||
}
|
||||
|
||||
contentType := detectContentType(file)
|
||||
|
||||
validator := NewFileValidator(MaxUploadSize, SupportedMimeTypes)
|
||||
if err := validator.ValidateFile(file.Filename, contentType, file.Size); err != nil {
|
||||
return UploadedFileResult{}, err
|
||||
}
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return UploadedFileResult{}, NewErrorWithCause(ErrCodeUploadFailed, "failed to open uploaded file", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
if opts.Filename == "" {
|
||||
opts.Filename = file.Filename
|
||||
}
|
||||
if opts.ContentType == "" {
|
||||
opts.ContentType = contentType
|
||||
}
|
||||
if opts.MaxSize == 0 {
|
||||
opts.MaxSize = MaxUploadSize
|
||||
}
|
||||
|
||||
fileID, err := service.Upload("", file.Filename, src, opts)
|
||||
if err != nil {
|
||||
return UploadedFileResult{}, err
|
||||
}
|
||||
|
||||
return UploadedFileResult{
|
||||
FileID: fileID,
|
||||
Filename: file.Filename,
|
||||
ContentType: contentType,
|
||||
Size: file.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UploadManyFromMultipart uploads multiple files and returns successes and failures.
|
||||
func UploadManyFromMultipart(service FileStoreService, files []*multipart.FileHeader, opts UploadOptions) ([]UploadedFileResult, []FailedUploadResult) {
|
||||
uploaded := make([]UploadedFileResult, 0, len(files))
|
||||
failed := make([]FailedUploadResult, 0)
|
||||
|
||||
for _, file := range files {
|
||||
result, err := UploadFromMultipartFile(service, file, opts)
|
||||
if err != nil {
|
||||
code := ErrCodeUploadFailed
|
||||
var fsErr *FileStoreError
|
||||
if errors.As(err, &fsErr) {
|
||||
code = fsErr.Code
|
||||
}
|
||||
failed = append(failed, FailedUploadResult{
|
||||
Filename: file.Filename,
|
||||
Error: err.Error(),
|
||||
Code: code,
|
||||
})
|
||||
continue
|
||||
}
|
||||
uploaded = append(uploaded, result)
|
||||
}
|
||||
|
||||
return uploaded, failed
|
||||
}
|
||||
|
||||
func detectContentType(file *multipart.FileHeader) string {
|
||||
contentType := file.Header.Get("Content-Type")
|
||||
if contentType == "" {
|
||||
ext := filepath.Ext(file.Filename)
|
||||
if detected := mime.TypeByExtension(ext); detected != "" {
|
||||
contentType = detected
|
||||
} else {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(strings.Split(contentType, ";")[0])
|
||||
}
|
||||
|
||||
// CollectMultipartFiles returns file headers from the "files" field, or a single "file" if present.
|
||||
func CollectMultipartFiles(form *multipart.Form) []*multipart.FileHeader {
|
||||
if form == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
files := form.File["files"]
|
||||
if len(files) > 0 {
|
||||
return files
|
||||
}
|
||||
if file := form.File["file"]; len(file) > 0 {
|
||||
return file
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user