* add migration guide and dto * add factory * add notifier * add repo * add services * coderabbit review --1 * coderabbit review --2 * coderabbit review --3 * further improvement * -- * fix * bookingWebhookFactory consideration and type fixes * cleanup * fix types * DI part1 * DI --part 2 * remove migrationGuide as we're WIP * using evyweb for DI -- 1 * DI --final * separate func instead of private class * adds a todo migration file * adjust structure * address feedback * remove todo_migrate * --1 * fix type * address feedback * add TODO comment * address requested changes --1 * address feedback --2 * restructure as per feedback * rename camelcase
136 lines
5.0 KiB
TypeScript
136 lines
5.0 KiB
TypeScript
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
|
|
|
import type { WebhookEventDTO } from "../dto/types";
|
|
import type { BookingPayloadBuilder } from "../factory/BookingPayloadBuilder";
|
|
import type { FormPayloadBuilder } from "../factory/FormPayloadBuilder";
|
|
import type { InstantMeetingBuilder } from "../factory/InstantMeetingBuilder";
|
|
import type { MeetingPayloadBuilder } from "../factory/MeetingPayloadBuilder";
|
|
import type { OOOPayloadBuilder } from "../factory/OOOPayloadBuilder";
|
|
import type { RecordingPayloadBuilder } from "../factory/RecordingPayloadBuilder";
|
|
import type { WebhookPayload } from "../factory/types";
|
|
import type { ILogger } from "../interface/infrastructure";
|
|
import type { IWebhookService } from "../interface/services";
|
|
import type { IWebhookNotificationHandler } from "../interface/webhook";
|
|
|
|
export class WebhookNotificationHandler implements IWebhookNotificationHandler {
|
|
private readonly log: ILogger;
|
|
|
|
constructor(
|
|
private readonly webhookService: IWebhookService,
|
|
private readonly bookingPayloadBuilder: BookingPayloadBuilder,
|
|
private readonly formPayloadBuilder: FormPayloadBuilder,
|
|
private readonly oooPayloadBuilder: OOOPayloadBuilder,
|
|
private readonly recordingPayloadBuilder: RecordingPayloadBuilder,
|
|
private readonly meetingPayloadBuilder: MeetingPayloadBuilder,
|
|
private readonly instantMeetingBuilder: InstantMeetingBuilder,
|
|
logger: ILogger
|
|
) {
|
|
this.log = logger.getSubLogger({ prefix: ["[WebhookNotificationHandler]"] });
|
|
}
|
|
|
|
async handleNotification(dto: WebhookEventDTO, isDryRun = false): Promise<void> {
|
|
const trigger = dto.triggerEvent;
|
|
|
|
try {
|
|
if (isDryRun) {
|
|
this.log.debug(`Dry run mode - skipping webhook notification for: ${trigger}`);
|
|
return;
|
|
}
|
|
|
|
const subscriptionParams = {
|
|
userId: dto.userId,
|
|
eventTypeId: dto.eventTypeId,
|
|
triggerEvent: trigger,
|
|
teamId: dto.teamId,
|
|
orgId: dto.orgId,
|
|
oAuthClientId: dto.platformClientId,
|
|
};
|
|
|
|
this.log.debug(`Querying for webhook subscribers with params:`, subscriptionParams);
|
|
|
|
const subscribers = await this.webhookService.getSubscribers(subscriptionParams);
|
|
|
|
if (subscribers.length === 0) {
|
|
this.log.debug(`No subscribers found for event: ${trigger}`, {
|
|
bookingId: dto.bookingId,
|
|
eventTypeId: dto.eventTypeId,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const webhookPayload = this.createPayload(dto);
|
|
|
|
await this.webhookService.processWebhooks(trigger, webhookPayload, subscribers);
|
|
|
|
this.log.debug(`Successfully processed webhook notification: ${trigger}`, {
|
|
subscriberCount: subscribers.length,
|
|
bookingId: dto.bookingId,
|
|
});
|
|
} catch (error) {
|
|
this.log.error(`Error handling webhook notification: ${trigger}`, {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
bookingId: dto.bookingId,
|
|
eventTypeId: dto.eventTypeId,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private createPayload(dto: WebhookEventDTO): WebhookPayload {
|
|
switch (dto.triggerEvent) {
|
|
// Booking events
|
|
case WebhookTriggerEvents.BOOKING_CREATED:
|
|
case WebhookTriggerEvents.BOOKING_CANCELLED:
|
|
case WebhookTriggerEvents.BOOKING_REQUESTED:
|
|
case WebhookTriggerEvents.BOOKING_RESCHEDULED:
|
|
case WebhookTriggerEvents.BOOKING_REJECTED:
|
|
case WebhookTriggerEvents.BOOKING_PAID:
|
|
case WebhookTriggerEvents.BOOKING_PAYMENT_INITIATED:
|
|
case WebhookTriggerEvents.BOOKING_NO_SHOW_UPDATED:
|
|
return this.bookingPayloadBuilder.build(dto);
|
|
|
|
// Form events
|
|
case WebhookTriggerEvents.FORM_SUBMITTED:
|
|
case WebhookTriggerEvents.FORM_SUBMITTED_NO_EVENT:
|
|
return this.formPayloadBuilder.build(dto);
|
|
|
|
// OOO events
|
|
case WebhookTriggerEvents.OOO_CREATED:
|
|
return this.oooPayloadBuilder.build(dto);
|
|
|
|
// Recording events
|
|
case WebhookTriggerEvents.RECORDING_READY:
|
|
case WebhookTriggerEvents.RECORDING_TRANSCRIPTION_GENERATED:
|
|
return this.recordingPayloadBuilder.build(dto);
|
|
|
|
// Meeting events
|
|
case WebhookTriggerEvents.MEETING_STARTED:
|
|
case WebhookTriggerEvents.MEETING_ENDED:
|
|
return this.meetingPayloadBuilder.build(dto);
|
|
|
|
// Instant meeting events
|
|
case WebhookTriggerEvents.INSTANT_MEETING:
|
|
return this.instantMeetingBuilder.build(dto);
|
|
|
|
// No-show events (special handling)
|
|
case WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW:
|
|
case WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW: {
|
|
return {
|
|
triggerEvent: dto.triggerEvent,
|
|
createdAt: dto.createdAt,
|
|
payload: {
|
|
bookingId: dto.bookingId,
|
|
webhook: dto.webhook,
|
|
},
|
|
};
|
|
}
|
|
|
|
default: {
|
|
// TypeScript exhaustiveness check - this should never happen if all cases are covered
|
|
const _exhaustiveCheck: never = dto;
|
|
throw new Error(`Unsupported triggerEvent: ${JSON.stringify(_exhaustiveCheck)}`);
|
|
}
|
|
}
|
|
}
|
|
}
|