Skip to content

Game management

Purpose

The Game Management area is the catalog control plane: the source-of-truth list of every game on the platform (in-house Plinko/Roulette/Mines/Keno/Limbo/Dice/Monkey-Run/Blackjack/Speed-Roulette + slot integrations BGaming/PM8/ST8/evogames + sportbook), with per-game commission, RTP curves, banner art, and "house settings" (theoretical RTP, max-multiplier, min/max bet). It also drives the home-page-grid.md because both surfaces share the same Game table.

Audience

Engineering (RTP / commission tuning), marketing (visibility / banner art), risk (max-multiplier and concentration controls).

Path in admin-fe

Screen URL Page
Game stats /game-management ebit-admin-fe/src/app/(dashboard)/game-management/page.tsx
Provider stats /games/stats/providers ebit-admin-fe/src/app/(dashboard)/games/stats/providers/page.tsx
Per-game detail /games/[slug] ebit-admin-fe/src/app/(dashboard)/games/[slug]/page.tsx
In-house games chart (RTP curves) /games-chart ebit-admin-fe/src/app/(dashboard)/games-chart/page.tsx
Slots provider hydrate /slots ebit-admin-fe/src/app/(dashboard)/slots/page.tsx

Backing API endpoints

Endpoint Source
GET /admin/casino/games/all (full catalog with stats) apps/api/src/casino/games/controller/games.admin.controller.ts:141
GET /admin/casino/games/:slug apps/api/src/casino/games/controller/games.admin.controller.ts:195
POST /admin/casino/games/:slug (update game) apps/api/src/casino/games/controller/games.admin.controller.ts:186
POST /admin/casino/games/main (home grid) apps/api/src/casino/games/controller/games.admin.controller.ts:60
POST /admin/casino/games/hydrate (refresh from providers) apps/api/src/casino/games/controller/games.admin.controller.ts:50
GET /admin/casino/games/providers apps/api/src/casino/games/controller/games.admin.controller.ts:68
GET /admin/casino/games/providers/:slug apps/api/src/casino/games/controller/games.admin.controller.ts:74
POST /admin/casino/games/providers/:slug/images apps/api/src/casino/games/controller/games.admin.controller.ts:81
POST /admin/casino/games/:slug/images apps/api/src/casino/games/controller/games.admin.controller.ts:153
PUT /admin/casino/games/commission (create commission rule) apps/api/src/casino/games/controller/games.admin.controller.ts:93
PATCH /admin/casino/games/commission (edit) apps/api/src/casino/games/controller/games.admin.controller.ts:106
DELETE /admin/casino/games/commission apps/api/src/casino/games/controller/games.admin.controller.ts:114
GET /admin/casino/games/commission (list) apps/api/src/casino/games/controller/games.admin.controller.ts:123
GET /admin/casino/games/commission/:id apps/api/src/casino/games/controller/games.admin.controller.ts:131
POST /admin/casino/games/house/settings (set house RTP / max-multiplier) apps/api/src/casino/games/controller/games.admin.controller.ts:166
GET /admin/casino/games/house/settings apps/api/src/casino/games/controller/games.admin.controller.ts:175
POST /admin/casino/slots/hydrate/st8 (ST8-specific) apps/api/src/casino/slots/admin.slot-games.controller.ts:30

Frontend wiring: ebit-admin-fe/src/queries/games/, queries/slots/, queries/game-management/.

Key actions

Action Required permission API call DB tables touched Audit-logged?
List catalog casino.games.view GET /admin/casino/games/all Game, GameProvider, joined stats yes
Drill one game casino.games.view GET /admin/casino/games/:slug same yes
Edit game (display name, max bet, RTP, etc.) casino.games.edit POST /admin/casino/games/:slug Game yes
Hydrate provider catalog (sync) casino.games.edit POST /admin/casino/games/hydrate Game, GameProvider (upsert) yes
Hydrate ST8 (provider-specific) casino.games.edit POST /admin/casino/slots/hydrate/st8 same, ST8-only yes
Set commission rule (per-game / -provider / -level) casino.games.commission.edit PUT /admin/casino/games/commission GameCommission yes
Edit commission casino.games.commission.edit PATCH /admin/casino/games/commission same yes
Delete commission casino.games.commission.edit DELETE /admin/casino/games/commission same yes
Set house settings (in-house RTP / max-multiplier / min-max bet) casino.games.edit POST /admin/casino/games/house/settings Game.houseSettings, Game.maxMultiplier yes
Upload game / provider banner casino.games.edit POST /admin/casino/games/:slug/images, POST /admin/casino/games/providers/:slug/images S3, Game.imageUrl, GameProvider.imageUrl yes
Home grid casino.games.edit POST /admin/casino/games/main Game.featured* yes

