add subscriptionType + refactor code

pr/2623
CarinaWolli 2022-04-25 16:21:48 +02:00
parent 0198285ccc
commit 4bfcefbc68
17 changed files with 150 additions and 144 deletions

View File

@ -29,8 +29,7 @@ export default function WebhookDialogForm(props: {
subscriberUrl: "",
active: true,
payloadTemplate: null,
isZapierSubscription,
} as Omit<TWebhook, "userId" | "createdAt" | "eventTypeId">,
} as Omit<TWebhook, "userId" | "createdAt" | "eventTypeId" | "subscriptionType">,
} = props;
const [useCustomPayloadTemplate, setUseCustomPayloadTemplate] = useState(!!defaultValues.payloadTemplate);

View File

@ -1,3 +1,4 @@
import { SubscriptionType } from "@prisma/client";
import { compile } from "handlebars";
import type { CalendarEvent } from "@calcom/types/Calendar";
@ -29,6 +30,7 @@ const sendPayload = async (
metadata?: { [key: string]: string };
rescheduleUid?: string;
},
subscriptionType: SubscriptionType,
template?: string | null
) => {
if (!subscriberUrl || !data) {
@ -38,13 +40,21 @@ const sendPayload = async (
const contentType =
!template || jsonParse(template) ? "application/json" : "application/x-www-form-urlencoded";
const body = template
? applyTemplate(template, data, contentType)
: JSON.stringify({
triggerEvent: triggerEvent,
createdAt: createdAt,
payload: data,
});
data.description = data.description || data.additionalNotes;
let body;
if (subscriptionType === SubscriptionType.ZAPIER) {
body = JSON.stringify(data);
} else if (template) {
body = applyTemplate(template, data, contentType);
} else {
body = JSON.stringify({
triggerEvent: triggerEvent,
createdAt: createdAt,
payload: data,
});
}
const response = await fetch(subscriberUrl, {
method: "POST",

View File

@ -1,4 +1,5 @@
import { WebhookTriggerEvents } from "@prisma/client";
import { SubscriptionType } from "@prisma/client";
import prisma from "@lib/prisma";
@ -6,10 +7,11 @@ export type GetSubscriberOptions = {
userId: number;
eventTypeId: number;
triggerEvent: WebhookTriggerEvents;
subscriptionType: SubscriptionType;
};
const getSubscribers = async (options: GetSubscriberOptions) => {
const { userId, eventTypeId } = options;
const { userId, eventTypeId, subscriptionType } = options;
const allWebhooks = await prisma.webhook.findMany({
where: {
OR: [
@ -27,14 +29,13 @@ const getSubscribers = async (options: GetSubscriberOptions) => {
active: {
equals: true,
},
isZapierSubscription: {
equals: false,
},
subscriptionType: subscriptionType,
},
},
select: {
subscriberUrl: true,
payloadTemplate: true,
subscriptionType: true,
},
});

View File

@ -1,37 +0,0 @@
import { WebhookTriggerEvents } from "@prisma/client";
import prisma from "@lib/prisma";
export type GetSubscriberOptions = {
userId: number;
triggerEvent: WebhookTriggerEvents;
};
const getZapierSubscribers = async (options: GetSubscriberOptions) => {
const { userId, triggerEvent } = options;
const allSubscribers = await prisma.webhook.findMany({
where: {
AND: [
{
userId,
},
{
isZapierSubscription: true,
},
{
eventTriggers: {
has: triggerEvent,
},
},
],
},
select: {
subscriberUrl: true,
payloadTemplate: true,
},
});
return allSubscribers;
};
export default getZapierSubscribers;

View File

@ -6,6 +6,7 @@ import {
SchedulingType,
WebhookTriggerEvents,
} from "@prisma/client";
import { SubscriptionType } from "@prisma/client";
import async from "async";
import dayjs from "dayjs";
import dayjsBusinessTime from "dayjs-business-time";
@ -40,7 +41,6 @@ import prisma from "@lib/prisma";
import { BookingCreateBody } from "@lib/types/booking";
import sendPayload from "@lib/webhooks/sendPayload";
import getSubscribers from "@lib/webhooks/subscriptions";
import getZapierSubscribers from "@lib/zapier/subscriptions";
import { getTranslation } from "@server/lib/i18n";
@ -750,11 +750,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
// Send Webhook call if hooked to BOOKING_CREATED & BOOKING_RESCHEDULED
const subscribers = await getSubscribers(subscriberOptions);
//todo: check if zapier is installed
const subscribers = await getSubscribers({
...subscriberOptions,
subscriptionType: SubscriptionType.WEBHOOK,
});
if (zapierAppInstalled) {
const zapierSubscribers = await getZapierSubscribers(subscriberOptions);
const zapierSubscribers = await getSubscribers({
...subscriberOptions,
subscriptionType: SubscriptionType.ZAPIER,
});
subscribers.push(...zapierSubscribers);
}
@ -772,6 +777,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
rescheduleUid,
metadata: reqBody.metadata,
},
sub.subscriptionType,
sub.payloadTemplate
).catch((e) => {
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${sub.subscriberUrl}`, e);

View File

@ -1,4 +1,5 @@
import { BookingStatus, Credential, WebhookTriggerEvents } from "@prisma/client";
import { SubscriptionType } from "@prisma/client";
import async from "async";
import dayjs from "dayjs";
import { NextApiRequest, NextApiResponse } from "next";
@ -15,7 +16,6 @@ import { sendCancelledEmails } from "@lib/emails/email-manager";
import prisma from "@lib/prisma";
import sendPayload from "@lib/webhooks/sendPayload";
import getSubscribers from "@lib/webhooks/subscriptions";
import getZapierSubscribers from "@lib/zapier/subscriptions";
import { getTranslation } from "@server/lib/i18n";
@ -149,19 +149,34 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
const subscribers = await getSubscribers(subscriberOptions);
const subscribers = await getSubscribers({
...subscriberOptions,
subscriptionType: SubscriptionType.WEBHOOK,
});
//const allSubscribersPromises = [getSubscribers(subscriberOptions)]
if (zapierAppInstalled) {
const zapierSubscribers = await getZapierSubscribers(subscriberOptions);
const zapierSubscribers = await getSubscribers({
...subscriberOptions,
subscriptionType: SubscriptionType.ZAPIER,
});
subscribers.push(...zapierSubscribers);
//allSubscribersPromises.push(getZapierSubscribers(subscriberOptions));
}
//await Promise.all(allSubscribersPromises)
const promises = subscribers.map((sub) =>
sendPayload(eventTrigger, new Date().toISOString(), sub.subscriberUrl, evt, sub.payloadTemplate).catch(
(e) => {
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${sub.subscriberUrl}`, e);
}
)
sendPayload(
eventTrigger,
new Date().toISOString(),
sub.subscriberUrl,
evt,
sub.subscriptionType,
sub.payloadTemplate
).catch((e) => {
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${sub.subscriberUrl}`, e);
})
);
await Promise.all(promises);

View File

@ -1,34 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@lib/prisma";
import findValidApiKey from "@lib/zapier/findValidApiKey";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.query.apiKey as string;
if (!apiKey) {
return res.status(401).json({ message: "No API key provided" });
}
const validKey = await findValidApiKey(apiKey);
if (!validKey) {
return res.status(401).json({ message: "API not valid" });
}
const subscriptionId = req.query.subscriptionId as string;
if (req.method === "DELETE") {
try {
await prisma.webhook.delete({
where: {
id: subscriptionId,
},
});
res.status(201).json({ message: "Subscription deleted." });
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Unable to delete subscription." });
}
}
}

View File

@ -1,3 +1,4 @@
import { SubscriptionType } from "@prisma/client";
import { v4 } from "uuid";
import { z } from "zod";
@ -21,14 +22,18 @@ export const webhookRouter = createProtectedRouter()
return await ctx.prisma.webhook.findMany({
where: {
eventTypeId: input.eventTypeId,
isZapierSubscription: false,
NOT: {
subscriptionType: SubscriptionType.ZAPIER,
},
},
});
}
return await ctx.prisma.webhook.findMany({
where: {
userId: ctx.user.id,
isZapierSubscription: false,
NOT: {
subscriptionType: SubscriptionType.ZAPIER,
},
},
});
},
@ -40,13 +45,15 @@ export const webhookRouter = createProtectedRouter()
active: z.boolean(),
payloadTemplate: z.string().nullable(),
eventTypeId: z.number().optional(),
subscriptionType: z.nativeEnum(SubscriptionType).optional(),
}),
async resolve({ ctx, input }) {
const subscriptionType = input.subscriptionType || SubscriptionType.WEBHOOK;
if (input.eventTypeId) {
return await ctx.prisma.webhook.create({
data: {
id: v4(),
isZapierSubscription: false,
subscriptionType,
...input,
},
});
@ -54,7 +61,7 @@ export const webhookRouter = createProtectedRouter()
return await ctx.prisma.webhook.create({
data: {
id: v4(),
isZapierSubscription: false,
subscriptionType,
userId: ctx.user.id,
...input,
},
@ -69,6 +76,7 @@ export const webhookRouter = createProtectedRouter()
active: z.boolean().optional(),
payloadTemplate: z.string().nullable(),
eventTypeId: z.number().optional(),
subscriptionType: z.nativeEnum(SubscriptionType).optional(),
}),
async resolve({ ctx, input }) {
const { id, ...data } = input;
@ -174,7 +182,14 @@ export const webhookRouter = createProtectedRouter()
};
try {
return await sendPayload(type, new Date().toISOString(), url, data, payloadTemplate);
return await sendPayload(
type,
new Date().toISOString(),
url,
data,
SubscriptionType.WEBHOOK,
payloadTemplate
);
} catch (_err) {
const error = getErrorFromUnknown(_err);
return {

View File

@ -1 +1,4 @@
export { default as add } from "./add";
export { default as listBookings } from "./subscriptions/listBookings";
export { default as deleteSubscription } from "./subscriptions/deleteSubscription";
export { default as addSubscription } from "./subscriptions/addSubscription";

View File

@ -1,8 +1,9 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { v4 } from "uuid";
import prisma from "@lib/prisma";
import findValidApiKey from "@lib/zapier/findValidApiKey";
import findValidApiKey from "@calcom/ee/lib/api/findValidApiKey";
import prisma from "@calcom/prisma";
import { SubscriptionType } from "@prisma/client";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.query.apiKey as string;
@ -28,13 +29,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
eventTriggers: [triggerEvent],
subscriberUrl,
active: true,
isZapierSubscription: true,
subscriptionType: SubscriptionType.ZAPIER,
},
});
res.status(201).json(createSubscription);
res.status(200).json(createSubscription);
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Unable to create subscription." });
return res.status(500).json({ message: "Could not create subscription." });
}
}
}

View File

@ -0,0 +1,29 @@
import type { NextApiRequest, NextApiResponse } from "next";
import findValidApiKey from "@calcom/ee/lib/api/findValidApiKey";
import prisma from "@calcom/prisma";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.query.apiKey as string;
if (!apiKey) {
return res.status(401).json({ message: "No API key provided" });
}
const validKey = await findValidApiKey(apiKey);
if (!validKey) {
return res.status(401).json({ message: "API not valid" });
}
const id = req.query.id as string; //maybe change that again
if (req.method === "DELETE") {
await prisma.webhook.delete({
where: {
id,
},
});
res.status(204).json({ message: "Subscription is deleted." });
}
}

View File

@ -1,9 +1,7 @@
import type { NextApiRequest, NextApiResponse } from "next";
import type { CalendarEvent } from "@calcom/types/Calendar";
import prisma from "@lib/prisma";
import findValidApiKey from "@lib/zapier/findValidApiKey";
import findValidApiKey from "@calcom/ee/lib/api/findValidApiKey";
import prisma from "@calcom/prisma";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.query.apiKey as string;
@ -20,7 +18,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (req.method === "GET") {
try {
const bookings = await prisma.booking.findFirst({
const bookings = await prisma.booking.findMany({
take:3,
where: {
userId: validKey.userId,
},
@ -41,9 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
const payload = [{ payload: { ...bookings } }];
res.status(201).json(payload);
res.status(201).json(bookings);
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Unable to get bookings." });

View File

@ -1,20 +1,14 @@
import { hashAPIKey } from "@calcom/ee/lib/api/apiKeys";
import prisma from "@lib/prisma";
import prisma from "@calcom/prisma";
const findValidApiKey = async (apiKey: string) => {
const hashedKey = hashAPIKey(apiKey.substring(4));
const hashedKey = hashAPIKey(apiKey.substring(process.env.API_KEY_PREFIX?.length || 0));
const validKey = await prisma.apiKey.findFirst({
where: {
AND: [
{
hashedKey,
},
{
createdAt: {
lt: new Date(Date.now()),
},
},
{
expiresAt: {
gte: new Date(Date.now()),

View File

@ -1,8 +0,0 @@
/*
Warnings:
- Added the required column `isZapierSubscription` to the `Webhook` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Webhook" ADD COLUMN "isZapierSubscription" BOOLEAN NOT NULL;

View File

@ -0,0 +1,11 @@
/*
Warnings:
- Added the required column `subscriptionType` to the `Webhook` table without a default value. This is not possible if the table is not empty.
*/
-- CreateEnum
CREATE TYPE "SubscriptionType" AS ENUM ('WEBHOOK', 'ZAPIER');
-- AlterTable
ALTER TABLE "Webhook" ADD COLUMN "subscriptionType" "SubscriptionType" NOT NULL;

View File

@ -363,18 +363,23 @@ enum WebhookTriggerEvents {
BOOKING_CANCELLED
}
enum SubscriptionType {
WEBHOOK
ZAPIER
}
model Webhook {
id String @id @unique
userId Int?
eventTypeId Int?
subscriberUrl String
payloadTemplate String?
createdAt DateTime @default(now())
active Boolean @default(true)
eventTriggers WebhookTriggerEvents[]
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
isZapierSubscription Boolean
id String @id @unique
userId Int?
eventTypeId Int?
subscriberUrl String
payloadTemplate String?
createdAt DateTime @default(now())
active Boolean @default(true)
eventTriggers WebhookTriggerEvents[]
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
subscriptionType SubscriptionType @default(WEBHOOK)
}
model ApiKey {

View File

@ -1,6 +1,6 @@
import * as z from "zod"
import * as imports from "../zod-utils"
import { WebhookTriggerEvents } from "@prisma/client"
import { WebhookTriggerEvents, SubscriptionType } from "@prisma/client"
import { CompleteUser, UserModel, CompleteEventType, EventTypeModel } from "./index"
export const _WebhookModel = z.object({
@ -12,7 +12,7 @@ export const _WebhookModel = z.object({
createdAt: z.date(),
active: z.boolean(),
eventTriggers: z.nativeEnum(WebhookTriggerEvents).array(),
isZapierSubscription: z.boolean(),
subscriptionType: z.nativeEnum(SubscriptionType),
})
export interface CompleteWebhook extends z.infer<typeof _WebhookModel> {