diff --git a/components/poll/manage-poll.tsx b/components/poll/manage-poll.tsx index efb7756ac..e291a67ce 100644 --- a/components/poll/manage-poll.tsx +++ b/components/poll/manage-poll.tsx @@ -32,7 +32,7 @@ const ManagePoll: React.VoidFunctionComponent<{ const queryClient = useQueryClient(); const { mutate: resetDates } = useMutation( async () => { - await axios.get(`/api/legacy/${poll.urlId}?reset=true`); + await axios.get(`/api/legacy/${poll.urlId}/reset`); }, { onSettled: () => { diff --git a/pages/api/legacy/[urlId].ts b/pages/api/legacy/[urlId].ts index 66b6325e8..2471fe23b 100644 --- a/pages/api/legacy/[urlId].ts +++ b/pages/api/legacy/[urlId].ts @@ -1,46 +1,11 @@ import { GetPollApiResponse } from "api-client/get-poll"; -import { ObjectId } from "mongodb"; import { NextApiRequest, NextApiResponse } from "next"; import { exclude, getQueryParam } from "utils/api-utils"; +import { LegacyPoll } from "utils/legacy-utils"; import { getMongoClient } from "utils/mongodb-client"; import { nanoid } from "utils/nanoid"; import { prisma } from "../../../db"; -interface LegacyPoll { - __private: { - verificationCode: string; - }; - _id: string; - title: string; - location: string; - isExample: boolean; - isDeleted: boolean; - isClosed: boolean; - emails: string[]; - description: string; - dates?: Date[]; - creator: { - name: string; - email: string; - isVerified: boolean; - allowNotifications: boolean; - }; - created: Date; - comments?: Array<{ - _id: ObjectId; - author: { - name: string; - }; - content: string; - created: Date; - }>; - participants?: Array<{ - _id: ObjectId; - name: string; - votes?: boolean[]; - }>; -} - export default async function handler( req: NextApiRequest, res: NextApiResponse, diff --git a/pages/api/legacy/[urlId]/reset.ts b/pages/api/legacy/[urlId]/reset.ts new file mode 100644 index 000000000..9609a53fc --- /dev/null +++ b/pages/api/legacy/[urlId]/reset.ts @@ -0,0 +1,24 @@ +import { GetPollApiResponse } from "api-client/get-poll"; +import { NextApiRequest, NextApiResponse } from "next"; +import { exclude, getQueryParam } from "utils/api-utils"; +import { resetDates } from "utils/legacy-utils"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + const urlId = getQueryParam(req, "urlId"); + + const poll = await resetDates(urlId); + + if (!poll) { + return res.status(404); + } + + return res.json({ + ...exclude(poll, "verificationCode"), + role: "admin", + urlId: poll.urlId, + pollId: poll.urlId, + }); +} diff --git a/pages/api/poll/[urlId]/index.ts b/pages/api/poll/[urlId]/index.ts index 26f3a7ef8..7a7fe70fe 100644 --- a/pages/api/poll/[urlId]/index.ts +++ b/pages/api/poll/[urlId]/index.ts @@ -1,5 +1,7 @@ import { GetPollApiResponse } from "api-client/get-poll"; import { NextApiResponse } from "next"; +import { resetDates } from "utils/legacy-utils"; +import { getMongoClient } from "utils/mongodb-client"; import { UpdatePollPayload } from "../../../../api-client/update-poll"; import { prisma } from "../../../../db"; import { exclude, withLink } from "../../../../utils/api-utils"; @@ -48,6 +50,25 @@ export default withLink( .json({ status: 404, message: "Poll not found" }); } + if ( + poll.legacy && + // has converted options without timezone + poll.options.some(({ value }) => value.indexOf("T") === -1) + ) { + // We need to reset the dates for polls that lost their timezone data because some users + // of the old version will end up seeing the wrong dates + const fixedPoll = await resetDates(poll.urlId); + + if (fixedPoll) { + return res.json({ + ...exclude(fixedPoll, "verificationCode"), + role: link.role, + urlId: link.urlId, + pollId: poll.urlId, + }); + } + } + return res.json({ ...exclude(poll, "verificationCode"), role: link.role, diff --git a/utils/legacy-utils.ts b/utils/legacy-utils.ts new file mode 100644 index 000000000..147f06c19 --- /dev/null +++ b/utils/legacy-utils.ts @@ -0,0 +1,113 @@ +import { ObjectId } from "mongodb"; +import { getMongoClient } from "./mongodb-client"; +import { prisma } from "../db"; + +export interface LegacyPoll { + __private: { + verificationCode: string; + }; + _id: string; + title: string; + location: string; + isExample: boolean; + isDeleted: boolean; + isClosed: boolean; + emails: string[]; + description: string; + dates?: Date[]; + creator: { + name: string; + email: string; + isVerified: boolean; + allowNotifications: boolean; + }; + created: Date; + comments?: Array<{ + _id: ObjectId; + author: { + name: string; + }; + content: string; + created: Date; + }>; + participants?: Array<{ + _id: ObjectId; + name: string; + votes?: boolean[]; + }>; +} + +export const resetDates = async (legacyPollId: string) => { + const client = await getMongoClient(); + if (!client) { + return; + } + const db = client.db("rallly-db"); + const collection = db.collection("events"); + + const legacyPoll = await collection.findOne({ + _id: legacyPollId, + }); + + if (!legacyPoll) { + return; + } + + const existingOptions = await prisma.option.findMany({ + where: { pollId: legacyPoll._id }, + orderBy: { + value: "asc", + }, + }); + + if (!existingOptions) { + return; + } + + const promises = []; + for (let i = 0; i < existingOptions.length; i++) { + const legacyOption = legacyPoll.dates?.find( + (date) => + date.toISOString().substring(0, 10) === existingOptions[i].value, + ); + if (legacyOption) { + promises.push( + prisma.option.update({ + where: { id: existingOptions[i].id }, + data: { + value: legacyOption.toISOString(), + }, + }), + ); + } + } + await prisma.$transaction(promises); + + const poll = await prisma.poll.findUnique({ + where: { + urlId: legacyPoll._id, + }, + include: { + options: { + include: { + votes: true, + }, + }, + participants: { + include: { + votes: true, + }, + orderBy: [ + { + createdAt: "desc", + }, + { name: "desc" }, + ], + }, + user: true, + links: true, + }, + }); + + return poll; +};