package server import ( "fmt" "github.com/gin-gonic/gin" "scrabble/backend/internal/adminconsole" "scrabble/backend/internal/gamelimits" ) // consoleLimits renders the per-tier, per-kind active-game limit form (backend.config), read from // the in-memory cache. func (s *Server) consoleLimits(c *gin.Context) { cfg := s.gamelimits.Get() s.renderConsole(c, "limits", "limits", "Game limits", adminconsole.GameLimitsView{ GuestVsAI: cfg.Guest.VsAI, GuestRandom: cfg.Guest.Random, GuestFriends: cfg.Guest.Friends, DurableVsAI: cfg.Durable.VsAI, DurableRandom: cfg.Durable.Random, DurableFriends: cfg.Durable.Friends, }) } // consoleUpdateLimits saves the active-game limits and refreshes the hot cache in place. Each // field is a per-kind cap: -1 is unlimited, 0 blocks the kind, a positive value caps concurrent games. func (s *Server) consoleUpdateLimits(c *gin.Context) { cfg := gamelimits.Config{ Guest: gamelimits.Limits{ VsAI: atoiForm(c, "guest_vs_ai"), Random: atoiForm(c, "guest_random"), Friends: atoiForm(c, "guest_friends"), }, Durable: gamelimits.Limits{ VsAI: atoiForm(c, "durable_vs_ai"), Random: atoiForm(c, "durable_random"), Friends: atoiForm(c, "durable_friends"), }, } if err := validateGameLimits(cfg); err != nil { s.renderConsoleMessage(c, "Invalid", err.Error(), "/_gm/limits") return } if err := s.gamelimits.Update(c.Request.Context(), cfg); err != nil { s.consoleError(c, err) return } s.renderConsoleMessage(c, "Saved", "game limits updated", "/_gm/limits") } // validateGameLimits rejects a limit below -1 (the unlimited sentinel); -1, 0 and any positive count // are valid. It mirrors the backend.config CHECK so a bad value is refused with a clean message // rather than a raw database error. func validateGameLimits(cfg gamelimits.Config) error { for _, v := range []int{ cfg.Guest.VsAI, cfg.Guest.Random, cfg.Guest.Friends, cfg.Durable.VsAI, cfg.Durable.Random, cfg.Durable.Friends, } { if v < gamelimits.Unlimited { return fmt.Errorf("a limit must be -1 (unlimited), 0, or a positive count") } } return nil }