- Legal: /privacy-policy and /datadeletion (shared LegalPageLayout, footer links, sitemap + integration tests, NEXT_PUBLIC_LEGAL_CONTACT_EMAIL). - Threads OAuth: server-side /api/threads-exchange and /threads-callback page (Suspense + client component, noindex, no leaked secrets). - Username + live wallet pill in top nav. New NavWalletBalance component polls /api/wallet, refreshes on tab focus, and listens to the `wallet:refresh` event bus so cash-outs and refunds update the nav in real time. Flash animation on balance changes. - useLiveWalletBalance hook now broadcasts `wallet:refresh` after every fetch so games, exchange panel, wallet actions, and nav all stay in sync without extra polling. - PvP fund-locking (`POST /api/games/rooms`): creator funds debited atomically with room creation; ledger entry tagged with `gameRoomId`. Joiner debit happens at join. Old double-debit of the creator is gone. - DELETE /api/games/rooms?id=... lets a creator cancel a WAITING room and get an idempotent refund. Coin Flip + Pong waiting screens show a Cancel & Refund button. - Pong/Coin Flip recovery: expiry sweep + boot-time `recoverOrphaned RoomsOnBoot()` (runs before listen()) refund both parties for any ACTIVE/expired rooms so a server restart never strands locked credits. - Schema migration `20260520000000_game_ledger_links` adds optional `gameSessionId` + `gameRoomId` FKs to LedgerEntry (with indexes) and extra indexes on GameSession/GameRoom for resume + sweep queries. - GET /api/games/active returns a user's active solo session + open rooms (sanitized — no mine/bomb positions). Mines and Tower clients rehydrate on mount so a refresh mid-round resumes instead of dropping. - ActiveGamesBanner surfaces unfinished rounds on /wallet and /casino with Resume / Rejoin / Cancel & refund actions. - ExchangePanel unified with useLiveWalletBalance; per-game header gets an "Open wallet →" chip; Dice clears stale result on roll; Blackjack reveals full dealer hand on natural blackjack/push; Mines refund label fixed; Tower final multiplier fixed; GameHistory error path; Prediction "Resolved" tab. - Site audit + redmeFIXES triage notes (REDME-FIXSES-TRIAGE.txt, SITE-AUDIT.txt). Co-authored-by: Cursor <cursoragent@cursor.com>
741 lines
33 KiB
Plaintext
741 lines
33 KiB
Plaintext
================================================================================
|
|
DEMOCRACY RISING / FUNDRAISING PLATFORM — DEEP SITE AUDIT
|
|
================================================================================
|
|
Generated: 2026-05-19 (UTC)
|
|
Scope: /root/fundraising-platform (source, config, Prisma, scripts, runtime)
|
|
Machine: Linux (Proxmox LXC), 4 vCPU, ~7.8 GiB RAM, 64 GiB disk (~7% used)
|
|
Service: fundraising-platform.service → npm run start → tsx server.ts :8008
|
|
Public: https://bwt.democracyrisingbwt.us (Cloudflare proxy, not direct to origin)
|
|
Typecheck: npm run lint (tsc --noEmit) — PASS at audit time
|
|
Build: npm run build — PASS (includes /threads-callback, /api/threads-exchange)
|
|
|
|
Severity legend:
|
|
[CRITICAL] Exploit, money loss, secret exposure, or production outage risk
|
|
[HIGH] Broken feature, data corruption risk, or major UX failure
|
|
[MEDIUM] Correctness edge case, maintainability, or meaningful perf waste
|
|
[LOW] Polish, SEO, docs, minor duplication
|
|
|
|
================================================================================
|
|
0. EXECUTIVE SUMMARY
|
|
================================================================================
|
|
|
|
This is a Next.js 16 App Router political fundraising app with:
|
|
- Stripe donations + webhooks
|
|
- Supporter credits (BWT) wallet + ledger
|
|
- 10 casino-style games (7 REST solo, 2 Socket.IO PvP, 1 prediction market)
|
|
- Community features (billboard, spotlight, cards, FAQ board, boost meter)
|
|
|
|
Overall code quality: TypeScript compiles clean; core wallet debits use atomic
|
|
updateMany; many game routes use optimistic locking (updateMany + outcome:"active").
|
|
The largest risks are DEPLOYMENT/DNS (Cloudflare serving stale static pages),
|
|
SOCKET/PONG in-memory state (single-process assumption), BLACKJACK transaction
|
|
gaps, and CLIENT-SIDE performance (always-on canvas animations + polling).
|
|
|
|
Approximate codebase: ~15,246 lines in src/**/*.ts(x), 561M node_modules, 1.1G .next
|
|
|
|
Top 10 actions (ordered):
|
|
1. Fix Cloudflare routing for bwt.democracyrisingbwt.us → origin :8008 (not static HTML)
|
|
2. Align NEXT_PUBLIC_SITE_URL / AUTH_URL with real public hostname
|
|
3. Rotate any secrets ever committed or logged (.env, old Threads static page)
|
|
4. Wrap blackjack natural/push/resolve in single DB transactions
|
|
5. Add gameSessionId FK on ledger entries for reconciliation
|
|
6. Document/require npm run start (not dev:next) for Socket.IO games
|
|
7. Reduce homepage/global canvas + polling load on this 8GB box
|
|
8. Delete or wire DonationCheckout.tsx (~400 lines dead)
|
|
9. Harden coin-flip/pong payout failure paths (room stuck ACTIVE)
|
|
10. Add rate limits on /api/register and game POST endpoints
|
|
|
|
================================================================================
|
|
1. DEPLOYMENT & INFRASTRUCTURE (CRITICAL FOR LIVE SITE)
|
|
================================================================================
|
|
|
|
1.1 [CRITICAL] Cloudflare intercepts traffic before Next.js
|
|
------------------------------------------------------------------------------
|
|
Symptom verified on live host:
|
|
- https://bwt.democracyrisingbwt.us/datadeletion → was 404 until service restart;
|
|
may still vary by cache
|
|
- https://bwt.democracyrisingbwt.us/threads-callback → serves OLD static HTML
|
|
("Threads Bot Setup", client-side secret exchange) NOT the Next.js route
|
|
- curl shows: server: cloudflare, cf-ray present
|
|
- DNS: bwt.democracyrisingbwt.us → 104.21.29.229 / 172.67.171.225 (Cloudflare)
|
|
- Origin direct :8008 on public IP → connection refused (port not exposed)
|
|
|
|
Local origin (correct):
|
|
- http://127.0.0.1:8008/threads-callback → Next.js page "Threads callback"
|
|
- http://127.0.0.1:8008/datadeletion → legal page
|
|
|
|
Old static /threads-callback HTML (served via Cloudflare) contains:
|
|
- client_id in page JS
|
|
- client_secret in browser fetch to graph.threads.net (SECURITY DISASTER)
|
|
- Inline OAuth flow bypassing /api/threads-exchange
|
|
|
|
Action: In Cloudflare dashboard, remove Workers/Pages/static asset rules for
|
|
/threads-callback, /datadeletion, /api/*; proxy pass-through to origin tunnel
|
|
or open 8008 via cloudflared. Purge cache after fix.
|
|
|
|
Files (new, correct): src/app/threads-callback/*, src/app/api/threads-exchange/route.ts
|
|
|
|
1.2 [HIGH] Environment URL mismatch
|
|
------------------------------------------------------------------------------
|
|
.env currently has:
|
|
NEXT_PUBLIC_SITE_URL=https://democratic.thetempleofdoom.com
|
|
Public users use:
|
|
https://bwt.democracyrisingbwt.us
|
|
|
|
Impact:
|
|
- Canonical URLs, Open Graph, JSON-LD, sitemap base wrong for BWT subdomain
|
|
- Socket.IO CORS allowedOrigin may reject browser if users hit bwt.* but env says democratic.*
|
|
- Stripe/NextAuth redirect_uri mismatches if callbacks use SITE_URL
|
|
|
|
Action: Set NEXT_PUBLIC_SITE_URL, NEXTAUTH_URL, AUTH_URL to the hostname users
|
|
actually use (or support multiple origins in server.ts CORS).
|
|
|
|
Reference: server.ts lines 18-22 (allowedOrigin), src/lib/public-env.ts
|
|
|
|
1.3 [MEDIUM] Single-process architecture
|
|
------------------------------------------------------------------------------
|
|
server.ts holds:
|
|
- pongRooms Map + setInterval game loops (20 Hz per active room)
|
|
- coin-flip resolution inline on join
|
|
- No Redis, no horizontal scaling
|
|
|
|
If you scale to 2+ Node processes: PvP games break, rooms split-brain.
|
|
|
|
Action: Document "single instance only" or migrate room state to Redis.
|
|
|
|
1.4 [LOW] systemd unit note
|
|
------------------------------------------------------------------------------
|
|
/etc/systemd/system/fundraising-platform.service uses npm run start (good).
|
|
Unknown key StartLimitIntervalSec ignored on older systemd — harmless.
|
|
|
|
NODE_OPTIONS=--max-old-space-size=8192 on an 7.8GB machine leaves little headroom
|
|
for PostgreSQL + OS. Consider 4096 unless you see OOM during build.
|
|
|
|
================================================================================
|
|
2. SECURITY
|
|
================================================================================
|
|
|
|
2.1 [CRITICAL] Secrets exposure vectors
|
|
------------------------------------------------------------------------------
|
|
- Old Cloudflare-hosted threads-callback HTML embeds THREADS client_secret in JS
|
|
- .env on server contains live Stripe sk_live_* (never commit; restrict file perms)
|
|
- Threads credentials in .env (THREADS_CLIENT_ID, THREADS_CLIENT_SECRET) — OK if
|
|
server-only; ensure .gitignore blocks .env (it does)
|
|
|
|
Action: Rotate Threads app secret + Stripe keys if static page was ever public.
|
|
chmod 600 /root/fundraising-platform/.env
|
|
|
|
2.2 [HIGH] No global API rate limiting
|
|
------------------------------------------------------------------------------
|
|
Unauthenticated or session-authenticated endpoints can be spammed:
|
|
- POST /api/register
|
|
- POST /api/games/* (each debits DB)
|
|
- POST /api/faq, /api/billboard, etc.
|
|
|
|
Action: nginx/cloudflare rate limits, or middleware with IP buckets.
|
|
|
|
2.3 [MEDIUM] Auth middleware disabled
|
|
------------------------------------------------------------------------------
|
|
src/middleware.ts → matcher: [] — no edge protection.
|
|
All auth is per-route auth() calls. Easy to forget on new routes.
|
|
|
|
Wallet page protected in page.tsx; most /api/* check session — good pattern but
|
|
not enforced centrally.
|
|
|
|
2.4 [MEDIUM] FAQ pending submissions
|
|
------------------------------------------------------------------------------
|
|
src/app/api/faq/route.ts GET:
|
|
- pending[] only returned when signedIn (line 29-44) — IMPROVED vs old audit
|
|
- Still: any logged-in user sees ALL pending submissions (moderation leak)
|
|
|
|
Action: pending visible to admin role only.
|
|
|
|
2.5 [MEDIUM] Socket.IO auth — PARTIALLY FIXED
|
|
------------------------------------------------------------------------------
|
|
server.ts resolveSocketUserId() uses getToken from cookie — GOOD.
|
|
CoinFlipRoom no longer sends client userId in join_room — GOOD.
|
|
|
|
Remaining:
|
|
- CORS origin single string; subdomains need array
|
|
- No explicit origin check on Engine.IO handshake beyond cors config
|
|
|
|
2.6 [LOW] Prediction market trust model
|
|
------------------------------------------------------------------------------
|
|
Creator resolves outcome (PATCH). No oracle — fine for demo, risky for prod.
|
|
|
|
2.7 [LOW] No email verification enforced
|
|
------------------------------------------------------------------------------
|
|
User.emailVerified in schema unused in credentials flow.
|
|
|
|
================================================================================
|
|
3. DEAD CODE, DUPLICATION, ORPHANS
|
|
================================================================================
|
|
|
|
3.1 [HIGH] Unused component — DonationCheckout.tsx
|
|
------------------------------------------------------------------------------
|
|
Path: src/components/DonationCheckout.tsx (~400 lines)
|
|
Imports: NONE in app (only EmbeddedDonationCheckout used)
|
|
- src/app/donate/page.tsx → EmbeddedDonationCheckout
|
|
- src/components/DonateSection.tsx → EmbeddedDonationCheckout
|
|
|
|
Action: Delete file or merge unique features into EmbeddedDonationCheckout.
|
|
|
|
3.2 [MEDIUM] Unused exports / dead helpers
|
|
------------------------------------------------------------------------------
|
|
- src/lib/auth-links.ts: LOGIN_RETURN_WALLET, LOGIN_RETURN_RAISED — no imports
|
|
- src/app/api/games/dice/route.ts: imports hashServerSeed, never used
|
|
- src/app/api/games/tower/route.ts: SAFE_PER_FLOOR constant was reported unused
|
|
(verify before delete)
|
|
- src/lib/exchange.ts: @deprecated BLW_* aliases — internal only
|
|
|
|
3.3 [MEDIUM] Duplicated game metadata
|
|
------------------------------------------------------------------------------
|
|
- src/app/casino/page.tsx — GAMES array (lobby cards)
|
|
- src/app/casino/[game]/page.tsx — GAME_META record
|
|
- src/components/casino/GameHistory.tsx — GAME_ICONS
|
|
|
|
Any new game requires 3 edits. Extract shared catalog module.
|
|
|
|
3.4 [MEDIUM] Duplicated settlement logic
|
|
------------------------------------------------------------------------------
|
|
mines/route.ts and tower/route.ts — nearly identical cashout/win transactions.
|
|
dice, slots, roulette — copy-paste instant-game transaction blocks.
|
|
crash/mines/tower use creditWalletCredits + manual ledger; dice uses same;
|
|
blackjack uses creditForWin/refundBet from game-ledger — INCONSISTENT.
|
|
|
|
Action: Single settleGameWin() / settleGameLoss() in game-ledger.ts.
|
|
|
|
3.5 [LOW] redmeFIXSES.md partially stale
|
|
------------------------------------------------------------------------------
|
|
Section 3 claims API-only features have no UI — FALSE for:
|
|
billboard, spotlight, cards, faq-board, boost (all have pages + fetch)
|
|
Section 1.1 socket userId spoof — FIXED in current server.ts
|
|
Section 1.2 CORS * — FIXED (restricted origin)
|
|
Section 2.1 public stats — BOTH use status:"succeeded" now
|
|
|
|
Keep redmeFIXSES.md or merge into this audit after triage.
|
|
|
|
================================================================================
|
|
4. GAME-BY-GAME AUDIT (LOGIC, BUGS, EXPLOITS)
|
|
================================================================================
|
|
|
|
Shared infrastructure:
|
|
- lib/provably-fair.ts — HMAC-SHA256, crash 3% edge, deriveMinePositions
|
|
- lib/game-ledger.ts — debitForBet, creditForWin, refundBet
|
|
- lib/wallet-safety.ts — atomic debit via updateMany gte check
|
|
- prisma GameSession — @@index([userId]), @@index([userId, gameType])
|
|
|
|
------------------------------------------------------------------------------
|
|
4.1 CRASH (REST: POST + PATCH) — src/app/api/games/crash/route.ts
|
|
------------------------------------------------------------------------------
|
|
Client: src/components/casino/CrashGame.tsx
|
|
|
|
Behavior:
|
|
- POST debits wager, stores crashAt in resultData (hidden until loss/cashout)
|
|
- PATCH tick: client sends currentMultiplier; server settles loss if >= crashAt
|
|
- PATCH cashout: pays floor(wager * cashoutAt)
|
|
|
|
Issues:
|
|
[MEDIUM] cashOut() sends `cashoutAt: multiplier` (React state) while tick probe
|
|
uses multiplierRef.current — possible 1-frame desync under load.
|
|
Fix: always send multiplierRef.current on cashout.
|
|
|
|
[MEDIUM] Tick interval every 220ms while running = ~4.5 req/s per active player.
|
|
Many concurrent crash players = DB load. Consider server-side timer or WS.
|
|
|
|
[LOW] Animation runs to 100x client-side even if crashAt is 2x — cosmetic only
|
|
until tick returns loss.
|
|
|
|
[LOW] No max wager cap server-side beyond integer >= 1.
|
|
|
|
Provably fair: serverSeed returned on end — good for verification.
|
|
|
|
------------------------------------------------------------------------------
|
|
4.2 DICE (REST: POST instant) — src/app/api/games/dice/route.ts
|
|
------------------------------------------------------------------------------
|
|
Client: src/components/casino/DiceGame.tsx
|
|
|
|
Issues:
|
|
[LOW] Unused import hashServerSeed (no commit hash returned to client)
|
|
[LOW] No serverSeedHash at bet time — weaker commit-reveal than mines/crash
|
|
|
|
Logic: roll 0-99, threshold 2-98, multiplier = 0.98/winProb — OK.
|
|
|
|
Transaction: single prisma.$transaction — GOOD.
|
|
|
|
------------------------------------------------------------------------------
|
|
4.3 MINES (REST: POST + PATCH) — src/app/api/games/mines/route.ts
|
|
------------------------------------------------------------------------------
|
|
Client: src/components/casino/MinesGame.tsx
|
|
|
|
Issues:
|
|
[MEDIUM] Loss path uses prisma.gameSession.update (not updateMany) — double-tile
|
|
race could overwrite outcome if two PATCHes in flight.
|
|
|
|
[MEDIUM] calcMultiplier() uses inverted product formula — verify house edge
|
|
matches advertised 1%; not unit tested.
|
|
|
|
[LOW] Cashout with 0 reveals refunds full wager (push) — intentional?
|
|
|
|
[LOW] minePositions sent to client on loss/cashout — correct for transparency;
|
|
during active play positions hidden — GOOD.
|
|
|
|
------------------------------------------------------------------------------
|
|
4.4 TOWER (REST: POST + PATCH) — src/app/api/games/tower/route.ts
|
|
------------------------------------------------------------------------------
|
|
Client: src/components/casino/TowerGame.tsx
|
|
|
|
Same settlement duplication as mines. Pre-generated bomb positions per floor.
|
|
|
|
Issues:
|
|
[MEDIUM] Same updateMany vs plain update race as mines on some paths
|
|
[LOW] FLOOR_MULTIPLIERS hardcoded — not derived from provably-fair module
|
|
|
|
------------------------------------------------------------------------------
|
|
4.5 SLOTS / ROULETTE (REST: POST instant)
|
|
------------------------------------------------------------------------------
|
|
Files: src/app/api/games/slots/route.ts, roulette/route.ts
|
|
Clients: SlotsGame.tsx, RouletteGame.tsx
|
|
|
|
Pattern: transaction wraps debit + credit + GameSession — GOOD.
|
|
|
|
Issues:
|
|
[LOW] No explicit max wager
|
|
[LOW] Outcome math not documented in UI paytable linkage to server
|
|
|
|
------------------------------------------------------------------------------
|
|
4.6 BLACKJACK (REST: POST + PATCH) — src/app/api/games/blackjack/route.ts
|
|
------------------------------------------------------------------------------
|
|
Client: src/components/casino/BlackjackGame.tsx
|
|
|
|
Issues:
|
|
[HIGH] Natural blackjack / push on POST (lines 91-100):
|
|
creditForWin/refundBet + gameSession.update are OUTSIDE initial transaction.
|
|
Failure between them → wallet credited but session still "active" or inverse.
|
|
|
|
[HIGH] resolveStand() (lines 157-193):
|
|
credit/refund then session update — not atomic.
|
|
Partial failure → duplicate payout or lost payout.
|
|
|
|
[MEDIUM] Card rank parsing: handValue uses c.slice(0,-1) — works for "10♠"
|
|
but fragile if format changes.
|
|
|
|
[MEDIUM] Double down debits second wager but doesn't link ledger memo to session.
|
|
|
|
Action: Wrap each resolution path in prisma.$transaction like dice route.
|
|
|
|
------------------------------------------------------------------------------
|
|
4.7 COIN FLIP (REST rooms + Socket.IO) — rooms/route.ts + server.ts
|
|
------------------------------------------------------------------------------
|
|
Client: src/components/casino/CoinFlipRoom.tsx
|
|
|
|
Flow:
|
|
- POST /api/games/rooms creates WAITING room (balance check only, no debit)
|
|
- Creator connects socket, waits
|
|
- Joiner connects → atomic claim → debit both → instant resolve
|
|
|
|
Issues:
|
|
[HIGH] Payout failure (server.ts lines 129-132): logs error, still emits
|
|
"result" to clients; room may stay ACTIVE — needs reconciliation job.
|
|
|
|
[MEDIUM] Creator never debited until joiner arrives — creator can spam WAITING
|
|
rooms (DB clutter). Expire job runs every 60s — OK.
|
|
|
|
[LOW] Instant resolution — no rematch within room
|
|
|
|
[LOW] GameHistory does not show coin flip (no GameSession row)
|
|
|
|
Socket auth: server-derived userId — GOOD.
|
|
|
|
------------------------------------------------------------------------------
|
|
4.8 PONG (REST rooms + Socket.IO + setInterval) — server.ts
|
|
------------------------------------------------------------------------------
|
|
Client: src/components/casino/PongGame.tsx
|
|
|
|
Issues:
|
|
[HIGH] Game loop setInterval(50ms) per room in Node process — CPU scales with
|
|
active pong rooms. On 4-core VPS, cap concurrent rooms.
|
|
|
|
[HIGH] pongRooms Map: if process crashes mid-game, debits already taken,
|
|
room may stay ACTIVE, interval orphaned until process exit.
|
|
|
|
[MEDIUM] paddle_move has no rate limit — flood events
|
|
|
|
[MEDIUM] Client/server paddle collision logic duplicated — desync possible
|
|
|
|
[LOW] No GameSession history entry
|
|
|
|
Payout path (lines 244-255): improved with async IIFE + logging — still emits
|
|
game_over even if payout fails.
|
|
|
|
------------------------------------------------------------------------------
|
|
4.9 PREDICTION MARKET — src/app/api/games/prediction/route.ts
|
|
------------------------------------------------------------------------------
|
|
Client: src/components/casino/PredictionMarket.tsx
|
|
|
|
POST bet: NOW uses single $transaction (debit + ledger + bet + market increment) — GOOD.
|
|
|
|
PATCH resolve: transaction with resolvedTo null guard — GOOD.
|
|
- Pays winners via tx.wallet.upsert — bypasses creditWalletCredits integer guard
|
|
if payout is 0 skipped; dust handling to first winner — clever
|
|
|
|
Issues:
|
|
[MEDIUM] GET markets: no auth required — OK for public markets
|
|
[LOW] No GameSession — not in GameHistory
|
|
[LOW] Creator can resolve early? — throws MARKET_NOT_ENDED if endsAt > now — GOOD
|
|
|
|
------------------------------------------------------------------------------
|
|
4.10 GAME HISTORY — src/app/api/games/history/route.ts + GameHistory.tsx
|
|
------------------------------------------------------------------------------
|
|
Issues:
|
|
[MEDIUM] Client: fetch without res.ok check — silent failure on 401
|
|
[MEDIUM] Only GameSession rows — PvP and prediction invisible
|
|
[LOW] No pagination UI (API may support cursor — verify route)
|
|
|
|
================================================================================
|
|
5. API ROUTES — COMPLETE INVENTORY & GAPS
|
|
================================================================================
|
|
|
|
37 HTTP route handlers under src/app/api/:
|
|
|
|
Auth: /api/auth/[...nextauth]
|
|
Register: /api/register
|
|
Public: /api/public/stats, /api/leaderboard, /api/exchange/rate
|
|
Wallet: /api/wallet, summary, ledger, donations, community
|
|
Stripe: create-checkout-session, create-payment-intent, webhooks/stripe
|
|
Missions: /api/missions, /api/missions/spend
|
|
Initiatives: /api/initiatives, [slug]/pledge
|
|
Poll: /api/polls/next-president
|
|
Rewards: catalog, redeem, raffle
|
|
Community: /api/billboard, /api/spotlight, /api/cards, /api/faq, /api/boost
|
|
Games: crash, dice, mines, tower, slots, blackjack, roulette, rooms,
|
|
prediction, history
|
|
Threads: /api/threads-exchange
|
|
|
|
Validation:
|
|
[MEDIUM] Game routes use manual req.json() casts — no Zod. Malformed body → 500.
|
|
[GOOD] FAQ, boost, cards, many others use Zod schemas.
|
|
|
|
Missing patterns:
|
|
- No If-None-Match / caching on public stats (force-dynamic everywhere)
|
|
- No request ID in logs
|
|
- Inconsistent error JSON shape { error } vs { issues }
|
|
|
|
------------------------------------------------------------------------------
|
|
5.1 Feature pages vs API (wiring status)
|
|
------------------------------------------------------------------------------
|
|
WIRED (page fetches API):
|
|
/billboard → /api/billboard
|
|
/spotlight → /api/spotlight
|
|
/cards → /api/cards
|
|
/faq-board → /api/faq
|
|
/boost → /api/boost
|
|
/casino/* → game APIs
|
|
/missions → /api/missions
|
|
/initiatives → /api/initiatives
|
|
/vote/* → /api/polls/next-president
|
|
/wallet → wallet APIs
|
|
/donate → Stripe checkout
|
|
|
|
HOMEPAGE mismatch:
|
|
[MEDIUM] ProgressSection → /api/public/stats (USD Stripe goal meter)
|
|
Boost page → /api/boost (BWT community epoch meter)
|
|
Marketing copy may conflate two different "meters" — clarify UX.
|
|
|
|
================================================================================
|
|
6. DATABASE / PRISMA
|
|
================================================================================
|
|
|
|
Schema: prisma/schema.prisma (515 lines, 8 migrations)
|
|
|
|
Indexes present:
|
|
LedgerEntry userId
|
|
GameSession userId, [userId, gameType]
|
|
GameRoom status
|
|
PollVote, Faq*, MovementBoost, etc.
|
|
|
|
Missing indexes (performance at scale):
|
|
[MEDIUM] GameSession.createdAt — history orders by time, filter by user
|
|
[MEDIUM] GameRoom [status, gameType, expiresAt] composite for lobby listing
|
|
[MEDIUM] Donation [status, createdAt] for leaderboard aggregations
|
|
[LOW] LedgerEntry [userId, createdAt] for wallet ledger pagination
|
|
|
|
Missing FKs:
|
|
[MEDIUM] LedgerEntry has no gameSessionId / gameRoomId — hard to audit bets
|
|
|
|
Enums: GameType, RoomStatus, LedgerType — comprehensive
|
|
|
|
------------------------------------------------------------------------------
|
|
6.1 Data integrity notes
|
|
------------------------------------------------------------------------------
|
|
[GOOD] Wallet debit uses conditional updateMany (balance >= amount)
|
|
[GOOD] Boost uses Serializable isolation for epoch milestone
|
|
[GOOD] public/stats and treasury both filter donation status succeeded
|
|
[MEDIUM] Movement boost milestone race under concurrent POSTs (documented in redmeFIXSES)
|
|
|
|
================================================================================
|
|
7. FRONTEND / UX / MALFORMED PATTERNS
|
|
================================================================================
|
|
|
|
7.1 Global layout performance killers
|
|
------------------------------------------------------------------------------
|
|
src/app/layout.tsx loads on EVERY page:
|
|
- CursorStreamers — full-screen canvas, mousemove listener, RAF loop
|
|
- SiteNav
|
|
- framer-motion on Hero and many sections
|
|
|
|
src/components/ParticleField.tsx — second full-section canvas on homepage Hero
|
|
src/components/SmokeWisps.tsx — additional animation layer
|
|
|
|
Impact on 8GB Linux VPS:
|
|
- Constant GPU/CPU use even on /datadeletion legal pages
|
|
- Battery drain for mobile users
|
|
- Competes with Node build (npm run build spikes memory)
|
|
|
|
Actions:
|
|
- Load CursorStreamers only on homepage or casino
|
|
- Respect prefers-reduced-motion (CursorStreamers already skips reduced motion)
|
|
- Lazy-load framer-motion below fold
|
|
|
|
7.2 Polling / duplicate fetches
|
|
------------------------------------------------------------------------------
|
|
Endpoints polled from multiple components without shared SWR/React Query:
|
|
|
|
/api/exchange/rate — 9+ separate useEffects across site
|
|
/api/wallet — ExchangePanel every 15s on casino pages
|
|
/api/public/stats — ProgressSection, HeroLiveStats
|
|
|
|
Action: Single client provider for rate + wallet; stale-while-revalidate.
|
|
|
|
7.3 Casino page architecture
|
|
------------------------------------------------------------------------------
|
|
src/app/casino/[game]/page.tsx:
|
|
- Server component fetches wallet once — GOOD
|
|
- ExchangePanel re-fetches wallet every 15s — balance can drift from server
|
|
- Game components receive static `balance` prop — not updated after wins/losses
|
|
unless user refreshes or ExchangePanel poll updates (partial fix)
|
|
|
|
[MEDIUM] User sees stale balance in wager inputs after big win until poll.
|
|
|
|
7.4 Error handling gaps
|
|
------------------------------------------------------------------------------
|
|
- GameHistory: no res.ok check
|
|
- Many fetch().then without .catch — silent network errors
|
|
- CrashGame tick: empty catch {} swallows errors — intentional comment
|
|
|
|
7.5 Accessibility
|
|
------------------------------------------------------------------------------
|
|
- Canvas decorations aria-hidden — GOOD
|
|
- Some icon-only buttons may lack aria-labels in casino grids
|
|
- Focus management on game phase transitions not tested
|
|
|
|
7.6 SEO / metadata
|
|
------------------------------------------------------------------------------
|
|
[MEDIUM] layout.tsx alternates.canonical hardcoded to "/" — subpages should
|
|
override (some do, e.g. privacy-policy)
|
|
|
|
[MEDIUM] sitemap.ts missing /casino, /missions, /boost, /billboard, etc.
|
|
|
|
[LOW] opengraph-image force-dynamic — regenerates every request
|
|
|
|
================================================================================
|
|
8. STRIPE / PAYMENTS / WALLET
|
|
================================================================================
|
|
|
|
- EmbeddedDonationCheckout → create-checkout-session (primary path)
|
|
- DonationCheckout → create-payment-intent (UNUSED)
|
|
- Webhook: src/app/api/webhooks/stripe/route.ts — signature verify — GOOD
|
|
|
|
Issues:
|
|
[HIGH] Ensure webhook endpoint not cached by Cloudflare (POST body must reach origin)
|
|
[MEDIUM] CREDIT_RATIO_CENTS_PER_USD — verify webhook grants match UI promises
|
|
[LOW] Guest checkout path — document in FAQ
|
|
|
|
================================================================================
|
|
9. AUTH
|
|
================================================================================
|
|
|
|
- next-auth v5 beta with Credentials provider
|
|
- Email OR username login — GOOD (lib/account-identifiers.ts)
|
|
- bcrypt password hashing — GOOD
|
|
- JWT session with role — GOOD
|
|
- No OAuth providers configured
|
|
|
|
Issues:
|
|
[MEDIUM] No password reset flow (forgot-password page is static)
|
|
[MEDIUM] No brute-force lockout on /api/auth/callback/credentials
|
|
[LOW] ADMIN role only via DB seed
|
|
|
|
================================================================================
|
|
10. TESTING & OBSERVABILITY
|
|
================================================================================
|
|
|
|
Scripts:
|
|
scripts/smoke-integration.ts — Prisma/DB
|
|
scripts/http-smoke.sh — curl stats + wallet 401
|
|
scripts/full-site-test.ts — extensive HTTP checks (manual, server must run)
|
|
scripts/seed-leaderboard.ts
|
|
|
|
Gaps:
|
|
[HIGH] No automated tests for game math or wallet settlement
|
|
[HIGH] No CI pipeline evident
|
|
[MEDIUM] console.error only — no structured logs, no correlation IDs
|
|
[MEDIUM] No health check endpoint (/api/health) for monitoring
|
|
[LOW] eslint installed but npm run lint = tsc only
|
|
|
|
================================================================================
|
|
11. LINUX / NODE PERFORMANCE TUNING (THIS MACHINE)
|
|
================================================================================
|
|
|
|
Current resources (audit snapshot):
|
|
RAM: 7.8 GiB total, ~1.6 GiB used, 6.2 GiB available
|
|
CPU: 4 cores
|
|
Disk: 64G volume, 7% used
|
|
node_modules: 561M
|
|
.next: 1.1G
|
|
|
|
Recommendations:
|
|
|
|
A. Process manager
|
|
- Keep single fundraising-platform.service instance
|
|
- Add MemoryMax=5G in systemd unit to avoid OOM killing Postgres
|
|
- Lower NODE_OPTIONS to --max-old-space-size=4096 for steady-state
|
|
|
|
B. PostgreSQL (localhost:5432)
|
|
- Ensure shared_buffers ~256MB, effective_cache_size ~2GB on this VM
|
|
- Enable log_min_duration_statement = 500 for slow query discovery
|
|
- Run EXPLAIN on leaderboard + poll aggregation queries
|
|
|
|
C. Next.js production
|
|
- Run `npm run build` after every deploy (already required for new routes)
|
|
- Consider output: 'standalone' in next.config for smaller runtime footprint
|
|
- Disable turbopack for production build if instability observed (using default)
|
|
|
|
D. Reverse proxy
|
|
- Prefer cloudflared tunnel → 127.0.0.1:8008 over exposing :8008
|
|
- Enable gzip/brotli at edge; Next already compresses
|
|
|
|
E. Static assets
|
|
- next.config.ts sets immutable cache for /_next/static — GOOD
|
|
- Ensure Cloudflare respects cache for hashed assets only
|
|
|
|
F. Background jobs
|
|
- server.ts setInterval 60s for room expiry — OK
|
|
- Add cron for stuck ACTIVE GameRoom reconciliation
|
|
|
|
G. Build/deploy workflow
|
|
- systemctl restart after git pull + npm run build
|
|
- Avoid running build while service uses CPU (schedule maintenance window)
|
|
|
|
H. Monitoring
|
|
- journalctl -u fundraising-platform -f
|
|
- Add node_exporter or simple /api/health returning { ok, db, version }
|
|
|
|
================================================================================
|
|
12. LEGAL / COMPLIANCE PAGES (RECENT)
|
|
================================================================================
|
|
|
|
Added:
|
|
/privacy-policy — src/app/privacy-policy/page.tsx
|
|
/datadeletion — src/app/datadeletion/page.tsx
|
|
/threads-callback — OAuth UI (blocked on live domain by Cloudflare static file)
|
|
|
|
Issues:
|
|
[MEDIUM] legal-contact falls back to privacy@example.com if env unset
|
|
[LOW] Template disclaimer on pages — counsel review still required
|
|
[LOW] threads-callback not in sitemap (robots noindex — OK)
|
|
|
|
================================================================================
|
|
13. PACKAGE / DEPENDENCY NOTES
|
|
================================================================================
|
|
|
|
next@16.2.6, react@19.2.6, prisma@7.8.0, next-auth@5.0.0-beta.31
|
|
socket.io@4.8.3 — matches client
|
|
framer-motion@12 — large bundle; tree-shake or lazy load
|
|
zod@4.4.3 — v4 API; ensure team knows breaking changes vs zod 3
|
|
|
|
[LOW] npm audit not run in this audit — run `npm audit` separately
|
|
|
|
================================================================================
|
|
14. FILE-BY-FILE HOTSPOT INDEX (QUICK REFERENCE)
|
|
================================================================================
|
|
|
|
server.ts Socket.IO, pong loop, coin flip, room expiry
|
|
src/middleware.ts matcher [] — auth not global
|
|
src/auth.ts Credentials only
|
|
src/app/api/games/blackjack/route.ts NON-ATOMIC payouts — FIX FIRST
|
|
src/app/api/games/crash/route.ts tick spam, cashout state sync
|
|
src/app/api/games/mines/route.ts loss race, ledger bypass
|
|
src/app/api/games/prediction/route.ts resolve logic OK; trust model
|
|
src/components/DonationCheckout.tsx DELETE (dead)
|
|
src/components/CursorStreamers.tsx perf — scope to homepage
|
|
src/components/ParticleField.tsx perf — homepage only
|
|
src/components/casino/ExchangePanel.tsx 15s polling
|
|
src/components/casino/CrashGame.tsx 220ms PATCH polling
|
|
src/components/casino/GameHistory.tsx no error handling
|
|
src/app/casino/[game]/page.tsx stale balance prop
|
|
src/lib/game-ledger.ts canonical money API
|
|
src/lib/provably-fair.ts all RNG
|
|
prisma/schema.prisma indexes + missing FKs
|
|
next.config.ts security headers, HSTS preload
|
|
.env / .env.example URL + Threads vars
|
|
|
|
================================================================================
|
|
15. SUGGESTED FIX ROADMAP (PHASED)
|
|
================================================================================
|
|
|
|
Phase 0 — Today (ops):
|
|
[ ] Cloudflare: route all paths to Node origin; delete static threads HTML
|
|
[ ] Fix NEXT_PUBLIC_SITE_URL for bwt.democracyrisingbwt.us
|
|
[ ] Rotate Threads secret if static page was public
|
|
[ ] systemctl restart after build
|
|
|
|
Phase 1 — Money safety (1-2 days dev):
|
|
[ ] Blackjack transactions
|
|
[ ] Coin-flip/pong stuck room reconciler
|
|
[ ] LedgerEntry.gameSessionId optional FK + backfill
|
|
|
|
Phase 2 — Games UX (2-3 days):
|
|
[ ] Unify game catalog
|
|
[ ] Fix CrashGame cashout ref
|
|
[ ] GameHistory error states + PvP rows (or document omission)
|
|
[ ] Balance sync via context after each game
|
|
|
|
Phase 3 — Performance (1-2 days):
|
|
[ ] Remove/limit CursorStreamers + ParticleField scope
|
|
[ ] Shared exchange rate provider
|
|
[ ] Add DB indexes listed above
|
|
[ ] Lower Node heap to 4GB
|
|
|
|
Phase 4 — Hardening (ongoing):
|
|
[ ] Rate limits
|
|
[ ] Health endpoint
|
|
[ ] Integration tests for dice/mines settlement
|
|
[ ] Delete DonationCheckout.tsx
|
|
|
|
================================================================================
|
|
16. AUDIT METHODOLOGY
|
|
================================================================================
|
|
|
|
Performed:
|
|
- Full tree listing src/, prisma/, scripts/
|
|
- tsc --noEmit (pass)
|
|
- npm run build route list (pass)
|
|
- Manual read of all game API routes + server.ts + key components
|
|
- Grep for TODO, console.*, fetch patterns, dead imports
|
|
- Live HTTP comparison Cloudflare vs 127.0.0.1:8008
|
|
- Subagent explore pass for architecture map
|
|
- Cross-check against redmeFIXSES.md (partially outdated)
|
|
|
|
Not performed:
|
|
- npm audit / dependency CVE scan
|
|
- Load testing (k6/ab) on game endpoints
|
|
- Stripe live transaction test
|
|
- Full line-by-line read of all 15k lines (hot paths covered)
|
|
- PostgreSQL EXPLAIN ANALYZE
|
|
- Accessibility audit tooling (axe)
|
|
|
|
================================================================================
|
|
END OF AUDIT — 16 sections, ~450+ line items reviewed
|
|
================================================================================
|