diff --git a/prisma/db.ts b/prisma/db.ts
index a0d1c5b4b..e7959f27e 100644
--- a/prisma/db.ts
+++ b/prisma/db.ts
@@ -1,5 +1,7 @@
import { PrismaClient } from "@prisma/client";
+import { softDeleteMiddleware } from "./middlewares/softDeleteMiddleware";
+
declare global {
// allow global `var` declarations
// eslint-disable-next-line no-var
@@ -8,4 +10,6 @@ declare global {
export const prisma = global.prisma || new PrismaClient();
+softDeleteMiddleware(prisma, "Poll");
+
if (process.env.NODE_ENV !== "production") global.prisma = prisma;
diff --git a/prisma/middlewares/softDeleteMiddleware.ts b/prisma/middlewares/softDeleteMiddleware.ts
new file mode 100644
index 000000000..85f53f952
--- /dev/null
+++ b/prisma/middlewares/softDeleteMiddleware.ts
@@ -0,0 +1,48 @@
+import { Prisma, PrismaClient } from "@prisma/client";
+
+export const softDeleteMiddleware = (
+ prisma: PrismaClient,
+ model: Prisma.ModelName,
+) => {
+ prisma.$use(async (params, next) => {
+ // We use middleware to handle soft deletes
+ // See: https://www.prisma.io/docs/concepts/components/prisma-client/middleware/soft-delete-middleware
+ if (params.model === model) {
+ if (params.action === "delete") {
+ // Delete queries
+ // Change action to an update
+ params.action = "update";
+ params.args["data"] = { deleted: true };
+ }
+ if (params.action == "deleteMany") {
+ // Delete many queries
+ params.action = "updateMany";
+ if (params.args.data != undefined) {
+ params.args.data["deleted"] = true;
+ } else {
+ params.args["data"] = { deleted: true };
+ }
+ }
+ if (params.action === "findUnique" || params.action === "findFirst") {
+ // Change to findFirst - you cannot filter
+ // by anything except ID / unique with findUnique
+ params.action = "findFirst";
+ // Add 'deleted' filter
+ // ID filter maintained
+ params.args.where["deleted"] = false;
+ }
+ if (params.action === "findMany") {
+ // Find many queries
+ if (params.args.where) {
+ if (params.args.where.deleted == undefined) {
+ // Exclude deleted records if they have not been explicitly requested
+ params.args.where["deleted"] = false;
+ }
+ } else {
+ params.args["where"] = { deleted: false };
+ }
+ }
+ }
+ return next(params);
+ });
+};
diff --git a/prisma/migrations/20220519075453_add_delete_column/migration.sql b/prisma/migrations/20220519075453_add_delete_column/migration.sql
new file mode 100644
index 000000000..a62246a56
--- /dev/null
+++ b/prisma/migrations/20220519075453_add_delete_column/migration.sql
@@ -0,0 +1,44 @@
+-- DropForeignKey
+ALTER TABLE "comments" DROP CONSTRAINT "comments_poll_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "links" DROP CONSTRAINT "links_poll_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "options" DROP CONSTRAINT "options_poll_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "participants" DROP CONSTRAINT "participants_poll_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "votes" DROP CONSTRAINT "votes_option_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "votes" DROP CONSTRAINT "votes_participant_id_fkey";
+
+-- DropForeignKey
+ALTER TABLE "votes" DROP CONSTRAINT "votes_poll_id_fkey";
+
+-- AlterTable
+ALTER TABLE "polls" ADD COLUMN "deleted" BOOLEAN NOT NULL DEFAULT false;
+
+-- AddForeignKey
+ALTER TABLE "links" ADD CONSTRAINT "links_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "polls"("url_id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "participants" ADD CONSTRAINT "participants_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "polls"("url_id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "options" ADD CONSTRAINT "options_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "polls"("url_id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "votes" ADD CONSTRAINT "votes_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "polls"("url_id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "votes" ADD CONSTRAINT "votes_participant_id_fkey" FOREIGN KEY ("participant_id") REFERENCES "participants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "votes" ADD CONSTRAINT "votes_option_id_fkey" FOREIGN KEY ("option_id") REFERENCES "options"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "comments" ADD CONSTRAINT "comments_poll_id_fkey" FOREIGN KEY ("poll_id") REFERENCES "polls"("url_id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/prisma/migrations/20220520115326_add_touch_column/migration.sql b/prisma/migrations/20220520115326_add_touch_column/migration.sql
new file mode 100644
index 000000000..6373917a4
--- /dev/null
+++ b/prisma/migrations/20220520115326_add_touch_column/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "polls" ADD COLUMN "touched_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 495c9ce2f..6186a7663 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -49,6 +49,8 @@ model Poll {
legacy Boolean @default(false)
closed Boolean @default(false)
notifications Boolean @default(false)
+ deleted Boolean @default(false)
+ touchedAt DateTime @default(now()) @map("touched_at")
@@map("polls")
}
@@ -64,7 +66,7 @@ model Link {
urlId String @id @unique @map("url_id")
role Role
pollId String @map("poll_id")
- poll Poll @relation(fields: [pollId], references: [urlId], onDelete: Cascade)
+ poll Poll @relation(fields: [pollId], references: [urlId])
createdAt DateTime @default(now()) @map("created_at")
@@unique([pollId, role])
@@ -77,7 +79,7 @@ model Participant {
user User? @relation(fields: [userId], references: [id])
userId String? @map("user_id")
guestId String? @map("guest_id")
- poll Poll @relation(fields: [pollId], references: [urlId], onDelete: Cascade)
+ poll Poll @relation(fields: [pollId], references: [urlId])
pollId String @map("poll_id")
votes Vote[]
createdAt DateTime @default(now()) @map("created_at")
@@ -91,7 +93,7 @@ model Option {
id String @id @default(cuid())
value String
pollId String @map("poll_id")
- poll Poll @relation(fields: [pollId], references: [urlId], onDelete: Cascade)
+ poll Poll @relation(fields: [pollId], references: [urlId])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
votes Vote[]
@@ -109,11 +111,11 @@ enum VoteType {
model Vote {
id String @id @default(cuid())
- participant Participant @relation(fields: [participantId], references: [id], onDelete: Cascade)
+ participant Participant @relation(fields: [participantId], references: [id])
participantId String @map("participant_id")
- option Option @relation(fields: [optionId], references: [id], onDelete: Cascade)
+ option Option @relation(fields: [optionId], references: [id])
optionId String @map("option_id")
- poll Poll @relation(fields: [pollId], references: [urlId], onDelete: Cascade)
+ poll Poll @relation(fields: [pollId], references: [urlId])
pollId String @map("poll_id")
type VoteType @default(yes)
createdAt DateTime @default(now()) @map("created_at")
@@ -125,7 +127,7 @@ model Vote {
model Comment {
id String @id @default(cuid())
content String
- poll Poll @relation(fields: [pollId], references: [urlId], onDelete: Cascade)
+ poll Poll @relation(fields: [pollId], references: [urlId])
pollId String @map("poll_id")
authorName String @map("author_name")
user User? @relation(fields: [userId], references: [id])
diff --git a/public/locales/en/app.json b/public/locales/en/app.json
index 09db6d730..272fe0805 100644
--- a/public/locales/en/app.json
+++ b/public/locales/en/app.json
@@ -56,5 +56,6 @@
"ifNeedBe": "If need be",
"areYouSure": "Are you sure?",
"deletePollDescription": "All data related to this poll will be deleted. This action cannot be undone. To confirm, please type “{{confirmText}}” in to the input below:",
- "deletePoll": "Delete poll"
+ "deletePoll": "Delete poll",
+ "demoPollNotice": "Demo polls are automatically deleted after 1 day"
}
diff --git a/src/components/badge.tsx b/src/components/badge.tsx
index 5c114b219..4b452e260 100644
--- a/src/components/badge.tsx
+++ b/src/components/badge.tsx
@@ -3,17 +3,19 @@ import React from "react";
const Badge: React.VoidFunctionComponent<{
children?: React.ReactNode;
- color?: "gray" | "amber" | "green";
+ color?: "gray" | "amber" | "green" | "red" | "blue";
className?: string;
}> = ({ children, color = "gray", className }) => {
return (