The operator portal (apps/portal) is built and live on this branch. It is a
standalone Cloudflare Worker that gives B2B operators a magic-link-authenticated window
into the Standout game catalogue — with per-game factsheets, permissioned embedded demos,
and a roadmap view.
| Feature | Detail |
|---|---|
| Magic-link login | Operator enters their email; Resend delivers a one-time link. The Worker validates the token and issues a signed session cookie. No passwords, no OAuth app to configure. |
| Game catalogue | A paginated table of all published games. Each row shows the game name, RTP, volatility, category, and a lock indicator for the operator's access grant. Rows link to the game factsheet. |
| Lock state per row | Each game row reflects the operator's current grant state: unlocked (demo access granted), locked (catalogue-visible but not demoed), or hidden (not published to this operator). The lock state is set by admins via the admin page. |
| Roadmap | A 6-month forward view of planned game releases and platform milestones, authored by the Standout team and published to operators. |
| Game factsheet | Per-game page with maths summary (RTP, volatility, max win), media (screenshots, video placeholder), and materials (spec sheet, jurisdiction matrix). Placeholders are shown where content is not yet published. |
| Permissioned embedded demos | If the operator has a demo grant for a game, the factsheet shows an embedded iframe of the live demo. The iframe URL is server-guarded — an unauthenticated or ungrant request returns 403. The admin page sets the demo URL and allocates per-operator demo access. |
| Admin page | Accessible to any @vhslab.com email or any email on the admin allowlist. Allows publishing games (setting their portal-visible status), setting the demo URL, setting a launch partner flag and launch date, and allocating per-operator demo access by email. |
| Per-game chat (upcoming) | A real-time chat panel on each factsheet connecting operators to the Standout team over Slack threads — see Portal ↔ Slack chat. Approved design, pending implementation. |
The portal uses three D1 tables as its own overlay on top of the shared games
table. The shared games table is the source of truth for game identity and
metadata; the portal tables add operator-specific state.
| Table | Key columns | Role |
|---|---|---|
games |
slug, title, rtp, volatility, category |
Shared source of truth. Seeded from the operator screenshot catalogue. Read-only from the portal's perspective. |
portal_games |
game_slug, published, demo_url, launch_partner, launch_date |
Portal-specific overlay. Controls whether a game is visible in the operator catalogue and what demo URL is served. |
portal_grants |
email, game_slug, granted_at |
Per-operator demo access grants. One row per (email, game_slug) pair. Checked on every factsheet and demo iframe request. |
portal_magic_links |
token, email, expires_at, used_at |
One-time login tokens. Tokens are single-use; used_at is set on redemption. Expired or already-used tokens return 401. |
[[d1_databases]]
in wrangler.toml with binding = "DB". No database_id
is hard-coded; the binding is resolved by Cloudflare at deploy. The same D1 instance is
shared with apps/web.
portal_magic_links row with a random token and 15-minute expiry, then calls Resend to send the link.used_at = now(), and issues a signed session cookie (HttpOnly, Secure, SameSite=Lax).verifySession() on every protected route; the email from the verified session is used to look up grants and admin status.
A session is treated as admin if the verified email ends in @vhslab.com OR
if the email is present in the hard-coded admin allowlist in the Worker. Admin sessions
can access the /admin route; non-admin sessions receive 403.
| Route | Who can access | What it shows |
|---|---|---|
/ |
Any authenticated operator | Game catalogue — published games with lock state per operator. |
/roadmap |
Any authenticated operator | 6-month forward view of planned releases and milestones. |
/games/:slug |
Any authenticated operator | Game factsheet — maths, media, materials, embedded demo if granted. |
/admin |
Admin emails only (@vhslab.com or allowlist) |
Publish games, set demo URLs, set launch partner/date, allocate per-email demo access. |
/api/portal/chat/ws |
Any authenticated operator (session cookie verified on upgrade) | WebSocket endpoint — routes to the ChatRoom Durable Object for real-time chat. See Portal ↔ Slack chat. (Upcoming.) |
/api/portal/slack/events |
Slack only (signature-verified, no session) | Inbound Slack Events API webhook — receives team replies and routes to the correct Durable Object. (Upcoming.) |