Add Democratic fundraising platform.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,7 +10,39 @@ 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 {
|
||||
@@ -21,6 +53,8 @@ enum UserRole {
|
||||
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?
|
||||
@@ -35,6 +69,18 @@ model User {
|
||||
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
|
||||
@@ -93,28 +139,131 @@ model LedgerEntry {
|
||||
type LedgerType
|
||||
memo String?
|
||||
|
||||
donationId String? @unique
|
||||
donation Donation? @relation(fields: [donationId], references: [id])
|
||||
redemptionId String? @unique
|
||||
redemption Redemption? @relation(fields: [redemptionId], references: [id])
|
||||
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
|
||||
userId String
|
||||
/// 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)
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
ledgerEntry LedgerEntry?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
@@ -148,6 +297,69 @@ model Redemption {
|
||||
@@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
|
||||
@@ -166,11 +378,137 @@ model RaffleEntry {
|
||||
tickets Int @default(1)
|
||||
creditsSpent Int
|
||||
|
||||
raffle Raffle @relation(fields: [raffleId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
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])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user