Tenant Model: - Renamed Company→Tenant (Company alias for compatibility) - Added: lat/lng, city, state, category - Updated: postgres, handlers, DTOs, schema SQL Seeder (cmd/seeder): - Generates 400 pharmacies in Anápolis/GO - 20-500 products per tenant - Haversine distance variation ±5km from center Product Search: - GET /products/search with advanced filters - Filters: price (min/max), expiration, distance - Haversine distance calculation (approx km) - Anonymous seller (only city/state shown until checkout) - Ordered by expiration date (nearest first) New domain types: - ProductWithDistance, ProductSearchFilter, ProductSearchPage - HaversineDistance function Updated tests for Category instead of Role
146 lines
7.2 KiB
Go
146 lines
7.2 KiB
Go
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":"/docs/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/v1/companies", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/companies", chain(http.HandlerFunc(h.ListCompanies), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/companies/{id}", chain(http.HandlerFunc(h.GetCompany), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("PATCH /api/v1/companies/{id}", chain(http.HandlerFunc(h.UpdateCompany), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("DELETE /api/v1/companies/{id}", chain(http.HandlerFunc(h.DeleteCompany), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("PATCH /api/v1/companies/{id}/verify", 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/{id}/rating", chain(http.HandlerFunc(h.GetCompanyRating), middleware.Logger, middleware.Gzip))
|
|
|
|
mux.Handle("POST /api/v1/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/products", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/products/search", chain(http.HandlerFunc(h.SearchProducts), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/products/{id}", chain(http.HandlerFunc(h.GetProduct), middleware.Logger, middleware.Gzip))
|
|
|
|
mux.Handle("PATCH /api/v1/products/{id}", chain(http.HandlerFunc(h.UpdateProduct), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("DELETE /api/v1/products/{id}", chain(http.HandlerFunc(h.DeleteProduct), 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/v1/orders", chain(http.HandlerFunc(h.CreateOrder), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/orders", chain(http.HandlerFunc(h.ListOrders), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/orders/{id}", chain(http.HandlerFunc(h.GetOrder), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("PATCH /api/v1/orders/{id}/status", chain(http.HandlerFunc(h.UpdateOrderStatus), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("DELETE /api/v1/orders/{id}", chain(http.HandlerFunc(h.DeleteOrder), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/orders/{id}/payment", 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 /docs/", httpSwagger.Handler(httpSwagger.URL("/docs/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
|
|
}
|