From fc4e3df02daf737c6178da3b4d7ac63c12f8541e Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Thu, 18 Dec 2025 08:07:45 -0300 Subject: [PATCH] feat: implement CORS and externalize payments config --- backend/.env.example | 7 +++++ backend/internal/config/config.go | 37 ++++++++++++++++-------- backend/internal/http/middleware/cors.go | 22 ++++++++++++++ backend/internal/payments/mercadopago.go | 6 ++-- backend/internal/server/server.go | 4 +-- backoffice/.env.example | 3 ++ 6 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 backend/.env.example create mode 100644 backend/internal/http/middleware/cors.go create mode 100644 backoffice/.env.example diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..ff7f8ea --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,7 @@ +DATABASE_URL=postgres://user:password@host:port/dbname?sslmode=disable +PORT=8080 +DB_MAX_OPEN_CONNS=25 +DB_MAX_IDLE_CONNS=25 +DB_CONN_MAX_IDLE=15m +MERCADOPAGO_BASE_URL=https://api.mercadopago.com +MARKETPLACE_COMMISSION=2.5 diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index d36bdf0..21efd6a 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -9,24 +9,28 @@ import ( // Config centralizes runtime configuration loaded from the environment. type Config struct { - AppName string - Port string - DatabaseURL string - MaxOpenConns int - MaxIdleConns int - ConnMaxIdle time.Duration + AppName string + Port string + DatabaseURL string + MaxOpenConns int + MaxIdleConns int + ConnMaxIdle time.Duration + MercadoPagoBaseURL string + MarketplaceCommission float64 } // Load reads configuration from environment variables and applies sane defaults // for local development. func Load() Config { cfg := Config{ - AppName: getEnv("APP_NAME", "saveinmed-performance-core"), - Port: getEnv("PORT", "8080"), - DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/saveinmed?sslmode=disable"), - MaxOpenConns: getEnvInt("DB_MAX_OPEN_CONNS", 15), - MaxIdleConns: getEnvInt("DB_MAX_IDLE_CONNS", 5), - ConnMaxIdle: getEnvDuration("DB_CONN_MAX_IDLE", 5*time.Minute), + AppName: getEnv("APP_NAME", "saveinmed-performance-core"), + Port: getEnv("PORT", "8080"), + DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/saveinmed?sslmode=disable"), + MaxOpenConns: getEnvInt("DB_MAX_OPEN_CONNS", 15), + MaxIdleConns: getEnvInt("DB_MAX_IDLE_CONNS", 5), + ConnMaxIdle: getEnvDuration("DB_CONN_MAX_IDLE", 5*time.Minute), + MercadoPagoBaseURL: getEnv("MERCADOPAGO_BASE_URL", "https://api.mercadopago.com"), + MarketplaceCommission: getEnvFloat("MARKETPLACE_COMMISSION", 2.5), } return cfg @@ -61,3 +65,12 @@ func getEnvDuration(key string, fallback time.Duration) time.Duration { } return fallback } + +func getEnvFloat(key string, fallback float64) float64 { + if value := os.Getenv(key); value != "" { + if parsed, err := strconv.ParseFloat(value, 64); err == nil { + return parsed + } + } + return fallback +} diff --git a/backend/internal/http/middleware/cors.go b/backend/internal/http/middleware/cors.go new file mode 100644 index 0000000..e6f4d4f --- /dev/null +++ b/backend/internal/http/middleware/cors.go @@ -0,0 +1,22 @@ +package middleware + +import "net/http" + +// CORS adds Cross-Origin Resource Sharing headers to the response. +// For now, it allows all origins (*). +func CORS(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + w.Header().Set("Access-Control-Max-Age", "86400") + + // Handle preflight requests + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusOK) + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/backend/internal/payments/mercadopago.go b/backend/internal/payments/mercadopago.go index 59a0795..cddcec0 100644 --- a/backend/internal/payments/mercadopago.go +++ b/backend/internal/payments/mercadopago.go @@ -14,10 +14,10 @@ type MercadoPagoGateway struct { BaseURL string } -func NewMercadoPagoGateway() *MercadoPagoGateway { +func NewMercadoPagoGateway(baseURL string, commission float64) *MercadoPagoGateway { return &MercadoPagoGateway{ - MarketplaceCommission: 2.5, - BaseURL: "https://api.mercadopago.com", + MarketplaceCommission: commission, + BaseURL: baseURL, } } diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 7fc5049..d16d795 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -36,7 +36,7 @@ func New(cfg config.Config) (*Server, error) { db.SetConnMaxIdleTime(cfg.ConnMaxIdle) repo := postgres.New(db) - gateway := payments.NewMercadoPagoGateway() + gateway := payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MarketplaceCommission) svc := usecase.NewService(repo, gateway) h := handler.New(svc) @@ -76,7 +76,7 @@ func (s *Server) Start(ctx context.Context) error { srv := &http.Server{ Addr: s.cfg.Addr(), - Handler: s.mux, + Handler: middleware.CORS(s.mux), ReadHeaderTimeout: 5 * time.Second, } diff --git a/backoffice/.env.example b/backoffice/.env.example new file mode 100644 index 0000000..fbb3786 --- /dev/null +++ b/backoffice/.env.example @@ -0,0 +1,3 @@ +DATABASE_URL=postgresql://user:password@host:port/dbname?schema=public +JWT_SECRET=secret-key +PORT=3000