diff --git a/lib/webhooks/constants.ts b/lib/webhooks/constants.ts new file mode 100644 index 00000000..b774d51d --- /dev/null +++ b/lib/webhooks/constants.ts @@ -0,0 +1,8 @@ +import { WebhookTriggerEvents } from "@prisma/client"; + +// this is exported as we can't use `WebhookTriggerEvents` in the frontend straight-off +export const WEBHOOK_TRIGGER_EVENTS = [ + WebhookTriggerEvents.BOOKING_CANCELLED, + WebhookTriggerEvents.BOOKING_CREATED, + WebhookTriggerEvents.BOOKING_RESCHEDULED, +] as const; diff --git a/pages/api/webhook.ts b/pages/api/webhook.ts deleted file mode 100644 index 7b2d7243..00000000 --- a/pages/api/webhook.ts +++ /dev/null @@ -1,50 +0,0 @@ -import dayjs from "dayjs"; -import utc from "dayjs/plugin/utc"; -import type { NextApiRequest, NextApiResponse } from "next"; -import short from "short-uuid"; -import { v5 as uuidv5 } from "uuid"; - -import { getSession } from "@lib/auth"; -import prisma from "@lib/prisma"; - -dayjs.extend(utc); - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const session = await getSession({ req }); - - if (!session?.user?.id) { - res.status(401).json({ message: "Not authenticated" }); - return; - } - - // List webhooks - if (req.method === "GET") { - const webhooks = await prisma.webhook.findMany({ - where: { - userId: session.user.id, - }, - }); - - return res.status(200).json({ webhooks: webhooks }); - } - - if (req.method === "POST") { - const translator = short(); - const seed = `${req.body.subscriberUrl}:${dayjs(new Date()).utc().format()}`; - const uid = translator.fromUUID(uuidv5(seed, uuidv5.URL)); - - await prisma.webhook.create({ - data: { - id: uid, - userId: session.user.id, - subscriberUrl: req.body.subscriberUrl, - eventTriggers: req.body.eventTriggers, - active: req.body.enabled, - }, - }); - - return res.status(201).json({ message: "Webhook created" }); - } - - res.status(404).json({ message: "Webhook not found" }); -} diff --git a/pages/api/webhooks/[hook]/index.ts b/pages/api/webhooks/[hook]/index.ts deleted file mode 100644 index 0b09c8e6..00000000 --- a/pages/api/webhooks/[hook]/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -import { getSession } from "@lib/auth"; -import prisma from "@lib/prisma"; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const session = await getSession({ req: req }); - const userId = session?.user?.id; - if (!userId) { - return res.status(401).json({ message: "Not authenticated" }); - } - - // GET /api/webhook/{hook} - const webhook = await prisma.webhook.findFirst({ - where: { - id: String(req.query.hook), - userId, - }, - }); - if (!webhook) { - return res.status(404).json({ message: "Invalid Webhook" }); - } - if (req.method === "GET") { - return res.status(200).json({ webhook }); - } - - // DELETE /api/webhook/{hook} - if (req.method === "DELETE") { - await prisma.webhook.delete({ - where: { - id: String(req.query.hook), - }, - }); - return res.status(200).json({}); - } - - if (req.method === "PATCH") { - await prisma.webhook.update({ - where: { - id: webhook.id, - }, - data: { - subscriberUrl: req.body.subscriberUrl, - eventTriggers: req.body.eventTriggers, - active: req.body.enabled, - }, - }); - - return res.status(200).json({ message: "Webhook updated successfully" }); - } -} diff --git a/pages/integrations/index.tsx b/pages/integrations/index.tsx index 3338f553..f825e3c0 100644 --- a/pages/integrations/index.tsx +++ b/pages/integrations/index.tsx @@ -1,25 +1,23 @@ import { + ChevronDownIcon, + ChevronUpIcon, PencilAltIcon, SwitchHorizontalIcon, TrashIcon, - ChevronDownIcon, - ChevronUpIcon, } from "@heroicons/react/outline"; import { ClipboardIcon } from "@heroicons/react/solid"; -import { WebhookTriggerEvents } from "@prisma/client"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible"; import Image from "next/image"; import React, { useState } from "react"; import { Controller, useForm, useWatch } from "react-hook-form"; -import { useMutation } from "react-query"; import { QueryCell } from "@lib/QueryCell"; import classNames from "@lib/classNames"; -import * as fetcher from "@lib/core/http/fetch-wrapper"; import { getErrorFromUnknown } from "@lib/errors"; import { useLocale } from "@lib/hooks/useLocale"; import showToast from "@lib/notification"; import { inferQueryOutput, trpc } from "@lib/trpc"; +import { WEBHOOK_TRIGGER_EVENTS } from "@lib/webhooks/constants"; import { Dialog, DialogContent, DialogFooter, DialogTrigger } from "@components/Dialog"; import { List, ListItem, ListItemText, ListItemTitle } from "@components/List"; @@ -40,16 +38,10 @@ import Switch from "@components/ui/Switch"; type TIntegrations = inferQueryOutput<"viewer.integrations">; type TWebhook = TIntegrations["webhooks"][number]; -const ALL_TRIGGERS: WebhookTriggerEvents[] = [ - // - "BOOKING_CREATED", - "BOOKING_RESCHEDULED", - "BOOKING_CANCELLED", -]; function WebhookListItem(props: { webhook: TWebhook; onEditWebhook: () => void }) { const { t } = useLocale(); const utils = trpc.useContext(); - const deleteWebhook = useMutation(async () => fetcher.remove(`/api/webhooks/${props.webhook.id}`, null), { + const deleteWebhook = trpc.useMutation("viewer.webhook.delete", { async onSuccess() { await utils.invalidateQueries(["viewer.integrations"]); }, @@ -110,7 +102,7 @@ function WebhookListItem(props: { webhook: TWebhook; onEditWebhook: () => void } title={t("delete_webhook")} confirmBtnText={t("confirm_delete_webhook")} cancelBtnText={t("cancel")} - onConfirm={() => deleteWebhook.mutate()}> + onConfirm={() => deleteWebhook.mutate({ id: props.webhook.id })}> {t("delete_webhook_confirmation_message")} @@ -185,7 +177,7 @@ function WebhookDialogForm(props: { const { defaultValues = { id: "", - eventTriggers: ALL_TRIGGERS, + eventTriggers: WEBHOOK_TRIGGER_EVENTS, subscriberUrl: "", active: true, }, @@ -194,7 +186,6 @@ function WebhookDialogForm(props: { const form = useForm({ defaultValues, }); - return (