515 lines
14 KiB
Plaintext
515 lines
14 KiB
Plaintext
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])
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([userId])
|
||
}
|
||
|
||
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)
|
||
|
||
@@index([userId])
|
||
@@index([userId, gameType])
|
||
}
|
||
|
||
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
|
||
|
||
@@index([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])
|
||
}
|