First public commit

This commit is contained in:
Luke Vella 2022-04-12 07:14:28 +01:00
commit e05cd62e53
228 changed files with 17717 additions and 0 deletions

View file

@ -0,0 +1,36 @@
import { NextApiRequest, NextApiResponse } from "next";
import { prisma } from "../../../../../db";
import { getQueryParam } from "../../../../../utils/api-utils";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const urlId = getQueryParam(req, "urlId");
const commentId = getQueryParam(req, "commentId");
const link = await prisma.link.findUnique({ where: { urlId } });
if (!link) {
return res.status(404).end();
}
switch (req.method) {
case "DELETE":
await prisma.comment.delete({
where: {
id_pollId: {
id: commentId,
pollId: link.pollId,
},
},
});
return res.end();
default:
return res
.status(405)
.json({ status: 405, message: "Method not allowed" });
}
}

View file

@ -0,0 +1,86 @@
import absoluteUrl from "utils/absolute-url";
import { prisma } from "../../../../../db";
import {
getAdminLink,
sendEmailTemplate,
withLink,
} from "../../../../../utils/api-utils";
export default withLink(async (req, res, link) => {
switch (req.method) {
case "GET": {
const comments = await prisma.comment.findMany({
where: {
pollId: link.pollId,
},
orderBy: [
{
createdAt: "asc",
},
],
});
return res.json({ comments });
}
case "POST": {
const newComment = await prisma.comment.create({
data: {
content: req.body.content,
pollId: link.pollId,
authorName: req.body.authorName,
},
});
if (link.role === "participant") {
const poll = await prisma.poll.findUnique({
where: {
urlId: link.pollId,
},
include: {
links: true,
user: true,
},
});
if (poll?.notifications && poll.verified && !poll.demo) {
// Get the admin link
const adminLink = getAdminLink(poll.links);
if (adminLink) {
const homePageUrl = absoluteUrl(req).origin;
const pollUrl = `${homePageUrl}/admin/${adminLink.urlId}`;
const unsubscribeUrl = `${pollUrl}?unsubscribe=true`;
try {
await sendEmailTemplate({
templateName: "new-comment",
to: poll.user.email,
subject: `Rallly: ${poll.title} - New Comment`,
templateVars: {
title: poll.title,
name: poll.authorName,
author: newComment.authorName,
pollUrl,
homePageUrl: absoluteUrl(req).origin,
supportEmail: process.env.NEXT_PUBLIC_SUPPORT_EMAIL,
unsubscribeUrl,
},
});
} catch (e) {
console.error(e);
}
} else {
console.log(`Missing admin link for poll: ${link.pollId}`);
}
}
}
return res.json(newComment);
}
default:
return res
.status(405)
.json({ status: 405, message: "Method not allowed" });
}
});

View file

@ -0,0 +1,138 @@
import { GetPollApiResponse } from "api-client/get-poll";
import { NextApiResponse } from "next";
import { UpdatePollPayload } from "../../../../api-client/update-poll";
import { prisma } from "../../../../db";
import { exclude, withLink } from "../../../../utils/api-utils";
export default withLink(
async (
req,
res: NextApiResponse<
GetPollApiResponse | { status: number; message: string }
>,
link,
) => {
const pollId = link.pollId;
switch (req.method) {
case "GET": {
const poll = await prisma.poll.findUnique({
where: {
urlId: pollId,
},
include: {
options: {
include: {
votes: true,
},
},
participants: {
include: {
votes: true,
},
orderBy: [
{
createdAt: "desc",
},
{ name: "desc" },
],
},
user: true,
links: link.role === "admin",
},
});
if (!poll) {
return res
.status(404)
.json({ status: 404, message: "Poll not found" });
}
return res.json({
...exclude(poll, "verificationCode"),
role: link.role,
urlId: link.urlId,
pollId: poll.urlId,
});
}
case "PATCH": {
if (link.role !== "admin") {
return res
.status(401)
.json({ status: 401, message: "Permission denied" });
}
const payload: Partial<UpdatePollPayload> = req.body;
if (payload.optionsToDelete && payload.optionsToDelete.length > 0) {
await prisma.option.deleteMany({
where: {
pollId,
id: {
in: payload.optionsToDelete,
},
},
});
}
if (payload.optionsToAdd && payload.optionsToAdd.length > 0) {
await prisma.option.createMany({
data: payload.optionsToAdd.map((optionValue) => ({
value: optionValue,
pollId,
})),
});
}
const poll = await prisma.poll.update({
where: {
urlId: pollId,
},
data: {
title: payload.title,
location: payload.location,
description: payload.description,
timeZone: payload.timeZone,
notifications: payload.notifications,
closed: payload.closed,
},
include: {
options: {
include: {
votes: true,
},
},
participants: {
include: {
votes: true,
},
orderBy: [
{
createdAt: "desc",
},
{ name: "desc" },
],
},
user: true,
links: true,
},
});
if (!poll) {
return res
.status(404)
.json({ status: 404, message: "Poll not found" });
}
return res.json({
...exclude(poll, "verificationCode"),
role: link.role,
urlId: link.urlId,
pollId: poll.urlId,
});
}
default:
return res
.status(405)
.json({ status: 405, message: "Method not allowed" });
}
},
);

