Files
democratic-money/SITE-AUDIT.txt
root 7eac42e820 Legal pages, Threads OAuth, live wallet pill, PvP money flow + resume
- 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>
2026-05-20 06:43:26 +00:00

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
================================================================================