Files
games/main.go
Ryan Hamamura c6885a069b
All checks were successful
CI / Deploy / test (pull_request) Successful in 14s
CI / Deploy / lint (pull_request) Successful in 25s
CI / Deploy / deploy (pull_request) Has been skipped
refactor: rename Go module from c4 to games
Rename module path github.com/ryanhamamura/c4 to
github.com/ryanhamamura/games across go.mod, all source files,
and golangci config.
2026-03-02 20:41:20 -10:00

123 lines
2.8 KiB
Go

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/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)
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."+gameID, nil) //nolint:errcheck // best-effort notification
})
snakeStore := snake.NewSnakeStore(queries)
snakeStore.SetNotifyFunc(func(gameID string) {
nc.Publish("snake."+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()
}