package server import ( "context" "fmt" "log/slog" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" ) // Server represents the HTTP server type Server struct { router *chi.Mux logger *slog.Logger addr string httpServer *http.Server } // New creates a new HTTP server with chi router and CORS enabled func New(addr string, logger *slog.Logger) *Server { s := &Server{ router: chi.NewRouter(), logger: logger, addr: addr, } s.setupMiddleware() return s } // setupMiddleware configures all middleware for the router func (s *Server) setupMiddleware() { // Request ID middleware s.router.Use(middleware.RequestID) // Real IP middleware s.router.Use(middleware.RealIP) // Rate limiting: 10 requests/second with burst of 30 // Generous limits since Cloudflare handles most protection rateLimiter := NewRateLimiter(10, 30) s.router.Use(rateLimiter.Middleware) // Structured logging middleware s.router.Use(s.loggingMiddleware) // Recover from panics s.router.Use(middleware.Recoverer) // Request timeout s.router.Use(middleware.Timeout(60 * time.Second)) // CORS configuration s.router.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{ "https://paragliding.scottyah.com", "http://localhost:3000", "http://localhost:5173", }, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, ExposedHeaders: []string{"Link"}, AllowCredentials: true, MaxAge: 300, })) // Compress responses s.router.Use(middleware.Compress(5)) } // loggingMiddleware logs HTTP requests with structured logging func (s *Server) loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) defer func() { s.logger.Info("request", "method", r.Method, "path", r.URL.Path, "status", ww.Status(), "bytes", ww.BytesWritten(), "duration", time.Since(start).String(), "request_id", middleware.GetReqID(r.Context()), ) }() next.ServeHTTP(ww, r) }) } // Router returns the chi router func (s *Server) Router() *chi.Mux { return s.router } // Start starts the HTTP server func (s *Server) Start() error { s.logger.Info("starting HTTP server", "addr", s.addr) s.httpServer = &http.Server{ Addr: s.addr, Handler: s.router, ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 120 * time.Second, } if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { return fmt.Errorf("server failed: %w", err) } return nil } // Shutdown gracefully shuts down the server func (s *Server) Shutdown(ctx context.Context) error { s.logger.Info("shutting down HTTP server") if s.httpServer == nil { return nil } return s.httpServer.Shutdown(ctx) }