Filters and views

  • Search — slug, displayName.
  • Provider — house, BGaming, PM8, ST8, evogames, sportbook.
  • Status — enabled / disabled.
  • Game type — slots / house / sportbook.
  • Date range for stats — wager / NGR.
  • Sort — by wagerUSD, NGR, marginPercent, betCount.
  • Per-game stats panel — total wager, total NGR, theoretical RTP, actual RTP, top players. Wagering totals are derived from Bet.

Common workflows

  1. Adjust max-multiplier on Plinko. Risk decides Plinko's whale-protection cap should drop from 10000× to 5000×. Engineer opens /games/plinko, edits "House settings", sets maxMultiplier = 5000, saves. New bets reject above threshold (apps/api/src/casino/house/plinko/).
  2. Bump commission on slots launch. Marketing wants 5% commission on a new BGaming title. Engineer creates a GameCommission rule scoped to slug, commission = 5%. Backend writes GameCommission, applied on bet-settle.
  3. Hydrate after BGaming adds 12 new slots. Click Hydrate. Backend pulls catalog from BGaming, upserts Game rows. Marketing tweaks display names, then orders them in home-page-grid.md.
  4. Disable a misbehaving game. Edit game, set enabled=false. Player site filters it out. Existing bets continue (no force-cancel).
  5. Audit RTP drift. Open /games-chart, pick "All in-house". Compare actual RTP vs theoretical over the date range. Spike beyond ±2% prompts deeper investigation in bets-history.md.

Edge cases / gotchas

  • Hydrate is destructive for display-name overrides. If marketing customized displayName and engineering hits Hydrate, the override is lost. Plan around hydrate.
  • commission rules stack hierarchically. Game-specific > provider-specific > global. Conflicts are deterministic but easy to misread.
  • House settings only apply to in-house games. For slots, RTP is provider-controlled. The endpoint accepts the value but ignores it for slots.
  • Image upload doesn't validate aspect ratio. Banners must be 16:9 to fit hero carousel; thumbnails 1:1. Wrong ratios stretch.
  • Disabling a game does not refund in-flight bets. Bets currently in PENDING_SETTLE settle as normal.
  • RTP drift is normal at low volume. Don't panic on 30 bets; tune alarms based on confidence intervals.
  • Sportbook is special. It carries its own catalog and is mostly read-only here (sportbook events/markets live elsewhere).
  • POST /admin/casino/games/main shares the home-grid endpoint. Game-list edits and grid edits go through the same Game row updates.
  • Home page grid (uses same Game table): home-page-grid.md
  • Per-game bet drill-down: bets-history.md
  • Provably-fair (per-bet seed validation for in-house games): flows/ → provably-fair
  • Adding a new game (recipe): recipes/add-game.md, recipes/add-game-provider-integration.md
  • Provider integrations source: apps/api/src/casino/slots/providers/{bgaming,pm8,st8,evogames}
  • House games source: apps/api/src/casino/house/{plinko,roulette,mines,keno,limbo,dice,monkey-run,blackjack,speed-roulette-api}
  • Permission keys: libs/auth/src/permissions/const.ts:120-131
  • Audit: admin-logs.md

Sequence — adjusting Plinko max-multiplier

sequenceDiagram
    actor risk
    participant admin-fe
    participant api
    participant pg as Postgres
    risk->>admin-fe: open /games/plinko, House Settings tab
    admin-fe->>api: GET /admin/casino/games/house/settings (filter slug=plinko)
    api-->>admin-fe: current { theoreticalRTP, maxMultiplier, minBet, maxBet }
    risk->>admin-fe: edit maxMultiplier 10000 → 5000, save
    admin-fe->>api: POST /admin/casino/games/house/settings { slug: plinko, maxMultiplier: 5000 }
    api->>api: PermissionGuard('casino.games.edit')
    api->>pg: UPDATE Game SET houseSettings.maxMultiplier=5000 WHERE slug='plinko'
    api->>pg: INSERT AdminActionLog
    api-->>admin-fe: 200 OK
    Note over admin-fe,pg: bet-place service reads max on next bet