diff --git a/packages/features/webhooks/lib/cron.ts b/packages/features/webhooks/lib/cron.ts index 79dc78a6ef..667e2fd21f 100644 --- a/packages/features/webhooks/lib/cron.ts +++ b/packages/features/webhooks/lib/cron.ts @@ -31,20 +31,35 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { lte: dayjs().toISOString(), }, }, + select: { + id: true, + jobName: true, + payload: true, + subscriberUrl: true, + webhook: { + select: { + secret: true, + }, + }, + }, }); + const fetchPromises: Promise[] = []; + // run jobs for (const job of jobsToRun) { // Fetch the webhook configuration so that we can get the secret. const [appId, subscriberId] = job.jobName.split("_"); + let webhook = job.webhook; - let webhook; - try { - webhook = await prisma.webhook.findUniqueOrThrow({ - where: { id: subscriberId, appId: appId !== "null" ? appId : null }, - }); - } catch { - logger.error(`Error finding webhook for subscriberId: ${subscriberId}, appId: ${appId}`); + if (!webhook) { + try { + webhook = await prisma.webhook.findUniqueOrThrow({ + where: { id: subscriberId, appId: appId !== "null" ? appId : null }, + }); + } catch { + logger.error(`Error finding webhook for subscriberId: ${subscriberId}, appId: ${appId}`); + } } const headers: Record = { @@ -56,33 +71,15 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { headers["X-Cal-Signature-256"] = createWebhookSignature({ secret: webhook.secret, body: job.payload }); } - try { - await fetch(job.subscriberUrl, { + fetchPromises.push( + fetch(job.subscriberUrl, { method: "POST", body: job.payload, headers, - }); - } catch (error) { - console.log(`Error running webhook trigger (retry count: ${job.retryCount}): ${error}`); - - // if job fails, retry again for 5 times. - if (job.retryCount < 5) { - await prisma.webhookScheduledTriggers.update({ - where: { - id: job.id, - }, - data: { - retryCount: { - increment: 1, - }, - startAfter: dayjs() - .add(5 * (job.retryCount + 1), "minutes") - .toISOString(), - }, - }); - return res.json({ ok: false }); - } - } + }).catch((error) => { + console.error(`Webhook trigger for subscriber url ${job.subscriberUrl} failed with error: ${error}`); + }) + ); const parsedJobPayload = JSON.parse(job.payload) as { id: number; // booking id @@ -122,6 +119,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { }); } + Promise.allSettled(fetchPromises); + res.json({ ok: true }); } diff --git a/packages/features/webhooks/lib/scheduleTrigger.ts b/packages/features/webhooks/lib/scheduleTrigger.ts index 9048be3c65..6664f9d31c 100644 --- a/packages/features/webhooks/lib/scheduleTrigger.ts +++ b/packages/features/webhooks/lib/scheduleTrigger.ts @@ -297,6 +297,11 @@ export async function scheduleTrigger( payload, startAfter: triggerEvent === WebhookTriggerEvents.MEETING_ENDED ? booking.endTime : booking.startTime, subscriberUrl, + webhook: { + connect: { + id: subscriber.id, + }, + }, }, }); diff --git a/packages/prisma/migrations/20240322152654_add_webhook_to_scheduled_webhook_triggers/migration.sql b/packages/prisma/migrations/20240322152654_add_webhook_to_scheduled_webhook_triggers/migration.sql new file mode 100644 index 0000000000..65b79c4ebb --- /dev/null +++ b/packages/prisma/migrations/20240322152654_add_webhook_to_scheduled_webhook_triggers/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "WebhookScheduledTriggers" ADD COLUMN "webhookId" TEXT; + +-- AddForeignKey +ALTER TABLE "WebhookScheduledTriggers" ADD CONSTRAINT "WebhookScheduledTriggers_webhookId_fkey" FOREIGN KEY ("webhookId") REFERENCES "Webhook"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 7b961a62d3..fd6e8f8cb9 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -691,23 +691,24 @@ enum WebhookTriggerEvents { } model Webhook { - id String @id @unique - userId Int? - teamId Int? - eventTypeId Int? + id String @id @unique + userId Int? + teamId Int? + eventTypeId Int? /// @zod.url() - subscriberUrl String - payloadTemplate String? - createdAt DateTime @default(now()) - active Boolean @default(true) - eventTriggers WebhookTriggerEvents[] - user User? @relation(fields: [userId], references: [id], onDelete: Cascade) - team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade) - eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade) - app App? @relation(fields: [appId], references: [slug], onDelete: Cascade) - appId String? - secret String? - platform Boolean @default(false) + subscriberUrl String + payloadTemplate String? + createdAt DateTime @default(now()) + active Boolean @default(true) + eventTriggers WebhookTriggerEvents[] + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) + team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade) + eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade) + app App? @relation(fields: [appId], references: [slug], onDelete: Cascade) + appId String? + secret String? + platform Boolean @default(false) + scheduledTriggers WebhookScheduledTriggers[] @@unique([userId, subscriberUrl], name: "courseIdentifier") } @@ -989,6 +990,8 @@ model WebhookScheduledTriggers { startAfter DateTime retryCount Int @default(0) createdAt DateTime? @default(now()) + webhookId String? + webhook Webhook? @relation(fields: [webhookId], references: [id], onDelete: Cascade) } enum WorkflowTemplates { diff --git a/packages/prisma/zod/webhook.ts b/packages/prisma/zod/webhook.ts index 5ba7998b37..6f5f2c0203 100755 --- a/packages/prisma/zod/webhook.ts +++ b/packages/prisma/zod/webhook.ts @@ -1,7 +1,7 @@ import * as z from "zod" import * as imports from "../zod-utils" import { WebhookTriggerEvents } from "@prisma/client" -import { CompleteUser, UserModel, CompleteTeam, TeamModel, CompleteEventType, EventTypeModel, CompleteApp, AppModel } from "./index" +import { CompleteUser, UserModel, CompleteTeam, TeamModel, CompleteEventType, EventTypeModel, CompleteApp, AppModel, CompleteWebhookScheduledTriggers, WebhookScheduledTriggersModel } from "./index" export const _WebhookModel = z.object({ id: z.string(), @@ -23,6 +23,7 @@ export interface CompleteWebhook extends z.infer { team?: CompleteTeam | null eventType?: CompleteEventType | null app?: CompleteApp | null + scheduledTriggers: CompleteWebhookScheduledTriggers[] } /** @@ -35,4 +36,5 @@ export const WebhookModel: z.ZodSchema = z.lazy(() => _WebhookM team: TeamModel.nullish(), eventType: EventTypeModel.nullish(), app: AppModel.nullish(), + scheduledTriggers: WebhookScheduledTriggersModel.array(), }))