package server import ( "context" "errors" "fmt" "log/slog" "net/http" "time" "ibd-trader/internal/auth" "ibd-trader/internal/config" "ibd-trader/internal/database" "ibd-trader/internal/ibd" "ibd-trader/internal/server/api/ibd/creds" "ibd-trader/internal/server/api/ibd/ibd50" "ibd-trader/internal/server/api/ibd/scrape" "ibd-trader/internal/server/auth/callback" "ibd-trader/internal/server/auth/login" "ibd-trader/internal/server/auth/user" middleware2 "ibd-trader/internal/server/middleware" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/redis/go-redis/v9" ) func StartServer( ctx context.Context, cfg *config.Config, logger *slog.Logger, db database.Database, auth *auth.Authenticator, client *ibd.Client, rClient *redis.Client, ) error { r := chi.NewRouter() r.Use(middleware.RealIP) r.Use(middleware.RequestID) r.Use(middleware.Recoverer) r.Use(middleware.Heartbeat("/healthz")) _ = NewMainHandler(logger, db, r) r.Route("/auth", func(r chi.Router) { r.Get("/login", login.Handler(logger, db, auth)) r.Get("/callback", callback.Handler(logger, db, db, auth)) r.Route("/user", func(r chi.Router) { r.Use(middleware2.Auth(db)) r.Get("/", user.Handler(logger, auth)) }) }) r.Route("/api", func(r chi.Router) { r.Use(middleware.NoCache) r.Use(middleware2.Auth(db)) r.Route("/ibd", func(r chi.Router) { r.Put("/creds", creds.Handler(logger, db)) r.Get("/ibd50", ibd50.Handler(logger, client)) r.Put("/scrape", scrape.Handler(logger, rClient)) }) }) logger.Info("Starting server", "port", cfg.Server.Port) srv := &http.Server{ Addr: fmt.Sprintf("0.0.0.0:%d", cfg.Server.Port), Handler: r, //ReadTimeout: 1 * time.Minute, //WriteTimeout: 1 * time.Minute, ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), } finishedCh := make(chan error) go func() { err := srv.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Error("Server failed", "error", err) } finishedCh <- err close(finishedCh) }() select { case err := <-finishedCh: // Server failed return err case <-ctx.Done(): logger.Info("Shutting down server") } // Shutdown server ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { logger.Error("Failed to shutdown server", "error", err) return err } // Wait for the server to finish err := <-finishedCh if errors.Is(err, http.ErrServerClosed) { return nil } return err } type MainHandler struct { logger *slog.Logger db database.Database } func NewMainHandler(logger *slog.Logger, db database.Database, r *chi.Mux) *MainHandler { h := &MainHandler{logger, db} r.Get("/readyz", h.Ready) return h } func (h *MainHandler) Ready(w http.ResponseWriter, r *http.Request) { // Check we can ping DB ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) defer cancel() err := h.db.Ping(ctx) if err != nil { http.Error(w, "DB not ready", http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("OK")) }