package server import ( "context" "log" "net/http" "time" _ "github.com/jackc/pgx/v5/stdlib" "github.com/jmoiron/sqlx" httpSwagger "github.com/swaggo/http-swagger" "github.com/saveinmed/backend-go/internal/config" "github.com/saveinmed/backend-go/internal/http/handler" "github.com/saveinmed/backend-go/internal/http/middleware" "github.com/saveinmed/backend-go/internal/payments" "github.com/saveinmed/backend-go/internal/repository/postgres" "github.com/saveinmed/backend-go/internal/usecase" ) // Server wires the infrastructure and exposes HTTP handlers. type Server struct { cfg config.Config db *sqlx.DB mux *http.ServeMux } func New(cfg config.Config) (*Server, error) { db, err := sqlx.Open("pgx", cfg.DatabaseURL) if err != nil { return nil, err } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) db.SetConnMaxIdleTime(cfg.ConnMaxIdle) repo := postgres.New(db) gateway := payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MarketplaceCommission) svc := usecase.NewService(repo, gateway, cfg.MarketplaceCommission, cfg.JWTSecret, cfg.JWTExpiresIn, cfg.PasswordPepper) h := handler.New(svc) mux := http.NewServeMux() // Root endpoint mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) response := `{"message":"💊 SaveInMed API is running!","docs":"/swagger/index.html","health":"/health","version":"1.0.0"}` _, _ = w.Write([]byte(response)) }) mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) auth := middleware.RequireAuth([]byte(cfg.JWTSecret)) adminOnly := middleware.RequireAuth([]byte(cfg.JWTSecret), "Admin") mux.Handle("POST /api/companies", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip)) mux.Handle("GET /api/companies", chain(http.HandlerFunc(h.ListCompanies), middleware.Logger, middleware.Gzip)) mux.Handle("PATCH /api/v1/companies/", chain(http.HandlerFunc(h.VerifyCompany), middleware.Logger, middleware.Gzip, adminOnly)) mux.Handle("GET /api/v1/companies/me", chain(http.HandlerFunc(h.GetMyCompany), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/companies/", chain(http.HandlerFunc(h.GetCompanyRating), middleware.Logger, middleware.Gzip)) mux.Handle("POST /api/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip)) mux.Handle("GET /api/products", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip)) mux.Handle("GET /api/v1/inventory", chain(http.HandlerFunc(h.ListInventory), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/v1/inventory/adjust", chain(http.HandlerFunc(h.AdjustInventory), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/orders", chain(http.HandlerFunc(h.CreateOrder), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/orders/", chain(http.HandlerFunc(h.GetOrder), middleware.Logger, middleware.Gzip, auth)) mux.Handle("PATCH /api/orders/", chain(http.HandlerFunc(h.UpdateOrderStatus), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/orders/", chain(http.HandlerFunc(h.CreatePaymentPreference), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/v1/shipments", chain(http.HandlerFunc(h.CreateShipment), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/shipments/", chain(http.HandlerFunc(h.GetShipmentByOrderID), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/v1/payments/webhook", chain(http.HandlerFunc(h.HandlePaymentWebhook), middleware.Logger, middleware.Gzip)) mux.Handle("POST /api/v1/reviews", chain(http.HandlerFunc(h.CreateReview), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/dashboard/seller", chain(http.HandlerFunc(h.GetSellerDashboard), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/dashboard/admin", chain(http.HandlerFunc(h.GetAdminDashboard), middleware.Logger, middleware.Gzip, adminOnly)) mux.Handle("POST /api/v1/auth/register", chain(http.HandlerFunc(h.Register), middleware.Logger, middleware.Gzip)) mux.Handle("POST /api/v1/auth/login", chain(http.HandlerFunc(h.Login), middleware.Logger, middleware.Gzip)) mux.Handle("POST /api/v1/users", chain(http.HandlerFunc(h.CreateUser), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/users", chain(http.HandlerFunc(h.ListUsers), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/users/", chain(http.HandlerFunc(h.GetUser), middleware.Logger, middleware.Gzip, auth)) mux.Handle("PUT /api/v1/users/", chain(http.HandlerFunc(h.UpdateUser), middleware.Logger, middleware.Gzip, auth)) mux.Handle("DELETE /api/v1/users/", chain(http.HandlerFunc(h.DeleteUser), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/v1/cart", chain(http.HandlerFunc(h.AddToCart), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/cart", chain(http.HandlerFunc(h.GetCart), middleware.Logger, middleware.Gzip, auth)) mux.Handle("DELETE /api/v1/cart/", chain(http.HandlerFunc(h.DeleteCartItem), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /swagger/", httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json"))) return &Server{cfg: cfg, db: db, mux: mux}, nil } // Start runs the HTTP server and ensures the database is reachable. func (s *Server) Start(ctx context.Context) error { if err := s.db.PingContext(ctx); err != nil { return err } repo := postgres.New(s.db) if err := repo.InitSchema(ctx); err != nil { return err } corsConfig := middleware.CORSConfig{AllowedOrigins: s.cfg.CORSOrigins} srv := &http.Server{ Addr: s.cfg.Addr(), Handler: middleware.CORSWithConfig(corsConfig)(s.mux), ReadHeaderTimeout: 5 * time.Second, } log.Printf("starting %s on %s", s.cfg.AppName, s.cfg.Addr()) return srv.ListenAndServe() } func chain(h http.Handler, middlewareFns ...func(http.Handler) http.Handler) http.Handler { for i := len(middlewareFns) - 1; i >= 0; i-- { h = middlewareFns[i](h) } return h }