feat: add RECORDING_READY webhook (#8277)
* feat: add RECORDING_READY webhook Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in> * fix: type errors and improvements Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in> --------- Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in> Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
co-authored by
Keith Williams
Hariom Balhara
parent
203a36e2b5
commit
d067b0782a
@@ -170,7 +170,7 @@ export const EventTeamWebhooksTab = ({
|
||||
</Dialog>
|
||||
{/* Edit webhook dialog */}
|
||||
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
|
||||
<DialogContent title={t("edit_webhook")}>
|
||||
<DialogContent enableOverflow title={t("edit_webhook")}>
|
||||
<WebhookForm
|
||||
webhook={webhookToEdit}
|
||||
apps={installedApps?.items.map((app) => app.slug)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { WebhookTriggerEvents } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -5,6 +6,8 @@ import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient";
|
||||
import { sendDailyVideoRecordingEmails } from "@calcom/emails";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
@@ -20,6 +23,38 @@ const downloadLinkSchema = z.object({
|
||||
download_link: z.string(),
|
||||
});
|
||||
|
||||
const triggerWebhook = async ({
|
||||
evt,
|
||||
downloadLink,
|
||||
booking,
|
||||
}: {
|
||||
evt: CalendarEvent;
|
||||
downloadLink: string;
|
||||
booking: {
|
||||
userId: number | undefined;
|
||||
eventTypeId: number | null;
|
||||
};
|
||||
}) => {
|
||||
const eventTrigger: WebhookTriggerEvents = "RECORDING_READY";
|
||||
// Send Webhook call if hooked to BOOKING.RECORDING_READY
|
||||
const subscriberOptions = {
|
||||
userId: booking.userId ?? 0,
|
||||
eventTypeId: booking.eventTypeId ?? 0,
|
||||
triggerEvent: eventTrigger,
|
||||
};
|
||||
const webhooks = await getWebhooks(subscriberOptions);
|
||||
|
||||
const promises = webhooks.map((webhook) =>
|
||||
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
|
||||
...evt,
|
||||
downloadLink,
|
||||
}).catch((e) => {
|
||||
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`, e);
|
||||
})
|
||||
);
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) {
|
||||
return res.status(405).json({ message: "No SendGrid API key or email" });
|
||||
@@ -51,6 +86,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
uid: true,
|
||||
location: true,
|
||||
isRecorded: true,
|
||||
eventTypeId: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -105,31 +141,36 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
},
|
||||
});
|
||||
|
||||
const response = await getDownloadLinkOfCalVideoByRecordingId(recordingId);
|
||||
const downloadLinkResponse = downloadLinkSchema.parse(response);
|
||||
const downloadLink = downloadLinkResponse.download_link;
|
||||
|
||||
const evt: CalendarEvent = {
|
||||
type: booking.title,
|
||||
title: booking.title,
|
||||
description: booking.description || undefined,
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
organizer: {
|
||||
email: booking.user?.email || "Email-less",
|
||||
name: booking.user?.name || "Nameless",
|
||||
timeZone: booking.user?.timeZone || "Europe/London",
|
||||
language: { translate: t, locale: booking?.user?.locale ?? "en" },
|
||||
},
|
||||
attendees: attendeesList,
|
||||
uid: booking.uid,
|
||||
};
|
||||
|
||||
await triggerWebhook({
|
||||
evt,
|
||||
downloadLink,
|
||||
booking: { userId: booking?.user?.id, eventTypeId: booking.eventTypeId },
|
||||
});
|
||||
|
||||
const isSendingEmailsAllowed = IS_SELF_HOSTED || session?.user?.belongsToActiveTeam;
|
||||
|
||||
// send emails to all attendees only when user has team plan
|
||||
if (isSendingEmailsAllowed) {
|
||||
const response = await getDownloadLinkOfCalVideoByRecordingId(recordingId);
|
||||
|
||||
const downloadLinkResponse = downloadLinkSchema.parse(response);
|
||||
const downloadLink = downloadLinkResponse.download_link;
|
||||
|
||||
const evt: CalendarEvent = {
|
||||
type: booking.title,
|
||||
title: booking.title,
|
||||
description: booking.description || undefined,
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
organizer: {
|
||||
email: booking.user?.email || "Email-less",
|
||||
name: booking.user?.name || "Nameless",
|
||||
timeZone: booking.user?.timeZone || "Europe/London",
|
||||
language: { translate: t, locale: booking?.user?.locale ?? "en" },
|
||||
},
|
||||
attendees: attendeesList,
|
||||
uid: booking.uid,
|
||||
};
|
||||
|
||||
await sendDailyVideoRecordingEmails(evt, downloadLink);
|
||||
return res.status(200).json({ message: "Success" });
|
||||
}
|
||||
|
||||
@@ -367,6 +367,7 @@
|
||||
"create_webhook": "Create Webhook",
|
||||
"booking_cancelled": "Booking Cancelled",
|
||||
"booking_rescheduled": "Booking Rescheduled",
|
||||
"recording_ready":"Recording Download Link Ready",
|
||||
"booking_created": "Booking Created",
|
||||
"meeting_ended": "Meeting Ended",
|
||||
"form_submitted": "Form Submitted",
|
||||
|
||||
@@ -34,6 +34,7 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record<string, WebhookTriggerEve
|
||||
{ value: WebhookTriggerEvents.BOOKING_CREATED, label: "booking_created" },
|
||||
{ value: WebhookTriggerEvents.BOOKING_RESCHEDULED, label: "booking_rescheduled" },
|
||||
{ value: WebhookTriggerEvents.MEETING_ENDED, label: "meeting_ended" },
|
||||
{ value: WebhookTriggerEvents.RECORDING_READY, label: "recording_ready" },
|
||||
],
|
||||
"routing-forms": [{ value: WebhookTriggerEvents.FORM_SUBMITTED, label: "form_submitted" }],
|
||||
} as const;
|
||||
|
||||
@@ -8,6 +8,7 @@ export const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP = {
|
||||
WebhookTriggerEvents.BOOKING_CREATED,
|
||||
WebhookTriggerEvents.BOOKING_RESCHEDULED,
|
||||
WebhookTriggerEvents.MEETING_ENDED,
|
||||
WebhookTriggerEvents.RECORDING_READY,
|
||||
] as const,
|
||||
"routing-forms": [WebhookTriggerEvents.FORM_SUBMITTED] as const,
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ type WebhookDataType = CalendarEvent &
|
||||
rescheduleEndTime?: string;
|
||||
triggerEvent: string;
|
||||
createdAt: string;
|
||||
downloadLink?: string;
|
||||
};
|
||||
|
||||
function getZapierPayload(data: CalendarEvent & EventTypeInfo & { status?: string }): string {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'RECORDING_READY';
|
||||
@@ -496,6 +496,7 @@ enum WebhookTriggerEvents {
|
||||
BOOKING_CANCELLED
|
||||
FORM_SUBMITTED
|
||||
MEETING_ENDED
|
||||
RECORDING_READY
|
||||
}
|
||||
|
||||
model Webhook {
|
||||
|
||||
Reference in New Issue
Block a user