Add Democratic fundraising platform.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
root
2026-05-16 00:47:44 +00:00
parent 89f9b8dc83
commit 391d90a754
141 changed files with 14989 additions and 914 deletions

View File

@@ -0,0 +1,33 @@
-- AlterEnum
ALTER TYPE "LedgerType" ADD VALUE 'DEBIT_POLL_VOTE';
-- CreateTable
CREATE TABLE "PollVote" (
"id" TEXT NOT NULL,
"pollSlug" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"displayName" TEXT NOT NULL,
"normalizedKey" TEXT NOT NULL,
"creditsSpent" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PollVote_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "PollVote_pollSlug_userId_key" ON "PollVote"("pollSlug", "userId");
-- CreateIndex
CREATE INDEX "PollVote_pollSlug_normalizedKey_idx" ON "PollVote"("pollSlug", "normalizedKey");
-- AddForeignKey
ALTER TABLE "PollVote" ADD CONSTRAINT "PollVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AlterTable
ALTER TABLE "LedgerEntry" ADD COLUMN "pollVoteId" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "LedgerEntry_pollVoteId_key" ON "LedgerEntry"("pollVoteId");
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_pollVoteId_fkey" FOREIGN KEY ("pollVoteId") REFERENCES "PollVote"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "LedgerEntry" ADD COLUMN "raffleEntryId" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "LedgerEntry_raffleEntryId_key" ON "LedgerEntry"("raffleEntryId");
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_raffleEntryId_fkey" FOREIGN KEY ("raffleEntryId") REFERENCES "RaffleEntry"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,9 @@
-- AlterTable: guest donations + optional donor contact (CRM / disclosure tooling)
ALTER TABLE "Donation" DROP CONSTRAINT IF EXISTS "Donation_userId_fkey";
ALTER TABLE "Donation" ALTER COLUMN "userId" DROP NOT NULL;
ALTER TABLE "Donation" ADD COLUMN "donorEmail" TEXT;
ALTER TABLE "Donation" ADD COLUMN "donorName" TEXT;
ALTER TABLE "Donation" ADD CONSTRAINT "Donation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,95 @@
-- AlterEnum
ALTER TYPE "LedgerType" ADD VALUE 'DEBIT_GAME_BET';
ALTER TYPE "LedgerType" ADD VALUE 'CREDIT_GAME_WIN';
ALTER TYPE "LedgerType" ADD VALUE 'CREDIT_GAME_REFUND';
-- CreateEnum
CREATE TYPE "GameType" AS ENUM ('CRASH', 'DICE', 'MINES', 'TOWER', 'SLOTS', 'BLACKJACK', 'ROULETTE', 'COIN_FLIP', 'PONG', 'PREDICTION');
-- CreateEnum
CREATE TYPE "RoomStatus" AS ENUM ('WAITING', 'ACTIVE', 'RESOLVED', 'EXPIRED');
-- CreateTable
CREATE TABLE "GameSession" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"gameType" "GameType" NOT NULL,
"wageredBLW" INTEGER NOT NULL,
"payoutBLW" INTEGER NOT NULL DEFAULT 0,
"multiplier" DOUBLE PRECISION NOT NULL DEFAULT 0,
"outcome" TEXT NOT NULL,
"serverSeed" TEXT NOT NULL,
"clientSeed" TEXT,
"resultData" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GameSession_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "GameSession_userId_idx" ON "GameSession"("userId");
-- CreateIndex
CREATE INDEX "GameSession_userId_gameType_idx" ON "GameSession"("userId", "gameType");
-- AddForeignKey
ALTER TABLE "GameSession" ADD CONSTRAINT "GameSession_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- CreateTable
CREATE TABLE "GameRoom" (
"id" TEXT NOT NULL,
"gameType" "GameType" NOT NULL,
"creatorId" TEXT NOT NULL,
"joinerId" TEXT,
"wageBLW" INTEGER NOT NULL,
"status" "RoomStatus" NOT NULL DEFAULT 'WAITING',
"resultData" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "GameRoom_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "GameRoom_status_idx" ON "GameRoom"("status");
-- CreateTable
CREATE TABLE "PredictionMarket" (
"id" TEXT NOT NULL,
"creatorId" TEXT NOT NULL,
"question" TEXT NOT NULL,
"endsAt" TIMESTAMP(3) NOT NULL,
"resolvedTo" BOOLEAN,
"totalYes" INTEGER NOT NULL DEFAULT 0,
"totalNo" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PredictionMarket_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "PredictionMarket_endsAt_idx" ON "PredictionMarket"("endsAt");
-- CreateTable
CREATE TABLE "PredictionBet" (
"id" TEXT NOT NULL,
"marketId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"side" BOOLEAN NOT NULL,
"blwAmount" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PredictionBet_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "PredictionBet_marketId_idx" ON "PredictionBet"("marketId");
-- CreateIndex
CREATE INDEX "PredictionBet_userId_idx" ON "PredictionBet"("userId");
-- AddForeignKey
ALTER TABLE "PredictionBet" ADD CONSTRAINT "PredictionBet_marketId_fkey" FOREIGN KEY ("marketId") REFERENCES "PredictionMarket"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PredictionBet" ADD CONSTRAINT "PredictionBet_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,33 @@
-- AlterEnum
ALTER TYPE "LedgerType" ADD VALUE 'DEBIT_MISSION_SPEND';
-- CreateTable
CREATE TABLE "MissionSpend" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"missionSlug" TEXT NOT NULL,
"missionTitle" TEXT NOT NULL,
"creditsSpent" INTEGER NOT NULL,
"supporterNote" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "MissionSpend_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "MissionSpend_userId_idx" ON "MissionSpend"("userId");
-- CreateIndex
CREATE INDEX "MissionSpend_missionSlug_idx" ON "MissionSpend"("missionSlug");
-- AddForeignKey
ALTER TABLE "MissionSpend" ADD CONSTRAINT "MissionSpend_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AlterTable
ALTER TABLE "LedgerEntry" ADD COLUMN "missionSpendId" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "LedgerEntry_missionSpendId_key" ON "LedgerEntry"("missionSpendId");
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_missionSpendId_fkey" FOREIGN KEY ("missionSpendId") REFERENCES "MissionSpend"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,15 @@
-- Official platform priorities + nullable creator for community rows (one non-null creatorId still unique)
CREATE TYPE "DemocraticInitiativeOrigin" AS ENUM ('PLATFORM', 'COMMUNITY');
ALTER TABLE "DemocraticInitiative" DROP CONSTRAINT IF EXISTS "DemocraticInitiative_creatorId_key";
ALTER TABLE "DemocraticInitiative" ADD COLUMN "origin" "DemocraticInitiativeOrigin" NOT NULL DEFAULT 'COMMUNITY';
ALTER TABLE "DemocraticInitiative" ADD COLUMN "sortOrder" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "DemocraticInitiative" ALTER COLUMN "creatorId" DROP NOT NULL;
CREATE UNIQUE INDEX "DemocraticInitiative_creatorId_key" ON "DemocraticInitiative"("creatorId");
CREATE INDEX "DemocraticInitiative_origin_sortOrder_idx" ON "DemocraticInitiative"("origin", "sortOrder");
CREATE INDEX "DemocraticInitiative_origin_createdAt_idx" ON "DemocraticInitiative"("origin", "createdAt");

View File

@@ -0,0 +1,26 @@
-- Add optional username column, backfill, then constrain.
ALTER TABLE "User" ADD COLUMN "username" TEXT;
UPDATE "User"
SET "username" =
LEFT(
COALESCE(
NULLIF(
regexp_replace(
lower(trim(split_part("email", '@', 1))),
'[^a-z0-9_]',
'_',
'g'
),
''
),
'supporter'
),
23
)
|| '_'
|| right(replace("id", '-', ''), 8);
ALTER TABLE "User" ALTER COLUMN "username" SET NOT NULL;
CREATE UNIQUE INDEX "User_username_key" ON "User" ("username");

View File

@@ -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_] (332). 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 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])
}

