Stage 0: scaffold monorepo, backend skeleton, docs, CI
Tests · Go / test (push) Successful in 32s

- go.work (Go 1.26.3) with backend module; deps added incrementally (gin+zap only)

- backend: /healthz + /readyz, env config, graceful shutdown

- docs: ARCHITECTURE, FUNCTIONAL (+ru mirror), TESTING

- PLAN.md (stage tracker + per-stage open details) and CLAUDE.md (per-stage workflow)

- .gitea go-unit CI (gofmt/vet/build/test)
This commit is contained in:
Ilia Denisov
2026-06-02 11:57:58 +02:00
commit effe6675bc
19 changed files with 1174 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
// 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)
}
}