From 6868474c92b5b3290eb6c219d04a3d7292eeea2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20L=C3=B3pez?= Date: Mon, 4 Oct 2021 19:30:00 -0600 Subject: [PATCH] Revert "Feature/cal 274 add webhooks (#628)" (#854) This reverts commit 4c07faefe7dbb1b6cc2118856911ee09c6b0d5f4. --- components/SettingsShell.tsx | 2 +- components/webhook/EditWebhook.tsx | 176 ---------- components/webhook/WebhookList.tsx | 23 -- components/webhook/WebhookListItem.tsx | 105 ------ .../Apple/AppleCalendarAdapter.ts | 3 - .../CalDav/CalDavCalendarAdapter.ts | 3 - lib/webhook.ts | 3 - lib/webhooks/sendPayload.tsx | 33 -- lib/webhooks/subscriberUrls.tsx | 27 -- pages/api/availability/eventtype.ts | 11 +- pages/api/book/event.ts | 12 - pages/api/cancel.ts | 41 +-- pages/api/eventType.ts | 115 ------- pages/api/webhook.ts | 49 --- pages/api/webhooks/[hook]/index.ts | 57 ---- pages/cancel/[uid].tsx | 27 +- pages/settings/embed.tsx | 307 +++--------------- .../migration.sql | 17 - prisma/schema.prisma | 15 - 19 files changed, 66 insertions(+), 960 deletions(-) delete mode 100644 components/webhook/EditWebhook.tsx delete mode 100644 components/webhook/WebhookList.tsx delete mode 100644 components/webhook/WebhookListItem.tsx delete mode 100644 lib/webhook.ts delete mode 100644 lib/webhooks/sendPayload.tsx delete mode 100644 lib/webhooks/subscriberUrls.tsx delete mode 100644 pages/api/eventType.ts delete mode 100644 pages/api/webhook.ts delete mode 100644 pages/api/webhooks/[hook]/index.ts delete mode 100644 prisma/migrations/20211004231654_add_webhook_model/migration.sql diff --git a/components/SettingsShell.tsx b/components/SettingsShell.tsx index 04c9117052..64a72b0b3a 100644 --- a/components/SettingsShell.tsx +++ b/components/SettingsShell.tsx @@ -15,7 +15,7 @@ export default function SettingsShell({ children }: { children: React.ReactNode href: "/settings/security", icon: KeyIcon, }, - { name: "Embed & Webhooks", href: "/settings/embed", icon: CodeIcon }, + { name: "Embed", href: "/settings/embed", icon: CodeIcon }, { name: "Teams", href: "/settings/teams", diff --git a/components/webhook/EditWebhook.tsx b/components/webhook/EditWebhook.tsx deleted file mode 100644 index 56ea9fc945..0000000000 --- a/components/webhook/EditWebhook.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { ArrowLeftIcon } from "@heroicons/react/solid"; -import { EventType } from "@prisma/client"; -import { useEffect, useRef, useState } from "react"; - -import showToast from "@lib/notification"; -import { Webhook } from "@lib/webhook"; - -import Button from "@components/ui/Button"; -import Switch from "@components/ui/Switch"; - -export default function EditTeam(props: { - webhook: Webhook; - eventTypes: EventType[]; - onCloseEdit: () => void; -}) { - const [bookingCreated, setBookingCreated] = useState( - props.webhook.eventTriggers.includes("booking_created") - ); - const [bookingRescheduled, setBookingRescheduled] = useState( - props.webhook.eventTriggers.includes("booking_rescheduled") - ); - const [bookingCancelled, setBookingCancelled] = useState( - props.webhook.eventTriggers.includes("booking_cancelled") - ); - const [webhookEnabled, setWebhookEnabled] = useState(props.webhook.active); - const [webhookEventTrigger, setWebhookEventTriggers] = useState([ - "BOOKING_CREATED", - "BOOKING_RESCHEDULED", - "BOOKING_CANCELLED", - ]); - const [btnLoading, setBtnLoading] = useState(false); - const subUrlRef = useRef() as React.MutableRefObject; - - useEffect(() => { - const arr = []; - bookingCreated && arr.push("BOOKING_CREATED"); - bookingRescheduled && arr.push("BOOKING_RESCHEDULED"); - bookingCancelled && arr.push("BOOKING_CANCELLED"); - setWebhookEventTriggers(arr); - }, [bookingCreated, bookingRescheduled, bookingCancelled, webhookEnabled]); - - const handleErrors = async (resp: Response) => { - if (!resp.ok) { - const err = await resp.json(); - throw new Error(err.message); - } - return resp.json(); - }; - - const updateWebhookHandler = (event) => { - event.preventDefault(); - setBtnLoading(true); - return fetch("/api/webhooks/" + props.webhook.id, { - method: "PATCH", - body: JSON.stringify({ - subscriberUrl: subUrlRef.current.value, - eventTriggers: webhookEventTrigger, - enabled: webhookEnabled, - }), - headers: { - "Content-Type": "application/json", - }, - }) - .then(handleErrors) - .then(() => { - showToast("Webhook updated successfully!", "success"); - setBtnLoading(false); - }); - }; - - return ( -
-
-
- -
-
-
-

Manage your webhook

-
-
-
-
-
-
- - - Event Triggers -
-
-
-

Booking Created

-
-
- { - setBookingCreated(!bookingCreated); - }} - /> -
-
-
-
-

