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¶
- 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", setsmaxMultiplier = 5000, saves. New bets reject above threshold (apps/api/src/casino/house/plinko/). - Bump commission on slots launch. Marketing wants 5% commission on a new BGaming title. Engineer creates a
GameCommissionrule scoped to slug, commission = 5%. Backend writesGameCommission, applied on bet-settle. - Hydrate after BGaming adds 12 new slots. Click Hydrate. Backend pulls catalog from BGaming, upserts
Gamerows. Marketing tweaks display names, then orders them in home-page-grid.md. - Disable a misbehaving game. Edit game, set
enabled=false. Player site filters it out. Existing bets continue (no force-cancel). - 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
displayNameand engineering hits Hydrate, the override is lost. Plan around hydrate. commissionrules 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_SETTLEsettle 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/mainshares the home-grid endpoint. Game-list edits and grid edits go through the sameGamerow updates.
Cross-links¶
- Home page grid (uses same
Gametable): 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