View File

@@ -1,7 +1,7 @@
import "dotenv/config";
import bcrypt from "bcryptjs";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@prisma/client";
import { DemocraticInitiativeOrigin, PrismaClient } from "@prisma/client";
import { Pool } from "pg";
const connectionString = process.env.DATABASE_URL;
@@ -20,9 +20,15 @@ async function main() {
const user = await prisma.user.upsert({
where: { email },
update: { passwordHash: hash, name: "Demo Supporter", role: "USER" },
update: {
passwordHash: hash,
name: "Demo Supporter",
role: "USER",
username: "portal_demo",
},
create: {
email,
username: "portal_demo",
name: "Demo Supporter",
passwordHash: hash,
role: "USER",
@@ -44,9 +50,11 @@ async function main() {
passwordHash: adminHash,
name: "Dr Jones",
role: "ADMIN",
username: "portal_admin",
},
create: {
email: adminEmail,
username: "portal_admin",
name: "Dr Jones",
passwordHash: adminHash,
role: "ADMIN",
@@ -95,6 +103,65 @@ async function main() {
},
});
const officialPriorities: Array<{ slug: string; sortOrder: number; title: string; description: string }> = [
{
slug: "priority-voting-access",
sortOrder: 0,
title: "Voting access & fair elections",
description:
"Protect early voting, drop boxes where allowed, and nonpartisan election administration — signal BWT here if this is the lane you want organizers and messaging to emphasize first.",
},
{
slug: "priority-climate-jobs",
sortOrder: 1,
title: "Climate jobs & clean infrastructure",
description:
"Invest in union-scale clean energy, grid resilience, and communities transitioning off volatile fossil cycles — pledge BWT to raise the salience of green industrial policy in the program.",
},
{
slug: "priority-health-affordability",
sortOrder: 2,
title: "Healthcare affordability",
description:
"Expand coverage choices, cap out-of-pocket shocks for families, and defend access to care — use BWT totals here to show democratic demand for health-centered field work.",
},
{
slug: "priority-workers-rights",
sortOrder: 3,
title: "Workers rights & wages",
description:
"Stand with organizing, overtime fairness, and safety nets that reward work — pledges aggregate as a live scoreboard for how loud supporters want labor-forward priorities.",
},
{
slug: "priority-community-safety",
sortOrder: 4,
title: "Community safety & prevention",
description:
"Fund violence interruption, mental health first responders, and common-sense safety policy without scapegoating — BWT here steers narrative and volunteer energy toward prevention-first democracy.",
},
];
for (const p of officialPriorities) {
await prisma.democraticInitiative.upsert({
where: { slug: p.slug },
update: {
title: p.title,
description: p.description,
origin: DemocraticInitiativeOrigin.PLATFORM,
sortOrder: p.sortOrder,
creatorId: null,
},
create: {
slug: p.slug,
title: p.title,
description: p.description,
origin: DemocraticInitiativeOrigin.PLATFORM,
sortOrder: p.sortOrder,
creatorId: null,
},
});
}
// eslint-disable-next-line no-console
console.log("Seed OK — demo:", email, "/", password);
// eslint-disable-next-line no-console