Files
games/main.go
Ryan Hamamura 2aa026b1d5
Some checks failed
CI / Deploy / test (pull_request) Successful in 8s
CI / Deploy / lint (pull_request) Failing after 46s
CI / Deploy / deploy (pull_request) Has been skipped
refactor: remove persister abstraction layer
Inline persistence logic directly into game stores and handlers:
- game/persist.go: DB mapping methods on GameStore and GameInstance
- snake/persist.go: DB mapping methods on SnakeStore and SnakeGameInstance
- Chat persistence inlined into c4game handlers
- Delete db/persister.go (GamePersister, SnakePersister, ChatPersister)
- Stores now take *repository.Queries directly instead of Persister interface
2026-03-02 12:30:33 -10:00

125 lines
2.8 KiB
Go

package main
import (
"context"
"embed"
"fmt"
"log/slog"
"net"
"net/http"
"os/signal"
"syscall"
"time"
"github.com/ryanhamamura/c4/config"
"github.com/ryanhamamura/c4/db"
"github.com/ryanhamamura/c4/db/repository"
"github.com/ryanhamamura/c4/game"
"github.com/ryanhamamura/c4/logging"
appnats "github.com/ryanhamamura/c4/nats"
"github.com/ryanhamamura/c4/router"
"github.com/ryanhamamura/c4/sessions"
"github.com/ryanhamamura/c4/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 := game.NewGameStore(queries)
store.SetNotifyFunc(func(gameID string) {
nc.Publish("game."+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,
)
if err := router.SetupRoutes(r, queries, sessionManager, nc, store, snakeStore, assets); err != nil {
return fmt.Errorf("setting up routes: %w", err)
}
// 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()
}