feat(finale): schema for phases, audience windows, favorite votes, notes, reveal
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LivePhase" AS ENUM ('ON_DECK', 'PRESENTING', 'QA', 'SCORING');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AudiencePhase" AS ENUM ('CLOSED', 'OPEN');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "LiveProgressCursor" ADD COLUMN "overrideSlide" TEXT,
|
||||
ADD COLUMN "phaseDurationSeconds" INTEGER,
|
||||
ADD COLUMN "phasePausedAccumMs" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "phasePausedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "phaseStartedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "projectPhase" "LivePhase" NOT NULL DEFAULT 'ON_DECK',
|
||||
ADD COLUMN "timingLogJson" JSONB;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "LiveVote" ADD COLUMN "comment" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN "allowOverallFavorite" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "audiencePhase" "AudiencePhase" NOT NULL DEFAULT 'CLOSED',
|
||||
ADD COLUMN "audienceWindowClosesAt" TIMESTAMP(3),
|
||||
ADD COLUMN "audienceWindowKey" TEXT,
|
||||
ADD COLUMN "audienceWindowOpenedAt" TIMESTAMP(3);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AudienceFavoriteVote" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionId" TEXT NOT NULL,
|
||||
"windowKey" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"audienceVoterId" TEXT NOT NULL,
|
||||
"ipAddress" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AudienceFavoriteVote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "LiveNote" (
|
||||
"id" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "LiveNote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RevealState" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'DRAFT',
|
||||
"stepsJson" JSONB NOT NULL,
|
||||
"currentStepIndex" INTEGER NOT NULL DEFAULT -1,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "RevealState_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AudienceFavoriteVote_sessionId_windowKey_ipAddress_idx" ON "AudienceFavoriteVote"("sessionId", "windowKey", "ipAddress");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AudienceFavoriteVote_sessionId_windowKey_projectId_idx" ON "AudienceFavoriteVote"("sessionId", "windowKey", "projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AudienceFavoriteVote_sessionId_windowKey_audienceVoterId_key" ON "AudienceFavoriteVote"("sessionId", "windowKey", "audienceVoterId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "LiveNote_userId_idx" ON "LiveNote"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "LiveNote_roundId_projectId_userId_key" ON "LiveNote"("roundId", "projectId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RevealState_sessionId_key" ON "RevealState"("sessionId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AudienceFavoriteVote" ADD CONSTRAINT "AudienceFavoriteVote_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "LiveVotingSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AudienceFavoriteVote" ADD CONSTRAINT "AudienceFavoriteVote_audienceVoterId_fkey" FOREIGN KEY ("audienceVoterId") REFERENCES "AudienceVoter"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AudienceFavoriteVote" ADD CONSTRAINT "AudienceFavoriteVote_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LiveNote" ADD CONSTRAINT "LiveNote_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LiveNote" ADD CONSTRAINT "LiveNote_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LiveNote" ADD CONSTRAINT "LiveNote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RevealState" ADD CONSTRAINT "RevealState_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "LiveVotingSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -347,6 +347,7 @@ model User {
|
||||
resourceAccess ResourceAccess[]
|
||||
submittedProjects Project[] @relation("ProjectSubmittedBy")
|
||||
liveVotes LiveVote[]
|
||||
liveNotes LiveNote[]
|
||||
|
||||
// Team membership & mentorship
|
||||
teamMemberships TeamMember[]
|
||||
@@ -657,6 +658,10 @@ model Project {
|
||||
finalistConfirmation FinalistConfirmation?
|
||||
externalLunchAttendees ExternalAttendee[]
|
||||
|
||||
// Grand-finale ceremony
|
||||
audienceFavoriteVotes AudienceFavoriteVote[]
|
||||
liveNotes LiveNote[]
|
||||
|
||||
@@index([programId])
|
||||
@@index([status])
|
||||
@@index([tags])
|
||||
@@ -1188,6 +1193,13 @@ model LiveVotingSession {
|
||||
audienceRequireId Boolean @default(false) // Require email/phone for audience
|
||||
audienceVotingDuration Int? // Minutes (null = same as jury)
|
||||
|
||||
// Audience favorite-vote window (grand finale)
|
||||
audiencePhase AudiencePhase @default(CLOSED)
|
||||
audienceWindowKey String? // 'CATEGORY:STARTUP' | 'CATEGORY:BUSINESS_CONCEPT' | 'OVERALL'
|
||||
audienceWindowOpenedAt DateTime?
|
||||
audienceWindowClosesAt DateTime?
|
||||
allowOverallFavorite Boolean @default(false) // admin toggle, decided day-of
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -1195,6 +1207,8 @@ model LiveVotingSession {
|
||||
round Round? @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
votes LiveVote[]
|
||||
audienceVoters AudienceVoter[]
|
||||
favoriteVotes AudienceFavoriteVote[]
|
||||
revealState RevealState?
|
||||
|
||||
@@index([status])
|
||||
}
|
||||
@@ -1211,6 +1225,9 @@ model LiveVote {
|
||||
// Criteria scores (used when votingMode="criteria")
|
||||
criterionScoresJson Json? @db.JsonB // { [criterionId]: score } - null for simple mode
|
||||
|
||||
// Optional overall comment from the juror (grand finale)
|
||||
comment String? @db.Text
|
||||
|
||||
// Audience voter link
|
||||
audienceVoterId String?
|
||||
|
||||
@@ -1240,11 +1257,79 @@ model AudienceVoter {
|
||||
// Relations
|
||||
session LiveVotingSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||
votes LiveVote[]
|
||||
favoriteVotes AudienceFavoriteVote[]
|
||||
|
||||
@@index([sessionId])
|
||||
@@index([token])
|
||||
}
|
||||
|
||||
// One pick-one-favorite vote per audience member per voting window.
|
||||
// windowKey snapshots LiveVotingSession.audienceWindowKey at cast time so
|
||||
// per-category and overall votes coexist in one table.
|
||||
model AudienceFavoriteVote {
|
||||
id String @id @default(cuid())
|
||||
sessionId String
|
||||
windowKey String // 'CATEGORY:STARTUP' | 'CATEGORY:BUSINESS_CONCEPT' | 'OVERALL'
|
||||
projectId String
|
||||
audienceVoterId String
|
||||
ipAddress String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
session LiveVotingSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||
audienceVoter AudienceVoter @relation(fields: [audienceVoterId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([sessionId, windowKey, audienceVoterId])
|
||||
@@index([sessionId, windowKey, ipAddress])
|
||||
@@index([sessionId, windowKey, projectId])
|
||||
}
|
||||
|
||||
// Per-juror per-project free-text notes taken during the live ceremony.
|
||||
// Resurfaces during deliberation.
|
||||
model LiveNote {
|
||||
id String @id @default(cuid())
|
||||
roundId String
|
||||
projectId String
|
||||
userId String
|
||||
content String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([roundId, projectId, userId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// Admin-driven results reveal for the big-screen ceremony view.
|
||||
// Steps beyond currentStepIndex are never exposed publicly.
|
||||
model RevealState {
|
||||
id String @id @default(cuid())
|
||||
sessionId String @unique
|
||||
status String @default("DRAFT") // DRAFT | ARMED | REVEALING | DONE
|
||||
stepsJson Json @db.JsonB // RevealStep[]
|
||||
currentStepIndex Int @default(-1)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
session LiveVotingSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
enum LivePhase {
|
||||
ON_DECK
|
||||
PRESENTING
|
||||
QA
|
||||
SCORING
|
||||
}
|
||||
|
||||
enum AudiencePhase {
|
||||
CLOSED
|
||||
OPEN
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TEAM MEMBERSHIP
|
||||
// =============================================================================
|
||||
@@ -2157,6 +2242,15 @@ model LiveProgressCursor {
|
||||
activeOrderIndex Int @default(0)
|
||||
isPaused Boolean @default(false)
|
||||
|
||||
// Per-project ceremony phase + server-stamped timer (grand finale)
|
||||
projectPhase LivePhase @default(ON_DECK)
|
||||
phaseStartedAt DateTime?
|
||||
phaseDurationSeconds Int?
|
||||
phasePausedAt DateTime?
|
||||
phasePausedAccumMs Int @default(0)
|
||||
timingLogJson Json? @db.JsonB // [{projectId, phase, startedAt, endedAt, configuredSeconds, overranSeconds}]
|
||||
overrideSlide String? // big-screen override: 'welcome' | 'break' | 'deliberation' | 'thanks'
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -2283,6 +2377,7 @@ model Round {
|
||||
notificationLogs NotificationLog[]
|
||||
cohorts Cohort[]
|
||||
liveCursor LiveProgressCursor?
|
||||
liveNotes LiveNote[]
|
||||
|
||||
@@unique([competitionId, slug])
|
||||
@@unique([competitionId, sortOrder])
|
||||
|
||||
Reference in New Issue
Block a user