- Add version package with build-time variables - Inject version via ldflags in Dockerfile using git describe - Show version in footer on every page - Log version and commit on server startup
124 lines
2.9 KiB
Go
124 lines
2.9 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/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()
|
|
}
|