133 lines
4.0 KiB
Go
133 lines
4.0 KiB
Go
package httpx
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
|
|
|
"royal-pop-backend/internal/config"
|
|
"royal-pop-backend/internal/database"
|
|
"royal-pop-backend/internal/inventory"
|
|
"royal-pop-backend/internal/mailer"
|
|
"royal-pop-backend/internal/orders"
|
|
)
|
|
|
|
type RouterConfig struct {
|
|
Config *config.Config
|
|
DB *database.DB
|
|
Orders *orders.Store
|
|
Stock *inventory.Store
|
|
Mailer *mailer.ResendMailer
|
|
}
|
|
|
|
func NewRouter(cfg RouterConfig) http.Handler {
|
|
router := chi.NewRouter()
|
|
|
|
router.Use(chimiddleware.RealIP)
|
|
router.Use(chimiddleware.RequestID)
|
|
router.Use(chimiddleware.Recoverer)
|
|
router.Use(chimiddleware.Timeout(30 * time.Second))
|
|
router.Use(corsMiddleware(cfg.Config.AllowedOrigins))
|
|
|
|
router.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) {
|
|
databaseStatus := "disabled"
|
|
if cfg.DB != nil {
|
|
healthCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
if err := cfg.DB.Health(healthCtx); err != nil {
|
|
WriteJSON(w, http.StatusServiceUnavailable, map[string]any{
|
|
"service": cfg.Config.AppName,
|
|
"status": "degraded",
|
|
"database": "unreachable",
|
|
})
|
|
return
|
|
}
|
|
databaseStatus = "ok"
|
|
}
|
|
|
|
WriteJSON(w, http.StatusOK, map[string]any{
|
|
"service": cfg.Config.AppName,
|
|
"status": "ok",
|
|
"database": databaseStatus,
|
|
})
|
|
})
|
|
|
|
router.Post("/webhooks/stripe", handleStripeWebhook(cfg.Config, cfg.Orders, cfg.Mailer))
|
|
|
|
router.Route("/v1", func(api chi.Router) {
|
|
api.Get("/inventory", handleListPublicInventory(cfg.Stock))
|
|
api.Get("/storefront/pricing", handleGetStorefrontPricing(cfg.Config))
|
|
api.Get("/", func(w http.ResponseWriter, _ *http.Request) {
|
|
WriteJSON(w, http.StatusOK, map[string]any{
|
|
"service": cfg.Config.AppName,
|
|
"status": "scaffolded",
|
|
"version": "v1",
|
|
})
|
|
})
|
|
|
|
api.Post("/checkout/payment-intent", handleCreatePaymentIntent(cfg.Config, cfg.Orders))
|
|
api.Get("/checkout/payment-intent/{paymentIntentID}", handleGetPaymentIntentStatus(cfg.Config, cfg.Orders))
|
|
api.Get("/client/session", handleGetClientSession(cfg.Config))
|
|
api.Post("/client/login", handleClientLogin(cfg.Config))
|
|
api.Post("/client/logout", handleClientLogout(cfg.Config))
|
|
|
|
api.Group(func(client chi.Router) {
|
|
client.Use(requireClientSession(cfg.Config))
|
|
client.Get("/client/orders", handleListClientOrders(cfg.Orders))
|
|
client.Get("/client/orders/{orderID}", handleGetClientOrder(cfg.Orders))
|
|
client.Patch("/client/orders/{orderID}", handleUpdateClientOrder(cfg.Orders, cfg.Mailer))
|
|
client.Get("/client/stock", handleListClientStock(cfg.Stock))
|
|
client.Put("/client/stock", handleUpsertClientStock(cfg.Stock))
|
|
client.Post("/client/reseed", handleClientReseed(cfg.DB))
|
|
})
|
|
})
|
|
|
|
router.NotFound(func(w http.ResponseWriter, _ *http.Request) {
|
|
WriteError(w, http.StatusNotFound, "not_found", "The requested endpoint does not exist.")
|
|
})
|
|
|
|
return router
|
|
}
|
|
|
|
func corsMiddleware(allowedOrigins []string) func(http.Handler) http.Handler {
|
|
normalized := make([]string, 0, len(allowedOrigins))
|
|
for _, origin := range allowedOrigins {
|
|
trimmed := strings.TrimSpace(origin)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
normalized = append(normalized, trimmed)
|
|
}
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
origin := r.Header.Get("Origin")
|
|
|
|
if origin != "" {
|
|
for _, allowedOrigin := range normalized {
|
|
if allowedOrigin == "*" || strings.EqualFold(allowedOrigin, origin) {
|
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
w.Header().Set("Vary", "Origin")
|
|
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, OPTIONS")
|
|
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|