feat: add survey view with voting functionality and admin panel

- Implemented SurveyView.vue for displaying and managing polls, including voting, comments, and results.
- Added new database tables for vote polls, votes, and comments in migration script.
- Created unique lottery logic for handling unique item rewards based on voting.
- Developed unit tests for unique lottery functionality to ensure deterministic behavior and correct counting of occupied unique items.
This commit is contained in:
2026-02-03 18:25:26 +00:00
parent d3277f204a
commit b0a8f768e9
15 changed files with 2531 additions and 22 deletions
+52
View File
@@ -514,3 +514,55 @@ model BoardComment {
@@index([postId, createdAt])
@@map("board_comment")
}
model VotePoll {
id Int @id @default(autoincrement())
title String
body String @default("")
options Json
multipleOptions Int @default(1) @map("multiple_options")
revealMode String @map("reveal_mode")
openerGeneralId Int @map("opener_general_id")
openerName String @map("opener_name")
startAt DateTime @default(now()) @map("start_at")
endAt DateTime? @map("end_at")
closedAt DateTime? @map("closed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
votes Vote[]
comments VoteComment[]
@@map("vote_poll")
}
model Vote {
id Int @id @default(autoincrement())
voteId Int @map("vote_id")
generalId Int @map("general_id")
nationId Int @map("nation_id")
selection Json
createdAt DateTime @default(now()) @map("created_at")
poll VotePoll @relation(fields: [voteId], references: [id], onDelete: Cascade)
@@unique([voteId, generalId])
@@index([voteId])
@@map("vote")
}
model VoteComment {
id Int @id @default(autoincrement())
voteId Int @map("vote_id")
generalId Int @map("general_id")
nationId Int @map("nation_id")
generalName String @map("general_name")
nationName String @map("nation_name")
text String
createdAt DateTime @default(now()) @map("created_at")
poll VotePoll @relation(fields: [voteId], references: [id], onDelete: Cascade)
@@index([voteId, createdAt])
@@map("vote_comment")
}
@@ -0,0 +1,40 @@
CREATE TABLE "vote_poll" (
"id" SERIAL PRIMARY KEY,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL DEFAULT '',
"options" JSONB NOT NULL,
"multiple_options" INTEGER NOT NULL DEFAULT 1,
"reveal_mode" TEXT NOT NULL,
"opener_general_id" INTEGER NOT NULL,
"opener_name" TEXT NOT NULL,
"start_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"end_at" TIMESTAMP(3) NULL,
"closed_at" TIMESTAMP(3) NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE "vote" (
"id" SERIAL PRIMARY KEY,
"vote_id" INTEGER NOT NULL REFERENCES "vote_poll"("id") ON DELETE CASCADE,
"general_id" INTEGER NOT NULL,
"nation_id" INTEGER NOT NULL,
"selection" JSONB NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX "vote_vote_general_uidx" ON "vote"("vote_id", "general_id");
CREATE INDEX "vote_vote_idx" ON "vote"("vote_id");
CREATE TABLE "vote_comment" (
"id" SERIAL PRIMARY KEY,
"vote_id" INTEGER NOT NULL REFERENCES "vote_poll"("id") ON DELETE CASCADE,
"general_id" INTEGER NOT NULL,
"nation_id" INTEGER NOT NULL,
"general_name" TEXT NOT NULL,
"nation_name" TEXT NOT NULL,
"text" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX "vote_comment_vote_created_idx" ON "vote_comment"("vote_id", "created_at");