feat(backend): per-tier, per-kind active-game limits with a guest funnel
CI / changes (pull_request) Successful in 5s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 20s
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m41s
CI / changes (pull_request) Successful in 5s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 20s
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m41s
Cap a player's simultaneous unfinished games per kind (vs_ai, random, friends) with independent guest and durable-account tiers, held in a new single-row backend.config table (-1 = unlimited) behind an in-memory cache and editable live in the admin console (/_gm/limits). Each game is tagged with games.game_kind on creation. This replaces the earlier flat MaxActiveQuickGames=10 combined cap: the per-tier/kind config is the single mechanism, enforced at the same handler gate (ensureUnderGameLimit by kind on lobby/enqueue) plus the durable friends cap in CreateInvitation. game.Service.AtGameLimit only resolves the tier and counts; the limit policy stays at the request edge. Guests are now refused friend requests, friend-code redemption, befriend-in-game and invitation creation outright (403 guest_forbidden) -- previously only the UI hid these. Admin: a kind column in both game lists and the config editor. Defaults: guest 1 vs_ai / 1 random / 0 friends; durable 10 / 10 / 10.
This commit is contained in:
@@ -70,6 +70,10 @@ func (s *Server) registerConsole(router *gin.Engine) {
|
||||
gm.POST("/bans/unban", s.consoleUnban)
|
||||
gm.GET("/games", s.consoleGames)
|
||||
gm.GET("/games/:id", s.consoleGameDetail)
|
||||
if s.gamelimits != nil {
|
||||
gm.GET("/limits", s.consoleLimits)
|
||||
gm.POST("/limits", s.consoleUpdateLimits)
|
||||
}
|
||||
gm.GET("/complaints", s.consoleComplaints)
|
||||
gm.GET("/complaints/:id", s.consoleComplaintDetail)
|
||||
gm.POST("/complaints/:id/resolve", s.consoleResolveComplaint)
|
||||
@@ -1244,7 +1248,7 @@ func (s *Server) consoleUUID(c *gin.Context, back string) (uuid.UUID, bool) {
|
||||
|
||||
// gameRow projects a game summary into its console row.
|
||||
func gameRow(g game.Game) adminconsole.GameRow {
|
||||
return adminconsole.GameRow{ID: g.ID.String(), Variant: g.Variant.String(), Status: g.Status, Players: g.Players, UpdatedAt: fmtTime(g.UpdatedAt), VsAI: g.VsAI}
|
||||
return adminconsole.GameRow{ID: g.ID.String(), Variant: g.Variant.String(), Status: g.Status, Players: g.Players, UpdatedAt: fmtTime(g.UpdatedAt), VsAI: g.VsAI, Kind: g.Kind.String()}
|
||||
}
|
||||
|
||||
// trimForm returns the trimmed value of a posted form field.
|
||||
|
||||
Reference in New Issue
Block a user