// Package server wires the backend's HTTP listener: the gin engine, its route // groups and the start/stop lifecycle. At this stage it serves only the // infrastructure probes; the domain route groups described in PLAN.md // (/api/v1/public, /user, /internal, /admin) are added by later stages. package server import ( "context" "errors" "net/http" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // shutdownTimeout bounds how long Run waits for in-flight requests to finish // during a graceful shutdown. const shutdownTimeout = 10 * time.Second // Server owns the gin engine and the underlying HTTP server. type Server struct { log *zap.Logger http *http.Server } // New returns a Server that will listen on addr. The logger receives lifecycle // and request diagnostics. func New(addr string, log *zap.Logger) *Server { gin.SetMode(gin.ReleaseMode) engine := gin.New() engine.Use(gin.Recovery()) registerProbes(engine) return &Server{ log: log, http: &http.Server{Addr: addr, Handler: engine}, } } // registerProbes installs the unauthenticated infrastructure probes: /healthz // reports process liveness and /readyz reports readiness to serve traffic. // Until later stages add real dependencies (Postgres, warmed caches), // readiness mirrors liveness. func registerProbes(engine *gin.Engine) { ok := func(c *gin.Context) { c.String(http.StatusOK, "ok") } engine.GET("/healthz", ok) engine.GET("/readyz", ok) } // Run starts the listener and blocks until ctx is cancelled, then shuts the // server down gracefully within shutdownTimeout. It returns the first error // that is not the expected http.ErrServerClosed. func (s *Server) Run(ctx context.Context) error { errc := make(chan error, 1) go func() { s.log.Info("http listener starting", zap.String("addr", s.http.Addr)) errc <- s.http.ListenAndServe() }() select { case err := <-errc: if errors.Is(err, http.ErrServerClosed) { return nil } return err case <-ctx.Done(): s.log.Info("http listener stopping") shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() return s.http.Shutdown(shutdownCtx) } }