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>
This commit is contained in:
root
2026-05-20 06:43:26 +00:00
parent c6a7b2e08d
commit 7eac42e820
48 changed files with 3005 additions and 227 deletions

View File

@@ -0,0 +1,26 @@
-- Link LedgerEntry rows to their originating GameSession / GameRoom so we can
-- reconcile money flow per round/room. Non-unique because a single session/room
-- produces multiple ledger lines (debit + win/refund).
ALTER TABLE "LedgerEntry" ADD COLUMN "gameSessionId" TEXT;
ALTER TABLE "LedgerEntry" ADD COLUMN "gameRoomId" TEXT;
CREATE INDEX "LedgerEntry_gameSessionId_idx" ON "LedgerEntry" ("gameSessionId");
CREATE INDEX "LedgerEntry_gameRoomId_idx" ON "LedgerEntry" ("gameRoomId");
ALTER TABLE "LedgerEntry"
ADD CONSTRAINT "LedgerEntry_gameSessionId_fkey"
FOREIGN KEY ("gameSessionId") REFERENCES "GameSession"("id")
ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "LedgerEntry"
ADD CONSTRAINT "LedgerEntry_gameRoomId_fkey"
FOREIGN KEY ("gameRoomId") REFERENCES "GameRoom"("id")
ON DELETE SET NULL ON UPDATE CASCADE;
-- Helper indexes for resume + sweep + admin queries.
CREATE INDEX "GameSession_userId_gameType_outcome_idx"
ON "GameSession" ("userId", "gameType", "outcome");
CREATE INDEX "GameRoom_status_expiresAt_idx" ON "GameRoom" ("status", "expiresAt");
CREATE INDEX "GameRoom_creatorId_status_idx" ON "GameRoom" ("creatorId", "status");

View File

@@ -167,9 +167,18 @@ model LedgerEntry {
movementBoostId String? @unique
movementBoost MovementBoost? @relation(fields: [movementBoostId], references: [id])
/// Optional links for casino/PvP reconciliation — not @unique because a single
/// session or room produces multiple ledger lines (debit + payout/refund).
gameSessionId String?
gameSession GameSession? @relation(fields: [gameSessionId], references: [id], onDelete: SetNull)
gameRoomId String?
gameRoom GameRoom? @relation(fields: [gameRoomId], references: [id], onDelete: SetNull)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([gameSessionId])
@@index([gameRoomId])
}
model PollVote {
@@ -310,10 +319,12 @@ model GameSession {
resultData Json?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
ledgerEntries LedgerEntry[]
@@index([userId])
@@index([userId, gameType])
@@index([userId, gameType, outcome])
}
model GameRoom {
@@ -327,7 +338,11 @@ model GameRoom {
createdAt DateTime @default(now())
expiresAt DateTime
ledgerEntries LedgerEntry[]
@@index([status])
@@index([status, expiresAt])
@@index([creatorId, status])
}
model PredictionMarket {