generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } enum LedgerType { CREDIT_DONATION DEBIT_SPEND DEBIT_RAFFLE DEBIT_POLL_VOTE DEBIT_MISSION_SPEND DEBIT_INITIATIVE_SPEND ADJUSTMENT DEBIT_GAME_BET CREDIT_GAME_WIN CREDIT_GAME_REFUND DEBIT_BILLBOARD DEBIT_SPOTLIGHT DEBIT_CARD_MINT DEBIT_FAQ_SUBMIT DEBIT_FAQ_VOTE DEBIT_BOOST } enum GameType { CRASH DICE MINES TOWER SLOTS BLACKJACK ROULETTE COIN_FLIP PONG PREDICTION } enum RoomStatus { WAITING ACTIVE RESOLVED EXPIRED } enum UserRole { USER ADMIN } model User { id String @id @default(cuid()) email String @unique /// Lowercase [a-z0-9_] (3–32). Used along with email for credentials login. username String @unique emailVerified DateTime? name String? image String? passwordHash String? role UserRole @default(USER) accounts Account[] sessions Session[] wallet Wallet? ledgerEntries LedgerEntry[] donations Donation[] raffleEntries RaffleEntry[] redemptions Redemption[] pollVotes PollVote[] missionSpends MissionSpend[] initiativeSpends InitiativeSpend[] createdInitiatives DemocraticInitiative[] gameSessions GameSession[] predictionBets PredictionBet[] billboardMessages BillboardMessage[] spotlightBids SpotlightBid[] supporterCards SupporterCard[] faqSubmissions FaqSubmission[] faqVotes FaqVote[] movementBoosts MovementBoost[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String refresh_token String? @db.Text access_token String? @db.Text expires_at Int? token_type String? scope String? id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model VerificationToken { identifier String token String @unique expires DateTime @@unique([identifier, token]) } model Wallet { id String @id @default(cuid()) userId String @unique balanceCredits Int @default(0) user User @relation(fields: [userId], references: [id], onDelete: Cascade) updatedAt DateTime @updatedAt } model LedgerEntry { id String @id @default(cuid()) userId String delta Int type LedgerType memo String? donationId String? @unique donation Donation? @relation(fields: [donationId], references: [id]) redemptionId String? @unique redemption Redemption? @relation(fields: [redemptionId], references: [id]) raffleEntryId String? @unique raffleEntry RaffleEntry? @relation(fields: [raffleEntryId], references: [id]) pollVoteId String? @unique pollVote PollVote? @relation(fields: [pollVoteId], references: [id]) missionSpendId String? @unique missionSpend MissionSpend? @relation(fields: [missionSpendId], references: [id]) initiativeSpendId String? @unique initiativeSpend InitiativeSpend? @relation(fields: [initiativeSpendId], references: [id]) createdAt DateTime @default(now()) billboardMessageId String? @unique billboardMessage BillboardMessage? @relation(fields: [billboardMessageId], references: [id]) spotlightBidId String? @unique spotlightBid SpotlightBid? @relation(fields: [spotlightBidId], references: [id]) supporterCardId String? @unique supporterCard SupporterCard? @relation(fields: [supporterCardId], references: [id]) faqSubmissionId String? @unique faqSubmission FaqSubmission? @relation(fields: [faqSubmissionId], references: [id]) faqVoteId String? @unique faqVote FaqVote? @relation(fields: [faqVoteId], references: [id]) 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 { id String @id @default(cuid()) pollSlug String userId String displayName String normalizedKey String creditsSpent Int user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@unique([pollSlug, userId]) @@index([pollSlug, normalizedKey]) } model MissionSpend { id String @id @default(cuid()) userId String missionSlug String missionTitle String creditsSpent Int supporterNote String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([userId]) @@index([missionSlug]) } enum DemocraticInitiativeOrigin { PLATFORM COMMUNITY } model DemocraticInitiative { id String @id @default(cuid()) slug String @unique /// Null for official platform priorities; set for community-authored initiatives. creatorId String? origin DemocraticInitiativeOrigin @default(COMMUNITY) /// Lower sorts first among PLATFORM rows; ignored for COMMUNITY (use createdAt). sortOrder Int @default(0) title String description String @db.Text creator User? @relation(fields: [creatorId], references: [id], onDelete: Cascade) spends InitiativeSpend[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([origin, sortOrder]) @@index([origin, createdAt]) @@index([createdAt]) } model InitiativeSpend { id String @id @default(cuid()) initiativeId String userId String creditsSpent Int supporterNote String? initiative DemocraticInitiative @relation(fields: [initiativeId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([initiativeId]) @@index([userId]) } model Donation { id String @id @default(cuid()) stripePaymentIntentId String @unique /// Null when guest checkout (still counts toward public totals). userId String? amountUsdCents Int creditsAwarded Int currency String @default("usd") status String @default("succeeded") /// Optional CRM fields from guest flow (never required to donate). donorEmail String? donorName String? user User? @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([userId]) } model PrizeSku { id String @id @default(cuid()) slug String @unique title String description String @db.Text costCredits Int stockHint String? redemptions Redemption[] } model Redemption { id String @id @default(cuid()) userId String prizeSkuId String creditsSpent Int user User @relation(fields: [userId], references: [id], onDelete: Cascade) prizeSku PrizeSku @relation(fields: [prizeSkuId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([userId]) } model GameSession { id String @id @default(cuid()) userId String gameType GameType wageredBLW Int payoutBLW Int @default(0) multiplier Float @default(0) outcome String serverSeed String clientSeed String? resultData Json? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntries LedgerEntry[] @@index([userId]) @@index([userId, gameType]) @@index([userId, gameType, outcome]) } model GameRoom { id String @id @default(cuid()) gameType GameType creatorId String joinerId String? wageBLW Int status RoomStatus @default(WAITING) resultData Json? createdAt DateTime @default(now()) expiresAt DateTime ledgerEntries LedgerEntry[] @@index([status]) @@index([status, expiresAt]) @@index([creatorId, status]) } model PredictionMarket { id String @id @default(cuid()) creatorId String question String endsAt DateTime resolvedTo Boolean? totalYes Int @default(0) totalNo Int @default(0) createdAt DateTime @default(now()) bets PredictionBet[] @@index([endsAt]) } model PredictionBet { id String @id @default(cuid()) marketId String userId String side Boolean blwAmount Int createdAt DateTime @default(now()) market PredictionMarket @relation(fields: [marketId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([marketId]) @@index([userId]) } model Raffle { id String @id @default(cuid()) slug String @unique title String description String @db.Text ticketCostCredits Int endsAt DateTime? entries RaffleEntry[] } model RaffleEntry { id String @id @default(cuid()) raffleId String userId String tickets Int @default(1) creditsSpent Int raffle Raffle @relation(fields: [raffleId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([raffleId]) @@index([userId]) } // ── Feature 1: Democracy Billboard ────────────────────────────────────────── // Users spend BWT to post a short rally-cry message on the live public ticker. // creditsSpent determines display weight (higher = longer / more prominent). model BillboardMessage { id String @id @default(cuid()) userId String displayName String message String @db.VarChar(140) creditsSpent Int expiresAt DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([expiresAt]) @@index([userId]) } // ── Feature 2: Issue Spotlight Auction ────────────────────────────────────── // Weekly auction: users bid BWT on a policy issue; most-funded issue becomes // the "Issue of the Week" featured on the homepage. model SpotlightBid { id String @id @default(cuid()) userId String issueSlug String issueTitle String creditsSpent Int weekOf String // ISO date string of Monday: "2026-05-11" user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([weekOf, issueSlug]) @@index([userId]) } // ── Feature 3: Supporter Trading Cards ────────────────────────────────────── // Users mint a collectible stat card (snapshot of their donor profile). // Tier 1–3 unlocked by cumulative credits ever earned. model SupporterCard { id String @id @default(cuid()) userId String tier Int @default(1) // 1 = Standard, 2 = Rare, 3 = Legendary serialNumber Int // sequential per user creditsSpent Int statsSnapshot Json // { totalDonatedUsd, creditsEarned, rank, etc. } user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([userId]) @@index([tier]) } // ── Feature 4: Community FAQ Board ────────────────────────────────────────── // Spend BWT to submit a question; spend 1 BWT to upvote others. // Admin can approve/reject — approved questions appear in the live FAQ. enum FaqStatus { PENDING APPROVED REJECTED } model FaqSubmission { id String @id @default(cuid()) userId String displayName String question String @db.VarChar(280) answer String? @db.Text // filled by admin on approval status FaqStatus @default(PENDING) creditsSpent Int voteTotal Int @default(0) user User @relation(fields: [userId], references: [id], onDelete: Cascade) votes FaqVote[] ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([status]) @@index([userId]) } model FaqVote { id String @id @default(cuid()) submissionId String userId String creditsSpent Int @default(1) submission FaqSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@unique([submissionId, userId]) @@index([submissionId]) @@index([userId]) } // ── Feature 5: Power the Movement Meter ───────────────────────────────────── // A community energy meter. Any user can add BWT to charge it up. // When the meter hits the target, a milestone event fires and all contributors // get a BWT bonus proportional to their contribution. model MovementBoost { id String @id @default(cuid()) userId String creditsSpent Int epochId Int @default(1) // increments each time the meter resets user User @relation(fields: [userId], references: [id], onDelete: Cascade) ledgerEntry LedgerEntry? createdAt DateTime @default(now()) @@index([epochId]) @@index([userId]) }