diff --git a/src/pages/api/house-keeping.ts b/src/pages/api/house-keeping.ts index 0cc2e00b4..e35de1145 100644 --- a/src/pages/api/house-keeping.ts +++ b/src/pages/api/house-keeping.ts @@ -4,6 +4,8 @@ import { NextApiRequest, NextApiResponse } from "next"; import { prisma } from "~/prisma/db"; +import { parseValue } from "../../utils/date-time-utils"; + /** * DANGER: This endpoint will permanently delete polls. */ @@ -24,12 +26,33 @@ export default async function handler( return; } - // soft delete polls that have not been accessed for over 30 days - const inactivePolls = await prisma.poll.deleteMany({ + // get polls that have not been accessed for over 30 days + const inactivePolls = await prisma.$queryRaw< + Array<{ id: string; max: string }> + >` + SELECT polls.id, MAX(options.value) FROM polls + JOIN options ON options.poll_id = polls.id + WHERE touched_at <= ${dayjs().add(-30, "days").toDate()} AND deleted = false + GROUP BY polls.id; + `; + + const pollsToSoftDelete: string[] = []; + + // keep polls that have options that are in the future + inactivePolls.forEach(({ id, max: value }) => { + const parsedValue = parseValue(value); + const date = + parsedValue.type === "date" ? parsedValue.date : parsedValue.end; + + if (dayjs(date).isBefore(dayjs())) { + pollsToSoftDelete.push(id); + } + }); + + const softDeletedPolls = await prisma.poll.deleteMany({ where: { - deleted: false, - touchedAt: { - lte: dayjs().add(-30, "days").toDate(), + id: { + in: pollsToSoftDelete, }, }, }); @@ -111,7 +134,7 @@ export default async function handler( } res.status(200).json({ - inactive: inactivePolls.count, + softDeleted: softDeletedPolls.count, deleted: pollIdsToDelete.length, }); } diff --git a/src/utils/date-time-utils.ts b/src/utils/date-time-utils.ts index 625f08794..9c0a5158a 100644 --- a/src/utils/date-time-utils.ts +++ b/src/utils/date-time-utils.ts @@ -155,3 +155,19 @@ export const expectTimeOption = (d: DateTimeOption): TimeOption => { } return d; }; + +export const parseValue = (value: string): DateTimeOption => { + if (isTimeSlot(value)) { + const [start, end] = value.split("/"); + return { + type: "timeSlot", + start, + end, + }; + } else { + return { + type: "date", + date: value, + }; + } +}; diff --git a/tests/house-keeping.spec.ts b/tests/house-keeping.spec.ts index dabd6210a..ba075ba79 100644 --- a/tests/house-keeping.spec.ts +++ b/tests/house-keeping.spec.ts @@ -75,10 +75,9 @@ test.beforeAll(async ({ request, baseURL }) => { participantUrlId: "p6", adminUrlId: "a6", }, - // Old demo poll { demo: true, - title: "Demo poll", + title: "Old demo poll", id: "demo-poll-old", type: "date", userId: "user1", @@ -86,6 +85,15 @@ test.beforeAll(async ({ request, baseURL }) => { participantUrlId: "p7", adminUrlId: "a7", }, + { + title: "Inactive poll with future option", + id: "inactive-poll-future-option", + type: "date", + userId: "user1", + touchedAt: dayjs().add(-30, "days").toDate(), + participantUrlId: "p8", + adminUrlId: "a8", + }, ], }); @@ -106,6 +114,21 @@ test.beforeAll(async ({ request, baseURL }) => { value: "2022-02-24", pollId: "deleted-poll-7d", }, + { + id: "option-4", + value: `${dayjs() + .add(10, "days") + .format("YYYY-MM-DDTHH:mm:ss")}/${dayjs() + .add(10, "days") + .add(1, "hour") + .format("YYYY-MM-DDTHH:mm:ss")}`, + pollId: "inactive-poll-future-option", + }, + { + id: "option-5", + value: dayjs().add(-1, "days").format("YYYY-MM-DD"), + pollId: "inactive-poll", + }, ], }); @@ -144,7 +167,7 @@ test.beforeAll(async ({ request, baseURL }) => { }); expect(await res.json()).toMatchObject({ - inactive: 1, + softDeleted: 1, deleted: 2, }); }); @@ -252,6 +275,16 @@ test("should delete old demo poll", async () => { expect(oldDemoPoll).toBeNull(); }); +test("should not delete poll that has options in the future", async () => { + const futureOptionPoll = await prisma.poll.findFirst({ + where: { + id: "inactive-poll-future-option", + }, + }); + + expect(futureOptionPoll).not.toBeNull(); +}); + // Teardown test.afterAll(async () => { await prisma.$executeRaw`DELETE FROM polls WHERE id IN (${Prisma.join([ @@ -263,4 +296,13 @@ test.afterAll(async () => { "demo-poll-new", "demo-poll-old", ])})`; + await prisma.$executeRaw`DELETE FROM options WHERE id IN (${Prisma.join([ + "active-poll", + "deleted-poll-6d", + "deleted-poll-7d", + "still-active-poll", + "inactive-poll", + "demo-poll-new", + "demo-poll-old", + ])})`; });