131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"aggios-app/backend/internal/api/middleware"
|
|
"aggios-app/backend/internal/config"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/minio/minio-go/v7"
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
)
|
|
|
|
// UploadHandler handles file upload endpoints
|
|
type UploadHandler struct {
|
|
minioClient *minio.Client
|
|
cfg *config.Config
|
|
}
|
|
|
|
// NewUploadHandler creates a new upload handler
|
|
func NewUploadHandler(cfg *config.Config) (*UploadHandler, error) {
|
|
// Initialize MinIO client
|
|
minioClient, err := minio.New(cfg.Minio.Endpoint, &minio.Options{
|
|
Creds: credentials.NewStaticV4(cfg.Minio.RootUser, cfg.Minio.RootPassword, ""),
|
|
Secure: cfg.Minio.UseSSL,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create MinIO client: %w", err)
|
|
}
|
|
|
|
// Ensure bucket exists
|
|
ctx := context.Background()
|
|
bucketName := cfg.Minio.BucketName
|
|
exists, err := minioClient.BucketExists(ctx, bucketName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check bucket existence: %w", err)
|
|
}
|
|
|
|
if !exists {
|
|
err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create bucket: %w", err)
|
|
}
|
|
}
|
|
|
|
return &UploadHandler{
|
|
minioClient: minioClient,
|
|
cfg: cfg,
|
|
}, nil
|
|
}
|
|
|
|
// UploadResponse represents the upload response
|
|
type UploadResponse struct {
|
|
FileID string `json:"file_id"`
|
|
FileName string `json:"file_name"`
|
|
FileURL string `json:"file_url"`
|
|
FileSize int64 `json:"file_size"`
|
|
}
|
|
|
|
// Upload handles file upload
|
|
func (h *UploadHandler) Upload(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
// Try to get user ID from context (optional for signup flow)
|
|
userIDStr, _ := r.Context().Value(middleware.UserIDKey).(string)
|
|
|
|
// Use temp tenant for unauthenticated uploads (signup flow)
|
|
tenantID := uuid.MustParse("00000000-0000-0000-0000-000000000000")
|
|
if userIDStr != "" {
|
|
// TODO: Query database to get tenant_id from user_id when authenticated
|
|
}
|
|
|
|
// Parse multipart form (max 10MB)
|
|
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
|
http.Error(w, "File too large (max 10MB)", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get file from form
|
|
file, header, err := r.FormFile("file")
|
|
if err != nil {
|
|
http.Error(w, "Failed to read file", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// Validate file type (images only)
|
|
contentType := header.Header.Get("Content-Type")
|
|
if !strings.HasPrefix(contentType, "image/") {
|
|
http.Error(w, "Only images are allowed", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Generate unique file ID
|
|
fileID := uuid.New()
|
|
ext := filepath.Ext(header.Filename)
|
|
objectName := fmt.Sprintf("tenants/%s/logos/%s%s", tenantID.String(), fileID.String(), ext)
|
|
|
|
// Upload to MinIO
|
|
ctx := context.Background()
|
|
_, err = h.minioClient.PutObject(ctx, h.cfg.Minio.BucketName, objectName, file, header.Size, minio.PutObjectOptions{
|
|
ContentType: contentType,
|
|
})
|
|
if err != nil {
|
|
http.Error(w, "Failed to upload file", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Generate public URL (replace internal hostname with localhost for browser access)
|
|
fileURL := fmt.Sprintf("http://localhost:9000/%s/%s", h.cfg.Minio.BucketName, objectName)
|
|
|
|
// Return response
|
|
response := UploadResponse{
|
|
FileID: fileID.String(),
|
|
FileName: header.Filename,
|
|
FileURL: fileURL,
|
|
FileSize: header.Size,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|