Full-stack implementation across 7 sprints: - Backend: 5 Prisma models (StrawPoll, Option, Vote, Comment, Challenge), 4 enums, POLLS_ADMIN role, admin CRUD routes, public voting/SSE/widget endpoints, BullMQ auto-close queue, rate limiting - Admin: StrawPollsPage with inline drawers (campaigns pattern), PollResults bar chart, sidebar under Advocacy - Public: dedicated poll lander with real-time SSE updates, browse page, anonymous voting with token dedup - MkDocs: straw-poll-widget.js hydration (inline vote + card link modes), GrapesJS block types - Social: feed activity (poll_voted), friend badge integration, challenge notifications, notification preferences - Feature flag: enablePolls toggle in Settings, FeatureGate, Zod schema Bunker Admin
169 lines
6.4 KiB
SQL
169 lines
6.4 KiB
SQL
-- CreateEnum
|
|
CREATE TYPE "StrawPollType" AS ENUM ('SINGLE_CHOICE', 'YES_NO_ABSTAIN');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "StrawPollStatus" AS ENUM ('DRAFT', 'ACTIVE', 'CLOSED', 'ARCHIVED');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "StrawPollIdentityMode" AS ENUM ('ANONYMOUS', 'TOKEN_GATED', 'AUTHENTICATED', 'MIXED');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "StrawPollResultVisibility" AS ENUM ('LIVE', 'AFTER_VOTE', 'AFTER_CLOSE', 'CREATOR_ONLY', 'PUBLIC_ALWAYS');
|
|
|
|
-- AlterEnum
|
|
-- This migration adds more than one value to an enum.
|
|
-- With PostgreSQL versions 11 and earlier, this is not possible
|
|
-- in a single migration. This can be worked around by creating
|
|
-- multiple migrations, each migration adding only one value to
|
|
-- the enum.
|
|
|
|
|
|
ALTER TYPE "NotificationType" ADD VALUE 'poll_closed';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'poll_results_available';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'poll_challenge';
|
|
|
|
-- AlterEnum
|
|
ALTER TYPE "UserRole" ADD VALUE 'POLLS_ADMIN';
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "site_settings" ADD COLUMN "enable_polls" BOOLEAN NOT NULL DEFAULT false;
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "straw_polls" (
|
|
"id" TEXT NOT NULL,
|
|
"slug" TEXT NOT NULL,
|
|
"title" VARCHAR(200) NOT NULL,
|
|
"description" TEXT,
|
|
"type" "StrawPollType" NOT NULL,
|
|
"status" "StrawPollStatus" NOT NULL DEFAULT 'DRAFT',
|
|
"identity_mode" "StrawPollIdentityMode" NOT NULL DEFAULT 'ANONYMOUS',
|
|
"result_visibility" "StrawPollResultVisibility" NOT NULL DEFAULT 'LIVE',
|
|
"allow_comments" BOOLEAN NOT NULL DEFAULT true,
|
|
"closes_at" TIMESTAMP(3),
|
|
"close_threshold" INTEGER,
|
|
"auto_close_job_id" TEXT,
|
|
"is_private" BOOLEAN NOT NULL DEFAULT false,
|
|
"created_by_user_id" TEXT NOT NULL,
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "straw_polls_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "straw_poll_options" (
|
|
"id" TEXT NOT NULL,
|
|
"poll_id" TEXT NOT NULL,
|
|
"label" VARCHAR(500) NOT NULL,
|
|
"sort_order" INTEGER NOT NULL DEFAULT 0,
|
|
|
|
CONSTRAINT "straw_poll_options_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "straw_poll_votes" (
|
|
"id" TEXT NOT NULL,
|
|
"poll_id" TEXT NOT NULL,
|
|
"option_id" TEXT NOT NULL,
|
|
"user_id" TEXT,
|
|
"voter_name" VARCHAR(100),
|
|
"voter_token" TEXT,
|
|
"voter_ip" TEXT,
|
|
"contact_id" TEXT,
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "straw_poll_votes_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "straw_poll_comments" (
|
|
"id" TEXT NOT NULL,
|
|
"poll_id" TEXT NOT NULL,
|
|
"user_id" TEXT,
|
|
"author_name" VARCHAR(100) NOT NULL,
|
|
"content" TEXT NOT NULL,
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
CONSTRAINT "straw_poll_comments_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "straw_poll_challenges" (
|
|
"id" TEXT NOT NULL,
|
|
"poll_id" TEXT NOT NULL,
|
|
"challenger_user_id" TEXT NOT NULL,
|
|
"challenged_user_id" TEXT NOT NULL,
|
|
"completed_at" TIMESTAMP(3),
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
CONSTRAINT "straw_poll_challenges_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "straw_polls_slug_key" ON "straw_polls"("slug");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "straw_polls_created_by_user_id_idx" ON "straw_polls"("created_by_user_id");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "straw_polls_status_idx" ON "straw_polls"("status");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "straw_poll_options_poll_id_idx" ON "straw_poll_options"("poll_id");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "straw_poll_votes_poll_id_idx" ON "straw_poll_votes"("poll_id");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "straw_poll_votes_option_id_idx" ON "straw_poll_votes"("option_id");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "straw_poll_votes_poll_id_user_id_key" ON "straw_poll_votes"("poll_id", "user_id");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "straw_poll_votes_poll_id_voter_token_key" ON "straw_poll_votes"("poll_id", "voter_token");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "straw_poll_votes_poll_id_voter_ip_key" ON "straw_poll_votes"("poll_id", "voter_ip");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "straw_poll_comments_poll_id_idx" ON "straw_poll_comments"("poll_id");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "straw_poll_challenges_poll_id_challenger_user_id_challenged_key" ON "straw_poll_challenges"("poll_id", "challenger_user_id", "challenged_user_id");
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_polls" ADD CONSTRAINT "straw_polls_created_by_user_id_fkey" FOREIGN KEY ("created_by_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_options" ADD CONSTRAINT "straw_poll_options_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "straw_polls"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_votes" ADD CONSTRAINT "straw_poll_votes_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "straw_polls"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_votes" ADD CONSTRAINT "straw_poll_votes_option_id_fkey" FOREIGN KEY ("option_id") REFERENCES "straw_poll_options"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_votes" ADD CONSTRAINT "straw_poll_votes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_votes" ADD CONSTRAINT "straw_poll_votes_contact_id_fkey" FOREIGN KEY ("contact_id") REFERENCES "contacts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_comments" ADD CONSTRAINT "straw_poll_comments_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "straw_polls"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_comments" ADD CONSTRAINT "straw_poll_comments_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_challenges" ADD CONSTRAINT "straw_poll_challenges_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "straw_polls"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_challenges" ADD CONSTRAINT "straw_poll_challenges_challenger_user_id_fkey" FOREIGN KEY ("challenger_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "straw_poll_challenges" ADD CONSTRAINT "straw_poll_challenges_challenged_user_id_fkey" FOREIGN KEY ("challenged_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|