Booking Rescheduled

-
-
- { - setBookingRescheduled(!bookingRescheduled); - }} - /> -
-
-
-
-

Booking Cancelled

-
-
- { - setBookingCancelled(!bookingCancelled); - }} - /> -
-
-
- Webhook Status -
-
-
-

Webhook Enabled

-
-
- { - setWebhookEnabled(!webhookEnabled); - }} - /> -
-
-
-
-
- -
-
-
-
-
- ); -} diff --git a/components/webhook/WebhookList.tsx b/components/webhook/WebhookList.tsx deleted file mode 100644 index a046a039b4..0000000000 --- a/components/webhook/WebhookList.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Webhook } from "@lib/webhook"; - -import WebhookListItem from "./WebhookListItem"; - -export default function WebhookList(props: { - webhooks: Webhook[]; - onChange: () => void; - onEditWebhook: (webhook: Webhook) => void; -}) { - return ( -
-
    - {props.webhooks.map((webhook: Webhook) => ( - props.onEditWebhook(webhook)}> - ))} -
-
- ); -} diff --git a/components/webhook/WebhookListItem.tsx b/components/webhook/WebhookListItem.tsx deleted file mode 100644 index 4357255a42..0000000000 --- a/components/webhook/WebhookListItem.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { TrashIcon, PencilAltIcon } from "@heroicons/react/outline"; - -import showToast from "@lib/notification"; -import { Webhook } from "@lib/webhook"; - -import { Dialog, DialogTrigger } from "@components/Dialog"; -import { Tooltip } from "@components/Tooltip"; -import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent"; -import Button from "@components/ui/Button"; - -export default function WebhookListItem(props: { - onChange: () => void; - key: number; - webhook: Webhook; - onEditWebhook: () => void; -}) { - const handleErrors = async (resp: Response) => { - if (!resp.ok) { - const err = await resp.json(); - throw new Error(err.message); - } - return resp.json(); - }; - - const deleteWebhook = (webhookId: string) => { - fetch("/api/webhooks/" + webhookId, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - }) - .then(handleErrors) - .then(() => { - showToast("Webhook removed successfully!", "success"); - props.onChange(); - }); - }; - - return ( -
  • -
    -
    - - {props.webhook.eventTriggers.map((eventTrigger, ind) => ( - - {eventTrigger} - - ))} - -
    -
    -
    - {props.webhook.subscriberUrl} -
    -
    -
    - {!props.webhook.active && ( - - Disabled - - )} - {!!props.webhook.active && ( - - Enabled - - )} - - - - - - - - - - - { - deleteWebhook(props.webhook.id); - }}> - Are you sure you want to delete this webhook? You will no longer receive Cal.com meeting data at - a specified URL, in real-time, when an event is scheduled or canceled . - - -
    -
    -
  • - ); -} diff --git a/lib/integrations/Apple/AppleCalendarAdapter.ts b/lib/integrations/Apple/AppleCalendarAdapter.ts index a619e8204b..1429b0c4f9 100644 --- a/lib/integrations/Apple/AppleCalendarAdapter.ts +++ b/lib/integrations/Apple/AppleCalendarAdapter.ts @@ -1,6 +1,5 @@ import { Credential } from "@prisma/client"; import dayjs from "dayjs"; -import utc from "dayjs/plugin/utc"; import ICAL from "ical.js"; import { createEvent, DurationObject, Attendee, Person } from "ics"; import { @@ -20,8 +19,6 @@ import logger from "@lib/logger"; import { IntegrationCalendar, CalendarApiAdapter, CalendarEvent } from "../../calendarClient"; import { stripHtml } from "../../emails/helpers"; -dayjs.extend(utc); - const log = logger.getChildLogger({ prefix: ["[[lib] apple calendar"] }); type EventBusyDate = Record<"start" | "end", Date>; diff --git a/lib/integrations/CalDav/CalDavCalendarAdapter.ts b/lib/integrations/CalDav/CalDavCalendarAdapter.ts index 633b5a02ee..c9027e83dd 100644 --- a/lib/integrations/CalDav/CalDavCalendarAdapter.ts +++ b/lib/integrations/CalDav/CalDavCalendarAdapter.ts @@ -1,6 +1,5 @@ import { Credential } from "@prisma/client"; import dayjs from "dayjs"; -import utc from "dayjs/plugin/utc"; import ICAL from "ical.js"; import { Attendee, createEvent, DurationObject, Person } from "ics"; import { @@ -20,8 +19,6 @@ import logger from "@lib/logger"; import { CalendarApiAdapter, CalendarEvent, IntegrationCalendar } from "../../calendarClient"; import { stripHtml } from "../../emails/helpers"; -dayjs.extend(utc); - const log = logger.getChildLogger({ prefix: ["[lib] caldav"] }); type EventBusyDate = Record<"start" | "end", Date>; diff --git a/lib/webhook.ts b/lib/webhook.ts deleted file mode 100644 index 91ee55f584..0000000000 --- a/lib/webhook.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Webhook as PrismaWebhook } from "@prisma/client"; - -export type Webhook = PrismaWebhook & { prevState: null }; diff --git a/lib/webhooks/sendPayload.tsx b/lib/webhooks/sendPayload.tsx deleted file mode 100644 index 9a64b9b522..0000000000 --- a/lib/webhooks/sendPayload.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { CalendarEvent } from "@lib/calendarClient"; - -const sendPayload = ( - triggerEvent: string, - createdAt: string, - subscriberUrl: string, - payload: CalendarEvent -): Promise => - new Promise((resolve, reject) => { - if (!subscriberUrl || !payload) { - return reject("Missing required elements to send webhook payload."); - } - - fetch(subscriberUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - triggerEvent: triggerEvent, - createdAt: createdAt, - payload: payload, - }), - }) - .then((response) => { - resolve(response); - }) - .catch((err) => { - reject(err); - }); - }); - -export default sendPayload; diff --git a/lib/webhooks/subscriberUrls.tsx b/lib/webhooks/subscriberUrls.tsx deleted file mode 100644 index aeab311ca5..0000000000 --- a/lib/webhooks/subscriberUrls.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { WebhookTriggerEvents } from "@prisma/client"; - -import prisma from "@lib/prisma"; - -const getSubscriberUrls = async (userId: number, triggerEvent: WebhookTriggerEvents): Promise => { - const allWebhooks = await prisma.webhook.findMany({ - where: { - userId: userId, - AND: { - eventTriggers: { - has: triggerEvent, - }, - active: { - equals: true, - }, - }, - }, - select: { - subscriberUrl: true, - }, - }); - const subscriberUrls = allWebhooks.map(({ subscriberUrl }) => subscriberUrl); - - return subscriberUrls; -}; - -export default getSubscriberUrls; diff --git a/pages/api/availability/eventtype.ts b/pages/api/availability/eventtype.ts index f68dca3c59..e5966b4f1b 100644 --- a/pages/api/availability/eventtype.ts +++ b/pages/api/availability/eventtype.ts @@ -136,6 +136,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const openingHours = req.body.availability.openingHours || []; // const overrides = req.body.availability.dateOverrides || []; + await prisma.availability.deleteMany({ + where: { + eventTypeId: +req.body.id, + }, + }); Promise.all( openingHours.map((schedule) => prisma.availability.create({ @@ -169,12 +174,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }, }); - await prisma.webhookEventTypes.deleteMany({ - where: { - eventTypeId: req.body.id, - }, - }); - await prisma.eventType.delete({ where: { id: req.body.id, diff --git a/pages/api/book/event.ts b/pages/api/book/event.ts index d728a54de1..c0aca847f4 100644 --- a/pages/api/book/event.ts +++ b/pages/api/book/event.ts @@ -19,8 +19,6 @@ import logger from "@lib/logger"; import prisma from "@lib/prisma"; import { BookingCreateBody } from "@lib/types/booking"; import { getBusyVideoTimes } from "@lib/videoClient"; -import sendPayload from "@lib/webhooks/sendPayload"; -import getSubscriberUrls from "@lib/webhooks/subscriberUrls"; dayjs.extend(dayjsBusinessDays); dayjs.extend(utc); @@ -466,16 +464,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) log.debug(`Booking ${user.username} completed`); - const eventTrigger = rescheduleUid ? "BOOKING_RESCHEDULED" : "BOOKING_CREATED"; - // Send Webhook call if hooked to BOOKING_CREATED & BOOKING_RESCHEDULED - const subscriberUrls = await getSubscriberUrls(user.id, eventTrigger); - const promises = subscriberUrls.map((url) => - sendPayload(eventTrigger, new Date().toISOString(), url, evt).catch((e) => { - console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${url}`, e); - }) - ); - await Promise.all(promises); - await prisma.booking.update({ where: { uid: booking.uid, diff --git a/pages/api/cancel.ts b/pages/api/cancel.ts index 91a544e3c3..bd796e2bd5 100644 --- a/pages/api/cancel.ts +++ b/pages/api/cancel.ts @@ -8,12 +8,10 @@ import { getSession } from "@lib/auth"; import { CalendarEvent, deleteEvent } from "@lib/calendarClient"; import prisma from "@lib/prisma"; import { deleteMeeting } from "@lib/videoClient"; -import sendPayload from "@lib/webhooks/sendPayload"; -import getSubscriberUrls from "@lib/webhooks/subscriberUrls"; export default async function handler(req, res) { // just bail if it not a DELETE - if (req.method !== "DELETE" && req.method !== "POST") { + if (req.method !== "DELETE") { return res.status(405).end(); } @@ -26,7 +24,6 @@ export default async function handler(req, res) { }, select: { id: true, - userId: true, user: { select: { id: true, @@ -51,7 +48,6 @@ export default async function handler(req, res) { startTime: true, endTime: true, uid: true, - eventTypeId: true, }, }); @@ -63,41 +59,6 @@ export default async function handler(req, res) { return res.status(403).json({ message: "Cannot cancel past events" }); } - const organizer = await prisma.user.findFirst({ - where: { - id: bookingToDelete.userId as number, - }, - select: { - name: true, - email: true, - timeZone: true, - }, - }); - - const evt: CalendarEvent = { - type: bookingToDelete?.title, - title: bookingToDelete?.title, - description: bookingToDelete?.description || "", - startTime: bookingToDelete?.startTime.toString(), - endTime: bookingToDelete?.endTime.toString(), - organizer: organizer, - attendees: bookingToDelete?.attendees.map((attendee) => { - const retObj = { name: attendee.name, email: attendee.email, timeZone: attendee.timeZone }; - return retObj; - }), - }; - - // Hook up the webhook logic here - const eventTrigger = "BOOKING_CANCELLED"; - // Send Webhook call if hooked to BOOKING.CANCELLED - const subscriberUrls = await getSubscriberUrls(bookingToDelete.userId, eventTrigger); - const promises = subscriberUrls.map((url) => - sendPayload(eventTrigger, new Date().toISOString(), url, evt).catch((e) => { - console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${url}`, e); - }) - ); - await Promise.all(promises); - // by cancelling first, and blocking whilst doing so; we can ensure a cancel // action always succeeds even if subsequent integrations fail cancellation. await prisma.booking.update({ diff --git a/pages/api/eventType.ts b/pages/api/eventType.ts deleted file mode 100644 index 66f5ef9b36..0000000000 --- a/pages/api/eventType.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Prisma } from "@prisma/client"; -import type { NextApiRequest, NextApiResponse } from "next"; -import { getSession } from "next-auth/client"; - -import prisma from "@lib/prisma"; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const session = await getSession({ req: req }); - - if (!session) { - res.status(401).json({ message: "Not authenticated" }); - return; - } - - const eventTypeSelect = Prisma.validator()({ - id: true, - title: true, - description: true, - length: true, - schedulingType: true, - slug: true, - hidden: true, - price: true, - currency: true, - users: { - select: { - id: true, - avatar: true, - name: true, - }, - }, - }); - - const user = await prisma.user.findUnique({ - where: { - id: session.user.id, - }, - select: { - id: true, - username: true, - name: true, - startTime: true, - endTime: true, - bufferTime: true, - avatar: true, - completedOnboarding: true, - createdDate: true, - plan: true, - teams: { - where: { - accepted: true, - }, - select: { - role: true, - team: { - select: { - id: true, - name: true, - slug: true, - logo: true, - members: { - select: { - userId: true, - }, - }, - eventTypes: { - select: eventTypeSelect, - }, - }, - }, - }, - }, - eventTypes: { - where: { - team: null, - }, - select: eventTypeSelect, - }, - }, - }); - - // backwards compatibility, TMP: - const typesRaw = await prisma.eventType.findMany({ - where: { - userId: session.user.id, - }, - select: eventTypeSelect, - }); - - type EventTypeGroup = { - teamId?: number | null; - profile?: { - slug: typeof user["username"]; - name: typeof user["name"]; - image: typeof user["avatar"]; - }; - metadata: { - membershipCount: number; - readOnly: boolean; - }; - eventTypes: (typeof user.eventTypes[number] & { $disabled?: boolean })[]; - }; - - const eventTypesHashMap = user.eventTypes.concat(typesRaw).reduce((hashMap, newItem) => { - const oldItem = hashMap[newItem.id] || {}; - hashMap[newItem.id] = { ...oldItem, ...newItem }; - return hashMap; - }, {} as Record); - const mergedEventTypes = Object.values(eventTypesHashMap).map((et, index) => ({ - ...et, - $disabled: user.plan === "FREE" && index > 0, - })); - - return res.status(200).json({ eventTypes: mergedEventTypes }); -} diff --git a/pages/api/webhook.ts b/pages/api/webhook.ts deleted file mode 100644 index ec4d786311..0000000000 --- a/pages/api/webhook.ts +++ /dev/null @@ -1,49 +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, - }, - }); - - 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 02861ac2ef..0000000000 --- a/pages/api/webhooks/[hook]/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { getSession } from "next-auth/client"; - -import prisma from "@lib/prisma"; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const session = await getSession({ req: req }); - if (!session) { - return res.status(401).json({ message: "Not authenticated" }); - } - - // GET /api/webhook/{hook} - const webhooks = await prisma.webhook.findFirst({ - where: { - id: String(req.query.hook), - userId: session.user.id, - }, - }); - if (req.method === "GET") { - return res.status(200).json({ webhooks: webhooks }); - } - - // 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") { - const webhook = await prisma.webhook.findUnique({ - where: { - id: req.query.hook as string, - }, - }); - - if (!webhook) { - return res.status(404).json({ message: "Invalid Webhook" }); - } - - await prisma.webhook.update({ - where: { - id: req.query.hook as string, - }, - 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/cancel/[uid].tsx b/pages/cancel/[uid].tsx index 4674685051..0d14bd4daa 100644 --- a/pages/cancel/[uid].tsx +++ b/pages/cancel/[uid].tsx @@ -63,24 +63,24 @@ export default function Type(props) { description={`Cancel ${props.booking && props.booking.title} | ${props.profile.name}`} />
    -
    -
    -