Files
calendar/packages/features/webhooks/lib/scheduleTrigger.ts
T
c28eb90928 chore: Upgrade prisma to 6.7.0 (#21264)
* chore: Upgrade prisma to 6.7.0

* Build fixes

* type fixes

Signed-off-by: Omar López <zomars@me.com>

* Update schema.prisma

* Patching

* Revert "Update schema.prisma"

This reverts commit 47d8618bf89ef4d007b30084df766f17281e21a1.

* Revert "Patching"

This reverts commit a1d2e3040e71690a44d4324db95d73b4d68c6adb.

* Revert schema changes

Signed-off-by: Omar López <zomars@me.com>

* WIP

Signed-off-by: Omar López <zomars@me.com>

* Update getPublicEvent.ts

* Update imports

Signed-off-by: Omar López <zomars@me.com>

* Update gitignore

Signed-off-by: Omar López <zomars@me.com>

* update remaining imports

Signed-off-by: Omar López <zomars@me.com>

* Delete .cursor/config.json

* Discard changes to packages/features/eventtypes/lib/getPublicEvent.ts

* Update _get.ts

* Update user.ts

* Update .gitignore

* update

* Update WorkflowStepContainer.tsx

* Update next-auth-custom-adapter.ts

* Update getPublicEvent.ts

* Update workflow.ts

* Update next-auth-custom-adapter.ts

* Update next-auth-options.ts

* Update bookingScenario.ts

* fix missing imports

* upgrades prismock

Signed-off-by: Omar López <zomars@me.com>

* patches prismock

Signed-off-by: Omar López <zomars@me.com>

* Update reschedule.test.ts

* Update prisma.ts

* patch prismock

Signed-off-by: Omar López <zomars@me.com>

* fix enums imports

Signed-off-by: Omar López <zomars@me.com>

* Revert "Update prisma.ts"

This reverts commit 64edcf8db54171ff4456c209d563b5d431d99619.

* Revert "patch prismock"

This reverts commit e95819113dc9d88e7130947aa120cd42710977c8.

* fix patch

* Fix test that overrun the boundary, it shouldn't test too much

* Move prisma import to changeSMSLockState

* Bring back broken test without illegal imports

* Merge with main and fix filter hosts by same round robin host

* Fixed buildDryRunBooking fn tests

* Fix and move ooo create or update handler test

* Fix packages/features/eventtypes/lib/isCurrentlyAvailable.test.ts

* Fix packages/trpc/server/routers/viewer/organizations/listMembers.handler.test.ts

* Mock @calcom/prisma

* Fix: verify-email.test.ts

* fix: Moved WebhookService test and fixed default import mock

* Fix: Added missing prisma mock, handleNewBooking uses that of course

* We're not testing createContext here

* fix: Prisma mock fix for listMembers.test.ts

* More fixes to broken testcases

* Forgot to remove borked test

* Prevent the need to mock a lot of dependencies by moving out buildBaseWhereCondition to its own file

* Temporarily skip getCalendarEvents, needs a rewrite

* Fix: turns out you can access protected in testcases

* fix further mocks

* Added packages/features/insights/server/buildBaseWhereCondition.ts, types

* Always great to have a mock and then not use it

* And one less again.

* fix: confirm.handler.test, didn't mock prisma

* fix: Address minor nit by @eunjae & fix ImpersonationProvider test

* Updated isPrismaAvailableCheck that doesn't crash on import

* fix: Get Prisma directly from the client, it usually involves the Validator and does not need 'local' inclusion

* Add zod-prisma-types without the generator enabled (commented out)

* Uncomment and see what happens

* Change method of import as imports did not work in Input Schemas

* Remove custom 'zod' booking model, it does not belong with Prisma

* Fix all other global Model imports

* Rewrite most schema includes AND remove barrel file

* Add bookingCreateBodySchema to features/bookings

* Flurry of type fixes for compatibility with new zod gen

* Refactor out the custom prisma type createEventTypeInput

* Work around nullable eventTypeLocations

* HandlePayment type fix

* More fixes, final fix remaining is CompleteEventType

* Should fix a bunch more booking related type errors

* Missed one

* Some props missing from BookingCreateBodySchema

* Fix location type in handleChildrenEventTypes

* Little bit hacky imo but it works

* Final type error \o/

* Forgot to include Prisma

* Do not include zod-utils in booker/types

* Oops, was already including Booker/types

* Fix membership type, also disallow updating createdAt/updatedAt, make part of patch/post

* Fix api v1 type errors

* Fix EventTypeDescription typings

* Remove getParserWithGeneric, use userBodySchema with UserSchema

* use centralized timeZoneSchema

* Implement feedback by @zomars

* Couple of WIP pushes

* Fix tests

* Type fixes in `handleChildrenEventTypes` test

* Try and parse metadata before use

* Change zod-prisma-types configuration for optimal performance

* Fix prisma validator error in `prisma/selects/credential`

* Disable seperate relations model, hits a bug

* Import absolute - this makes rollup work in @platform/libraries

* Attempt at removing resolutions override

* Refactor using `Prisma.validator` to `satisfies`

* Build atoms using @calcom/prisma/client

* Build atoms using @calcom/prisma/client

* fixes

* Update eventTypeSelect.ts

* Adjust `eventTypeMetaDataSchemaWithUntypedApps` from `unknown` to `record(any)`

* `EventTypeDescription` rely on `descriptionAsSafeHTML` instead of `description`

* Add `seatsPerTimeSlot` to event type public select

* Fix typing in `users-public-view` getServerSide props

* Add missing `schedulingType` to prop

* chore: bump platform libraries

* Function return type is illegal, not sure how this passed eslint (#21567)

* Merged with main

* Update updateTokenObject.ts

* Update handleResponse.ts

* Update index.ts

* Update handleChildrenEventTypes.ts

* Update booking-idempotency-key.ts

* Update WebhookService.test.ts

* Update events.test.ts

* Update queued-response.test.ts

* Update events.test.ts

* Update getRoutedUrl.test.ts

* fix: type checks

Signed-off-by: Omar López <zomars@me.com>

* fixes

Signed-off-by: Omar López <zomars@me.com>

* chore: bump platform libraries

* Update yarn.lock

* more fixes

Signed-off-by: Omar López <zomars@me.com>

* fixes

Signed-off-by: Omar López <zomars@me.com>

* biuld fixes

* chore: bump platform libraries

* Update conferencing.repository.ts

* Update conferencing.repository.ts

* Update getCalendarsEvents.test.ts

* Update vite.config.js

* chore: bump platform libraries

* Update users.ts

* Discard changes to docs/api-reference/v2/openapi.json

* Update vite.config.ts

* updated platform libraries

* Update get.handler.test.ts

* Update get.handler.test.ts

* Update schema.prisma

* Discard changes to docs/api-reference/v2/openapi.json

* Update next-auth-custom-adapter.ts

* Update team.ts

* Flurry of type fixes

* Fix majority of insight related type errors

* Type fixes for unlink of account

* Make user nullable again

* Fixed a bunch of unit tests and one type error

* Attempted mock fix

* Attempted fix for Attribute type

* Ensure default import becomes prisma, but not direct usage

* Import default as prisma in prisma.module

* Add attributeOption to attribute type

* Fix calcom/prisma mock

* Refactor Prisma client imports to @calcom/prisma/client

Updated all imports from '@prisma/client' to '@calcom/prisma/client' across tests and repository files for consistency and to use the correct Prisma client package. This change improves maintainability and ensures the correct client is referenced throughout the codebase.

* Undo removal of max-warnings=0 to get main to merge

* Remove unit tests for e2e fixtures, provide new prisma mock

* Mock @calcom/prisma in event manager

* Mock @calcom/prisma in event manager

* Add correct format even with --no-verify

* Mock prisma in CalendarManager

* Add mock for permission-check.service

* Better injection in PrismaApiKeyRepository imports

* More mock fixes :)

* Fix listMembers.handler.test

* Fix User import

* Appropriately adjust all types to be imported as types, there were a lot of types imported as normal deps

* Why was this a thing?

* Strictly speaking; Not using prismock anymore

* Ditched patch file for prismock

* Fix output.service.ts platform type imports, need concrete for plainToClass

* Better typing and tests for unlinkConnectedAccount.handler

* Small type fix

* Disable calendar cache tests as they are dependent on prismock

* chore: bump platform lib

* getRoutedUrl test remove of unused import

* Extract select to external const on getEventTypesFromDB

* Direct select of userSelect from selects/user

* fix type error from merging 23653

* Fixed integration tests by removing hardcoded values that were possible due to mocking, but as its now directly hitting the db no longer

* fix: vite config atoms prisma client type location

* revert: example app prisma client

* revert: example app prisma client

* bump platform libs

* fix: use class instead of type for DI of PlatformBookingsService

* update platform libs

* remove unused variable

* chore: generate prisma client for api v2

* fix: api v2 e2e

* fix: atoms e2e

* fix: atoms e2e

* fix: atoms e2e

* fix: api v2 e2e

* fix: tsconfig apiv2 enums

* publish libraries

* Simplify check for existence teamId

---------

Signed-off-by: Omar López <zomars@me.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: cal.com <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
2025-09-11 15:27:50 +01:00

671 lines
18 KiB
TypeScript

import { v4 } from "uuid";
import { selectOOOEntries } from "@calcom/app-store/zapier/api/subscriptions/listOOOEntries";
import dayjs from "@calcom/dayjs";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import tasker from "@calcom/features/tasker";
import { DailyLocationType, getHumanReadableLocationValue } from "@calcom/lib/location";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { withReporting } from "@calcom/lib/sentryWrapper";
import { getTranslation } from "@calcom/lib/server/i18n";
import { prisma } from "@calcom/prisma";
import type { Prisma, Webhook, Booking, ApiKey } from "@calcom/prisma/client";
import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums";
const SCHEDULING_TRIGGER: WebhookTriggerEvents[] = [
WebhookTriggerEvents.MEETING_ENDED,
WebhookTriggerEvents.MEETING_STARTED,
];
const NO_SHOW_TRIGGERS: WebhookTriggerEvents[] = [
WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
];
const log = logger.getSubLogger({ prefix: ["[node-scheduler]"] });
export async function addSubscription({
appApiKey,
triggerEvent,
subscriberUrl,
appId,
account,
}: {
appApiKey?: ApiKey;
triggerEvent: WebhookTriggerEvents;
subscriberUrl: string;
appId: string;
account?: {
id: number;
name: string | null;
isTeam: boolean;
} | null;
}) {
try {
const userId = appApiKey ? appApiKey.userId : account && !account.isTeam ? account.id : null;
const teamId = appApiKey ? appApiKey.teamId : account && account.isTeam ? account.id : null;
const createSubscription = await prisma.webhook.create({
data: {
id: v4(),
userId,
teamId,
eventTriggers: [triggerEvent],
subscriberUrl,
active: true,
appId: appId,
},
});
if (
triggerEvent === WebhookTriggerEvents.MEETING_ENDED ||
triggerEvent === WebhookTriggerEvents.MEETING_STARTED
) {
//schedule job for already existing bookings
const where: Prisma.BookingWhereInput = {};
if (teamId) {
where.eventType = { teamId };
} else {
where.eventType = { userId };
}
const bookings = await prisma.booking.findMany({
where: {
...where,
startTime: {
gte: new Date(),
},
status: BookingStatus.ACCEPTED,
},
include: {
eventType: {
select: {
bookingFields: true,
},
},
attendees: {
select: {
name: true,
email: true,
},
},
},
});
const bookingsWithCalEventResponses = bookings.map((booking) => {
return {
...booking,
...getCalEventResponses({
bookingFields: booking.eventType?.bookingFields ?? null,
booking,
}),
};
});
for (const booking of bookingsWithCalEventResponses) {
scheduleTrigger({
booking,
subscriberUrl: createSubscription.subscriberUrl,
subscriber: {
id: createSubscription.id,
appId: createSubscription.appId,
},
triggerEvent,
});
}
}
return createSubscription;
} catch (error) {
const userId = appApiKey ? appApiKey.userId : account && !account.isTeam ? account.id : null;
const teamId = appApiKey ? appApiKey.teamId : account && account.isTeam ? account.id : null;
log.error(
`Error creating subscription for ${teamId ? `team ${teamId}` : `user ${userId}`}.`,
safeStringify(error)
);
}
}
export async function deleteSubscription({
appApiKey,
webhookId,
appId,
account,
}: {
appApiKey?: ApiKey;
webhookId: string;
appId: string;
account?: {
id: number;
name: string | null;
isTeam: boolean;
} | null;
}) {
const userId = appApiKey ? appApiKey.userId : account && !account.isTeam ? account.id : null;
const teamId = appApiKey ? appApiKey.teamId : account && account.isTeam ? account.id : null;
try {
let where: Prisma.WebhookWhereInput = {};
if (teamId) {
where = { teamId };
} else {
where = { userId };
}
const deleteWebhook = await prisma.webhook.delete({
where: {
...where,
appId: appId,
id: webhookId,
},
});
if (!deleteWebhook) {
throw new Error(`Unable to delete webhook ${webhookId}`);
}
return deleteWebhook;
} catch (err) {
const userId = appApiKey ? appApiKey.userId : account && !account.isTeam ? account.id : null;
const teamId = appApiKey ? appApiKey.teamId : account && account.isTeam ? account.id : null;
log.error(
`Error deleting subscription for user ${
teamId ? `team ${teamId}` : `userId ${userId}`
}, webhookId ${webhookId}`,
safeStringify(err)
);
}
}
export async function listBookings(
appApiKey?: ApiKey,
account?: {
id: number;
name: string | null;
isTeam: boolean;
} | null
) {
const userId = appApiKey ? appApiKey.userId : account && !account.isTeam ? account.id : null;
const teamId = appApiKey ? appApiKey.teamId : account && account.isTeam ? account.id : null;
try {
const where: Prisma.BookingWhereInput = {};
if (teamId) {
where.eventType = {
OR: [{ teamId }, { parent: { teamId } }],
};
} else {
where.eventType = { userId };
}
const bookings = await prisma.booking.findMany({
take: 3,
where: where,
orderBy: {
id: "desc",
},
select: {
title: true,
description: true,
customInputs: true,
responses: true,
startTime: true,
endTime: true,
location: true,
cancellationReason: true,
status: true,
user: {
select: {
username: true,
name: true,
email: true,
timeZone: true,
locale: true,
},
},
eventType: {
select: {
title: true,
description: true,
requiresConfirmation: true,
price: true,
currency: true,
length: true,
bookingFields: true,
team: true,
},
},
attendees: {
select: {
name: true,
email: true,
timeZone: true,
},
},
},
});
if (bookings.length === 0) {
return [];
}
const t = await getTranslation(bookings[0].user?.locale ?? "en", "common");
const updatedBookings = bookings.map((booking) => {
return {
...booking,
...getCalEventResponses({
bookingFields: booking.eventType?.bookingFields ?? null,
booking,
}),
location: getHumanReadableLocationValue(booking.location || "", t),
};
});
return updatedBookings;
} catch (err) {
const userId = appApiKey ? appApiKey.userId : account && !account.isTeam ? account.id : null;
const teamId = appApiKey ? appApiKey.teamId : account && account.isTeam ? account.id : null;
log.error(
`Error retrieving list of bookings for ${teamId ? `team ${teamId}` : `user ${userId}`}.`,
safeStringify(err)
);
}
}
export async function scheduleTrigger({
booking,
subscriberUrl,
subscriber,
triggerEvent,
isDryRun = false,
}: {
booking: { id: number; endTime: Date; startTime: Date };
subscriberUrl: string;
subscriber: { id: string; appId: string | null };
triggerEvent: WebhookTriggerEvents;
isDryRun?: boolean;
}) {
if (isDryRun) return;
try {
const payload = JSON.stringify({ triggerEvent, ...booking });
await prisma.webhookScheduledTriggers.create({
data: {
payload,
appId: subscriber.appId,
startAfter: triggerEvent === WebhookTriggerEvents.MEETING_ENDED ? booking.endTime : booking.startTime,
subscriberUrl,
webhook: {
connect: {
id: subscriber.id,
},
},
booking: {
connect: {
id: booking.id,
},
},
},
});
} catch (error) {
console.error("Error cancelling scheduled jobs", error);
}
}
async function _deleteWebhookScheduledTriggers({
booking,
appId,
triggerEvent,
webhookId,
userId,
teamId,
isDryRun = false,
}: {
booking?: { id: number; uid: string };
appId?: string | null;
triggerEvent?: WebhookTriggerEvents;
webhookId?: string;
userId?: number;
teamId?: number;
isDryRun?: boolean;
}) {
if (isDryRun) return;
try {
if (appId && (userId || teamId)) {
const where: Prisma.BookingWhereInput = {};
if (userId) {
where.eventType = { userId };
} else {
where.eventType = { teamId };
}
await prisma.webhookScheduledTriggers.deleteMany({
where: {
appId: appId,
booking: where,
},
});
} else {
if (booking) {
await prisma.webhookScheduledTriggers.deleteMany({
where: {
bookingId: booking.id,
},
});
} else if (webhookId) {
const where: Prisma.WebhookScheduledTriggersWhereInput = { webhookId: webhookId };
if (triggerEvent) {
const shouldContain = `"triggerEvent":"${triggerEvent}"`;
where.payload = { contains: shouldContain };
}
await prisma.webhookScheduledTriggers.deleteMany({
where,
});
}
}
} catch (error) {
console.error("Error deleting webhookScheduledTriggers ", error);
}
}
export const deleteWebhookScheduledTriggers = withReporting(
_deleteWebhookScheduledTriggers,
"deleteWebhookScheduledTriggers"
);
async function fetchBookingsFromWebhook(
webhook: Pick<Webhook, "id" | "userId" | "teamId" | "eventTypeId">
): Promise<Booking[]> {
const currentTime = new Date();
const where: Prisma.BookingWhereInput = {
AND: [{ status: BookingStatus.ACCEPTED }],
OR: [{ startTime: { gt: currentTime }, endTime: { gt: currentTime } }],
};
let bookings: Booking[] = [];
if (Array.isArray(where.AND)) {
if (webhook.teamId) {
const org = await prisma.team.findFirst({
where: {
id: webhook.teamId,
isOrganization: true,
},
select: {
id: true,
children: {
select: {
id: true,
},
},
members: {
select: {
userId: true,
},
},
},
});
// checking if teamId is an org id
if (org) {
const teamEvents = await prisma.eventType.findMany({
where: {
teamId: {
in: org.children.map((team) => team.id),
},
},
select: {
bookings: {
where,
},
},
});
const teamEventBookings = teamEvents.flatMap((event) => event.bookings);
const teamBookingsId = teamEventBookings.map((booking) => booking.id);
const orgMemberIds = org.members.map((member) => member.userId);
where.AND.push({
userId: {
in: orgMemberIds,
},
});
// don't want to get the team bookings again
where.AND.push({
id: {
notIn: teamBookingsId,
},
});
const userBookings = await prisma.booking.findMany({
where,
});
// add teams bookings and users bookings to get total org bookings
bookings = teamEventBookings.concat(userBookings);
} else {
const teamEvents = await prisma.eventType.findMany({
where: {
teamId: webhook.teamId,
},
select: {
bookings: {
where,
},
},
});
bookings = teamEvents.flatMap((event) => event.bookings);
}
} else {
if (webhook.eventTypeId) {
where.AND.push({ eventTypeId: webhook.eventTypeId });
} else if (webhook.userId) {
where.AND.push({ userId: webhook.userId });
}
bookings = await prisma.booking.findMany({
where,
});
}
}
return bookings;
}
export async function updateTriggerForExistingBookings(
webhook: Webhook,
existingEventTriggers: WebhookTriggerEvents[],
updatedEventTriggers: WebhookTriggerEvents[]
) {
const addedEventTriggers = updatedEventTriggers.filter(
(trigger) => !existingEventTriggers.includes(trigger) && SCHEDULING_TRIGGER.includes(trigger)
);
const removedEventTriggers = existingEventTriggers.filter(
(trigger) => !updatedEventTriggers.includes(trigger) && SCHEDULING_TRIGGER.includes(trigger)
);
const addedNoShowTriggers = updatedEventTriggers.filter(
(trigger) => !existingEventTriggers.includes(trigger) && NO_SHOW_TRIGGERS.includes(trigger)
);
const removedNoShowTriggers = existingEventTriggers.filter(
(trigger) => !updatedEventTriggers.includes(trigger) && NO_SHOW_TRIGGERS.includes(trigger)
);
if (
addedEventTriggers.length === 0 &&
removedEventTriggers.length === 0 &&
addedNoShowTriggers.length === 0 &&
removedNoShowTriggers.length === 0
)
return;
const bookings = await fetchBookingsFromWebhook(webhook);
if (bookings.length === 0) return;
if (addedEventTriggers.length > 0 || addedNoShowTriggers.length > 0 || removedNoShowTriggers.length > 0) {
const allPromises = bookings.flatMap((booking) => {
return [
...addedEventTriggers.map(async (triggerEvent) => {
if (NO_SHOW_TRIGGERS.includes(triggerEvent)) return;
await scheduleTrigger({
booking,
subscriberUrl: webhook.subscriberUrl,
subscriber: webhook,
triggerEvent,
});
}),
...addedNoShowTriggers.map(async (triggerEvent) => {
await scheduleNoShowTaskForBooking(booking, webhook, triggerEvent);
}),
...removedNoShowTriggers.map((triggerEvent) =>
cancelNoShowTasksForBooking({
bookingUid: booking.uid,
triggerEvent,
})
),
];
});
await Promise.all(allPromises);
}
const promise = removedEventTriggers.map((triggerEvent) =>
deleteWebhookScheduledTriggers({ triggerEvent, webhookId: webhook.id })
);
await Promise.all(promise);
}
export async function listOOOEntries(
appApiKey?: ApiKey,
account?: {
id: number;
name: string | null;
isTeam: boolean;
} | null
) {
const userId = appApiKey ? appApiKey.userId : account && !account.isTeam ? account.id : null;
const teamId = appApiKey ? appApiKey.teamId : account && account.isTeam ? account.id : null;
try {
const where: Prisma.OutOfOfficeEntryWhereInput = {};
if (teamId) {
where.user = {
teams: {
some: {
teamId,
},
},
};
} else if (userId) {
where.userId = userId;
}
// early return
if (!where.userId && !where.user) {
return [];
}
const oooEntries = await prisma.outOfOfficeEntry.findMany({
where: {
...where,
},
take: 3,
orderBy: {
id: "desc",
},
select: selectOOOEntries,
});
if (oooEntries.length === 0) {
return [];
}
return oooEntries;
} catch (err) {
log.error(
`Error retrieving list of ooo entries for user ${userId}. or teamId ${teamId}`,
safeStringify(err)
);
}
}
export async function cancelNoShowTasksForBooking({
bookingUid,
triggerEvent,
webhook,
}: {
bookingUid?: string;
triggerEvent?: WebhookTriggerEvents;
webhook?: Pick<Webhook, "id" | "userId" | "teamId" | "eventTypeId">;
}) {
if (bookingUid) {
if (triggerEvent && !NO_SHOW_TRIGGERS.includes(triggerEvent)) return;
if (triggerEvent === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW) {
await tasker.cancelWithReference(bookingUid, "triggerHostNoShowWebhook");
} else if (triggerEvent === WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW) {
await tasker.cancelWithReference(bookingUid, "triggerGuestNoShowWebhook");
} else {
await prisma.task.deleteMany({
where: {
referenceUid: bookingUid,
},
});
}
} else if (webhook) {
const bookings = await fetchBookingsFromWebhook(webhook);
if (bookings.length === 0) return;
const promises = bookings.map(async (booking) => {
return await prisma.task.deleteMany({
where: {
referenceUid: booking.uid,
},
});
});
await Promise.all(promises);
}
}
export async function scheduleNoShowTaskForBooking(
booking: { id: number; uid: string; startTime: Date; location: string | null },
webhook: Webhook,
triggerEvent: WebhookTriggerEvents
) {
if (!webhook.time || !webhook.timeUnit || !booking.startTime || !booking.location) return;
const isCalVideoLocation = booking.location === DailyLocationType || booking.location?.trim() === "";
if (!isCalVideoLocation) return;
if (
triggerEvent !== WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW &&
triggerEvent !== WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
)
return;
const scheduledAt = dayjs(booking.startTime)
.add(webhook.time ?? 0, webhook.timeUnit?.toLowerCase() as dayjs.ManipulateType)
.toDate();
const taskType =
triggerEvent === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW
? "triggerHostNoShowWebhook"
: "triggerGuestNoShowWebhook";
await tasker.create(
taskType,
{
triggerEvent,
bookingId: booking.id,
webhook: {
...webhook,
time: webhook.time ?? 0,
timeUnit: webhook.timeUnit ?? "HOUR",
},
},
{
scheduledAt,
referenceUid: booking.uid,
}
);
}