diff --git a/api/resolvers/item.js b/api/resolvers/item.js index f502b08b47..1b196f90f3 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -1034,8 +1034,7 @@ export default { } const options = await models.$queryRaw` - SELECT "PollOption".id, option, count("PollVote"."userId")::INTEGER as count, - coalesce(bool_or("PollVote"."userId" = ${me?.id}), 'f') as "meVoted" + SELECT "PollOption".id, option, count("PollVote".id)::INTEGER as count FROM "PollOption" LEFT JOIN "PollVote" on "PollVote"."pollOptionId" = "PollOption".id WHERE "PollOption"."itemId" = ${item.id} @@ -1043,9 +1042,16 @@ export default { ORDER BY "PollOption".id ASC ` + const meVoted = await models.pollBlindVote.findFirst({ + where: { + userId: me?.id, + itemId: item.id + } + }) + const poll = {} poll.options = options - poll.meVoted = options.some(o => o.meVoted) + poll.meVoted = !!meVoted poll.count = options.reduce((t, o) => t + o.count, 0) return poll diff --git a/prisma/migrations/20240527175411_anon_poll_vote/migration.sql b/prisma/migrations/20240527175411_anon_poll_vote/migration.sql new file mode 100644 index 0000000000..edc7684775 --- /dev/null +++ b/prisma/migrations/20240527175411_anon_poll_vote/migration.sql @@ -0,0 +1,92 @@ +-- CreateTable +CREATE TABLE "PollBlindVote" ( + "id" SERIAL NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "itemId" INTEGER NOT NULL, + "userId" INTEGER NOT NULL, + + CONSTRAINT "PollBlindVote_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "PollBlindVote" ON "PollBlindVote"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PollBlindVote.itemId_userId_unique" ON "PollBlindVote"("itemId", "userId"); + +-- AddForeignKey +ALTER TABLE "PollBlindVote" ADD CONSTRAINT "PollBlindVote_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PollBlindVote" ADD CONSTRAINT "PollBlindVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- migrate existing poll votes +INSERT INTO "PollBlindVote" ("itemId", "userId") + SELECT "itemId", "userId" FROM "PollVote"; + +/* + Warnings: + + - You are about to drop the column `userId` on the `PollVote` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "PollVote" DROP CONSTRAINT "PollVote_userId_fkey"; + +-- DropIndex +DROP INDEX "PollVote.itemId_userId_unique"; + +-- DropIndex +DROP INDEX "PollVote.userId_index"; + +-- AlterTable +ALTER TABLE "PollVote" DROP COLUMN "userId"; + +-- update `poll_vote` function to update both "PollVote" and "PollBlindVote" tables +-- create poll vote +-- if user hasn't already voted +-- charges user item.pollCost +-- adds POLL to ItemAct +-- adds PollVote +-- adds PollBlindVote +CREATE OR REPLACE FUNCTION poll_vote(option_id INTEGER, user_id INTEGER) +RETURNS "Item" +LANGUAGE plpgsql +AS $$ +DECLARE + item "Item"; + option "PollOption"; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + SELECT * INTO option FROM "PollOption" where id = option_id; + IF option IS NULL THEN + RAISE EXCEPTION 'INVALID_POLL_OPTION'; + END IF; + + SELECT * INTO item FROM "Item" where id = option."itemId"; + IF item IS NULL THEN + RAISE EXCEPTION 'POLL_DOES_NOT_EXIST'; + END IF; + + IF item."userId" = user_id THEN + RAISE EXCEPTION 'POLL_OWNER_CANT_VOTE'; + END IF; + + -- no longer check `PollVote` to see if a user has voted. Instead, check `PollBlindVote` + IF EXISTS (SELECT 1 FROM "PollBlindVote" WHERE "itemId" = item.id AND "userId" = user_id) THEN + RAISE EXCEPTION 'POLL_VOTE_ALREADY_EXISTS'; + END IF; + + PERFORM item_act(item.id, user_id, 'POLL', item."pollCost"); + + INSERT INTO "PollVote" (created_at, updated_at, "itemId", "pollOptionId") + VALUES (now_utc(), now_utc(), item.id, option_id); + + INSERT INTO "PollBlindVote" (created_at, updated_at, "itemId", "userId") + VALUES (now_utc(), now_utc(), item.id, user_id); + + RETURN item; +END; +$$; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d72be91953..24840612ef 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -79,7 +79,6 @@ model User { actions ItemAct[] mentions Mention[] messages Message[] - PollVote PollVote[] PushSubscriptions PushSubscription[] ReferralAct ReferralAct[] Streak Streak[] @@ -125,6 +124,7 @@ model User { Replies Reply[] walletLogs WalletLog[] Reminder Reminder[] + PollBlindVote PollBlindVote[] @@index([photoId]) @@index([createdAt], map: "users.created_at_index") @@ -423,6 +423,7 @@ model Item { Ancestors Reply[] @relation("AncestorReplyItem") Replies Reply[] Reminder Reminder[] + PollBlindVote PollBlindVote[] @@index([uploadId]) @@index([lastZapAt]) @@ -500,16 +501,25 @@ model PollVote { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @default(now()) @updatedAt @map("updated_at") - userId Int itemId Int pollOptionId Int item Item @relation(fields: [itemId], references: [id], onDelete: Cascade) pollOption PollOption @relation(fields: [pollOptionId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - @@unique([itemId, userId], map: "PollVote.itemId_userId_unique") @@index([pollOptionId], map: "PollVote.pollOptionId_index") - @@index([userId], map: "PollVote.userId_index") +} + +model PollBlindVote { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @updatedAt @map("updated_at") + itemId Int + userId Int + item Item @relation(fields: [itemId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([itemId, userId], map: "PollBlindVote.itemId_userId_unique") + @@index([userId], map: "PollBlindVote") } enum BillingType {