Files
democratic-money/prisma/schema.prisma
2026-05-16 00:47:44 +00:00

515 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_] (332). 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 13 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])
}