gohorsejobs/backend/internal/api/handlers/storage_handler.go
2026-01-03 20:21:29 -03:00

90 lines
2.6 KiB
Go

package handlers
import (
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"strings"
"time"
"github.com/rede5/gohorsejobs/backend/internal/api/middleware"
"github.com/rede5/gohorsejobs/backend/internal/services"
)
type StorageHandler struct {
storageService *services.StorageService
}
func NewStorageHandler(s *services.StorageService) *StorageHandler {
return &StorageHandler{storageService: s}
}
// GetUploadURL returns a pre-signed URL for uploading a file.
// Clients upload directly to this URL.
// Query Params:
// - filename: Original filename
// - contentType: MIME type
// - folder: Optional folder (e.g. 'avatars', 'resumes')
func (h *StorageHandler) GetUploadURL(w http.ResponseWriter, r *http.Request) {
// Authentication required
userIDVal := r.Context().Value(middleware.ContextUserID)
userID, ok := userIDVal.(string)
if !ok || userID == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
filename := r.URL.Query().Get("filename")
contentType := r.URL.Query().Get("contentType")
folder := r.URL.Query().Get("folder")
if filename == "" {
http.Error(w, "Filename is required", http.StatusBadRequest)
return
}
if folder == "" {
folder = "uploads" // Default
}
// Validate folder
validFolders := map[string]bool{"avatars": true, "resumes": true, "logos": true, "uploads": true}
if !validFolders[folder] {
http.Error(w, "Invalid folder", http.StatusBadRequest)
return
}
// Generate a unique key
ext := filepath.Ext(filename)
if ext == "" {
// Attempt to guess from contentType if needed, or just allow no ext
}
// Key format: {folder}/{userID}/{timestamp}_{random}{ext}
// Using user ID ensures isolation if needed, or use a UUID.
key := fmt.Sprintf("%s/%s/%d_%s", folder, userID, time.Now().Unix(), strings.ReplaceAll(filename, " ", "_"))
url, err := h.storageService.GetPresignedUploadURL(r.Context(), key, contentType)
if err != nil {
// If credentials missing, log error and return 500
// "storage credentials incomplete" might mean admin needs to configure them.
http.Error(w, "Failed to generate upload URL: "+err.Error(), http.StatusInternalServerError)
return
}
publicURL, err := h.storageService.GetPublicURL(r.Context(), key)
if err != nil {
http.Error(w, "Failed to generate public URL: "+err.Error(), http.StatusInternalServerError)
return
}
// Return simple JSON
resp := map[string]string{
"url": url,
"key": key, // Client needs key to save to DB profile
"publicUrl": publicURL, // Public URL for immediate use
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}