Cactus v10 — Architecture Reference
Plataforma multi-tenant iGaming 100% Cloudflare edge. Target: 100 brands, 30M usuários, 400M tx/dia.
1. Topologia global
Tudo no edge Cloudflare. Apex Worker é o único entry-point externo; faz service bindings pra todos os Workers especializados.
2. Workers index
Inventário completo. URLs internas via service binding; URLs externas via apex proxy.
| Worker | Função | Bindings | Endpoints públicos |
|---|---|---|---|
igaming-apex | Entry point, WAF, rate limit, geo, proxy | +15 service bindings, CONFIG_KV, RATELIM_KV | evian.bet/* |
igaming-auth | JWT, login, OTP, password reset | D1, CONFIG_KV, SESSIONS_KV, EVENTS_QUEUE | /api/auth/* |
igaming-wallet | Wallets, balances, transfers | D1, EVENTS_QUEUE | /api/wallet/* |
igaming-wallet-do | WalletDO single-writer per player | WALLET_DO (DO), SYNC_QUEUE, EVENTS_QUEUE | /wallet-do/{id}/{op} |
igaming-games | Crash/Mines/Tower + CrashRoundDO | CRASH_DO, WALLET_SERVICE, EVENTS_QUEUE | /api/games/* |
igaming-casino-orchestrator | Casino aggregator framework | D1, WALLET_SERVICE, EVENTS_QUEUE | /api/casino/* |
igaming-payments | Payment adapter framework | D1, WALLET_SERVICE, PAYMENT_EVENTS_QUEUE | /api/payments/* |
igaming-push | VAPID push notifications | D1, CONFIG_KV | /push/* |
igaming-comms | Email (Resend) + SMS (Twilio) + WhatsApp | D1, CONFIG_KV, SESSIONS_KV | /comms/* |
igaming-livechat | WebSocket chat + DM + ChatDO | CHAT_DO (DO), D1 | /api/chat/* |
igaming-social | Feed, posts, likes, comments | D1, EVENTS_QUEUE | /api/social/* |
igaming-reports | Analytics, BI reports MCP-shape | D1, AE (Analytics Engine) | /api/reports/* |
igaming-kyc | KYC tiers via Sumsub | D1, R2 (kyc docs), EVENTS_QUEUE | /api/kyc/* |
igaming-bonus | Bonus engine + wagering tracker | D1, WALLET_SERVICE, EVENTS_QUEUE | /api/bonus/* |
igaming-vip | VIP tiers + loyalty points | D1, EVENTS_QUEUE | /api/vip/* |
igaming-rg | Responsible Gaming limits | D1, EVENTS_QUEUE | /api/rg/* |
igaming-affiliate | Referral + CPA + RevShare | D1, EVENTS_QUEUE | /api/affiliate/* |
igaming-gdpr | Data export + right-to-deletion | D1, R2 exports, cron daily | /api/me/data-export |
igaming-backoffice | Admin auth + tenants + audit | D1, CONFIG_KV | /api/bo/* |
igaming-monitor | Status page + uptime cron | D1, CONFIG_KV, cron 2min | /monitor/status |
igaming-d1-batcher | Queue → batched INSERT D1 | 5x D1 (all shards), CONFIG_KV | queue consumer |
igaming-archiver | D1 hot → R2 Iceberg cron | 5x D1, R2, cron 03:00 UTC | /archive/* |
igaming-do-backup | WalletDO snapshot weekly | WALLET_DO_SERVICE, R2, cron SUN 04:00 | /backup/* |
igaming-analytics-consumer | Queue events → D1 + AE | D1, AE | queue consumer |
igaming-kanban-api | Kanban backend (boards/cards) | D1, CONFIG_KV | /api/kanban/* |
3. Request flow — apex middleware stack
Toda request HTTP entra pelo apex e passa por 4 camadas antes de chegar no Worker downstream.
(scanner UA, threat_score) CF->>Apex: forward Apex->>KV: rate limit check (KV-backed sliding window) alt rate limit excedido KV-->>Apex: 429 Apex-->>Client: 429 Too Many Requests end Apex->>KV: tenant lookup (hostname → tenant_id) Apex->>KV: geo-block check (CF-IPCountry vs tenant.restricted_countries) alt geo blocked Apex-->>Client: 451 Unavailable For Legal Reasons end Apex->>Apex: enrich headers (X-CF-Country/City/ASN) Apex->>DS: service binding fetch (proxyReq) DS-->>Apex: response Apex-->>Client: response + CORS headers
4. Crash WebSocket — bet + cashout em ~30ms
Player abre WS persistente. Bet/cashout viajam via WS já aberto (sem TCP handshake) com ACK direto pro socket. Wallet credit em waitUntil.
5. WalletDO sync pipeline
Single-writer per player. SQLite local no DO (consistency forte). Sync periódico pra D1 via queue.
6. Payment routing engine
Adapter pattern + routing por priority+weight+circuit breaker+failover. 7 providers no catálogo (mock, pix-direct, stripe, mercadopago, btag, coinbase, nowpayments).
7. Casino launch + callback
Player lança game → orchestrator gera session → provider iframe URL. Provider chama nosso wallet via callback HMAC.
8. D1 sharding strategy
Tenant_id → brand shard. Player_id → ledger shard via crc32(). Mapeamento em CONFIG_KV[arch:shard_map] com cache 60s.
s0_legacy
evian_bet] B1[cactus-brands-s1
brands 1-20] B2[cactus-brands-s2
brands 21-40] end subgraph "Ledger shards (per-player_id)" L0[player-ledger
s0_legacy
crc32 mod 2 = 0] L1[cactus-ledger-s1
s1
crc32 mod 2 = 1] end subgraph "Global (small tables)" G1[(tenants)] G2[(admin_users)] G3[(payment_providers)] G4[(casino_providers)] end Router -->|getBrandDb tenant_id| B0 Router --> B1 Router --> B2 Router -->|getLedgerDbForPlayer pid| L0 Router --> L1 Router -->|getGlobalDb| G1
9. Archive D1 hot → R2 Iceberg (nightly)
Cron 03:00 UTC. Move rows com archived=0 AND created_at < 90d pra R2 em layout Iceberg-compatible (JSONL.gz). DELETE com grace 10d.
warehouse/tenant=X/database=Y/table=Z/year/month/day/part-uuid.jsonl.gz] R2Put --> Snapshot[Iceberg snapshot manifest
_metadata/snapshot-ts-shard.json] Snapshot --> Mark[UPDATE archived=1] Mark --> Delete[DELETE WHERE archived=1 AND created_at < 100d] style Cron fill:#f59e0b,color:#000 style R2Put fill:#22c55e,color:#000
10. GDPR data export self-service
Player baixa ZIP JSON.gz com TODOS seus dados em <30s. Right-to-deletion com 14d cooldown.
11. Push notifications flow (VAPID)
VAPID via Web Crypto (sem deps). Anonymous subscribe permitido (device_id link).
12. WAF & Security layers
4 camadas de defesa. Per-tenant geo dinâmico via CONFIG_KV[tenant:X:config].restricted_countries.
| Camada | Implementação | Status |
|---|---|---|
| 1. CF Zone WAF | 5 custom rules (UA scanner, threat_score, geo edge) | ✅ Active |
| 2. Rate Limit Worker | 16 rules KV-backed (login 10/60s, withdraw 5/60s, OTP 3/5min, generic 600/min) | ✅ Active |
| 3. Geo-block per-tenant | Runtime check CF-IPCountry vs tenant.restricted_countries → HTTP 451 | ✅ Active |
| 4. CORS + Origin validation | Apex middleware bloqueia mutating requests com Origin inesperado | ✅ Active |
| 5. Bot Fight Mode | CF nativo + UA validation no apex | ✅ Active |
| 6. Service Binding gateway | Workers só falam entre si via service bindings (sem fetch HTTP loopback CF 1042) | ✅ Active |
| 7. Circuit Breakers | KV state machine per (provider×tenant), 5 falhas/60s → open 30s | ✅ Active |
13. Events pipeline (200+ event types)
Toda ação importante emite event. Pipeline: Worker → Queue → Consumer → D1 + Analytics Engine.
batch 100 / 5s] Consumer --> AE[(Analytics Engine
5M writes/s capacity)] Consumer --> D1[(D1 events_recent_90d)] AE -->|SQL aggregate| Reports[igaming-reports] D1 -->|recent queries| Reports Reports -->|BI dashboards| BO[Backoffice /bo/reports]
14. Deployment + CI/CD
Tudo via wrangler. Pages Projects pra frontends. D1 + R2 + KV criados via CF API.
| Asset | Tipo | URL |
|---|---|---|
| evian-bet-frontend | Pages | staging.evian.bet |
| cactus-v10-backoffice | Pages | bo.cactus-v10.com |
| igaming-docs | Pages | docs-new-stack.cactus-v10.com |
| cactus-kanban | Pages | kanban.cactus-v10.com |
| cactus-docs (esta página) | Pages | docs.cactus-v10.com |
15. API endpoints index
Snapshot dos endpoints mais importantes. Lista completa em /api/admin/openapi (TODO).
Auth
POST /api/auth/register POST /api/auth/login POST /api/auth/logout POST /api/auth/refresh POST /api/auth/forgot-password POST /api/auth/reset-password POST /api/auth/2fa/setup GET /api/auth/me
Wallet
GET /api/wallet/balance GET /api/wallet/balances GET /api/wallet/history?limit=&offset= POST /api/wallet/transfer POST /api/wallet/exchange
Games (Crash/Mines/Tower)
WS /api/games/crash/connect?token=JWT
ws msg: {type: 'bet'|'cashout'|'ping'}
ws ack: {type: 'bet_ack'|'cashout_ack'|'pong'}
GET /api/games/crash/state
GET /api/games/crash/history
POST /api/games/crash/bet (fallback)
POST /api/games/crash/cashout (fallback)
POST /api/games/mines/start
POST /api/games/mines/reveal
POST /api/games/tower/start
POST /api/games/tower/reveal
POST /api/games/verify (provably fair)
Payments
GET /api/payments/methods?country=BR¤cy=BRL POST /api/payments/deposit POST /api/payments/withdrawal POST /api/payments/webhook/:provider GET /api/admin/payments/providers GET /api/admin/payments/tenants/:tid/config PATCH /api/admin/payments/tenants/:tid/config POST /api/admin/payments/test/:provider GET /api/admin/payments/circuit-breakers
Casino
GET /api/casino/games?category=slots&provider=&limit= POST /api/casino/launch/:game_id POST /api/casino/callback/:provider GET /api/admin/casino/providers GET /api/admin/casino/tenants/:tid/config POST /api/admin/casino/sync/:provider
GDPR / Player rights
POST /api/me/data-export GET /api/me/data-export/jobs GET /api/me/data-export/jobs/:id GET /api/me/data-export/download/:id POST /api/me/account-deletion GET /api/me/account-deletion DELETE /api/me/account-deletion
Backoffice + System
GET /api/bo/players?search=&status= GET /api/bo/players/:id/full POST /api/bo/players/:id/lock POST /api/bo/players/:id/credit GET /api/admin/system/tenants PATCH /api/admin/system/tenants/:id GET /api/reports/overview GET /monitor/status (público) GET /api/kanban/boards GET /api/kanban/boards/:slug
16. Events catalog (300+ events, 29 namespaces)
Sistema 100% orientado a eventos no shape bigtec (PostHog/Mixpanel/Amplitude). Source: evian-bet-frontend/src/lib/events.ts. Pipeline: track() → POST /api/events/track → Queue igaming-events → Consumer → D1 events_recent_90d + Analytics Engine.
Universal envelope
Toda chamada de emit.{namespace}.{action}({data}) adiciona automaticamente:
{
event_type: 'bet.placed',
ts_client: 1716922800000,
session_id: 'sess_uuid',
device_id: 'dev_uuid',
player_id: 'plr_uuid' | null,
page_url, page_path, referrer,
user_agent_hash, viewport,
utm_source, utm_medium, utm_campaign, utm_content, utm_term,
// ↓ added server-side from CF
ip_country, ip_city, ip_region, ip_asn, ip_isp,
// ↓ event-specific payload
data: { game_id, bet_id, amount, currency, ... }
}
29 Namespaces
Eventos de uso
import { emit } from '@/lib/events'
emit.bet.placed({ game_id: 'crash', bet_id, amount: 50, currency: 'BRL', via: 'ws' })
emit.funnel.firstDepositCompleted({ time_to_first_deposit_sec: 142, amount_minor: 5000, currency: 'BRL' })
emit.fraud_signal.vpnDetected({ provider: 'NordVPN' })
emit.experiment.assigned({ experiment: 'new-cashout-button-color', variant: 'green' })
emit.performance.webVitalLcp({ value_ms: 2400, path: '/cassino', rating: 'good' })
17. Filters & dimensions (analytics)
Toda query Reports/BI no BO suporta esses filtros. Documenta o esquema de cubo OLAP.
Dimensões (filtros)
| Categoria | Dimensões | Origem |
|---|---|---|
| Identidade | tenant_id, player_id, session_id, device_id, anonymous_id | Envelope universal |
| Geo | ip_country, ip_region, ip_city, ip_postal_code, ip_timezone, ip_asn, ip_isp | CF cf-data headers |
| Dispositivo | device_type (desktop/mobile/tablet), os, browser, browser_version, viewport_w, viewport_h, pixel_ratio | UA parsing + JS |
| Attribution | utm_source, utm_medium, utm_campaign, utm_content, utm_term, referrer, landing_page, gclid, fbclid, affiliate_code | URL params + localStorage first_touch |
| Perfil | vip_level, kyc_level, language, primary_currency, registration_date_bucket, age_bucket, gender, segment_id[] | players table join |
| Tempo | hour, day, week, month, quarter, year, day_of_week, hour_of_day | Derivado de ts_client / ts_server |
| Game | game_id, provider_id, category, subcategory, currency_played, bet_size_bucket, rtp_bucket, volatility | casino_games_catalog join |
| Comportamento | session_count_bucket, ltv_bucket, days_since_last_session, days_since_first_deposit, churn_risk_score | Computed |
| Experimento | experiment_assignments[] (active variants) | Workers experiment binding |
| A/B segments | custom_segment[] (e.g. "high_value", "at_risk", "vip_diamond") | Computed via segmentation worker |
Métricas (aggregations)
| Métrica | Cálculo | Where |
|---|---|---|
| DAU/WAU/MAU | COUNT(DISTINCT player_id) by day/week/month | events_recent |
| Sessions | COUNT(DISTINCT session_id) | events_recent |
| Avg session duration | AVG(session.end.duration_ms) | events session.end |
| Page views per session | SUM(page.view) / sessions | events page.view |
| Conversion rate signup→deposit | funnel.first_deposit_completed / funnel.signup_completed | events funnel.* |
| D1/D7/D30/D90 retention | Cohort matrix: % returning N days after signup | events signup.completed + session.start |
| LTV | SUM(deposits) - SUM(withdrawals) per player | ledger_entries |
| ARPU/ARPPU | NGR / total_users e NGR / paying_users | ledger_entries |
| GGR | SUM(bet.amount) - SUM(win_amount) | events bet.settled + bet.placed |
| NGR | GGR - bonuses - fees | computed |
| RTP real | SUM(win) / SUM(bet) | events bet.* |
| Churn rate | % players inactive > 30d | last_login_at |
| Latency P50/P90/P99 | Histogram percentiles em events performance.api_latency_slow | Analytics Engine |
| Push CTR | notification_clicked / notification_displayed | events push.* |
| Email open rate | email_opened / email_delivered | events comms.* |
| Bet decision time | Histogram bet.cashout_clicked.decision_time_ms | Analytics Engine |
Exemplos SQL (Analytics Engine)
-- DAU últimos 30d por country SELECT blob1 AS country, formatDateTime(timestamp, '%Y-%m-%d') AS day, COUNT(DISTINCT blob2) AS dau FROM events_analytics WHERE timestamp > now() - INTERVAL '30' DAY AND blob3 = 'session.start' GROUP BY country, day ORDER BY day DESC, dau DESC; -- Conversion funnel signup → first deposit por UTM source SELECT blob4 AS utm_source, countIf(blob3 = 'funnel.signup_completed') AS signups, countIf(blob3 = 'funnel.first_deposit_completed') AS first_deposits, countIf(blob3 = 'funnel.first_deposit_completed') / countIf(blob3 = 'funnel.signup_completed') AS conv_rate FROM events_analytics WHERE timestamp > now() - INTERVAL '7' DAY GROUP BY utm_source ORDER BY signups DESC; -- Bet decision time histogram (P50/P90/P99) SELECT quantile(0.5)(double1) AS p50_ms, quantile(0.9)(double1) AS p90_ms, quantile(0.99)(double1) AS p99_ms FROM events_analytics WHERE blob3 = 'bet.cashout_clicked' AND timestamp > now() - INTERVAL '24' HOUR;
18. Backoffice inventory (12 BOs + System)
| BO | Nome | Status | URL |
|---|---|---|---|
| BO-01 | Player Management (360°) | ● Live | /bo/player · 8 tabs · 22 endpoints |
| BO-02 | Finance | ● Live (refinement pending) | /bo/finance · /providers |
| BO-03 | Bonus Management | ● Live | /bo/bonus · catálogo + analytics + grant manual |
| BO-04 | Marketing / CRM | ○ Stub | K-021 (backlog) |
| BO-05 | Anti-Fraud | ● Live (basic) | /bo/fraud · 6 tabs |
| BO-06 | Risk Management | ○ Stub | K-022 (backlog) |
| BO-07 | KYC Operations | ● Partial | K-023 (expansão pendente) |
| BO-08 | Affiliate Management | ○ Stub | K-024 (backlog) |
| BO-09 | Content (Games/Banners/CMS/Tema) | ● Live | /bo/content |
| BO-10 | Reports & BI | ● Live | /bo/reports · 12 KPIs + cohort + funnel |
| BO-11 | Compliance & Regulatory Reports | ○ Stub | K-025 (backlog) |
| BO-12 | Operations Dashboard | ○ Stub | K-026 (backlog) |
| System (super_admin) | |||
| SYS | Overview / Admin Users / Audit | ● Live | /system/{overview,users,audit} |
| SYS | Tenants Management | ● Live | /system/tenants + editor com 11 tabs |
| SYS | Analytics & Realtime | ● Live | /system/analytics, /realtime |
| SYS | CMS | ● Live | /system/cms |
| SYS | Status & Monitor | ● Live | /system/status · 19 services monitored |
| SYS | WAF & Security | ● Live | /system/waf · zone rules + apex middlewares + per-tenant |
| SYS | Payment Providers | ● Live | /system/payment-providers · 7 providers no catálogo |
| SYS | Casino Providers | ● Live | /system/casino-providers · 8 providers |
19. Data model — D1 tabelas críticas
D1 tables: 37 em platform-main + 8 em player-ledger + 11 em cactus-brands-s1/s2 + 4 em cactus-ledger-s1 + 5 em cactus-kanban = ~65 tables distribuídas por sharding.