Replace CDN-hosted datastar beta.11 with local v1.0.0-RC.7 to fix client-side expression incompatibilities with the Go SDK. Also fix quoted CSS class keys in data-class expressions, harden session cookie settings (named cookie, Secure flag), simplify SetupRoutes to not return an error, and regenerate templ output.
123 lines
2.8 KiB
Go
123 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,
|
|
)
|
|
|
|
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()
|
|
}
|