From 4017c0e28fcf0546aaf295c0ddd59a331e151560 Mon Sep 17 00:00:00 2001 From: Kartik Saini <41051387+kart1ka@users.noreply.github.com> Date: Wed, 5 Jun 2024 01:46:46 +0530 Subject: [PATCH] feat: org-wide webhooks (#15144) * feat: include org in teams list * feat: org-wide webhooks * Move getOrgIdFromMemberOrTeamId to packages/lib --- apps/web/pages/api/recorded-daily-video.ts | 8 ++- .../settings/developer/webhooks/index.tsx | 4 +- .../pages/settings/developer/webhooks/new.tsx | 4 +- .../app-store/routing-forms/trpc/utils.ts | 9 ++- .../bookings/lib/handleBookingRequested.ts | 7 ++ .../bookings/lib/handleCancelBooking.ts | 7 +- .../bookings/lib/handleConfirmation.ts | 17 +++-- .../features/bookings/lib/handleNewBooking.ts | 11 +++- .../instant-meeting/handleInstantMeeting.ts | 20 +++++- packages/features/webhooks/lib/getWebhooks.ts | 7 +- .../features/webhooks/lib/scheduleTrigger.ts | 64 +++++++++++++++++-- packages/lib/getOrgIdFromMemberOrTeamId.ts | 49 ++++++++++++++ .../procedures/teamsAndUserProfilesQuery.ts | 11 ++-- .../teamsAndUserProfilesQuery.handler.ts | 25 ++++++-- .../teamsAndUserProfilesQuery.schema.ts | 9 +++ .../viewer/bookings/confirm.handler.ts | 4 ++ .../bookings/requestReschedule.handler.ts | 6 +- .../trpc/server/routers/viewer/payments.tsx | 7 +- .../viewer/webhook/getByViewer.handler.ts | 54 +++++++--------- .../CreateButtonWithTeamsList.tsx | 4 +- 20 files changed, 264 insertions(+), 63 deletions(-) create mode 100644 packages/lib/getOrgIdFromMemberOrTeamId.ts create mode 100644 packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.schema.ts diff --git a/apps/web/pages/api/recorded-daily-video.ts b/apps/web/pages/api/recorded-daily-video.ts index cf3fe95ecaf6ac..3fcb734960718b 100644 --- a/apps/web/pages/api/recorded-daily-video.ts +++ b/apps/web/pages/api/recorded-daily-video.ts @@ -7,6 +7,7 @@ import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient import { sendDailyVideoRecordingEmails } from "@calcom/emails"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -60,11 +61,16 @@ const triggerWebhook = async ({ // Send Webhook call if hooked to BOOKING.RECORDING_READY const triggerForUser = !booking.teamId || (booking.teamId && booking.eventTypeParentId); + const organizerUserId = triggerForUser ? booking.userId : null; + + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: organizerUserId, teamId: booking.teamId }); + const subscriberOptions = { - userId: triggerForUser ? booking.userId : null, + userId: organizerUserId, eventTypeId: booking.eventTypeId, triggerEvent: eventTrigger, teamId: booking.teamId, + orgId, }; const webhooks = await getWebhooks(subscriberOptions); diff --git a/apps/web/pages/settings/developer/webhooks/index.tsx b/apps/web/pages/settings/developer/webhooks/index.tsx index 67fc89e4ae629a..ad42580c232ca7 100644 --- a/apps/web/pages/settings/developer/webhooks/index.tsx +++ b/apps/web/pages/settings/developer/webhooks/index.tsx @@ -1,9 +1,9 @@ -import WeebhooksView from "@calcom/features/webhooks/pages/webhooks-view"; +import WebhooksView from "@calcom/features/webhooks/pages/webhooks-view"; import type { CalPageWrapper } from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper"; -const Page = WeebhooksView as CalPageWrapper; +const Page = WebhooksView as CalPageWrapper; Page.PageWrapper = PageWrapper; export default Page; diff --git a/apps/web/pages/settings/developer/webhooks/new.tsx b/apps/web/pages/settings/developer/webhooks/new.tsx index 5c957b16ac0902..df39aea3f9c489 100644 --- a/apps/web/pages/settings/developer/webhooks/new.tsx +++ b/apps/web/pages/settings/developer/webhooks/new.tsx @@ -1,9 +1,9 @@ -import WeebhookNewView from "@calcom/features/webhooks/pages/webhook-new-view"; +import WebhookNewView from "@calcom/features/webhooks/pages/webhook-new-view"; import type { CalPageWrapper } from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper"; -const Page = WeebhookNewView as CalPageWrapper; +const Page = WebhookNewView as CalPageWrapper; Page.PageWrapper = PageWrapper; export default Page; diff --git a/packages/app-store/routing-forms/trpc/utils.ts b/packages/app-store/routing-forms/trpc/utils.ts index 4cc3a4d93dc01a..bd825e54cefe27 100644 --- a/packages/app-store/routing-forms/trpc/utils.ts +++ b/packages/app-store/routing-forms/trpc/utils.ts @@ -2,6 +2,7 @@ import type { App_RoutingForms_Form, User } from "@prisma/client"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import logger from "@calcom/lib/logger"; import { WebhookTriggerEvents } from "@calcom/prisma/client"; import type { Ensure } from "@calcom/types/utils"; @@ -33,9 +34,15 @@ export async function onFormSubmission( }; } + const { userId, teamId } = getWebhookTargetEntity(form); + + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); + const subscriberOptions = { + userId, + teamId, + orgId, triggerEvent: WebhookTriggerEvents.FORM_SUBMITTED, - ...getWebhookTargetEntity(form), }; const webhooks = await getWebhooks(subscriberOptions); diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index 3d785987b5f0d5..c6869c64fdbde6 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -2,6 +2,7 @@ import { sendAttendeeRequestEmail, sendOrganizerRequestEmail } from "@calcom/ema import { getWebhookPayloadForBooking } from "@calcom/features/bookings/lib/getWebhookPayloadForBooking"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { WebhookTriggerEvents } from "@calcom/prisma/enums"; @@ -39,12 +40,18 @@ export async function handleBookingRequested(args: { await sendOrganizerRequestEmail({ ...evt }); await sendAttendeeRequestEmail({ ...evt }, evt.attendees[0]); + const orgId = await getOrgIdFromMemberOrTeamId({ + memberId: booking.userId, + teamId: booking.eventType?.teamId, + }); + try { const subscribersBookingRequested = await getWebhooks({ userId: booking.userId, eventTypeId: booking.eventTypeId, triggerEvent: WebhookTriggerEvents.BOOKING_REQUESTED, teamId: booking.eventType?.teamId, + orgId, }); const webhookPayload = getWebhookPayloadForBooking({ diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 57d2b6d88fc433..8a9c5441aadba0 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -16,6 +16,7 @@ import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/sc import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; @@ -183,12 +184,16 @@ async function handler(req: CustomRequest) { }, }); const triggerForUser = !teamId || (teamId && bookingToDelete.eventType?.parentId); + const organizerUserId = triggerForUser ? bookingToDelete.userId : null; + + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: organizerUserId, teamId }); const subscriberOptions = { - userId: triggerForUser ? bookingToDelete.userId : null, + userId: organizerUserId, eventTypeId: bookingToDelete.eventTypeId as number, triggerEvent: eventTrigger, teamId, + orgId, }; const eventTypeInfo: EventTypeInfo = { eventTitle: bookingToDelete?.eventType?.title || null, diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index af20d51014d0d2..98423f8b6f3e21 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -10,6 +10,7 @@ import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -302,23 +303,30 @@ export async function handleConfirmation(args: { const triggerForUser = !teamId || (teamId && booking.eventType?.parentId); + const userId = triggerForUser ? booking.userId : null; + + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); + const subscribersBookingCreated = await getWebhooks({ - userId: triggerForUser ? booking.userId : null, + userId, eventTypeId: booking.eventTypeId, triggerEvent: WebhookTriggerEvents.BOOKING_CREATED, teamId, + orgId, }); const subscribersMeetingStarted = await getWebhooks({ - userId: triggerForUser ? booking.userId : null, + userId, eventTypeId: booking.eventTypeId, triggerEvent: WebhookTriggerEvents.MEETING_STARTED, teamId: booking.eventType?.teamId, + orgId, }); const subscribersMeetingEnded = await getWebhooks({ - userId: triggerForUser ? booking.userId : null, + userId, eventTypeId: booking.eventTypeId, triggerEvent: WebhookTriggerEvents.MEETING_ENDED, teamId: booking.eventType?.teamId, + orgId, }); const scheduleTriggerPromises: Promise[] = []; @@ -381,10 +389,11 @@ export async function handleConfirmation(args: { if (paid) { let paymentExternalId: string | undefined; const subscriberMeetingPaid = await getWebhooks({ - userId: triggerForUser ? booking.userId : null, + userId, eventTypeId: booking.eventTypeId, triggerEvent: WebhookTriggerEvents.BOOKING_PAID, teamId: booking.eventType?.teamId, + orgId, }); const bookingWithPayment = await prisma.booking.findFirst({ where: { diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index cef165bc7bf67e..73afba46e8bfe5 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -70,6 +70,7 @@ import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import getPaymentAppData from "@calcom/lib/getPaymentAppData"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { HttpError } from "@calcom/lib/http-error"; @@ -1663,11 +1664,16 @@ async function handler( const triggerForUser = !teamId || (teamId && eventType.parentId); + const organizerUserId = triggerForUser ? organizerUser.id : null; + + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: organizerUserId, teamId }); + const subscriberOptions: GetSubscriberOptions = { - userId: triggerForUser ? organizerUser.id : null, + userId: organizerUserId, eventTypeId, triggerEvent: WebhookTriggerEvents.BOOKING_CREATED, teamId, + orgId, }; const eventTrigger: WebhookTriggerEvents = rescheduleUid @@ -1681,6 +1687,7 @@ async function handler( eventTypeId, triggerEvent: WebhookTriggerEvents.MEETING_ENDED, teamId, + orgId, }; const subscriberOptionsMeetingStarted = { @@ -1688,6 +1695,7 @@ async function handler( eventTypeId, triggerEvent: WebhookTriggerEvents.MEETING_STARTED, teamId, + orgId, }; // For seats, if the booking already exists then we want to add the new attendee to the existing booking @@ -2335,6 +2343,7 @@ async function handler( eventTypeId, triggerEvent: WebhookTriggerEvents.BOOKING_PAYMENT_INITIATED, teamId, + orgId, }; await handleWebhookTrigger({ subscriberOptions: subscriberOptionsPaymentInitiated, diff --git a/packages/features/instant-meeting/handleInstantMeeting.ts b/packages/features/instant-meeting/handleInstantMeeting.ts index c148422c2bce16..5385e93789d602 100644 --- a/packages/features/instant-meeting/handleInstantMeeting.ts +++ b/packages/features/instant-meeting/handleInstantMeeting.ts @@ -17,6 +17,7 @@ import { getFullName } from "@calcom/features/form-builder/utils"; import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; import { isPrismaObjOrUndefined } from "@calcom/lib"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import logger from "@calcom/lib/logger"; import { getTranslation } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; @@ -25,18 +26,32 @@ import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums"; const handleInstantMeetingWebhookTrigger = async (args: { eventTypeId: number; webhookData: Record; + teamId: number; }) => { + const orgId = (await getOrgIdFromMemberOrTeamId({ teamId: args.teamId })) ?? 0; + try { const eventTrigger = WebhookTriggerEvents.INSTANT_MEETING; const subscribers = await prisma.webhook.findMany({ where: { + OR: [ + { + teamId: { + in: [orgId, args.teamId], + }, + }, + { + eventTypeId: args.eventTypeId, + }, + ], AND: { - eventTypeId: args.eventTypeId, eventTriggers: { has: eventTrigger, }, - active: true, + active: { + equals: true, + }, }, }, select: { @@ -213,6 +228,7 @@ async function handler(req: NextApiRequest) { await handleInstantMeetingWebhookTrigger({ eventTypeId: eventType.id, webhookData, + teamId: eventType.team?.id, }); return { diff --git a/packages/features/webhooks/lib/getWebhooks.ts b/packages/features/webhooks/lib/getWebhooks.ts index 57c4424ee563be..83ec6cc4924a1f 100644 --- a/packages/features/webhooks/lib/getWebhooks.ts +++ b/packages/features/webhooks/lib/getWebhooks.ts @@ -7,12 +7,15 @@ export type GetSubscriberOptions = { eventTypeId?: number | null; triggerEvent: WebhookTriggerEvents; teamId?: number | null; + orgId?: number | null; }; const getWebhooks = async (options: GetSubscriberOptions, prisma: PrismaClient = defaultPrisma) => { const userId = options.userId ?? 0; const eventTypeId = options.eventTypeId ?? 0; const teamId = options.teamId ?? 0; + const orgId = options.orgId ?? 0; + // if we have userId and teamId it is a managed event type and should trigger for team and user const allWebhooks = await prisma.webhook.findMany({ where: { @@ -27,7 +30,9 @@ const getWebhooks = async (options: GetSubscriberOptions, prisma: PrismaClient = eventTypeId, }, { - teamId, + teamId: { + in: [teamId, orgId], + }, }, ], AND: { diff --git a/packages/features/webhooks/lib/scheduleTrigger.ts b/packages/features/webhooks/lib/scheduleTrigger.ts index 8054e1400054c9..f52399a82913ec 100644 --- a/packages/features/webhooks/lib/scheduleTrigger.ts +++ b/packages/features/webhooks/lib/scheduleTrigger.ts @@ -355,18 +355,72 @@ export async function updateTriggerForExistingBookings( if (Array.isArray(where.AND)) { if (webhook.teamId) { - const teamEvents = await prisma.eventType.findMany({ + const org = await prisma.team.findFirst({ where: { - teamId: webhook.teamId, + id: webhook.teamId, + isOrganization: true, }, select: { - bookings: { - where, + 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); + bookings = teamEvents.flatMap((event) => event.bookings); + } } else { if (webhook.eventTypeId) { where.AND.push({ eventTypeId: webhook.eventTypeId }); diff --git a/packages/lib/getOrgIdFromMemberOrTeamId.ts b/packages/lib/getOrgIdFromMemberOrTeamId.ts new file mode 100644 index 00000000000000..eaf5a1d9fbe152 --- /dev/null +++ b/packages/lib/getOrgIdFromMemberOrTeamId.ts @@ -0,0 +1,49 @@ +import prisma from "@calcom/prisma"; + +export default async function getOrgIdFromMemberOrTeamId(args: { + memberId?: number | null; + teamId?: number | null; +}) { + const userId = args.memberId ?? 0; + const teamId = args.teamId ?? 0; + + const orgId = await prisma.team.findFirst({ + where: { + OR: [ + { + AND: [ + { + members: { + some: { + userId, + accepted: true, + }, + }, + }, + { + isOrganization: true, + }, + ], + }, + { + AND: [ + { + children: { + some: { + id: teamId, + }, + }, + }, + { + isOrganization: true, + }, + ], + }, + ], + }, + select: { + id: true, + }, + }); + return orgId?.id; +} diff --git a/packages/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery.ts b/packages/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery.ts index 7a10afce7bead1..ed0a5e21248eab 100644 --- a/packages/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery.ts +++ b/packages/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery.ts @@ -1,7 +1,10 @@ import authedProcedure from "../../../procedures/authedProcedure"; +import { ZTeamsAndUserProfilesQueryInputSchema } from "../teamsAndUserProfilesQuery.schema"; -export const teamsAndUserProfilesQuery = authedProcedure.query(async ({ ctx }) => { - const handler = (await import("../teamsAndUserProfilesQuery.handler")).teamsAndUserProfilesQuery; +export const teamsAndUserProfilesQuery = authedProcedure + .input(ZTeamsAndUserProfilesQueryInputSchema) + .query(async ({ ctx, input }) => { + const handler = (await import("../teamsAndUserProfilesQuery.handler")).teamsAndUserProfilesQuery; - return handler({ ctx }); -}); + return handler({ ctx, input }); + }); diff --git a/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts b/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts index d40eae9e58ef79..03878e37574be0 100644 --- a/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts @@ -7,14 +7,17 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; import { TRPCError } from "@trpc/server"; +import type { TTeamsAndUserProfilesQueryInputSchema } from "./teamsAndUserProfilesQuery.schema"; + type TeamsAndUserProfileOptions = { ctx: { user: NonNullable; prisma: PrismaClient; }; + input: TTeamsAndUserProfilesQueryInputSchema; }; -export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOptions) => { +export const teamsAndUserProfilesQuery = async ({ ctx, input }: TeamsAndUserProfileOptions) => { const { prisma } = ctx; const user = await prisma.user.findUnique({ @@ -56,15 +59,27 @@ export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOpti throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); } - const nonOrgTeams = user.teams - .filter((membership) => !membership.team.isOrganization) - .map((membership) => ({ + let teamsData; + + if (input?.includeOrg) { + teamsData = user.teams.map((membership) => ({ ...membership, team: { ...membership.team, metadata: teamMetadataSchema.parse(membership.team.metadata), }, })); + } else { + teamsData = user.teams + .filter((membership) => !membership.team.isOrganization) + .map((membership) => ({ + ...membership, + team: { + ...membership.team, + metadata: teamMetadataSchema.parse(membership.team.metadata), + }, + })); + } return [ { @@ -76,7 +91,7 @@ export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOpti }), readOnly: false, }, - ...nonOrgTeams.map((membership) => ({ + ...teamsData.map((membership) => ({ teamId: membership.team.id, name: membership.team.name, slug: membership.team.slug ? `team/${membership.team.slug}` : null, diff --git a/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.schema.ts b/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.schema.ts new file mode 100644 index 00000000000000..bcf20f4aa672ba --- /dev/null +++ b/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.schema.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const ZTeamsAndUserProfilesQueryInputSchema = z + .object({ + includeOrg: z.boolean().optional(), + }) + .optional(); + +export type TTeamsAndUserProfilesQueryInputSchema = z.infer; diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 65a29612b17568..5ae8c60bb87de2 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -10,6 +10,7 @@ import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhoo import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib"; import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { getTranslation } from "@calcom/lib/server"; import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials"; @@ -370,12 +371,15 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { }, }); + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); + // send BOOKING_REJECTED webhooks const subscriberOptions = { userId: booking.userId, eventTypeId: booking.eventTypeId, triggerEvent: WebhookTriggerEvents.BOOKING_REJECTED, teamId, + orgId, }; const eventTrigger: WebhookTriggerEvents = WebhookTriggerEvents.BOOKING_REJECTED; const eventTypeInfo: EventTypeInfo = { diff --git a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts index 4d4616b7135ab2..5aabcfa2f2f405 100644 --- a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts @@ -16,6 +16,7 @@ import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/sc import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import { isPrismaObjOrUndefined } from "@calcom/lib"; import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -290,13 +291,16 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule }); const triggerForUser = !teamId || (teamId && bookingToReschedule.eventType?.parentId); + const userId = triggerForUser ? bookingToReschedule.userId : null; + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); // Send Webhook call if hooked to BOOKING.CANCELLED const subscriberOptions = { - userId: triggerForUser ? bookingToReschedule.userId : null, + userId, eventTypeId: bookingToReschedule.eventTypeId as number, triggerEvent: eventTrigger, teamId, + orgId, }; const webhooks = await getWebhooks(subscriberOptions); const promises = webhooks.map((webhook) => diff --git a/packages/trpc/server/routers/viewer/payments.tsx b/packages/trpc/server/routers/viewer/payments.tsx index d065aadf066842..cf4675e1affcda 100644 --- a/packages/trpc/server/routers/viewer/payments.tsx +++ b/packages/trpc/server/routers/viewer/payments.tsx @@ -4,6 +4,7 @@ import appStore from "@calcom/app-store"; import dayjs from "@calcom/dayjs"; import { sendNoShowFeeChargedEmail } from "@calcom/emails"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTranslation } from "@calcom/lib/server/i18n"; import sendPayload from "@calcom/lib/server/webhooks/sendPayload"; import type { CalendarEvent } from "@calcom/types/Calendar"; @@ -124,10 +125,14 @@ export const paymentsRouter = router({ throw new TRPCError({ code: "NOT_FOUND", message: `Could not generate payment data` }); } + const userId = ctx.user.id || 0; + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId }); + const subscriberOptions = { - userId: ctx.user.id || 0, + userId, eventTypeId: booking.eventTypeId || 0, triggerEvent: WebhookTriggerEvents.BOOKING_PAID, + orgId, }; const subscribers = await getWebhooks(subscriberOptions); diff --git a/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts index 9b62ac8a9b829a..8048e2673508ec 100644 --- a/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts +++ b/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts @@ -111,37 +111,29 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => { membershipRole: membership.role, })); - const teamWebhookGroups: WebhookGroup[] = user.teams - .filter((mmship) => { - return !mmship.team.isOrganization; - }) - .map((membership) => { - const orgMembership = teamMemberships.find( - (teamM) => teamM.teamId === membership.team.parentId - )?.membershipRole; - return { - teamId: membership.team.id, - profile: { - name: membership.team.name, - slug: membership.team.slug - ? !membership.team.parentId - ? `/team` - : `${membership.team.slug}` - : null, - image: `${bookerUrl}/team/${membership.team.slug}/avatar.png`, - }, - metadata: { - readOnly: - membership.role === - (membership.team.parentId - ? orgMembership && compareMembership(orgMembership, membership.role) - ? orgMembership - : MembershipRole.MEMBER - : MembershipRole.MEMBER), - }, - webhooks: membership.team.webhooks.filter(filterWebhooks), - }; - }); + const teamWebhookGroups: WebhookGroup[] = user.teams.map((membership) => { + const orgMembership = teamMemberships.find( + (teamM) => teamM.teamId === membership.team.parentId + )?.membershipRole; + return { + teamId: membership.team.id, + profile: { + name: membership.team.name, + slug: membership.team.slug ? (!membership.team.parentId ? `/team` : `${membership.team.slug}`) : null, + image: `${bookerUrl}/team/${membership.team.slug}/avatar.png`, + }, + metadata: { + readOnly: + membership.role === + (membership.team.parentId + ? orgMembership && compareMembership(orgMembership, membership.role) + ? orgMembership + : MembershipRole.MEMBER + : MembershipRole.MEMBER), + }, + webhooks: membership.team.webhooks.filter(filterWebhooks), + }; + }); webhookGroups = webhookGroups.concat(teamWebhookGroups); diff --git a/packages/ui/components/createButton/CreateButtonWithTeamsList.tsx b/packages/ui/components/createButton/CreateButtonWithTeamsList.tsx index 9c95770d69ecc1..dde43c8b219e5a 100644 --- a/packages/ui/components/createButton/CreateButtonWithTeamsList.tsx +++ b/packages/ui/components/createButton/CreateButtonWithTeamsList.tsx @@ -10,7 +10,9 @@ export function CreateButtonWithTeamsList( isAdmin?: boolean; } ) { - const query = trpc.viewer.teamsAndUserProfilesQuery.useQuery(); + const query = trpc.viewer.teamsAndUserProfilesQuery.useQuery({ + includeOrg: true, + }); if (!query.data) return null; const teamsAndUserProfiles: Option[] = query.data