View file

@ -0,0 +1,48 @@
import { prisma } from "../../../../../db";
import { withLink, getQueryParam } from "../../../../../utils/api-utils";
export default withLink(async (req, res, link) => {
const participantId = getQueryParam(req, "participantId");
const pollId = link.pollId;
switch (req.method) {
case "PATCH":
await prisma.participant.update({
where: {
id_pollId: {
id: participantId,
pollId,
},
},
data: {
votes: {
deleteMany: {
pollId,
},
createMany: {
data: req.body.votes.map((optionId: string) => ({
optionId,
pollId,
})),
},
},
name: req.body.name,
},
});
return res.end();
case "DELETE":
await prisma.participant.delete({
where: {
id_pollId: {
id: participantId,
pollId,
},
},
});
return res.end();
default:
}
});

View file

@ -0,0 +1,82 @@
import absoluteUrl from "utils/absolute-url";
import { AddParticipantPayload } from "../../../../../api-client/add-participant";
import { prisma } from "../../../../../db";
import {
getAdminLink,
sendEmailTemplate,
withLink,
} from "../../../../../utils/api-utils";
export default withLink(async (req, res, link) => {
switch (req.method) {
case "POST": {
const payload: AddParticipantPayload = req.body;
const participant = await prisma.participant.create({
data: {
pollId: link.pollId,
name: payload.name,
votes: {
createMany: {
data: payload.votes.map((optionId) => ({
optionId,
pollId: link.pollId,
})),
},
},
},
include: {
votes: true,
},
});
if (link.role === "participant") {
const poll = await prisma.poll.findUnique({
where: {
urlId: link.pollId,
},
include: {
user: true,
links: true,
},
});
if (poll?.notifications && poll.verified && !poll.demo) {
// Get the admin link
const adminLink = getAdminLink(poll.links);
if (adminLink) {
const homePageUrl = absoluteUrl(req).origin;
const pollUrl = `${homePageUrl}/admin/${adminLink.urlId}`;
const unsubscribeUrl = `${pollUrl}?unsubscribe=true`;
try {
await sendEmailTemplate({
templateName: "new-participant",
to: poll.user.email,
subject: `Rallly: ${poll.title} - New Participant`,
templateVars: {
title: poll.title,
name: poll.authorName,
participantName: participant.name,
pollUrl,
homePageUrl: absoluteUrl(req).origin,
supportEmail: process.env.NEXT_PUBLIC_SUPPORT_EMAIL,
unsubscribeUrl,
},
});
} catch (e) {
console.error(e);
}
} else {
console.error(`Missing admin link for poll: ${link.pollId}`);
}
}
}
return res.json(participant);
}
default:
return res.status(405).json({ ok: 1 });
}
});

View file

@ -0,0 +1,32 @@
import { prisma } from "../../../../db";
import { withLink } from "../../../../utils/api-utils";
export default withLink(async (req, res, link) => {
if (req.method === "POST") {
if (link.role !== "admin") {
return res.status(401).end();
}
const { verificationCode } = req.body;
try {
await prisma.poll.update({
where: {
urlId_verificationCode: {
urlId: link.pollId,
verificationCode,
},
},
data: {
verified: true,
},
});
return res.end();
} catch {
console.error(
`Failed to verify poll "${link.pollId}" with code ${verificationCode}`,
);
return res.status(500).end();
}
}
return res.status(405).end();
});

