Files
aggios.app/backend/internal/api/handlers/files.go
Erik Silva 2a112f169d refactor: redesign planos interface with design system patterns
- Create CreatePlanModal component with Headless UI Dialog
- Implement dark mode support throughout plans UI
- Update plans/page.tsx with professional card layout
- Update plans/[id]/page.tsx with consistent styling
- Add proper spacing, typography, and color consistency
- Implement smooth animations and transitions
- Add success/error message feedback
- Improve form UX with better input styling
2025-12-13 19:26:38 -03:00

105 lines
2.7 KiB
Go

package handlers
import (
"context"
"fmt"
"io"
"log"
"net/http"
"strings"
"aggios-app/backend/internal/config"
"github.com/gorilla/mux"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
type FilesHandler struct {
config *config.Config
}
func NewFilesHandler(cfg *config.Config) *FilesHandler {
return &FilesHandler{
config: cfg,
}
}
// ServeFile serves files from MinIO through the API
func (h *FilesHandler) ServeFile(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
// Get the file path (everything after /api/files/{bucket}/)
prefix := fmt.Sprintf("/api/files/%s/", bucket)
filePath := strings.TrimPrefix(r.URL.Path, prefix)
if filePath == "" {
http.Error(w, "File path is required", http.StatusBadRequest)
return
}
// Whitelist de buckets públicos permitidos
allowedBuckets := map[string]bool{
"aggios-logos": true,
}
if !allowedBuckets[bucket] {
log.Printf("🚫 Access denied to bucket: %s", bucket)
http.Error(w, "Access denied", http.StatusForbidden)
return
}
// Proteção contra path traversal
if strings.Contains(filePath, "..") {
log.Printf("🚫 Path traversal attempt detected: %s", filePath)
http.Error(w, "Invalid path", http.StatusBadRequest)
return
}
log.Printf("📁 Serving file: bucket=%s, path=%s", bucket, filePath)
// Initialize MinIO client
minioClient, err := minio.New("aggios-minio:9000", &minio.Options{
Creds: credentials.NewStaticV4("minioadmin", "M1n10_S3cur3_P@ss_2025!", ""),
Secure: false,
})
if err != nil {
log.Printf("Failed to create MinIO client: %v", err)
http.Error(w, "Storage service unavailable", http.StatusInternalServerError)
return
}
// Get object from MinIO
ctx := context.Background()
object, err := minioClient.GetObject(ctx, bucket, filePath, minio.GetObjectOptions{})
if err != nil {
log.Printf("Failed to get object: %v", err)
http.Error(w, "File not found", http.StatusNotFound)
return
}
defer object.Close()
// Get object info for content type and size
objInfo, err := object.Stat()
if err != nil {
log.Printf("Failed to stat object: %v", err)
http.Error(w, "File not found", http.StatusNotFound)
return
}
// Set appropriate headers
w.Header().Set("Content-Type", objInfo.ContentType)
w.Header().Set("Content-Length", fmt.Sprintf("%d", objInfo.Size))
w.Header().Set("Cache-Control", "public, max-age=31536000") // Cache for 1 year
w.Header().Set("Access-Control-Allow-Origin", "*")
// Copy file content to response
_, err = io.Copy(w, object)
if err != nil {
log.Printf("Failed to copy object content: %v", err)
return
}
log.Printf("✅ File served successfully: %s", filePath)
}