photum/backend/internal/storage/s3.go
NANDO9322 cd196a0275 feat(auth): adiciona tipo profissional ao schema e corrige avatar
- Adiciona coluna `tipo_profissional` à tabela `usuarios`
- Atualiza handlers e services do Backend Go para persistir o tipo
- Atualiza registro no Frontend para enviar o nome da função (ex: "Cinegrafista")
- Corrige uploads S3 para compatibilidade com Civo (PathStyle)
- Script para definir política pública de leitura no bucket S3
- Adiciona fallback para imagens de avatar na Navbar
2025-12-22 12:37:42 -03:00

91 lines
3 KiB
Go

package storage
import (
"context"
"fmt"
"log"
"photum-backend/internal/config"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
awsConfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
type S3Service struct {
Client *s3.Client
PresignClient *s3.PresignClient
Bucket string
Region string
}
func NewS3Service(cfg *config.Config) *S3Service {
// Custom Resolver for Civo Object Store
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: cfg.S3Endpoint,
SigningRegion: region,
}, nil
})
awsCfg, err := awsConfig.LoadDefaultConfig(context.TODO(),
awsConfig.WithRegion(cfg.S3Region),
awsConfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(cfg.S3AccessKey, cfg.S3SecretKey, "")),
awsConfig.WithEndpointResolverWithOptions(customResolver),
)
if err != nil {
log.Fatalf("unable to load SDK config, %v", err)
}
client := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
o.UsePathStyle = true
})
presignClient := s3.NewPresignClient(client)
return &S3Service{
Client: client,
PresignClient: presignClient,
Bucket: cfg.S3Bucket,
Region: cfg.S3Region,
}
}
// GeneratePresignedURL generates a PUT presigned URL for uploading a file
// returns (uploadUrl, publicUrl, error)
func (s *S3Service) GeneratePresignedURL(filename string, contentType string) (string, string, error) {
key := fmt.Sprintf("photum-dev/%d_%s", time.Now().Unix(), filename)
req, err := s.PresignClient.PresignPutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(s.Bucket),
Key: aws.String(key),
ContentType: aws.String(contentType),
}, s3.WithPresignExpires(15*time.Minute))
if err != nil {
return "", "", fmt.Errorf("failed to sign request: %v", err)
}
// Construct public URL - Path Style
// URL: https://objectstore.nyc1.civo.com/rede5/uploads/...
// We need to clean the endpoint string if it has https:// prefix for Sprintf if we construct manually,
// or just reuse the known endpoint structure.
// cfg.S3Endpoint is "https://objectstore.nyc1.civo.com"
// Assuming s.Client.Options().BaseEndpoint is not easily accessible here without plumbing,
// we will construct it based on the hardcoded knowledge of Civo or pass endpoint to struct.
// But simply: S3Endpoint + "/" + Bucket + "/" + key is the standard path style.
// Note: config.S3Endpoint includes "https://" based on .env
// We entered: S3_ENDPOINT=https://objectstore.nyc1.civo.com
// So: https://objectstore.nyc1.civo.com/rede5/key
// However, we don't have access to cfg here directly, but we rely on hardcoding for Civo in previous step or we should store Endpoint in struct.
// Better to store Endpoint in struct to be clean.
// For now, I'll use the domain directly as I did before, but path style.
publicURL := fmt.Sprintf("https://%s/%s/%s", "objectstore.nyc1.civo.com", s.Bucket, key)
return req.URL, publicURL, nil
}