View file

@ -0,0 +1,103 @@
import { addDays, addMinutes, format } from "date-fns";
import { NextApiRequest, NextApiResponse } from "next";
import { nanoid } from "utils/nanoid";
import { prisma } from "../../../db";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
switch (req.method) {
case "POST": {
const adminUrlId = await nanoid();
const demoUser = { name: "John Example", email: "noreply@rallly.co" };
const today = new Date();
const poll = await prisma.poll.create({
data: {
urlId: await nanoid(),
verificationCode: await nanoid(),
title: "Lunch Meeting Demo",
type: "date",
location: "Starbucks, 901 New York Avenue",
description:
"This poll has been automatically generated just for you! Feel free to try out all the different features and when you're ready, you can schedule a new poll.",
authorName: "John Example",
verified: true,
demo: true,
user: {
connectOrCreate: {
where: {
email: demoUser.email,
},
create: demoUser,
},
},
options: {
createMany: {
data: [...Array(4)].map((_, i) => {
return { value: format(addDays(today, i + 1), "yyyy-MM-dd") };
}),
},
},
links: {
createMany: {
data: [
{
role: "admin",
urlId: adminUrlId,
},
{
role: "participant",
urlId: await nanoid(),
},
],
},
},
participants: {
createMany: {
data: [
{ name: "John", createdAt: addMinutes(today, -1) },
{ name: "Alex", createdAt: addMinutes(today, -2) },
{ name: "Mark", createdAt: addMinutes(today, -3) },
{ name: "Samantha", createdAt: addMinutes(today, -4) },
],
},
},
},
include: {
options: true,
participants: true,
},
});
const voteData: Array<{
optionId: string;
participantId: string;
pollId: string;
}> = [];
// Randomly generate votes
poll.options.forEach((option) => {
poll.participants.forEach((participant) => {
if (Math.random() >= 0.5) {
voteData.push({
optionId: option.id,
participantId: participant.id,
pollId: poll.urlId,
});
}
});
});
await prisma.vote.createMany({
data: voteData,
});
return res.json({ urlId: adminUrlId });
}
default:
return res.status(405).json({ ok: 1 });
}
}

89
pages/api/poll/index.ts Normal file
View file

@ -0,0 +1,89 @@
import { NextApiRequest, NextApiResponse } from "next";
import { sendEmailTemplate } from "utils/api-utils";
import { nanoid } from "utils/nanoid";
import { CreatePollPayload } from "../../../api-client/create-poll";
import { prisma } from "../../../db";
import absoluteUrl from "../../../utils/absolute-url";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
switch (req.method) {
case "POST": {
const adminUrlId = await nanoid();
const payload: CreatePollPayload = req.body;
const poll = await prisma.poll.create({
data: {
urlId: await nanoid(),
verificationCode: await nanoid(),
title: payload.title,
type: payload.type,
timeZone: payload.timeZone,
location: payload.location,
description: payload.description,
authorName: payload.user.name,
demo: payload.demo,
user: {
connectOrCreate: {
where: {
email: payload.user.email,
},
create: {
id: await nanoid(),
...payload.user,
},
},
},
options: {
createMany: {
data: payload.options.map((value) => ({
value,
})),
},
},
links: {
createMany: {
data: [
{
urlId: adminUrlId,
role: "admin",
},
{
urlId: await nanoid(),
role: "participant",
},
],
},
},
},
});
const homePageUrl = absoluteUrl(req).origin;
const pollUrl = `${homePageUrl}/admin/${adminUrlId}`;
const verifyEmailUrl = `${pollUrl}?code=${poll.verificationCode}`;
try {
await sendEmailTemplate({
templateName: "new-poll",
to: payload.user.email,
subject: `Rallly: ${poll.title} - Verify your email address`,
templateVars: {
title: poll.title,
name: payload.user.name,
pollUrl,
verifyEmailUrl,
homePageUrl,
supportEmail: process.env.NEXT_PUBLIC_SUPPORT_EMAIL,
},
});
} catch (e) {
console.error(e);
}
return res.json({ urlId: adminUrlId, authorName: poll.authorName });
}
default:
return res.status(405).end();
}
}