Add ActionCampaign + Document models for volunteer dashboard

Foundation schema for the FAFC-style volunteer dashboard. Adds:
- ActionCampaign / ActionStep / ActionStepCompletion (stacked-action
  mini-campaigns where steps reference existing entities like videos,
  petitions, ticketed events; completion is detected at query time
  against the per-user model for each step kind)
- Document model for downloadable resources (PDFs etc.) — Photo's
  EXIF/sharp pipeline can't host non-image files
- Shift.kind discriminator (ShiftKind enum) so training shifts can
  surface separately on the dashboard
- TicketedEvent.featured for the "Take Action" CTA tile

Bunker Admin
This commit is contained in:
bunker-admin 2026-04-10 21:26:56 -06:00
parent 5f0ae6bc5a
commit 3fc67cd81a
2 changed files with 2228 additions and 1962 deletions

View File

@ -0,0 +1,130 @@
-- CreateEnum
CREATE TYPE "ShiftKind" AS ENUM ('CANVASS', 'TRAINING', 'EVENT_STAFFING', 'PHONE_BANK', 'OTHER');
-- CreateEnum
CREATE TYPE "ActionStepKind" AS ENUM ('WATCH_VIDEO', 'SUBMIT_INFLUENCE', 'SIGN_PETITION', 'RSVP_EVENT', 'SIGNUP_SHIFT', 'JOIN_CHALLENGE', 'VISIT_LINK', 'CUSTOM');
-- CreateEnum
CREATE TYPE "ActionStepCompletionSource" AS ENUM ('AUTO', 'SELF_REPORTED');
-- AlterTable
ALTER TABLE "shifts" ADD COLUMN "kind" "ShiftKind" NOT NULL DEFAULT 'CANVASS';
-- AlterTable
ALTER TABLE "ticketed_events" ADD COLUMN "featured" BOOLEAN NOT NULL DEFAULT false;
-- CreateTable
CREATE TABLE "action_campaigns" (
"id" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"reward_text" TEXT,
"is_active" BOOLEAN NOT NULL DEFAULT false,
"starts_at" TIMESTAMP(3),
"ends_at" TIMESTAMP(3),
"min_steps_for_reward" INTEGER,
"created_by_user_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "action_campaigns_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "action_steps" (
"id" TEXT NOT NULL,
"campaign_id" TEXT NOT NULL,
"order" INTEGER NOT NULL,
"kind" "ActionStepKind" NOT NULL,
"label" TEXT NOT NULL,
"description" TEXT,
"target_id" TEXT,
"target_url" TEXT,
"auto_complete" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "action_steps_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "action_step_completions" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"step_id" TEXT NOT NULL,
"source" "ActionStepCompletionSource" NOT NULL DEFAULT 'AUTO',
"completed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "action_step_completions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "documents" (
"id" TEXT NOT NULL,
"path" TEXT NOT NULL,
"filename" TEXT NOT NULL,
"original_filename" TEXT,
"title" TEXT,
"description" TEXT,
"mime_type" TEXT NOT NULL,
"file_size" BIGINT,
"page_count" INTEGER,
"thumbnail_path" TEXT,
"category" TEXT,
"tags" JSONB,
"is_published" BOOLEAN NOT NULL DEFAULT true,
"position" INTEGER DEFAULT 0,
"uploader_id" TEXT,
"download_count" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "documents_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "action_campaigns_slug_key" ON "action_campaigns"("slug");
-- CreateIndex
CREATE INDEX "idx_action_campaigns_active" ON "action_campaigns"("is_active");
-- CreateIndex
CREATE INDEX "idx_action_steps_campaign" ON "action_steps"("campaign_id");
-- CreateIndex
CREATE UNIQUE INDEX "action_steps_campaign_id_order_key" ON "action_steps"("campaign_id", "order");
-- CreateIndex
CREATE INDEX "idx_action_step_completions_user" ON "action_step_completions"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "action_step_completions_user_id_step_id_key" ON "action_step_completions"("user_id", "step_id");
-- CreateIndex
CREATE UNIQUE INDEX "documents_path_key" ON "documents"("path");
-- CreateIndex
CREATE INDEX "idx_documents_published" ON "documents"("is_published");
-- CreateIndex
CREATE INDEX "idx_documents_category" ON "documents"("category");
-- CreateIndex
CREATE INDEX "idx_documents_created_at" ON "documents"("created_at");
-- AddForeignKey
ALTER TABLE "action_campaigns" ADD CONSTRAINT "action_campaigns_created_by_user_id_fkey" FOREIGN KEY ("created_by_user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "action_steps" ADD CONSTRAINT "action_steps_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "action_campaigns"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "action_step_completions" ADD CONSTRAINT "action_step_completions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "action_step_completions" ADD CONSTRAINT "action_step_completions_step_id_fkey" FOREIGN KEY ("step_id") REFERENCES "action_steps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "documents" ADD CONSTRAINT "documents_uploader_id_fkey" FOREIGN KEY ("uploader_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;

File diff suppressed because it is too large Load Diff