package main import ( "context" "embed" "fmt" "log/slog" "net" "net/http" "os/signal" "syscall" "time" "github.com/ryanhamamura/games/config" "github.com/ryanhamamura/games/connect4" "github.com/ryanhamamura/games/db" "github.com/ryanhamamura/games/db/repository" "github.com/ryanhamamura/games/logging" appnats "github.com/ryanhamamura/games/nats" "github.com/ryanhamamura/games/router" "github.com/ryanhamamura/games/sessions" "github.com/ryanhamamura/games/snake" "github.com/ryanhamamura/games/version" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/rs/zerolog/log" "golang.org/x/sync/errgroup" ) //go:embed assets var assets embed.FS func main() { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() cfg := config.Global logging.SetupLogger(cfg.Environment, cfg.LogLevel) if err := run(ctx); err != nil && err != http.ErrServerClosed { log.Fatal().Err(err).Msg("server error") } } func run(ctx context.Context) error { cfg := config.Global addr := fmt.Sprintf("%s:%s", cfg.Host, cfg.Port) slog.Info("server starting", "addr", addr, "version", version.Version, "commit", version.Commit) defer slog.Info("server shutdown complete") eg, egctx := errgroup.WithContext(ctx) // Database cleanupDB, err := db.Init(cfg.DBPath) if err != nil { return fmt.Errorf("initializing database: %w", err) } defer cleanupDB() queries := repository.New(db.DB) // Sessions sessionManager, cleanupSessions := sessions.SetupSessionManager(db.DB) defer cleanupSessions() // NATS nc, cleanupNATS, err := appnats.SetupNATS(egctx) if err != nil { return fmt.Errorf("setting up NATS: %w", err) } defer cleanupNATS() // Game stores store := connect4.NewStore(queries) store.SetNotifyFunc(func(gameID string) { nc.Publish(connect4.GameSubject(gameID), nil) //nolint:errcheck // best-effort notification }) snakeStore := snake.NewSnakeStore(queries) snakeStore.SetNotifyFunc(func(gameID string) { nc.Publish(snake.GameSubject(gameID), nil) //nolint:errcheck // best-effort notification }) // Router logger := log.Logger r := chi.NewMux() r.Use( logging.RequestLogger(&logger, cfg.Environment), middleware.Recoverer, sessionManager.LoadAndSave, ) router.SetupRoutes(r, queries, sessionManager, nc, store, snakeStore, assets) // HTTP server srv := &http.Server{ Addr: addr, Handler: r, ReadHeaderTimeout: 10 * time.Second, BaseContext: func(l net.Listener) context.Context { return egctx }, } eg.Go(func() error { err := srv.ListenAndServe() if err != nil && err != http.ErrServerClosed { return fmt.Errorf("server error: %w", err) } return nil }) eg.Go(func() error { <-egctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() slog.Debug("shutting down server...") return srv.Shutdown(shutdownCtx) }) return eg.Wait() }