From 63712df993e0bfc8d8a3496a8494b2a588534a65 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 9 Oct 2025 03:25:48 +0530 Subject: [PATCH 01/58] fix: disable cal branding --- packages/emails/src/components/V2BaseEmailHtml.tsx | 3 ++- packages/emails/src/templates/BaseScheduledEmail.tsx | 2 +- packages/features/CalendarEventBuilder.ts | 8 ++++++++ packages/features/bookings/lib/handleNewBooking.ts | 8 ++++++++ .../bookings/lib/handleNewBooking/getEventTypesFromDB.ts | 6 ++++++ packages/types/Calendar.d.ts | 1 + 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/emails/src/components/V2BaseEmailHtml.tsx b/packages/emails/src/components/V2BaseEmailHtml.tsx index 5e4c90e7c4e133..95a0155c22e50a 100644 --- a/packages/emails/src/components/V2BaseEmailHtml.tsx +++ b/packages/emails/src/components/V2BaseEmailHtml.tsx @@ -22,6 +22,7 @@ export const V2BaseEmailHtml = (props: { title?: string; subtitle?: React.ReactNode; headerType?: BodyHeadType; + hideLogo?: boolean; }) => { return ( @@ -182,7 +183,7 @@ export const V2BaseEmailHtml = (props: { - + {!props.hideLogo && } diff --git a/packages/emails/src/templates/BaseScheduledEmail.tsx b/packages/emails/src/templates/BaseScheduledEmail.tsx index 0e45b55a1a1fc1..5fa4068710d633 100644 --- a/packages/emails/src/templates/BaseScheduledEmail.tsx +++ b/packages/emails/src/templates/BaseScheduledEmail.tsx @@ -64,7 +64,7 @@ export const BaseScheduledEmail = ( return ( Date: Thu, 9 Oct 2025 04:29:02 +0530 Subject: [PATCH 02/58] fix: disable branding in cancelation email --- .../bookings/lib/getBookingToDelete.ts | 7 +++ .../bookings/lib/handleCancelBooking.ts | 48 +++++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/packages/features/bookings/lib/getBookingToDelete.ts b/packages/features/bookings/lib/getBookingToDelete.ts index 7310589c515d04..a68a2c9f2e444f 100644 --- a/packages/features/bookings/lib/getBookingToDelete.ts +++ b/packages/features/bookings/lib/getBookingToDelete.ts @@ -23,6 +23,7 @@ export async function getBookingToDelete(id: number | undefined, uid: string | u name: true, destinationCalendar: true, locale: true, + hideBranding: true, }, }, location: true, @@ -53,6 +54,12 @@ export async function getBookingToDelete(id: number | undefined, uid: string | u id: true, name: true, parentId: true, + hideBranding: true, + parent: { + select: { + hideBranding: true, + }, + }, }, }, parentId: true, diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 12f6b60c85eaab..4fda08219610e3 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -5,6 +5,7 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; import { sendCancelledEmailsAndSMS } from "@calcom/emails"; +import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation"; import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund"; @@ -17,10 +18,10 @@ import { } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; -import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { HttpError } from "@calcom/lib/http-error"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; @@ -239,6 +240,39 @@ async function handler(input: CancelBookingInput) { bookingToDelete.eventType?.team?.parentId ?? ownerProfile?.organizationId ?? null ); + // For user events, we need to fetch the user's profile to get the organization ID + const userOrganizationId = !bookingToDelete.eventType?.team + ? ( + await prisma.profile.findFirst({ + where: { + userId: bookingToDelete.userId, + }, + select: { + organizationId: true, + }, + }) + )?.organizationId + : null; + + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: bookingToDelete.eventTypeId ?? 0, + team: bookingToDelete.eventType?.team ?? null, + owner: bookingToDelete.user ?? null, + organizationId: bookingToDelete.eventType?.team?.parentId ?? userOrganizationId ?? null, + }); + + log.debug("Branding configuration", { + hideBranding, + eventTypeId: bookingToDelete.eventTypeId, + teamHideBranding: bookingToDelete.eventType?.team?.hideBranding, + teamParentHideBranding: bookingToDelete.eventType?.team?.parent?.hideBranding, + ownerHideBranding: bookingToDelete.user?.hideBranding, + teamParentId: bookingToDelete.eventType?.team?.parentId, + userOrganizationId, + finalOrganizationId: bookingToDelete.eventType?.team?.parentId ?? userOrganizationId, + isTeamEvent: !!bookingToDelete.eventType?.team, + }); + const evt: CalendarEvent = { bookerUrl, title: bookingToDelete?.title, @@ -291,6 +325,7 @@ async function handler(input: CancelBookingInput) { iCalUID: bookingToDelete.iCalUID, iCalSequence: bookingToDelete.iCalSequence + 1, platformClientId, + hideBranding, platformRescheduleUrl, platformCancelUrl, hideOrganizerEmail: bookingToDelete.eventType?.hideOrganizerEmail, @@ -354,7 +389,7 @@ async function handler(input: CancelBookingInput) { }, }, }, - hideBranding: !!bookingToDelete.eventType?.owner?.hideBranding, + hideBranding, }); let updatedBookings: { @@ -569,12 +604,19 @@ async function handler(input: CancelBookingInput) { try { // TODO: if emails fail try to requeue them - if (!platformClientId || (platformClientId && arePlatformEmailsEnabled)) + if (!platformClientId || (platformClientId && arePlatformEmailsEnabled)) { + log.debug("Sending cancellation emails with branding config", { + hideBranding: evt.hideBranding, + platformClientId, + arePlatformEmailsEnabled, + }); + await sendCancelledEmailsAndSMS( evt, { eventName: bookingToDelete?.eventType?.eventName }, bookingToDelete?.eventType?.metadata as EventTypeMetadata ); + } } catch (error) { log.error("Error deleting event", error); } From 83188b9cc63239230531622ea1fdec205acea003 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 9 Oct 2025 04:59:53 +0530 Subject: [PATCH 03/58] fix: disable branding for confirmation booking email --- .../bookings/lib/handleConfirmation.ts | 63 ++++++++++++++----- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index b85fde89512e99..4539f599cd0f23 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -16,6 +16,7 @@ import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { WorkflowService } from "@calcom/lib/server/service/workflows"; @@ -95,6 +96,47 @@ export async function handleConfirmation(args: { const metadata: AdditionalInformation = {}; const workflows = await getAllWorkflowsFromEventType(eventType, booking.userId); + // Compute hideBranding early for use in confirmation emails + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: eventType?.teamId ?? null }, + parentId: eventType?.parentId ?? null, + }, + }); + const triggerForUser = !teamId || (teamId && eventType?.parentId); + const userId = triggerForUser ? booking.userId : null; + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); + + // Fetch full event type data for hideBranding logic + const fullEventType = eventType?.id + ? await prisma.eventType.findUnique({ + where: { id: eventType.id }, + select: { + id: true, + team: { + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }, + teamId: true, + }, + }) + : null; + + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: eventType?.id ?? 0, + team: fullEventType?.team ?? null, + owner: user as any ?? null, + organizationId: orgId ?? null, + }); + if (results.length > 0 && results.every((res) => !res.success)) { const error = { errorCode: "BookingCreatingMeetingFailed", @@ -132,7 +174,7 @@ export async function handleConfirmation(args: { if (emailsEnabled) { await sendScheduledEmailsAndSMS( - { ...evt, additionalInformation: metadata }, + { ...evt, additionalInformation: metadata, hideBranding }, undefined, isHostConfirmationEmailsDisabled, isAttendeeConfirmationEmailDisabled, @@ -316,19 +358,6 @@ export async function handleConfirmation(args: { updatedBookings.push(updatedBooking); } - const teamId = await getTeamIdFromEventType({ - eventType: { - team: { id: eventType?.teamId ?? null }, - parentId: eventType?.parentId ?? null, - }, - }); - - const triggerForUser = !teamId || (teamId && eventType?.parentId); - - const userId = triggerForUser ? booking.userId : null; - - const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); - const bookerUrl = await getBookerBaseUrl(orgId ?? null); //Workflows - set reminders for confirmed events @@ -356,7 +385,7 @@ export async function handleConfirmation(args: { evt: evtOfBooking, workflows, requiresConfirmation: false, - hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding, + hideBranding, seatReferenceUid: evt.attendeeSeatId, isPlatformNoEmail: !emailsEnabled && Boolean(platformClientParams?.platformClientId), }); @@ -366,7 +395,7 @@ export async function handleConfirmation(args: { workflows, smsReminderNumber: updatedBookings[index].smsReminderNumber, calendarEvent: evtOfBooking, - hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding, + hideBranding, isConfirmedByDefault: true, isNormalBookingOrFirstRecurringSlot: isFirstBooking, isRescheduleEvent: false, @@ -577,7 +606,7 @@ export async function handleConfirmation(args: { workflows, smsReminderNumber: booking.smsReminderNumber, calendarEvent: calendarEventForWorkflow, - hideBranding: !!updatedBookings[0].eventType?.owner?.hideBranding, + hideBranding, triggers: [WorkflowTriggerEvents.BOOKING_PAID], }); } catch (error) { From 80aa75356da356d95280d900392acd19610f9aa2 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 9 Oct 2025 21:51:45 +0530 Subject: [PATCH 04/58] disable branding in seated bookings --- .../bookings/lib/handleBookingRequested.ts | 43 +++++++++++++++-- .../attendeeRescheduleSeatedBooking.ts | 36 +++++++++++++- .../owner/combineTwoSeatedBookings.ts | 44 +++++++++++++++-- .../owner/moveSeatedBookingToNewTimeSlot.ts | 47 ++++++++++++++++--- 4 files changed, 154 insertions(+), 16 deletions(-) diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index a09629a7e17c06..e2fc030fa50692 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -3,10 +3,12 @@ import { getWebhookPayloadForBooking } from "@calcom/features/bookings/lib/getWe import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { WorkflowService } from "@calcom/lib/server/service/workflows"; +import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { WebhookTriggerEvents, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; @@ -59,9 +61,44 @@ export async function handleBookingRequested(args: { log.debug("Emails: Sending booking requested emails"); - await sendOrganizerRequestEmail({ ...evt }, booking?.eventType?.metadata as EventTypeMetadata); + const teamForBranding = booking.eventType?.teamId + ? await prisma.team.findUnique({ + where: { id: booking.eventType.teamId }, + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }) + : null; + + const organizationIdForBranding = teamForBranding?.parentId + ? teamForBranding.parentId + : ( + await prisma.profile.findFirst({ + where: { userId: booking.userId ?? undefined }, + select: { organizationId: true }, + }) + )?.organizationId ?? null; + + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: booking.eventType?.id ?? 0, + team: (teamForBranding as any) ?? null, + owner: null, + organizationId: organizationIdForBranding, + }); + + await sendOrganizerRequestEmail( + ({ ...evt, hideBranding } as any), + booking?.eventType?.metadata as EventTypeMetadata + ); await sendAttendeeRequestEmailAndSMS( - { ...evt }, + ({ ...evt, hideBranding } as any), evt.attendees[0], booking?.eventType?.metadata as EventTypeMetadata ); @@ -106,7 +143,7 @@ export async function handleBookingRequested(args: { await WorkflowService.scheduleWorkflowsFilteredByTriggerEvent({ workflows, smsReminderNumber: booking.smsReminderNumber, - hideBranding: !!booking.eventType?.owner?.hideBranding, + hideBranding, calendarEvent: { ...evt, bookerUrl: evt.bookerUrl as string, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index bcad39ff092bc3..09156898814fa5 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -3,6 +3,7 @@ import { cloneDeep } from "lodash"; import { sendRescheduledSeatEmailAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; import type { Person, CalendarEvent } from "@calcom/types/Calendar"; @@ -20,6 +21,37 @@ const attendeeRescheduleSeatedBooking = async ( ) => { const { tAttendees, bookingSeat, bookerEmail, evt, eventType } = rescheduleSeatedBookingObject; let { originalRescheduledBooking } = rescheduleSeatedBookingObject; + const { organizerUser } = rescheduleSeatedBookingObject; + + const teamForBranding = eventType.team?.id + ? await prisma.team.findUnique({ + where: { id: eventType.team.id }, + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }) + : null; + + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: eventType.id, + team: (teamForBranding as any) ?? null, + owner: organizerUser ?? null, + organizationId: (await (async () => { + if (teamForBranding?.parentId) return teamForBranding.parentId; + const organizerProfile = await prisma.profile.findFirst({ + where: { userId: organizerUser.id }, + select: { organizationId: true }, + }); + return organizerProfile?.organizationId ?? null; + })()), + }); seatAttendee["language"] = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" }; @@ -51,7 +83,7 @@ const attendeeRescheduleSeatedBooking = async ( // We don't want to trigger rescheduling logic of the original booking originalRescheduledBooking = null; - await sendRescheduledSeatEmailAndSMS(evt, seatAttendee as Person, eventType.metadata); + await sendRescheduledSeatEmailAndSMS({ ...evt, hideBranding } as any, seatAttendee as Person, eventType.metadata); return null; } @@ -93,7 +125,7 @@ const attendeeRescheduleSeatedBooking = async ( await eventManager.updateCalendarAttendees(copyEvent, newTimeSlotBooking); - await sendRescheduledSeatEmailAndSMS(copyEvent, seatAttendee as Person, eventType.metadata); + await sendRescheduledSeatEmailAndSMS({ ...copyEvent, hideBranding } as any, seatAttendee as Person, eventType.metadata); const filteredAttendees = originalRescheduledBooking?.attendees.filter((attendee) => { return attendee.email !== bookerEmail; }); diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index d61544611885c1..a44d4414ca0f63 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -6,6 +6,7 @@ import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -30,6 +31,7 @@ const combineTwoSeatedBookings = async ( isConfirmedByDefault, additionalNotes, rescheduleReason, + organizerUser, } = rescheduleSeatedBookingObject; let { evt } = rescheduleSeatedBookingObject; // Merge two bookings together @@ -134,14 +136,46 @@ const combineTwoSeatedBookings = async ( : calendarResult?.updatedEvent?.iCalUID || undefined; if (noEmail !== true && isConfirmedByDefault) { + const teamForBranding = eventType.team?.id + ? await prisma.team.findUnique({ + where: { id: eventType.team.id }, + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }) + : null; + + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: eventType.id, + team: (teamForBranding as any) ?? null, + owner: organizerUser ?? null, + organizationId: (await (async () => { + if (teamForBranding?.parentId) return teamForBranding.parentId; + const organizerProfile = await prisma.profile.findFirst({ + where: { userId: organizerUser.id }, + select: { organizationId: true }, + }); + return organizerProfile?.organizationId ?? null; + })()), + }); // TODO send reschedule emails to attendees of the old booking loggerWithEventDetails.debug("Emails: Sending reschedule emails - handleSeats"); await sendRescheduledEmailsAndSMS( - { - ...copyEvent, - additionalNotes, // Resets back to the additionalNote input and not the override value - cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, // Removable code prefix to differentiate cancellation from rescheduling for email - }, + ( + { + ...copyEvent, + additionalNotes, + cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, + hideBranding, + } as any + ), eventType.metadata ); } diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index 5fd6fb858528ab..851c0235e26beb 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -12,6 +12,7 @@ import { findBookingQuery } from "../../../handleNewBooking/findBookingQuery"; import { handleAppsStatus } from "../../../handleNewBooking/handleAppsStatus"; import type { createLoggerWithEventDetails } from "../../../handleNewBooking/logger"; import type { SeatedBooking, RescheduleSeatedBookingObject } from "../../types"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; const moveSeatedBookingToNewTimeSlot = async ( rescheduleSeatedBookingObject: RescheduleSeatedBookingObject, @@ -29,9 +30,40 @@ const moveSeatedBookingToNewTimeSlot = async ( isConfirmedByDefault, additionalNotes, } = rescheduleSeatedBookingObject; + + const teamForBranding = eventType.team?.id + ? await prisma.team.findUnique({ + where: { id: eventType.team.id }, + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }) + : null; + + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: eventType.id, + team: (teamForBranding as any) ?? null, + owner: organizerUser ?? null, + organizationId: (await (async () => { + if (teamForBranding?.parentId) return teamForBranding.parentId; + const organizerProfile = await prisma.profile.findFirst({ + where: { userId: organizerUser.id }, + select: { organizationId: true }, + }); + return organizerProfile?.organizationId ?? null; + })()), + }); + let { evt } = rescheduleSeatedBookingObject; - const newBooking: (Booking & { appsStatus?: AppsStatus[] }) | null = await prisma.booking.update({ + const newBooking: Booking & { appsStatus?: AppsStatus[] } = await prisma.booking.update({ where: { id: seatedBooking.id, }, @@ -91,11 +123,14 @@ const moveSeatedBookingToNewTimeSlot = async ( const copyEvent = cloneDeep(evt); loggerWithEventDetails.debug("Emails: Sending reschedule emails - handleSeats"); await sendRescheduledEmailsAndSMS( - { - ...copyEvent, - additionalNotes, // Resets back to the additionalNote input and not the override value - cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, // Removable code prefix to differentiate cancellation from rescheduling for email - }, + ( + { + ...copyEvent, + additionalNotes, + cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, + hideBranding, + } as any + ), eventType.metadata ); } From fff5d0ce8a6c606f2a2aed3eff474f7d0c4d1405 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 10 Oct 2025 00:43:59 +0530 Subject: [PATCH 05/58] disable branding for declined bookings --- .../bookings/lib/handleBookingRequested.ts | 13 ++++- .../bookings/lib/handleConfirmation.ts | 32 +++++++---- .../viewer/bookings/confirm.handler.ts | 53 +++++++++++++++++-- 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index e2fc030fa50692..3ff506453624d8 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -86,10 +86,21 @@ export async function handleBookingRequested(args: { }) )?.organizationId ?? null; + // Fetch user data for branding when there's no team + const userForBranding = !booking.eventType?.teamId && booking.userId + ? await prisma.user.findUnique({ + where: { id: booking.userId }, + select: { + id: true, + hideBranding: true, + }, + }) + : null; + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventType?.id ?? 0, team: (teamForBranding as any) ?? null, - owner: null, + owner: userForBranding, organizationId: organizationIdForBranding, }); diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 4539f599cd0f23..d57117da78cc6e 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -96,7 +96,6 @@ export async function handleConfirmation(args: { const metadata: AdditionalInformation = {}; const workflows = await getAllWorkflowsFromEventType(eventType, booking.userId); - // Compute hideBranding early for use in confirmation emails const teamId = await getTeamIdFromEventType({ eventType: { team: { id: eventType?.teamId ?? null }, @@ -107,10 +106,11 @@ export async function handleConfirmation(args: { const userId = triggerForUser ? booking.userId : null; const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); + const eventTypeId = eventType?.id ?? booking.eventTypeId ?? null; // Fetch full event type data for hideBranding logic - const fullEventType = eventType?.id + const fullEventType = eventTypeId ? await prisma.eventType.findUnique({ - where: { id: eventType.id }, + where: { id: eventTypeId }, select: { id: true, team: { @@ -130,12 +130,24 @@ export async function handleConfirmation(args: { }) : null; - const hideBranding = await shouldHideBrandingForEvent({ - eventTypeId: eventType?.id ?? 0, - team: fullEventType?.team ?? null, - owner: user as any ?? null, - organizationId: orgId ?? null, - }); + const userForBranding = !fullEventType?.teamId && booking.userId + ? await prisma.user.findUnique({ + where: { id: booking.userId }, + select: { + id: true, + hideBranding: true, + }, + }) + : null; + + const hideBranding = eventTypeId + ? await shouldHideBrandingForEvent({ + eventTypeId, + team: fullEventType?.team ?? null, + owner: userForBranding, + organizationId: orgId ?? null, + }) + : false; if (results.length > 0 && results.every((res) => !res.success)) { const error = { @@ -174,7 +186,7 @@ export async function handleConfirmation(args: { if (emailsEnabled) { await sendScheduledEmailsAndSMS( - { ...evt, additionalInformation: metadata, hideBranding }, + ({ ...evt, additionalInformation: metadata, hideBranding } as any), undefined, isHostConfirmationEmailsDisabled, isAttendeeConfirmationEmailDisabled, diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 2051bf0ddb2ef8..0155d7e6bfbd9d 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -12,6 +12,7 @@ import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/ import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/lib/server/getUsersCredentials"; @@ -345,10 +346,6 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { }); } - if (emailsEnabled) { - await sendDeclinedEmailsAndSMS(evt, booking.eventType?.metadata as EventTypeMetadata); - } - const teamId = await getTeamIdFromEventType({ eventType: { team: { id: booking.eventType?.teamId ?? null }, @@ -358,6 +355,54 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); + if (emailsEnabled) { + const fullEventType = booking.eventTypeId + ? await prisma.eventType.findUnique({ + where: { id: booking.eventTypeId }, + select: { + id: true, + team: { + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }, + teamId: true, + }, + }) + : null; + + const userForBranding = !fullEventType?.teamId && booking.userId + ? await prisma.user.findUnique({ + where: { id: booking.userId }, + select: { + id: true, + hideBranding: true, + }, + }) + : null; + + const hideBranding = booking.eventTypeId + ? await shouldHideBrandingForEvent({ + eventTypeId: booking.eventTypeId, + team: fullEventType?.team ?? null, + owner: userForBranding, + organizationId: orgId ?? null, + }) + : false; + + await sendDeclinedEmailsAndSMS( + ({ ...evt, hideBranding } as any), + booking.eventType?.metadata as EventTypeMetadata + ); + } + // send BOOKING_REJECTED webhooks const subscriberOptions: GetSubscriberOptions = { userId: booking.userId, From cbfd22df74da3c2c7b62b0f6c0adad03f9c1b3fe Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 10 Oct 2025 01:10:04 +0530 Subject: [PATCH 06/58] avoid unnecessary db call when emails disabled --- .../bookings/lib/handleBookingRequested.ts | 40 +++++----- .../viewer/bookings/confirm.handler.ts | 80 +++++++++++-------- 2 files changed, 67 insertions(+), 53 deletions(-) diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index 3ff506453624d8..1c426840e189ed 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -3,8 +3,8 @@ import { getWebhookPayloadForBooking } from "@calcom/features/bookings/lib/getWe import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { WorkflowService } from "@calcom/lib/server/service/workflows"; @@ -87,29 +87,33 @@ export async function handleBookingRequested(args: { )?.organizationId ?? null; // Fetch user data for branding when there's no team - const userForBranding = !booking.eventType?.teamId && booking.userId - ? await prisma.user.findUnique({ - where: { id: booking.userId }, - select: { - id: true, - hideBranding: true, - }, - }) - : null; + const userForBranding = + !booking.eventType?.teamId && booking.userId + ? await prisma.user.findUnique({ + where: { id: booking.userId }, + select: { + id: true, + hideBranding: true, + }, + }) + : null; - const hideBranding = await shouldHideBrandingForEvent({ - eventTypeId: booking.eventType?.id ?? 0, - team: (teamForBranding as any) ?? null, - owner: userForBranding, - organizationId: organizationIdForBranding, - }); + const eventTypeId = booking.eventType?.id ?? booking.eventTypeId ?? null; + const hideBranding = eventTypeId + ? await shouldHideBrandingForEvent({ + eventTypeId, + team: (teamForBranding as any) ?? null, + owner: userForBranding, + organizationId: organizationIdForBranding, + }) + : false; await sendOrganizerRequestEmail( - ({ ...evt, hideBranding } as any), + { ...evt, hideBranding } as any, booking?.eventType?.metadata as EventTypeMetadata ); await sendAttendeeRequestEmailAndSMS( - ({ ...evt, hideBranding } as any), + { ...evt, hideBranding } as any, evt.attendees[0], booking?.eventType?.metadata as EventTypeMetadata ); diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 0155d7e6bfbd9d..54ef634fe8bd05 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -355,50 +355,60 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); - if (emailsEnabled) { - const fullEventType = booking.eventTypeId - ? await prisma.eventType.findUnique({ - where: { id: booking.eventTypeId }, + const eventTypeIdForBranding = booking.eventTypeId ?? null; + let hideBranding = false; + let userForBranding: { id: number; hideBranding: boolean | null } | null = null; + let fullEventType: { + team: { + id: number | null; + hideBranding: boolean | null; + parentId: number | null; + parent: { hideBranding: boolean | null } | null; + } | null; + teamId: number | null; + } | null = null; + + if (eventTypeIdForBranding) { + fullEventType = await prisma.eventType.findUnique({ + where: { id: eventTypeIdForBranding }, + select: { + team: { select: { id: true, - team: { + hideBranding: true, + parentId: true, + parent: { select: { - id: true, hideBranding: true, - parentId: true, - parent: { - select: { - hideBranding: true, - }, - }, }, }, - teamId: true, }, - }) - : null; + }, + teamId: true, + }, + }); - const userForBranding = !fullEventType?.teamId && booking.userId - ? await prisma.user.findUnique({ - where: { id: booking.userId }, - select: { - id: true, - hideBranding: true, - }, - }) - : null; - - const hideBranding = booking.eventTypeId - ? await shouldHideBrandingForEvent({ - eventTypeId: booking.eventTypeId, - team: fullEventType?.team ?? null, - owner: userForBranding, - organizationId: orgId ?? null, - }) - : false; + if (!fullEventType?.teamId && booking.userId) { + userForBranding = await prisma.user.findUnique({ + where: { id: booking.userId }, + select: { + id: true, + hideBranding: true, + }, + }); + } + hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: eventTypeIdForBranding, + team: fullEventType?.team ?? null, + owner: userForBranding, + organizationId: orgId ?? null, + }); + } + + if (emailsEnabled) { await sendDeclinedEmailsAndSMS( - ({ ...evt, hideBranding } as any), + { ...evt, hideBranding } as any, booking.eventType?.metadata as EventTypeMetadata ); } @@ -444,7 +454,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { slug: booking.eventType?.slug as string, }, }, - hideBranding: !!booking.eventType?.owner?.hideBranding, + hideBranding, triggers: [WorkflowTriggerEvents.BOOKING_REJECTED], }); } catch (error) { From de2ce3bcdc318d48a5fb379f45f29278418033b7 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Sun, 12 Oct 2025 00:45:04 +0530 Subject: [PATCH 07/58] made hideBranding mandatory for email functions --- .../web/app/api/cron/bookingReminder/route.ts | 6 +- .../bookingScenario/MockPaymentService.ts | 1 + .../_utils/payments/handlePaymentSuccess.ts | 2 +- .../stripepayment/lib/PaymentService.ts | 1 + packages/app-store/videoClient.ts | 4 +- packages/emails/email-manager.ts | 61 ++++++++++++------- .../emails/src/components/EmailSpacer.tsx | 59 ++++++++++++++++++ .../emails/src/components/V2BaseEmailHtml.tsx | 1 + .../bookings/lib/BookingEmailSmsHandler.ts | 11 ++-- .../bookings/lib/handleBookingRequested.ts | 4 +- .../bookings/lib/handleCancelBooking.ts | 5 +- .../bookings/lib/handleConfirmation.ts | 2 +- .../handleSeats/cancel/cancelAttendeeSeat.ts | 2 +- .../lib/handleSeats/create/createNewSeat.ts | 2 +- .../attendeeRescheduleSeatedBooking.ts | 4 +- .../bookings/lib/payment/handleNoShowFee.ts | 2 +- .../bookings/lib/payment/handleRefundError.ts | 1 + .../credentials/handleDeleteCredential.ts | 1 + packages/features/ee/payments/api/webhook.ts | 5 +- .../roundRobinManualReassignment.ts | 39 +++++++++++- .../ee/round-robin/roundRobinReassignment.ts | 39 +++++++++++- .../loggedInViewer/connectAndJoin.handler.ts | 1 + .../viewer/bookings/addGuests.handler.ts | 38 +++++++++++- .../viewer/bookings/confirm.handler.ts | 2 +- .../viewer/bookings/editLocation.handler.ts | 45 +++++++++++++- 25 files changed, 285 insertions(+), 53 deletions(-) create mode 100644 packages/emails/src/components/EmailSpacer.tsx diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index a70199956df5e8..978447471ce52f 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -13,6 +13,9 @@ import { BookingStatus, ReminderType } from "@calcom/prisma/enums"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; +// Define CalendarEventWithBranding type locally since it's not exported +type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; + async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); @@ -139,9 +142,10 @@ async function postHandler(request: NextRequest) { uid: booking.uid, recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent), destinationCalendar: selectedDestinationCalendar ? [selectedDestinationCalendar] : [], + hideBranding: false, }; - await sendOrganizerRequestReminderEmail(evt, booking?.eventType?.metadata as EventTypeMetadata); + await sendOrganizerRequestReminderEmail(evt as CalendarEventWithBranding, booking?.eventType?.metadata as EventTypeMetadata); await prisma.reminderMail.create({ data: { diff --git a/apps/web/test/utils/bookingScenario/MockPaymentService.ts b/apps/web/test/utils/bookingScenario/MockPaymentService.ts index a10087696e832c..0614d8ac5e3df1 100644 --- a/apps/web/test/utils/bookingScenario/MockPaymentService.ts +++ b/apps/web/test/utils/bookingScenario/MockPaymentService.ts @@ -70,6 +70,7 @@ export function getMockPaymentService() { // TODO: App implementing PaymentService is supposed to send email by itself at the moment. await sendAwaitingPaymentEmailAndSMS({ ...event, + hideBranding: event.hideBranding ?? false, paymentInfo: { link: createPaymentLink(/*{ paymentUid: paymentData.uid, diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index dda7a8c9af7451..8a5f671c2e05b7 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -96,7 +96,7 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number) log.debug(`handling booking request for eventId ${eventType.id}`); } } else if (areEmailsEnabled) { - await sendScheduledEmailsAndSMS({ ...evt }, undefined, undefined, undefined, eventType.metadata); + await sendScheduledEmailsAndSMS({ ...evt, hideBranding: evt.hideBranding ?? false }, undefined, undefined, undefined, eventType.metadata); } throw new HttpCode({ diff --git a/packages/app-store/stripepayment/lib/PaymentService.ts b/packages/app-store/stripepayment/lib/PaymentService.ts index 5dd16cb879a34c..1238532096a6b9 100644 --- a/packages/app-store/stripepayment/lib/PaymentService.ts +++ b/packages/app-store/stripepayment/lib/PaymentService.ts @@ -391,6 +391,7 @@ export class PaymentService implements IAbstractPaymentService { await sendAwaitingPaymentEmailAndSMS( { ...event, + hideBranding: event.hideBranding ?? false, attendees: attendeesToEmail, paymentInfo: { link: createPaymentLink({ diff --git a/packages/app-store/videoClient.ts b/packages/app-store/videoClient.ts index 0495d8800e75e3..bd25983e67db07 100644 --- a/packages/app-store/videoClient.ts +++ b/packages/app-store/videoClient.ts @@ -117,7 +117,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv returnObject = { ...returnObject, createdEvent: createdMeeting, success: true }; log.debug("created Meeting", safeStringify(returnObject)); } catch (err) { - await sendBrokenIntegrationEmail(calEvent, "video"); + await sendBrokenIntegrationEmail({ ...calEvent, hideBranding: calEvent.hideBranding ?? false }, "video"); log.error( "createMeeting failed", safeStringify(err), @@ -146,7 +146,7 @@ const updateMeeting = async ( const canCallUpdateMeeting = !!(credential && bookingRef); const updatedMeeting = canCallUpdateMeeting ? await firstVideoAdapter?.updateMeeting(bookingRef, calEvent).catch(async (e) => { - await sendBrokenIntegrationEmail(calEvent, "video"); + await sendBrokenIntegrationEmail({ ...calEvent, hideBranding: calEvent.hideBranding ?? false }, "video"); log.error("updateMeeting failed", e, calEvent); success = false; return undefined; diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts index 74463692c4efac..2fd316a9503da2 100644 --- a/packages/emails/email-manager.ts +++ b/packages/emails/email-manager.ts @@ -13,6 +13,9 @@ import { withReporting } from "@calcom/lib/sentryWrapper"; import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { CalendarEvent, Person } from "@calcom/types/Calendar"; +// Require branding flag for all email flows that accept CalendarEvent +type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; + import AwaitingPaymentSMS from "../sms/attendee/awaiting-payment-sms"; import CancelledSeatSMS from "../sms/attendee/cancelled-seat-sms"; import EventCancelledSMS from "../sms/attendee/event-cancelled-sms"; @@ -104,7 +107,7 @@ const eventTypeDisableHostEmail = (metadata?: EventTypeMetadata) => { }; const _sendScheduledEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, eventNameObject?: EventNameObjectType, hostEmailDisabled?: boolean, attendeeEmailDisabled?: boolean, @@ -162,7 +165,7 @@ export const sendRoundRobinScheduledEmailsAndSMS = async ({ eventTypeMetadata, reassigned, }: { - calEvent: CalendarEvent; + calEvent: CalendarEventWithBranding; members: Person[]; eventTypeMetadata?: EventTypeMetadata; reassigned?: { name: string | null; email: string; reason?: string; byUser?: string }; @@ -185,7 +188,7 @@ export const sendRoundRobinScheduledEmailsAndSMS = async ({ }; export const sendRoundRobinRescheduledEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, teamMembersAndAttendees: Person[], eventTypeMetadata?: EventTypeMetadata ) => { @@ -221,7 +224,7 @@ export const sendRoundRobinUpdatedEmailsAndSMS = async ({ calEvent, eventTypeMetadata, }: { - calEvent: CalendarEvent; + calEvent: CalendarEventWithBranding; eventTypeMetadata?: EventTypeMetadata; }) => { if (eventTypeDisableAttendeeEmail(eventTypeMetadata)) return; @@ -234,7 +237,7 @@ export const sendRoundRobinUpdatedEmailsAndSMS = async ({ }; export const sendRoundRobinCancelledEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, members: Person[], eventTypeMetadata?: EventTypeMetadata, reassignedTo?: { name: string | null; email: string; reason?: string } @@ -259,7 +262,7 @@ export const sendRoundRobinCancelledEmailsAndSMS = async ( }; export const sendRoundRobinReassignedEmailsAndSMS = async (args: { - calEvent: CalendarEvent; + calEvent: CalendarEventWithBranding; members: Person[]; reassignedTo: { name: string | null; email: string }; eventTypeMetadata?: EventTypeMetadata; @@ -285,7 +288,7 @@ export const sendRoundRobinReassignedEmailsAndSMS = async (args: { }; const _sendRescheduledEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, eventTypeMetadata?: EventTypeMetadata ) => { const calendarEvent = formatCalEvent(calEvent); @@ -321,7 +324,7 @@ export const sendRescheduledEmailsAndSMS = withReporting( ); export const sendRescheduledSeatEmailAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, attendee: Person, eventTypeMetadata?: EventTypeMetadata ) => { @@ -342,7 +345,7 @@ export const sendRescheduledSeatEmailAndSMS = async ( }; export const sendScheduledSeatsEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, invitee: Person, newSeat: boolean, showAttendees: boolean, @@ -387,7 +390,7 @@ export const sendScheduledSeatsEmailsAndSMS = async ( }; export const sendCancelledSeatEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, cancelledAttendee: Person, eventTypeMetadata?: EventTypeMetadata ) => { @@ -413,7 +416,10 @@ export const sendCancelledSeatEmailsAndSMS = async ( await cancelledSeatSMS.sendSMSToAttendee(cancelledAttendee); }; -const _sendOrganizerRequestEmail = async (calEvent: CalendarEvent, eventTypeMetadata?: EventTypeMetadata) => { +const _sendOrganizerRequestEmail = async ( + calEvent: CalendarEventWithBranding, + eventTypeMetadata?: EventTypeMetadata +) => { if (eventTypeDisableHostEmail(eventTypeMetadata)) return; const calendarEvent = formatCalEvent(calEvent); @@ -436,7 +442,7 @@ export const sendOrganizerRequestEmail = withReporting( ); const _sendAttendeeRequestEmailAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, attendee: Person, eventTypeMetadata?: EventTypeMetadata ) => { @@ -454,7 +460,7 @@ export const sendAttendeeRequestEmailAndSMS = withReporting( ); export const sendDeclinedEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, eventTypeMetadata?: EventTypeMetadata ) => { if (eventTypeDisableAttendeeEmail(eventTypeMetadata)) return; @@ -474,7 +480,7 @@ export const sendDeclinedEmailsAndSMS = async ( }; export const sendCancelledEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, eventNameObject: Pick, eventTypeMetadata?: EventTypeMetadata ) => { @@ -534,7 +540,7 @@ export const sendCancelledEmailsAndSMS = async ( }; export const sendOrganizerRequestReminderEmail = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, eventTypeMetadata?: EventTypeMetadata ) => { if (eventTypeDisableHostEmail(eventTypeMetadata)) return; @@ -554,7 +560,7 @@ export const sendOrganizerRequestReminderEmail = async ( }; export const sendAwaitingPaymentEmailAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, eventTypeMetadata?: EventTypeMetadata ) => { if (eventTypeDisableAttendeeEmail(eventTypeMetadata)) return; @@ -570,7 +576,7 @@ export const sendAwaitingPaymentEmailAndSMS = async ( await awaitingPaymentSMS.sendSMSToAttendees(); }; -export const sendOrganizerPaymentRefundFailedEmail = async (calEvent: CalendarEvent) => { +export const sendOrganizerPaymentRefundFailedEmail = async (calEvent: CalendarEventWithBranding) => { const emailsToSend: Promise[] = []; emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent }))); @@ -638,7 +644,7 @@ export const sendRequestRescheduleEmailAndSMS = async ( }; export const sendLocationChangeEmailsAndSMS = async ( - calEvent: CalendarEvent, + calEvent: CalendarEventWithBranding, eventTypeMetadata?: EventTypeMetadata ) => { const calendarEvent = formatCalEvent(calEvent); @@ -669,7 +675,7 @@ export const sendLocationChangeEmailsAndSMS = async ( const eventLocationChangedSMS = new EventLocationChangedSMS(calendarEvent); await eventLocationChangedSMS.sendSMSToAttendees(); }; -export const sendAddGuestsEmails = async (calEvent: CalendarEvent, newGuests: string[]) => { +export const sendAddGuestsEmails = async (calEvent: CalendarEventWithBranding, newGuests: string[]) => { const calendarEvent = formatCalEvent(calEvent); const emailsToSend: Promise[] = []; @@ -699,7 +705,10 @@ export const sendFeedbackEmail = async (feedback: Feedback) => { await sendEmail(() => new FeedbackEmail(feedback)); }; -export const sendBrokenIntegrationEmail = async (evt: CalendarEvent, type: "video" | "calendar") => { +export const sendBrokenIntegrationEmail = async ( + evt: CalendarEventWithBranding, + type: "video" | "calendar" +) => { const calendarEvent = formatCalEvent(evt); await sendEmail(() => new BrokenIntegrationEmail(calendarEvent, type)); }; @@ -740,14 +749,17 @@ export const sendSlugReplacementEmail = async ({ export const sendNoShowFeeChargedEmail = async ( attendee: Person, - evt: CalendarEvent, + evt: CalendarEventWithBranding, eventTypeMetadata?: EventTypeMetadata ) => { if (eventTypeDisableAttendeeEmail(eventTypeMetadata)) return; await sendEmail(() => new NoShowFeeChargedEmail(evt, attendee)); }; -export const sendDailyVideoRecordingEmails = async (calEvent: CalendarEvent, downloadLink: string) => { +export const sendDailyVideoRecordingEmails = async ( + calEvent: CalendarEventWithBranding, + downloadLink: string +) => { const calendarEvent = formatCalEvent(calEvent); const emailsToSend: Promise[] = []; @@ -763,7 +775,10 @@ export const sendDailyVideoRecordingEmails = async (calEvent: CalendarEvent, dow await Promise.all(emailsToSend); }; -export const sendDailyVideoTranscriptEmails = async (calEvent: CalendarEvent, transcripts: string[]) => { +export const sendDailyVideoTranscriptEmails = async ( + calEvent: CalendarEventWithBranding, + transcripts: string[] +) => { const emailsToSend: Promise[] = []; emailsToSend.push(sendEmail(() => new OrganizerDailyVideoDownloadTranscriptEmail(calEvent, transcripts))); diff --git a/packages/emails/src/components/EmailSpacer.tsx b/packages/emails/src/components/EmailSpacer.tsx new file mode 100644 index 00000000000000..e50e4f131e4808 --- /dev/null +++ b/packages/emails/src/components/EmailSpacer.tsx @@ -0,0 +1,59 @@ +import RawHtml from "./RawHtml"; +import Row from "./Row"; + +const CommentIE = ({ html = "" }) => ${html}`} />; + +interface EmailSpacerProps { + height?: number; +} + +const EmailSpacer = ({ height = 16 }: EmailSpacerProps) => { + return ( + <> + `} + /> +
+ +
+ + + + + + + + ); +}; + +export default EmailSpacer; diff --git a/packages/emails/src/components/V2BaseEmailHtml.tsx b/packages/emails/src/components/V2BaseEmailHtml.tsx index 95a0155c22e50a..b72560e74fc3b5 100644 --- a/packages/emails/src/components/V2BaseEmailHtml.tsx +++ b/packages/emails/src/components/V2BaseEmailHtml.tsx @@ -5,6 +5,7 @@ import EmailHead from "./EmailHead"; import EmailScheduledBodyHeaderContent from "./EmailScheduledBodyHeaderContent"; import EmailSchedulingBodyDivider from "./EmailSchedulingBodyDivider"; import EmailSchedulingBodyHeader, { BodyHeadType } from "./EmailSchedulingBodyHeader"; +import EmailSpacer from "./EmailSpacer"; import RawHtml from "./RawHtml"; import Row from "./Row"; diff --git a/packages/features/bookings/lib/BookingEmailSmsHandler.ts b/packages/features/bookings/lib/BookingEmailSmsHandler.ts index 0aaa973b95d872..22ff3dd1df00fb 100644 --- a/packages/features/bookings/lib/BookingEmailSmsHandler.ts +++ b/packages/features/bookings/lib/BookingEmailSmsHandler.ts @@ -128,6 +128,7 @@ export class BookingEmailSmsHandler { await sendRescheduledEmailsAndSMS( { ...evt, + hideBranding: evt.hideBranding ?? false, additionalInformation, additionalNotes, cancellationReason: `$RCH$${rescheduleReason || ""}`, @@ -227,17 +228,17 @@ export class BookingEmailSmsHandler { try { await Promise.all([ sendRoundRobinRescheduledEmailsAndSMS( - { ...copyEventAdditionalInfo, iCalUID }, + { ...copyEventAdditionalInfo, iCalUID, hideBranding: copyEventAdditionalInfo.hideBranding ?? false }, rescheduledMembers, metadata ), sendRoundRobinScheduledEmailsAndSMS({ - calEvent: copyEventAdditionalInfo, + calEvent: { ...copyEventAdditionalInfo, hideBranding: copyEventAdditionalInfo.hideBranding ?? false }, members: newBookedMembers, eventTypeMetadata: metadata, }), sendRoundRobinCancelledEmailsAndSMS( - cancelledRRHostEvt, + { ...cancelledRRHostEvt, hideBranding: cancelledRRHostEvt.hideBranding ?? false }, cancelledMembers, metadata, reassignedTo @@ -281,7 +282,7 @@ export class BookingEmailSmsHandler { try { await sendScheduledEmailsAndSMS( - { ...evt, additionalInformation, additionalNotes, customInputs }, + { ...evt, additionalInformation, additionalNotes, customInputs, hideBranding: evt.hideBranding ?? false }, eventNameObject, isHostConfirmationEmailsDisabled, isAttendeeConfirmationEmailDisabled, @@ -311,7 +312,7 @@ export class BookingEmailSmsHandler { safeStringify({ calEvent: getPiiFreeCalendarEvent(evt) }) ); - const eventWithNotes = { ...evt, additionalNotes }; + const eventWithNotes = { ...evt, additionalNotes, hideBranding: evt.hideBranding ?? false }; try { await Promise.all([ diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index 1c426840e189ed..c6aeb6ac93117f 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -109,11 +109,11 @@ export async function handleBookingRequested(args: { : false; await sendOrganizerRequestEmail( - { ...evt, hideBranding } as any, + { ...evt, hideBranding }, booking?.eventType?.metadata as EventTypeMetadata ); await sendAttendeeRequestEmailAndSMS( - { ...evt, hideBranding } as any, + { ...evt, hideBranding }, evt.attendees[0], booking?.eventType?.metadata as EventTypeMetadata ); diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 4fda08219610e3..8f79af8a12962c 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -5,6 +5,7 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; import { sendCancelledEmailsAndSMS } from "@calcom/emails"; +type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation"; @@ -325,7 +326,7 @@ async function handler(input: CancelBookingInput) { iCalUID: bookingToDelete.iCalUID, iCalSequence: bookingToDelete.iCalSequence + 1, platformClientId, - hideBranding, + hideBranding: hideBranding ?? false, platformRescheduleUrl, platformCancelUrl, hideOrganizerEmail: bookingToDelete.eventType?.hideOrganizerEmail, @@ -612,7 +613,7 @@ async function handler(input: CancelBookingInput) { }); await sendCancelledEmailsAndSMS( - evt, + evt as CalendarEventWithBranding, { eventName: bookingToDelete?.eventType?.eventName }, bookingToDelete?.eventType?.metadata as EventTypeMetadata ); diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index d57117da78cc6e..786bd6615f4770 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -186,7 +186,7 @@ export async function handleConfirmation(args: { if (emailsEnabled) { await sendScheduledEmailsAndSMS( - ({ ...evt, additionalInformation: metadata, hideBranding } as any), + ({ ...evt, additionalInformation: metadata, hideBranding }), undefined, isHostConfirmationEmailsDisabled, isAttendeeConfirmationEmailDisabled, diff --git a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts index 1a59292c841e0b..bd7f4b37a103ab 100644 --- a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts +++ b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts @@ -124,7 +124,7 @@ async function cancelAttendeeSeat( const tAttendees = await getTranslation(attendee.locale ?? "en", "common"); await sendCancelledSeatEmailsAndSMS( - evt, + { ...evt, hideBranding: !!(bookingToDelete.eventType?.team?.hideBranding || bookingToDelete.user?.hideBranding) }, { ...attendee, language: { translate: tAttendees, locale: attendee.locale ?? "en" }, diff --git a/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts b/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts index 640533b9199380..314e83f4547cc6 100644 --- a/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts +++ b/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts @@ -142,7 +142,7 @@ const createNewSeat = async ( isAttendeeConfirmationEmailDisabled = allowDisablingAttendeeConfirmationEmails(workflows); } await sendScheduledSeatsEmailsAndSMS( - copyEvent, + { ...copyEvent, hideBranding: copyEvent.hideBranding ?? false }, inviteeToAdd, newSeat, !!eventType.seatsShowAttendees, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 09156898814fa5..49002b80466443 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -83,7 +83,7 @@ const attendeeRescheduleSeatedBooking = async ( // We don't want to trigger rescheduling logic of the original booking originalRescheduledBooking = null; - await sendRescheduledSeatEmailAndSMS({ ...evt, hideBranding } as any, seatAttendee as Person, eventType.metadata); + await sendRescheduledSeatEmailAndSMS({ ...evt, hideBranding }, seatAttendee as Person, eventType.metadata); return null; } @@ -125,7 +125,7 @@ const attendeeRescheduleSeatedBooking = async ( await eventManager.updateCalendarAttendees(copyEvent, newTimeSlotBooking); - await sendRescheduledSeatEmailAndSMS({ ...copyEvent, hideBranding } as any, seatAttendee as Person, eventType.metadata); + await sendRescheduledSeatEmailAndSMS({ ...copyEvent, hideBranding }, seatAttendee as Person, eventType.metadata); const filteredAttendees = originalRescheduledBooking?.attendees.filter((attendee) => { return attendee.email !== bookerEmail; }); diff --git a/packages/features/bookings/lib/payment/handleNoShowFee.ts b/packages/features/bookings/lib/payment/handleNoShowFee.ts index b5f102366f350e..c0faaf040311e5 100644 --- a/packages/features/bookings/lib/payment/handleNoShowFee.ts +++ b/packages/features/bookings/lib/payment/handleNoShowFee.ts @@ -157,7 +157,7 @@ export const handleNoShowFee = async ({ throw new Error("Payment processing failed"); } - await sendNoShowFeeChargedEmail(attendee, evt, eventTypeMetdata); + await sendNoShowFeeChargedEmail(attendee, { ...evt, hideBranding: evt.hideBranding ?? false }, eventTypeMetdata); return paymentData; } catch (err) { diff --git a/packages/features/bookings/lib/payment/handleRefundError.ts b/packages/features/bookings/lib/payment/handleRefundError.ts index 9332a5aa776687..7ec7f9428008c4 100644 --- a/packages/features/bookings/lib/payment/handleRefundError.ts +++ b/packages/features/bookings/lib/payment/handleRefundError.ts @@ -6,6 +6,7 @@ const handleRefundError = async (opts: { event: CalendarEvent; reason: string; p try { await sendOrganizerPaymentRefundFailedEmail({ ...opts.event, + hideBranding: opts.event.hideBranding ?? false, paymentInfo: { reason: opts.reason, id: opts.paymentId }, }); } catch (e) { diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 3581a4fe106275..4fe275211b93ad 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -356,6 +356,7 @@ const handleDeleteCredential = async ({ members: [], } : undefined, + hideBranding: false, }, { eventName: booking?.eventType?.eventName, diff --git a/packages/features/ee/payments/api/webhook.ts b/packages/features/ee/payments/api/webhook.ts index dbf145bbbc1459..ba2adb40b5e315 100644 --- a/packages/features/ee/payments/api/webhook.ts +++ b/packages/features/ee/payments/api/webhook.ts @@ -128,8 +128,9 @@ const handleSetupSuccess = async (event: Stripe.Event) => { platformClientParams: platformOAuthClient ? getPlatformParams(platformOAuthClient) : undefined, }); } else if (areEmailsEnabled) { - await sendOrganizerRequestEmail({ ...evt }, eventType.metadata); - await sendAttendeeRequestEmailAndSMS({ ...evt }, evt.attendees[0], eventType.metadata); + const evtWithBranding = { ...evt, hideBranding: evt.hideBranding ?? false }; + await sendOrganizerRequestEmail(evtWithBranding, eventType.metadata); + await sendAttendeeRequestEmailAndSMS(evtWithBranding, evt.attendees[0], eventType.metadata); } }; diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 11b1221c7f393c..572dfd7005ea78 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -36,6 +36,7 @@ import { prisma } from "@calcom/prisma"; import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { EventTypeMetadata, PlatformClientParams } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { handleRescheduleEventManager } from "./handleRescheduleEventManager"; import type { BookingSelectResult } from "./utils/bookingSelect"; @@ -312,6 +313,38 @@ export const roundRobinManualReassignment = async ({ conferenceCredentialId: conferenceCredentialId ?? undefined, }; + // Ensure branding flag is provided to all email flows + try { + const teamForBranding = eventType.teamId + ? await prisma.team.findUnique({ + where: { id: eventType.teamId }, + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { select: { hideBranding: true } }, + }, + }) + : null; + const organizationIdForBranding = teamForBranding?.parentId + ? teamForBranding.parentId + : ( + await prisma.profile.findFirst({ + where: { userId: organizer.id }, + select: { organizationId: true }, + }) + )?.organizationId ?? null; + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: eventType.id, + team: (teamForBranding as any) ?? null, + owner: { id: organizer.id, hideBranding: null } as any, + organizationId: organizationIdForBranding, + }); + (evt as any).hideBranding = hideBranding; + } catch (_) { + (evt as any).hideBranding = false; + } + const credentials = await prisma.credential.findMany({ where: { userId: newUser.id }, include: { user: { select: { email: true } } }, @@ -383,7 +416,7 @@ export const roundRobinManualReassignment = async ({ // Send emails if (emailsEnabled) { await sendRoundRobinScheduledEmailsAndSMS({ - calEvent: evtWithoutCancellationReason, + calEvent: { ...evtWithoutCancellationReason, hideBranding: evtWithoutCancellationReason.hideBranding ?? false }, members: [ { ...newUser, @@ -413,7 +446,7 @@ export const roundRobinManualReassignment = async ({ if (previousRRHost && emailsEnabled) { await sendRoundRobinReassignedEmailsAndSMS({ - calEvent: cancelledEvt, + calEvent: { ...cancelledEvt, hideBranding: cancelledEvt.hideBranding ?? false }, members: [ { ...previousRRHost, @@ -432,7 +465,7 @@ export const roundRobinManualReassignment = async ({ if (emailsEnabled && dayjs(evt.startTime).isAfter(dayjs())) { // send email with event updates to attendees await sendRoundRobinUpdatedEmailsAndSMS({ - calEvent: evtWithoutCancellationReason, + calEvent: { ...evtWithoutCancellationReason, hideBranding: evtWithoutCancellationReason.hideBranding ?? false }, eventTypeMetadata: eventType?.metadata as EventTypeMetadata, }); } diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index 380fc01572d4a8..2f9c8c2276ffa9 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -35,6 +35,7 @@ import { prisma } from "@calcom/prisma"; import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils"; import type { EventTypeMetadata, PlatformClientParams } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { handleRescheduleEventManager } from "./handleRescheduleEventManager"; import { handleWorkflowsUpdate } from "./roundRobinManualReassignment"; @@ -341,6 +342,38 @@ export const roundRobinReassignment = async ({ ...(platformClientParams ? platformClientParams : {}), }; + // Ensure branding flag is provided to all email flows + try { + const teamForBranding = eventType.teamId + ? await prisma.team.findUnique({ + where: { id: eventType.teamId }, + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { select: { hideBranding: true } }, + }, + }) + : null; + const organizationIdForBranding = teamForBranding?.parentId + ? teamForBranding.parentId + : ( + await prisma.profile.findFirst({ + where: { userId: organizer.id }, + select: { organizationId: true }, + }) + )?.organizationId ?? null; + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: eventType.id, + team: (teamForBranding as any) ?? null, + owner: { id: organizer.id, hideBranding: null } as any, + organizationId: organizationIdForBranding, + }); + (evt as any).hideBranding = hideBranding; + } catch (_) { + (evt as any).hideBranding = false; + } + const credentials = await prisma.credential.findMany({ where: { userId: organizer.id, @@ -415,7 +448,7 @@ export const roundRobinReassignment = async ({ // Send to new RR host if (emailsEnabled) { await sendRoundRobinScheduledEmailsAndSMS({ - calEvent: evtWithoutCancellationReason, + calEvent: { ...evtWithoutCancellationReason, hideBranding: evtWithoutCancellationReason.hideBranding ?? false }, members: [ { ...reassignedRRHost, @@ -461,7 +494,7 @@ export const roundRobinReassignment = async ({ if (emailsEnabled) { await sendRoundRobinReassignedEmailsAndSMS({ - calEvent: cancelledRRHostEvt, + calEvent: { ...cancelledRRHostEvt, hideBranding: cancelledRRHostEvt.hideBranding ?? false }, members: [ { ...previousRRHost, @@ -482,7 +515,7 @@ export const roundRobinReassignment = async ({ if (emailsEnabled && dayjs(evt.startTime).isAfter(dayjs())) { // send email with event updates to attendees await sendRoundRobinUpdatedEmailsAndSMS({ - calEvent: evtWithoutCancellationReason, + calEvent: { ...evtWithoutCancellationReason, hideBranding: evtWithoutCancellationReason.hideBranding ?? false }, eventTypeMetadata: eventType?.metadata as EventTypeMetadata, }); } diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts index 2f12e57a1a25d4..67648d47349e2c 100644 --- a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts @@ -227,6 +227,7 @@ export const Handler = async ({ ctx, input }: Options) => { await sendScheduledEmailsAndSMS( { ...evt, + hideBranding: evt.hideBranding ?? false, }, undefined, false, diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 5d872cd04df5cd..e272185e1aaa76 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -1,5 +1,6 @@ import dayjs from "@calcom/dayjs"; import { sendAddGuestsEmails } from "@calcom/emails"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; @@ -182,7 +183,42 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => { await eventManager.updateCalendarAttendees(evt, booking); try { - await sendAddGuestsEmails(evt, guests); + const teamForBranding = booking.eventType?.teamId + ? await prisma.team.findUnique({ + where: { id: booking.eventType.teamId }, + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { select: { hideBranding: true } }, + }, + }) + : null; + const organizationIdForBranding = teamForBranding?.parentId + ? teamForBranding.parentId + : ( + await prisma.profile.findFirst({ + where: { userId: booking.userId || undefined }, + select: { organizationId: true }, + }) + )?.organizationId ?? null; + const userForBranding = !booking.eventType?.teamId + ? await prisma.user.findUnique({ + where: { id: booking.userId || 0 }, + select: { id: true, hideBranding: true }, + }) + : null; + const eventTypeId = booking.eventTypeId; + const hideBranding = eventTypeId + ? await shouldHideBrandingForEvent({ + eventTypeId, + team: (teamForBranding as any) ?? null, + owner: userForBranding, + organizationId: organizationIdForBranding, + }) + : false; + + await sendAddGuestsEmails({ ...evt, hideBranding }, guests); } catch (err) { console.log("Error sending AddGuestsEmails"); } diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 54ef634fe8bd05..30ed20c42a79da 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -408,7 +408,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { if (emailsEnabled) { await sendDeclinedEmailsAndSMS( - { ...evt, hideBranding } as any, + { ...evt, hideBranding }, booking.eventType?.metadata as EventTypeMetadata ); } diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index 7838b91c5689d4..615ed75921fe89 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -3,6 +3,7 @@ import type { z } from "zod"; import { getEventLocationType, OrganizerDefaultConferencingAppType } from "@calcom/app-store/locations"; import { getAppFromSlug } from "@calcom/app-store/utils"; import { sendLocationChangeEmailsAndSMS } from "@calcom/emails"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { buildCalEventFromBooking } from "@calcom/lib/buildCalEventFromBooking"; @@ -284,8 +285,50 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { }); try { + const teamForBranding = booking.eventType?.teamId + ? await prisma.team.findUnique({ + where: { id: booking.eventType.teamId }, + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }) + : null; + + const organizationIdForBranding = teamForBranding?.parentId + ? teamForBranding.parentId + : ( + await prisma.profile.findFirst({ + where: { userId: booking.userId || undefined }, + select: { organizationId: true }, + }) + )?.organizationId ?? null; + + const userForBranding = !booking.eventType?.teamId + ? await prisma.user.findUnique({ + where: { id: booking.userId || 0 }, + select: { id: true, hideBranding: true }, + }) + : null; + + const eventTypeId = booking.eventTypeId; + const hideBranding = eventTypeId + ? await shouldHideBrandingForEvent({ + eventTypeId, + team: (teamForBranding as any) ?? null, + owner: userForBranding, + organizationId: organizationIdForBranding, + }) + : false; + await sendLocationChangeEmailsAndSMS( - { ...evt, additionalInformation }, + { ...evt, additionalInformation, hideBranding }, booking?.eventType?.metadata as EventTypeMetadata ); } catch (error) { From 24f61b6ae2ca8d3ab02dd7c3e0952f9a045fb5a0 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Sun, 12 Oct 2025 15:15:14 +0530 Subject: [PATCH 08/58] Remove unused code --- .../web/app/api/cron/bookingReminder/route.ts | 1 - packages/emails/email-manager.ts | 1 - .../emails/src/components/EmailSpacer.tsx | 59 ------------------- .../emails/src/components/V2BaseEmailHtml.tsx | 1 - .../bookings/lib/handleBookingRequested.ts | 1 - .../bookings/lib/handleCancelBooking.ts | 1 - .../bookings/lib/handleConfirmation.ts | 27 +++++---- .../roundRobinManualReassignment.ts | 1 - .../ee/round-robin/roundRobinReassignment.ts | 1 - 9 files changed, 14 insertions(+), 79 deletions(-) delete mode 100644 packages/emails/src/components/EmailSpacer.tsx diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 978447471ce52f..3af740f1069d21 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -13,7 +13,6 @@ import { BookingStatus, ReminderType } from "@calcom/prisma/enums"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -// Define CalendarEventWithBranding type locally since it's not exported type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; async function postHandler(request: NextRequest) { diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts index 2fd316a9503da2..03d0805c83bc9a 100644 --- a/packages/emails/email-manager.ts +++ b/packages/emails/email-manager.ts @@ -13,7 +13,6 @@ import { withReporting } from "@calcom/lib/sentryWrapper"; import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { CalendarEvent, Person } from "@calcom/types/Calendar"; -// Require branding flag for all email flows that accept CalendarEvent type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; import AwaitingPaymentSMS from "../sms/attendee/awaiting-payment-sms"; diff --git a/packages/emails/src/components/EmailSpacer.tsx b/packages/emails/src/components/EmailSpacer.tsx deleted file mode 100644 index e50e4f131e4808..00000000000000 --- a/packages/emails/src/components/EmailSpacer.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import RawHtml from "./RawHtml"; -import Row from "./Row"; - -const CommentIE = ({ html = "" }) => ${html}`} />; - -interface EmailSpacerProps { - height?: number; -} - -const EmailSpacer = ({ height = 16 }: EmailSpacerProps) => { - return ( - <> -
+
`} + /> +
+ +
`} - /> -
- -
- - - - - - - - ); -}; - -export default EmailSpacer; diff --git a/packages/emails/src/components/V2BaseEmailHtml.tsx b/packages/emails/src/components/V2BaseEmailHtml.tsx index b72560e74fc3b5..95a0155c22e50a 100644 --- a/packages/emails/src/components/V2BaseEmailHtml.tsx +++ b/packages/emails/src/components/V2BaseEmailHtml.tsx @@ -5,7 +5,6 @@ import EmailHead from "./EmailHead"; import EmailScheduledBodyHeaderContent from "./EmailScheduledBodyHeaderContent"; import EmailSchedulingBodyDivider from "./EmailSchedulingBodyDivider"; import EmailSchedulingBodyHeader, { BodyHeadType } from "./EmailSchedulingBodyHeader"; -import EmailSpacer from "./EmailSpacer"; import RawHtml from "./RawHtml"; import Row from "./Row"; diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index c6aeb6ac93117f..5ad161a367d374 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -86,7 +86,6 @@ export async function handleBookingRequested(args: { }) )?.organizationId ?? null; - // Fetch user data for branding when there's no team const userForBranding = !booking.eventType?.teamId && booking.userId ? await prisma.user.findUnique({ diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 8f79af8a12962c..495787a58bb4a7 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -241,7 +241,6 @@ async function handler(input: CancelBookingInput) { bookingToDelete.eventType?.team?.parentId ?? ownerProfile?.organizationId ?? null ); - // For user events, we need to fetch the user's profile to get the organization ID const userOrganizationId = !bookingToDelete.eventType?.team ? ( await prisma.profile.findFirst({ diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 786bd6615f4770..573f126f3133ef 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -1,6 +1,8 @@ import { eventTypeAppMetadataOptionalSchema } from "@calcom/app-store/zod-utils"; import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder"; import { sendScheduledEmailsAndSMS } from "@calcom/emails"; +import type { EventManagerUser } from "@calcom/features/bookings/lib/EventManager"; +import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { allowDisablingAttendeeConfirmationEmails, allowDisablingHostConfirmationEmails, @@ -11,8 +13,6 @@ import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; -import type { EventManagerUser } from "@calcom/features/bookings/lib/EventManager"; -import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; @@ -107,7 +107,7 @@ export async function handleConfirmation(args: { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); const eventTypeId = eventType?.id ?? booking.eventTypeId ?? null; - // Fetch full event type data for hideBranding logic + const fullEventType = eventTypeId ? await prisma.eventType.findUnique({ where: { id: eventTypeId }, @@ -130,7 +130,8 @@ export async function handleConfirmation(args: { }) : null; - const userForBranding = !fullEventType?.teamId && booking.userId + const userForBranding = + !fullEventType?.teamId && booking.userId ? await prisma.user.findUnique({ where: { id: booking.userId }, select: { @@ -140,14 +141,14 @@ export async function handleConfirmation(args: { }) : null; - const hideBranding = eventTypeId - ? await shouldHideBrandingForEvent({ - eventTypeId, - team: fullEventType?.team ?? null, - owner: userForBranding, - organizationId: orgId ?? null, - }) - : false; + const hideBranding = eventTypeId + ? await shouldHideBrandingForEvent({ + eventTypeId, + team: fullEventType?.team ?? null, + owner: userForBranding, + organizationId: orgId ?? null, + }) + : false; if (results.length > 0 && results.every((res) => !res.success)) { const error = { @@ -186,7 +187,7 @@ export async function handleConfirmation(args: { if (emailsEnabled) { await sendScheduledEmailsAndSMS( - ({ ...evt, additionalInformation: metadata, hideBranding }), + { ...evt, additionalInformation: metadata, hideBranding }, undefined, isHostConfirmationEmailsDisabled, isAttendeeConfirmationEmailDisabled, diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 572dfd7005ea78..08d59fc53f207d 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -313,7 +313,6 @@ export const roundRobinManualReassignment = async ({ conferenceCredentialId: conferenceCredentialId ?? undefined, }; - // Ensure branding flag is provided to all email flows try { const teamForBranding = eventType.teamId ? await prisma.team.findUnique({ diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index 2f9c8c2276ffa9..33c0f89af99a4a 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -342,7 +342,6 @@ export const roundRobinReassignment = async ({ ...(platformClientParams ? platformClientParams : {}), }; - // Ensure branding flag is provided to all email flows try { const teamForBranding = eventType.teamId ? await prisma.team.findUnique({ From 383a72e86eca7d12d954134ff0076c0e3a079ebe Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 13 Oct 2025 12:02:51 +0530 Subject: [PATCH 09/58] Addressed coderabits comments --- .../web/app/api/cron/bookingReminder/route.ts | 37 ++++++++++++++++++- .../handleSeats/cancel/cancelAttendeeSeat.ts | 2 +- .../attendeeRescheduleSeatedBooking.ts | 11 +++++- .../bookings/lib/handleSeats/types.d.ts | 1 + .../credentials/handleDeleteCredential.ts | 20 +++++++++- 5 files changed, 65 insertions(+), 6 deletions(-) diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 3af740f1069d21..c9d3ebae92e75f 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -7,6 +7,7 @@ import { sendOrganizerRequestReminderEmail } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import { BookingStatus, ReminderType } from "@calcom/prisma/enums"; @@ -59,14 +60,27 @@ async function postHandler(request: NextRequest) { timeZone: true, destinationCalendar: true, isPlatformManaged: true, + hideBranding: true, platformOAuthClients: { select: { id: true, areEmailsEnabled: true } }, }, }, eventType: { select: { + id: true, recurringEvent: true, bookingFields: true, metadata: true, + team: { + select: { + id: true, + hideBranding: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }, }, }, responses: true, @@ -118,6 +132,27 @@ async function postHandler(request: NextRequest) { const attendeesList = await Promise.all(attendeesListPromises); const selectedDestinationCalendar = booking.destinationCalendar || user.destinationCalendar; + + const userOrganizationId = !booking.eventType?.team && booking.user + ? ( + await prisma.profile.findFirst({ + where: { + userId: booking.user.id, + }, + select: { + organizationId: true, + }, + }) + )?.organizationId + : null; + + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: booking.eventType?.id ?? 0, + team: booking.eventType?.team ?? null, + owner: booking.user ?? null, + organizationId: userOrganizationId ?? null, + }); + const evt: CalendarEvent = { type: booking.title, title: booking.title, @@ -141,7 +176,7 @@ async function postHandler(request: NextRequest) { uid: booking.uid, recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent), destinationCalendar: selectedDestinationCalendar ? [selectedDestinationCalendar] : [], - hideBranding: false, + hideBranding, }; await sendOrganizerRequestReminderEmail(evt as CalendarEventWithBranding, booking?.eventType?.metadata as EventTypeMetadata); diff --git a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts index 14fa4734c6a1f5..b39ed0c34557e7 100644 --- a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts +++ b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts @@ -124,7 +124,7 @@ async function cancelAttendeeSeat( const tAttendees = await getTranslation(attendee.locale ?? "en", "common"); await sendCancelledSeatEmailsAndSMS( - { ...evt, hideBranding: !!(bookingToDelete.eventType?.team?.hideBranding || bookingToDelete.user?.hideBranding) }, + { ...evt, hideBranding: !!(bookingToDelete.eventType?.team?.hideBranding || bookingToDelete.eventType?.team?.parent?.hideBranding || bookingToDelete.user?.hideBranding) }, { ...attendee, language: { translate: tAttendees, locale: attendee.locale ?? "en" }, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 49002b80466443..bb121a24af6443 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -23,7 +23,14 @@ const attendeeRescheduleSeatedBooking = async ( let { originalRescheduledBooking } = rescheduleSeatedBookingObject; const { organizerUser } = rescheduleSeatedBookingObject; - const teamForBranding = eventType.team?.id + type TeamForBranding = { + id: number; + hideBranding: boolean | null; + parentId: number | null; + parent: { hideBranding: boolean | null } | null; + }; + + const teamForBranding: TeamForBranding | null = eventType.team?.id ? await prisma.team.findUnique({ where: { id: eventType.team.id }, select: { @@ -41,7 +48,7 @@ const attendeeRescheduleSeatedBooking = async ( const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, - team: (teamForBranding as any) ?? null, + team: teamForBranding ?? null, owner: organizerUser ?? null, organizationId: (await (async () => { if (teamForBranding?.parentId) return teamForBranding.parentId; diff --git a/packages/features/bookings/lib/handleSeats/types.d.ts b/packages/features/bookings/lib/handleSeats/types.d.ts index 4449df78f49485..2330c9af0dddac 100644 --- a/packages/features/bookings/lib/handleSeats/types.d.ts +++ b/packages/features/bookings/lib/handleSeats/types.d.ts @@ -7,6 +7,7 @@ import type { AppsStatus, CalendarEvent } from "@calcom/types/Calendar"; import type { Booking } from "../handleNewBooking/createBooking"; import type { NewBookingEventType } from "../handleNewBooking/getEventTypesFromDB"; import type { OriginalRescheduledBooking } from "../handleNewBooking/originalRescheduledBookingUtils"; +import type { OrganizerUser } from "../handleNewBooking/loadUsers"; export type BookingSeat = Prisma.BookingSeatGetPayload<{ include: { booking: true; attendee: true } }> | null; export type Invitee = { diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 50956a6047765b..a3e67f67c59e7c 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -16,6 +16,7 @@ import { buildNonDelegationCredential } from "@calcom/lib/delegationCredential"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getTranslation } from "@calcom/lib/server/i18n"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { bookingMinimalSelect, prisma } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { AppCategories, BookingStatus } from "@calcom/prisma/enums"; @@ -226,6 +227,7 @@ const handleDeleteCredential = async ({ ...bookingMinimalSelect, recurringEventId: true, userId: true, + eventTypeId: true, responses: true, user: { select: { @@ -236,6 +238,7 @@ const handleDeleteCredential = async ({ name: true, destinationCalendar: true, locale: true, + hideBranding: true, }, }, location: true, @@ -261,13 +264,18 @@ const handleDeleteCredential = async ({ select: { id: true, name: true, + hideBranding: true, + parent: { + select: { + hideBranding: true, + }, + }, }, }, metadata: true, }, }, uid: true, - eventTypeId: true, destinationCalendar: true, }, }); @@ -318,6 +326,14 @@ const handleDeleteCredential = async ({ const attendeesList = await Promise.all(attendeesListPromises); const tOrganizer = await getTranslation(booking?.user?.locale ?? "en", "common"); + + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: booking.eventTypeId ?? 0, + team: booking.eventType?.team ?? null, + owner: booking.user ?? null, + organizationId: null, + }); + await sendCancelledEmailsAndSMS( { type: booking?.eventType?.title as string, @@ -356,7 +372,7 @@ const handleDeleteCredential = async ({ members: [], } : undefined, - hideBranding: false, + hideBranding, }, { eventName: booking?.eventType?.eventName, From 865178d0f418c4769422bc869bb612968a4741ec Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 13 Oct 2025 12:39:14 +0530 Subject: [PATCH 10/58] Addressed coderabits comments --- .../attendeeRescheduleSeatedBooking.ts | 1 + .../roundRobinManualReassignment.ts | 35 ++++++++++++------- .../ee/round-robin/utils/bookingSelect.ts | 1 + 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index bb121a24af6443..5a1a9b072053d7 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -52,6 +52,7 @@ const attendeeRescheduleSeatedBooking = async ( owner: organizerUser ?? null, organizationId: (await (async () => { if (teamForBranding?.parentId) return teamForBranding.parentId; + if (!organizerUser) return null; const organizerProfile = await prisma.profile.findFirst({ where: { userId: organizerUser.id }, select: { organizationId: true }, diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index ae1936ca54e291..ea2a8efcaa4b48 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -314,7 +314,14 @@ export const roundRobinManualReassignment = async ({ }; try { - const teamForBranding = eventType.teamId + type TeamForBranding = { + id: number; + hideBranding: boolean | null; + parentId: number | null; + parent: { hideBranding: boolean | null } | null; + }; + + const teamForBranding: TeamForBranding | null = eventType.teamId ? await prisma.team.findUnique({ where: { id: eventType.teamId }, select: { @@ -325,23 +332,25 @@ export const roundRobinManualReassignment = async ({ }, }) : null; - const organizationIdForBranding = teamForBranding?.parentId - ? teamForBranding.parentId - : ( - await prisma.profile.findFirst({ - where: { userId: organizer.id }, - select: { organizationId: true }, - }) - )?.organizationId ?? null; + const organizationIdForBranding = + teamForBranding?.parentId ?? + orgId ?? + ( + await prisma.profile.findFirst({ + where: { userId: organizer.id, organizationId: orgId ?? undefined }, + select: { organizationId: true }, + }) + )?.organizationId ?? + null; const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, - team: (teamForBranding as any) ?? null, - owner: { id: organizer.id, hideBranding: null } as any, + team: teamForBranding ?? null, + owner: { id: organizer.id, hideBranding: organizer.hideBranding ?? null }, organizationId: organizationIdForBranding, }); - (evt as any).hideBranding = hideBranding; + evt.hideBranding = hideBranding; } catch (_) { - (evt as any).hideBranding = false; + evt.hideBranding = false; } const credentials = await prisma.credential.findMany({ diff --git a/packages/features/ee/round-robin/utils/bookingSelect.ts b/packages/features/ee/round-robin/utils/bookingSelect.ts index 2a636108fa7851..afeee3fc41fb4f 100644 --- a/packages/features/ee/round-robin/utils/bookingSelect.ts +++ b/packages/features/ee/round-robin/utils/bookingSelect.ts @@ -22,6 +22,7 @@ export const bookingSelect = { locale: true, timeZone: true, timeFormat: true, + hideBranding: true, destinationCalendar: true, credentials: { select: credentialForCalendarServiceSelect, From d0d93ba1e244559ba6dcf71bd89efe0a79c9c9c8 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 13 Oct 2025 12:53:17 +0530 Subject: [PATCH 11/58] Addressed coderabits comments --- .../features/ee/round-robin/roundRobinManualReassignment.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index ea2a8efcaa4b48..dfdf411f0a0221 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -349,7 +349,8 @@ export const roundRobinManualReassignment = async ({ organizationId: organizationIdForBranding, }); evt.hideBranding = hideBranding; - } catch (_) { + } catch (error) { + roundRobinReassignLogger.error("Failed to compute hideBranding", error); evt.hideBranding = false; } From 08b2dd355f84c6b4a6de434b91e78b869f7d2a84 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 13 Oct 2025 13:14:58 +0530 Subject: [PATCH 12/58] Addressed coderabits comments --- .../ee/round-robin/roundRobinManualReassignment.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index dfdf411f0a0221..2e859eb8390613 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -345,7 +345,7 @@ export const roundRobinManualReassignment = async ({ const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: teamForBranding ?? null, - owner: { id: organizer.id, hideBranding: organizer.hideBranding ?? null }, + owner: organizer, organizationId: organizationIdForBranding, }); evt.hideBranding = hideBranding; @@ -580,7 +580,7 @@ export async function handleWorkflowsUpdate({ emailSubject: workflowStep.emailSubject || undefined, emailBody: workflowStep.reminderBody || undefined, sender: workflowStep.sender || SENDER_NAME, - hideBranding: true, + hideBranding: evt.hideBranding ?? false, includeCalendarEvent: workflowStep.includeCalendarEvent, workflowStepId: workflowStep.id, verifiedAt: workflowStep.verifiedAt, @@ -645,8 +645,6 @@ export async function handleWorkflowsUpdate({ eventType: { slug: eventType.slug }, bookerUrl, }, - hideBranding: !!eventType?.owner?.hideBranding, + hideBranding: evt.hideBranding ?? false, }); } - -export default roundRobinManualReassignment; From 714f725c46eef3db6963ba554e9fd20fb87b0211 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 17 Oct 2025 03:54:18 +0530 Subject: [PATCH 13/58] no primsa outside repositories structure, better error handling, no duplicate code --- .../web/app/api/cron/bookingReminder/route.ts | 5 +- .../_utils/payments/handlePaymentSuccess.ts | 4 +- .../emails/__tests__/withHideBranding.test.ts | 90 ++++++ packages/emails/email-manager.ts | 4 + .../handleCancelBooking.integration.test.ts | 89 ++++++ .../handleConfirmation.integration.test.ts | 138 +++++++++ .../bookings/lib/BookingEmailSmsHandler.ts | 16 +- .../bookings/lib/handleBookingRequested.ts | 55 +--- .../bookings/lib/handleCancelBooking.ts | 25 +- .../bookings/lib/handleConfirmation.ts | 53 +--- .../features/bookings/lib/handleNewBooking.ts | 8 +- .../attendeeRescheduleSeatedBooking.ts | 17 +- .../owner/combineTwoSeatedBookings.ts | 17 +- .../owner/moveSeatedBookingToNewTimeSlot.ts | 17 +- .../bookings/lib/payment/handleNoShowFee.ts | 4 +- .../features/conferencing/lib/videoClient.ts | 6 +- ...obinManualReassignment.integration.test.ts | 144 +++++++++ .../roundRobinManualReassignment.ts | 56 +--- .../ee/round-robin/roundRobinReassignment.ts | 19 +- .../branding/BrandingApplicationService.ts | 60 ++++ packages/lib/branding/README.md | 51 ++++ .../BrandingApplicationService.test.ts | 282 ++++++++++++++++++ packages/lib/branding/types.ts | 26 ++ .../repository/branding/BrandingRepository.ts | 48 +++ .../__tests__/BrandingRepository.test.ts | 187 ++++++++++++ .../viewer/bookings/addGuests.handler.ts | 21 +- .../viewer/bookings/confirm.handler.ts | 47 +-- .../viewer/bookings/editLocation.handler.ts | 21 +- 28 files changed, 1245 insertions(+), 265 deletions(-) create mode 100644 packages/emails/__tests__/withHideBranding.test.ts create mode 100644 packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts create mode 100644 packages/features/bookings/__tests__/handleConfirmation.integration.test.ts create mode 100644 packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts create mode 100644 packages/lib/branding/BrandingApplicationService.ts create mode 100644 packages/lib/branding/README.md create mode 100644 packages/lib/branding/__tests__/BrandingApplicationService.test.ts create mode 100644 packages/lib/branding/types.ts create mode 100644 packages/lib/server/repository/branding/BrandingRepository.ts create mode 100644 packages/lib/server/repository/branding/__tests__/BrandingRepository.test.ts diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index c9d3ebae92e75f..80be6e03487813 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -3,7 +3,7 @@ import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import dayjs from "@calcom/dayjs"; -import { sendOrganizerRequestReminderEmail } from "@calcom/emails"; +import { sendOrganizerRequestReminderEmail, withHideBranding } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; @@ -14,7 +14,6 @@ import { BookingStatus, ReminderType } from "@calcom/prisma/enums"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); @@ -179,7 +178,7 @@ async function postHandler(request: NextRequest) { hideBranding, }; - await sendOrganizerRequestReminderEmail(evt as CalendarEventWithBranding, booking?.eventType?.metadata as EventTypeMetadata); + await sendOrganizerRequestReminderEmail(withHideBranding(evt), booking?.eventType?.metadata as EventTypeMetadata); await prisma.reminderMail.create({ data: { diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index 8a5f671c2e05b7..eb51c43597e168 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -1,5 +1,5 @@ import { eventTypeAppMetadataOptionalSchema } from "@calcom/app-store/zod-utils"; -import { sendScheduledEmailsAndSMS } from "@calcom/emails"; +import { sendScheduledEmailsAndSMS, withHideBranding } from "@calcom/emails"; import { doesBookingRequireConfirmation } from "@calcom/features/bookings/lib/doesBookingRequireConfirmation"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { handleBookingRequested } from "@calcom/features/bookings/lib/handleBookingRequested"; @@ -96,7 +96,7 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number) log.debug(`handling booking request for eventId ${eventType.id}`); } } else if (areEmailsEnabled) { - await sendScheduledEmailsAndSMS({ ...evt, hideBranding: evt.hideBranding ?? false }, undefined, undefined, undefined, eventType.metadata); + await sendScheduledEmailsAndSMS(withHideBranding(evt), undefined, undefined, undefined, eventType.metadata); } throw new HttpCode({ diff --git a/packages/emails/__tests__/withHideBranding.test.ts b/packages/emails/__tests__/withHideBranding.test.ts new file mode 100644 index 00000000000000..3eeb0afe117ce8 --- /dev/null +++ b/packages/emails/__tests__/withHideBranding.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect } from "vitest"; +import { withHideBranding } from "../email-manager"; +import type { CalendarEvent } from "@calcom/types/Calendar"; + +describe("withHideBranding", () => { + const baseEvent: CalendarEvent = { + uid: "test-uid", + title: "Test Event", + startTime: "2024-01-15T10:00:00Z", + endTime: "2024-01-15T11:00:00Z", + attendees: [], + organizer: { name: "Test Organizer", email: "test@example.com" }, + }; + + it("should add hideBranding: false when not present", () => { + const result = withHideBranding(baseEvent); + + expect(result).toEqual({ + ...baseEvent, + hideBranding: false, + }); + }); + + it("should preserve existing hideBranding value", () => { + const eventWithBranding = { ...baseEvent, hideBranding: true }; + const result = withHideBranding(eventWithBranding); + + expect(result).toEqual({ + ...baseEvent, + hideBranding: true, + }); + }); + + it("should use explicit value when provided", () => { + const result = withHideBranding(baseEvent, true); + + expect(result).toEqual({ + ...baseEvent, + hideBranding: true, + }); + }); + + it("should override existing value with explicit value", () => { + const eventWithBranding = { ...baseEvent, hideBranding: false }; + const result = withHideBranding(eventWithBranding, true); + + expect(result).toEqual({ + ...baseEvent, + hideBranding: true, + }); + }); + + it("should default to false when explicit value is undefined", () => { + const eventWithBranding = { ...baseEvent, hideBranding: true }; + const result = withHideBranding(eventWithBranding, undefined); + + expect(result).toEqual({ + ...baseEvent, + hideBranding: true, // Should preserve existing value when explicit is undefined + }); + }); + + it("should handle null hideBranding correctly", () => { + const eventWithNullBranding = { ...baseEvent, hideBranding: null }; + const result = withHideBranding(eventWithNullBranding); + + expect(result).toEqual({ + ...baseEvent, + hideBranding: false, // Should default to false when null + }); + }); + + it("should handle undefined hideBranding correctly", () => { + const eventWithUndefinedBranding = { ...baseEvent, hideBranding: undefined }; + const result = withHideBranding(eventWithUndefinedBranding); + + expect(result).toEqual({ + ...baseEvent, + hideBranding: false, // Should default to false when undefined + }); + }); + + it("should return CalendarEventWithBranding type", () => { + const result = withHideBranding(baseEvent); + + // Type check - this should compile without errors + expect(typeof result.hideBranding).toBe("boolean"); + expect(result.hideBranding).toBe(false); + }); +}); diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts index 03d0805c83bc9a..fa9562b89d650a 100644 --- a/packages/emails/email-manager.ts +++ b/packages/emails/email-manager.ts @@ -15,6 +15,10 @@ import type { CalendarEvent, Person } from "@calcom/types/Calendar"; type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; +export function withHideBranding(calEvent: CalendarEvent, explicit?: boolean): CalendarEventWithBranding { + return { ...calEvent, hideBranding: explicit ?? calEvent.hideBranding ?? false }; +} + import AwaitingPaymentSMS from "../sms/attendee/awaiting-payment-sms"; import CancelledSeatSMS from "../sms/attendee/cancelled-seat-sms"; import EventCancelledSMS from "../sms/attendee/event-cancelled-sms"; diff --git a/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts b/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts new file mode 100644 index 00000000000000..401b96cb19d6b6 --- /dev/null +++ b/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts @@ -0,0 +1,89 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { handleCancelBooking } from "../lib/handleCancelBooking"; +import type { PrismaClient } from "@calcom/prisma"; + +// Mock the BrandingApplicationService +vi.mock("@calcom/lib/branding/BrandingApplicationService", () => ({ + BrandingApplicationService: vi.fn().mockImplementation(() => ({ + computeHideBranding: vi.fn().mockResolvedValue(true), + })), +})); + +// Mock other dependencies +vi.mock("@calcom/emails", () => ({ + sendCancelledEmailsAndSMS: vi.fn(), +})); + +vi.mock("@calcom/features/webhooks/lib/sendOrSchedulePayload", () => ({ + default: vi.fn(), +})); + +describe("handleCancelBooking Integration", () => { + let mockPrisma: PrismaClient; + + beforeEach(() => { + mockPrisma = { + booking: { + findUnique: vi.fn().mockResolvedValue({ + id: 1, + eventTypeId: 1, + userId: 1, + eventType: { + team: { id: 1, hideBranding: true, parentId: null, parent: null }, + }, + user: { id: 1, hideBranding: false }, + }), + update: vi.fn().mockResolvedValue({}), + }, + profile: { + findFirst: vi.fn().mockResolvedValue({ + organizationId: 123, + }), + }, + } as any; + }); + + it("should include hideBranding in cancellation email calls", async () => { + const { sendCancelledEmailsAndSMS } = await import("@calcom/emails"); + + await handleCancelBooking({ + bookingId: 1, + reason: "Test cancellation", + prisma: mockPrisma, + }); + + // Verify that sendCancelledEmailsAndSMS was called with hideBranding + expect(sendCancelledEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + hideBranding: true, + }), + expect.any(Object), + expect.any(Object) + ); + }); + + it("should handle branding computation errors gracefully", async () => { + const { BrandingApplicationService } = await import("@calcom/lib/branding/BrandingApplicationService"); + const mockService = new BrandingApplicationService(mockPrisma); + + // Mock service to return false on error + vi.mocked(mockService.computeHideBranding).mockResolvedValue(false); + + const { sendCancelledEmailsAndSMS } = await import("@calcom/emails"); + + await handleCancelBooking({ + bookingId: 1, + reason: "Test cancellation", + prisma: mockPrisma, + }); + + // Verify that sendCancelledEmailsAndSMS was called with hideBranding: false + expect(sendCancelledEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + hideBranding: false, + }), + expect.any(Object), + expect.any(Object) + ); + }); +}); diff --git a/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts b/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts new file mode 100644 index 00000000000000..05506c3eb5c3bc --- /dev/null +++ b/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts @@ -0,0 +1,138 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { handleConfirmation } from "../lib/handleConfirmation"; +import type { PrismaClient } from "@calcom/prisma"; + +// Mock the BrandingApplicationService +vi.mock("@calcom/lib/branding/BrandingApplicationService", () => ({ + BrandingApplicationService: vi.fn().mockImplementation(() => ({ + computeHideBranding: vi.fn().mockResolvedValue(true), + })), +})); + +// Mock other dependencies +vi.mock("@calcom/emails", () => ({ + sendScheduledEmailsAndSMS: vi.fn(), +})); + +vi.mock("@calcom/features/webhooks/lib/sendOrSchedulePayload", () => ({ + default: vi.fn(), +})); + +describe("handleConfirmation Integration", () => { + let mockPrisma: PrismaClient; + + beforeEach(() => { + mockPrisma = { + eventType: { + findUnique: vi.fn().mockResolvedValue({ + id: 1, + team: { id: 1, hideBranding: true, parentId: null, parent: null }, + teamId: 1, + }), + }, + user: { + findUnique: vi.fn().mockResolvedValue({ + id: 1, + hideBranding: false, + }), + }, + profile: { + findFirst: vi.fn().mockResolvedValue({ + organizationId: 123, + }), + }, + } as any; + }); + + it("should include hideBranding in email calls", async () => { + const { sendScheduledEmailsAndSMS } = await import("@calcom/emails"); + + const mockBooking = { + id: 1, + userId: 1, + eventTypeId: 1, + startTime: new Date("2024-01-15T10:00:00Z"), + endTime: new Date("2024-01-15T11:00:00Z"), + uid: "test-uid", + }; + + const mockEventType = { + id: 1, + team: { id: 1, hideBranding: true, parentId: null, parent: null }, + teamId: 1, + }; + + await handleConfirmation({ + user: { id: 1, username: "testuser" }, + evt: { + uid: "test-uid", + title: "Test Event", + startTime: "2024-01-15T10:00:00Z", + endTime: "2024-01-15T11:00:00Z", + attendees: [], + organizer: { name: "Test Organizer", email: "test@example.com" }, + }, + prisma: mockPrisma, + bookingId: 1, + booking: mockBooking, + eventType: mockEventType, + }); + + // Verify that sendScheduledEmailsAndSMS was called with hideBranding + expect(sendScheduledEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + hideBranding: true, + }), + expect.any(Object) + ); + }); + + it("should handle branding computation errors gracefully", async () => { + const { BrandingApplicationService } = await import("@calcom/lib/branding/BrandingApplicationService"); + const mockService = new BrandingApplicationService(mockPrisma); + + // Mock service to return false on error + vi.mocked(mockService.computeHideBranding).mockResolvedValue(false); + + const { sendScheduledEmailsAndSMS } = await import("@calcom/emails"); + + const mockBooking = { + id: 1, + userId: 1, + eventTypeId: 1, + startTime: new Date("2024-01-15T10:00:00Z"), + endTime: new Date("2024-01-15T11:00:00Z"), + uid: "test-uid", + }; + + const mockEventType = { + id: 1, + team: { id: 1, hideBranding: true, parentId: null, parent: null }, + teamId: 1, + }; + + await handleConfirmation({ + user: { id: 1, username: "testuser" }, + evt: { + uid: "test-uid", + title: "Test Event", + startTime: "2024-01-15T10:00:00Z", + endTime: "2024-01-15T11:00:00Z", + attendees: [], + organizer: { name: "Test Organizer", email: "test@example.com" }, + }, + prisma: mockPrisma, + bookingId: 1, + booking: mockBooking, + eventType: mockEventType, + }); + + // Verify that sendScheduledEmailsAndSMS was called with hideBranding: false + expect(sendScheduledEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + hideBranding: false, + }), + expect.any(Object) + ); + }); +}); diff --git a/packages/features/bookings/lib/BookingEmailSmsHandler.ts b/packages/features/bookings/lib/BookingEmailSmsHandler.ts index 22ff3dd1df00fb..6761f29baab1cf 100644 --- a/packages/features/bookings/lib/BookingEmailSmsHandler.ts +++ b/packages/features/bookings/lib/BookingEmailSmsHandler.ts @@ -15,6 +15,7 @@ import { sendScheduledEmailsAndSMS, sendOrganizerRequestEmail, sendAttendeeRequestEmailAndSMS, + withHideBranding, } from "@calcom/emails"; import type { BookingType } from "@calcom/features/bookings/lib/handleNewBooking/originalRescheduledBookingUtils"; import type { EventNameObjectType } from "@calcom/features/eventtypes/lib/eventNaming"; @@ -126,13 +127,12 @@ export class BookingEmailSmsHandler { } = data; await sendRescheduledEmailsAndSMS( - { + withHideBranding({ ...evt, - hideBranding: evt.hideBranding ?? false, additionalInformation, additionalNotes, cancellationReason: `$RCH$${rescheduleReason || ""}`, - }, + }), metadata ); } @@ -228,17 +228,17 @@ export class BookingEmailSmsHandler { try { await Promise.all([ sendRoundRobinRescheduledEmailsAndSMS( - { ...copyEventAdditionalInfo, iCalUID, hideBranding: copyEventAdditionalInfo.hideBranding ?? false }, + withHideBranding({ ...copyEventAdditionalInfo, iCalUID }), rescheduledMembers, metadata ), sendRoundRobinScheduledEmailsAndSMS({ - calEvent: { ...copyEventAdditionalInfo, hideBranding: copyEventAdditionalInfo.hideBranding ?? false }, + calEvent: withHideBranding(copyEventAdditionalInfo), members: newBookedMembers, eventTypeMetadata: metadata, }), sendRoundRobinCancelledEmailsAndSMS( - { ...cancelledRRHostEvt, hideBranding: cancelledRRHostEvt.hideBranding ?? false }, + withHideBranding(cancelledRRHostEvt), cancelledMembers, metadata, reassignedTo @@ -282,7 +282,7 @@ export class BookingEmailSmsHandler { try { await sendScheduledEmailsAndSMS( - { ...evt, additionalInformation, additionalNotes, customInputs, hideBranding: evt.hideBranding ?? false }, + withHideBranding({ ...evt, additionalInformation, additionalNotes, customInputs }), eventNameObject, isHostConfirmationEmailsDisabled, isAttendeeConfirmationEmailDisabled, @@ -312,7 +312,7 @@ export class BookingEmailSmsHandler { safeStringify({ calEvent: getPiiFreeCalendarEvent(evt) }) ); - const eventWithNotes = { ...evt, additionalNotes, hideBranding: evt.hideBranding ?? false }; + const eventWithNotes = withHideBranding({ ...evt, additionalNotes }); try { await Promise.all([ diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index 5ad161a367d374..b35a538406155c 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -4,7 +4,7 @@ import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { WorkflowService } from "@calcom/lib/server/service/workflows"; @@ -61,51 +61,16 @@ export async function handleBookingRequested(args: { log.debug("Emails: Sending booking requested emails"); - const teamForBranding = booking.eventType?.teamId - ? await prisma.team.findUnique({ - where: { id: booking.eventType.teamId }, - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { - select: { - hideBranding: true, - }, - }, - }, - }) - : null; - - const organizationIdForBranding = teamForBranding?.parentId - ? teamForBranding.parentId - : ( - await prisma.profile.findFirst({ - where: { userId: booking.userId ?? undefined }, - select: { organizationId: true }, - }) - )?.organizationId ?? null; - - const userForBranding = - !booking.eventType?.teamId && booking.userId - ? await prisma.user.findUnique({ - where: { id: booking.userId }, - select: { - id: true, - hideBranding: true, - }, - }) - : null; - const eventTypeId = booking.eventType?.id ?? booking.eventTypeId ?? null; - const hideBranding = eventTypeId - ? await shouldHideBrandingForEvent({ - eventTypeId, - team: (teamForBranding as any) ?? null, - owner: userForBranding, - organizationId: organizationIdForBranding, - }) - : false; + let hideBranding = false; + + if (eventTypeId) { + const brandingService = new BrandingApplicationService(prisma); + hideBranding = await brandingService.computeHideBranding({ + eventTypeId, + ownerIdFallback: booking.userId ?? null, + }); + } await sendOrganizerRequestEmail( { ...evt, hideBranding }, diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 495787a58bb4a7..a67a6896fe2551 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -4,8 +4,7 @@ import { DailyLocationType } from "@calcom/app-store/constants"; import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; -import { sendCancelledEmailsAndSMS } from "@calcom/emails"; -type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; +import { sendCancelledEmailsAndSMS, withHideBranding } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation"; @@ -22,7 +21,7 @@ import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import { HttpError } from "@calcom/lib/http-error"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; @@ -254,12 +253,18 @@ async function handler(input: CancelBookingInput) { )?.organizationId : null; - const hideBranding = await shouldHideBrandingForEvent({ - eventTypeId: bookingToDelete.eventTypeId ?? 0, - team: bookingToDelete.eventType?.team ?? null, - owner: bookingToDelete.user ?? null, - organizationId: bookingToDelete.eventType?.team?.parentId ?? userOrganizationId ?? null, - }); + let hideBranding = false; + + if (bookingToDelete.eventTypeId) { + const brandingService = new BrandingApplicationService(prisma); + hideBranding = await brandingService.computeHideBranding({ + eventTypeId: bookingToDelete.eventTypeId, + teamContext: bookingToDelete.eventType?.team ?? null, + owner: bookingToDelete.user ?? null, + organizationId: bookingToDelete.eventType?.team?.parentId ?? userOrganizationId ?? null, + ownerIdFallback: bookingToDelete.userId, + }); + } log.debug("Branding configuration", { hideBranding, @@ -612,7 +617,7 @@ async function handler(input: CancelBookingInput) { }); await sendCancelledEmailsAndSMS( - evt as CalendarEventWithBranding, + withHideBranding(evt, hideBranding), { eventName: bookingToDelete?.eventType?.eventName }, bookingToDelete?.eventType?.metadata as EventTypeMetadata ); diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 573f126f3133ef..c2c0016613720c 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -16,7 +16,7 @@ import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { WorkflowService } from "@calcom/lib/server/service/workflows"; @@ -108,47 +108,16 @@ export async function handleConfirmation(args: { const eventTypeId = eventType?.id ?? booking.eventTypeId ?? null; - const fullEventType = eventTypeId - ? await prisma.eventType.findUnique({ - where: { id: eventTypeId }, - select: { - id: true, - team: { - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { - select: { - hideBranding: true, - }, - }, - }, - }, - teamId: true, - }, - }) - : null; - - const userForBranding = - !fullEventType?.teamId && booking.userId - ? await prisma.user.findUnique({ - where: { id: booking.userId }, - select: { - id: true, - hideBranding: true, - }, - }) - : null; - - const hideBranding = eventTypeId - ? await shouldHideBrandingForEvent({ - eventTypeId, - team: fullEventType?.team ?? null, - owner: userForBranding, - organizationId: orgId ?? null, - }) - : false; + let hideBranding = false; + + if (eventTypeId) { + const brandingService = new BrandingApplicationService(prisma); + hideBranding = await brandingService.computeHideBranding({ + eventTypeId, + organizationId: orgId ?? null, + ownerIdFallback: booking.userId ?? null, + }); + } if (results.length > 0 && results.every((res) => !res.success)) { const error = { diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index ca55ce70793af9..8c1f1382bbf8bb 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -125,7 +125,7 @@ import { validateBookingTimeIsNotOutOfBounds } from "./handleNewBooking/validate import { validateEventLength } from "./handleNewBooking/validateEventLength"; import handleSeats from "./handleSeats/handleSeats"; import type { IBookingService } from "./interfaces/IBookingService"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; const translator = short(); const log = logger.getSubLogger({ prefix: ["[api] book:user"] }); @@ -1222,9 +1222,11 @@ async function handler( }); const organizerOrganizationId = organizerOrganizationProfile?.organizationId; - const hideBranding = await shouldHideBrandingForEvent({ + + const brandingService = new BrandingApplicationService(prisma); + const hideBranding = await brandingService.computeHideBranding({ eventTypeId: eventType.id, - team: eventType.team ?? null, + teamContext: eventType.team ?? null, owner: organizerUser ?? null, organizationId: organizerOrganizationId ?? null, }); diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 5a1a9b072053d7..7ffc0d0c43b905 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -3,7 +3,7 @@ import { cloneDeep } from "lodash"; import { sendRescheduledSeatEmailAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; import type { Person, CalendarEvent } from "@calcom/types/Calendar"; @@ -46,19 +46,12 @@ const attendeeRescheduleSeatedBooking = async ( }) : null; - const hideBranding = await shouldHideBrandingForEvent({ + const brandingService = new BrandingApplicationService(prisma); + const hideBranding = await brandingService.computeHideBranding({ eventTypeId: eventType.id, - team: teamForBranding ?? null, + teamContext: teamForBranding ?? null, owner: organizerUser ?? null, - organizationId: (await (async () => { - if (teamForBranding?.parentId) return teamForBranding.parentId; - if (!organizerUser) return null; - const organizerProfile = await prisma.profile.findFirst({ - where: { userId: organizerUser.id }, - select: { organizationId: true }, - }); - return organizerProfile?.organizationId ?? null; - })()), + ownerIdFallback: organizerUser?.id ?? null, }); seatAttendee["language"] = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" }; diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index a44d4414ca0f63..fd8d4ae59fb189 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -6,7 +6,8 @@ import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; +import type { TeamBrandingContext } from "@calcom/lib/branding/types"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -152,18 +153,12 @@ const combineTwoSeatedBookings = async ( }) : null; - const hideBranding = await shouldHideBrandingForEvent({ + const brandingService = new BrandingApplicationService(prisma); + const hideBranding = await brandingService.computeHideBranding({ eventTypeId: eventType.id, - team: (teamForBranding as any) ?? null, + teamContext: (teamForBranding as TeamBrandingContext) ?? null, owner: organizerUser ?? null, - organizationId: (await (async () => { - if (teamForBranding?.parentId) return teamForBranding.parentId; - const organizerProfile = await prisma.profile.findFirst({ - where: { userId: organizerUser.id }, - select: { organizationId: true }, - }); - return organizerProfile?.organizationId ?? null; - })()), + ownerIdFallback: organizerUser?.id ?? null, }); // TODO send reschedule emails to attendees of the old booking loggerWithEventDetails.debug("Emails: Sending reschedule emails - handleSeats"); diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index 851c0235e26beb..ab0252af1b6dbd 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -12,7 +12,8 @@ import { findBookingQuery } from "../../../handleNewBooking/findBookingQuery"; import { handleAppsStatus } from "../../../handleNewBooking/handleAppsStatus"; import type { createLoggerWithEventDetails } from "../../../handleNewBooking/logger"; import type { SeatedBooking, RescheduleSeatedBookingObject } from "../../types"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; +import type { TeamBrandingContext } from "@calcom/lib/branding/types"; const moveSeatedBookingToNewTimeSlot = async ( rescheduleSeatedBookingObject: RescheduleSeatedBookingObject, @@ -47,18 +48,12 @@ const moveSeatedBookingToNewTimeSlot = async ( }) : null; - const hideBranding = await shouldHideBrandingForEvent({ + const brandingService = new BrandingApplicationService(prisma); + const hideBranding = await brandingService.computeHideBranding({ eventTypeId: eventType.id, - team: (teamForBranding as any) ?? null, + teamContext: (teamForBranding as TeamBrandingContext) ?? null, owner: organizerUser ?? null, - organizationId: (await (async () => { - if (teamForBranding?.parentId) return teamForBranding.parentId; - const organizerProfile = await prisma.profile.findFirst({ - where: { userId: organizerUser.id }, - select: { organizationId: true }, - }); - return organizerProfile?.organizationId ?? null; - })()), + ownerIdFallback: organizerUser?.id ?? null, }); let { evt } = rescheduleSeatedBookingObject; diff --git a/packages/features/bookings/lib/payment/handleNoShowFee.ts b/packages/features/bookings/lib/payment/handleNoShowFee.ts index c0faaf040311e5..6f9ecd9166e8e9 100644 --- a/packages/features/bookings/lib/payment/handleNoShowFee.ts +++ b/packages/features/bookings/lib/payment/handleNoShowFee.ts @@ -2,7 +2,7 @@ import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; -import { sendNoShowFeeChargedEmail } from "@calcom/emails"; +import { sendNoShowFeeChargedEmail, withHideBranding } from "@calcom/emails"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { ErrorWithCode } from "@calcom/lib/errors"; import logger from "@calcom/lib/logger"; @@ -157,7 +157,7 @@ export const handleNoShowFee = async ({ throw new Error("Payment processing failed"); } - await sendNoShowFeeChargedEmail(attendee, { ...evt, hideBranding: evt.hideBranding ?? false }, eventTypeMetdata); + await sendNoShowFeeChargedEmail(attendee, withHideBranding(evt), eventTypeMetdata); return paymentData; } catch (err) { diff --git a/packages/features/conferencing/lib/videoClient.ts b/packages/features/conferencing/lib/videoClient.ts index ae595f407fc8cb..9025ea2353ab44 100644 --- a/packages/features/conferencing/lib/videoClient.ts +++ b/packages/features/conferencing/lib/videoClient.ts @@ -4,7 +4,7 @@ import { v5 as uuidv5 } from "uuid"; import { DailyLocationType } from "@calcom/app-store/constants"; import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys"; import { getVideoAdapters } from "@calcom/app-store/getVideoAdapters"; -import { sendBrokenIntegrationEmail } from "@calcom/emails"; +import { sendBrokenIntegrationEmail, withHideBranding } from "@calcom/emails"; import { getUid } from "@calcom/lib/CalEventParser"; import logger from "@calcom/lib/logger"; import { getPiiFreeCalendarEvent, getPiiFreeCredential } from "@calcom/lib/piiFreeData"; @@ -80,7 +80,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv returnObject = { ...returnObject, createdEvent: createdMeeting, success: true }; log.debug("created Meeting", safeStringify(returnObject)); } catch (err) { - await sendBrokenIntegrationEmail({ ...calEvent, hideBranding: calEvent.hideBranding ?? false }, "video"); + await sendBrokenIntegrationEmail(withHideBranding(calEvent), "video"); log.error( "createMeeting failed", safeStringify(err), @@ -109,7 +109,7 @@ const updateMeeting = async ( const canCallUpdateMeeting = !!(credential && bookingRef); const updatedMeeting = canCallUpdateMeeting ? await firstVideoAdapter?.updateMeeting(bookingRef, calEvent).catch(async (e) => { - await sendBrokenIntegrationEmail({ ...calEvent, hideBranding: calEvent.hideBranding ?? false }, "video"); + await sendBrokenIntegrationEmail(withHideBranding(calEvent), "video"); log.error("updateMeeting failed", e, calEvent); success = false; return undefined; diff --git a/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts b/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts new file mode 100644 index 00000000000000..6a36628487f728 --- /dev/null +++ b/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts @@ -0,0 +1,144 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { roundRobinManualReassignment } from "../roundRobinManualReassignment"; +import type { PrismaClient } from "@calcom/prisma"; + +// Mock the BrandingApplicationService +vi.mock("@calcom/lib/branding/BrandingApplicationService", () => ({ + BrandingApplicationService: vi.fn().mockImplementation(() => ({ + computeHideBranding: vi.fn().mockResolvedValue(true), + })), +})); + +// Mock other dependencies +vi.mock("@calcom/emails", () => ({ + sendRoundRobinScheduledEmailsAndSMS: vi.fn(), + sendRoundRobinReassignedEmailsAndSMS: vi.fn(), + sendRoundRobinUpdatedEmailsAndSMS: vi.fn(), +})); + +describe("roundRobinManualReassignment Integration", () => { + let mockPrisma: PrismaClient; + + beforeEach(() => { + mockPrisma = { + booking: { + findUnique: vi.fn().mockResolvedValue({ + id: 1, + eventTypeId: 1, + userId: 1, + eventType: { + id: 1, + teamId: 1, + team: { id: 1, hideBranding: true, parentId: null, parent: null }, + }, + user: { id: 1, hideBranding: false }, + }), + update: vi.fn().mockResolvedValue({}), + }, + user: { + findUnique: vi.fn().mockResolvedValue({ + id: 2, + email: "newuser@example.com", + }), + }, + credential: { + findMany: vi.fn().mockResolvedValue([]), + }, + profile: { + findFirst: vi.fn().mockResolvedValue({ + organizationId: 123, + }), + }, + } as any; + }); + + it("should include hideBranding in round-robin email calls", async () => { + const { + sendRoundRobinScheduledEmailsAndSMS, + sendRoundRobinReassignedEmailsAndSMS, + sendRoundRobinUpdatedEmailsAndSMS + } = await import("@calcom/emails"); + + await roundRobinManualReassignment({ + bookingId: 1, + newUserId: 2, + prisma: mockPrisma, + }); + + // Verify that all email functions were called with hideBranding + expect(sendRoundRobinScheduledEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + calEvent: expect.objectContaining({ + hideBranding: true, + }), + }), + expect.any(Object) + ); + + expect(sendRoundRobinReassignedEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + calEvent: expect.objectContaining({ + hideBranding: true, + }), + }), + expect.any(Object) + ); + + expect(sendRoundRobinUpdatedEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + calEvent: expect.objectContaining({ + hideBranding: true, + }), + }), + expect.any(Object) + ); + }); + + it("should handle branding computation errors gracefully", async () => { + const { BrandingApplicationService } = await import("@calcom/lib/branding/BrandingApplicationService"); + const mockService = new BrandingApplicationService(mockPrisma); + + // Mock service to return false on error + vi.mocked(mockService.computeHideBranding).mockResolvedValue(false); + + const { + sendRoundRobinScheduledEmailsAndSMS, + sendRoundRobinReassignedEmailsAndSMS, + sendRoundRobinUpdatedEmailsAndSMS + } = await import("@calcom/emails"); + + await roundRobinManualReassignment({ + bookingId: 1, + newUserId: 2, + prisma: mockPrisma, + }); + + // Verify that all email functions were called with hideBranding: false + expect(sendRoundRobinScheduledEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + calEvent: expect.objectContaining({ + hideBranding: false, + }), + }), + expect.any(Object) + ); + + expect(sendRoundRobinReassignedEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + calEvent: expect.objectContaining({ + hideBranding: false, + }), + }), + expect.any(Object) + ); + + expect(sendRoundRobinUpdatedEmailsAndSMS).toHaveBeenCalledWith( + expect.objectContaining({ + calEvent: expect.objectContaining({ + hideBranding: false, + }), + }), + expect.any(Object) + ); + }); +}); diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 2e859eb8390613..565388e48da21f 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -8,6 +8,7 @@ import { sendRoundRobinReassignedEmailsAndSMS, sendRoundRobinScheduledEmailsAndSMS, sendRoundRobinUpdatedEmailsAndSMS, + withHideBranding, } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; @@ -36,7 +37,7 @@ import { prisma } from "@calcom/prisma"; import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { EventTypeMetadata, PlatformClientParams } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import { handleRescheduleEventManager } from "./handleRescheduleEventManager"; import type { BookingSelectResult } from "./utils/bookingSelect"; @@ -313,46 +314,13 @@ export const roundRobinManualReassignment = async ({ conferenceCredentialId: conferenceCredentialId ?? undefined, }; - try { - type TeamForBranding = { - id: number; - hideBranding: boolean | null; - parentId: number | null; - parent: { hideBranding: boolean | null } | null; - }; - - const teamForBranding: TeamForBranding | null = eventType.teamId - ? await prisma.team.findUnique({ - where: { id: eventType.teamId }, - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { select: { hideBranding: true } }, - }, - }) - : null; - const organizationIdForBranding = - teamForBranding?.parentId ?? - orgId ?? - ( - await prisma.profile.findFirst({ - where: { userId: organizer.id, organizationId: orgId ?? undefined }, - select: { organizationId: true }, - }) - )?.organizationId ?? - null; - const hideBranding = await shouldHideBrandingForEvent({ - eventTypeId: eventType.id, - team: teamForBranding ?? null, - owner: organizer, - organizationId: organizationIdForBranding, - }); - evt.hideBranding = hideBranding; - } catch (error) { - roundRobinReassignLogger.error("Failed to compute hideBranding", error); - evt.hideBranding = false; - } + const brandingService = new BrandingApplicationService(prisma); + const hideBranding = await brandingService.computeHideBranding({ + eventTypeId: eventType.id, + organizationId: orgId ?? null, + ownerIdFallback: organizer.id, + }); + evt.hideBranding = hideBranding; const credentials = await prisma.credential.findMany({ where: { userId: newUser.id }, @@ -425,7 +393,7 @@ export const roundRobinManualReassignment = async ({ // Send emails if (emailsEnabled) { await sendRoundRobinScheduledEmailsAndSMS({ - calEvent: { ...evtWithoutCancellationReason, hideBranding: evtWithoutCancellationReason.hideBranding ?? false }, + calEvent: withHideBranding(evtWithoutCancellationReason), members: [ { ...newUser, @@ -455,7 +423,7 @@ export const roundRobinManualReassignment = async ({ if (previousRRHost && emailsEnabled) { await sendRoundRobinReassignedEmailsAndSMS({ - calEvent: { ...cancelledEvt, hideBranding: cancelledEvt.hideBranding ?? false }, + calEvent: withHideBranding(cancelledEvt), members: [ { ...previousRRHost, @@ -474,7 +442,7 @@ export const roundRobinManualReassignment = async ({ if (emailsEnabled && dayjs(evt.startTime).isAfter(dayjs())) { // send email with event updates to attendees await sendRoundRobinUpdatedEmailsAndSMS({ - calEvent: { ...evtWithoutCancellationReason, hideBranding: evtWithoutCancellationReason.hideBranding ?? false }, + calEvent: withHideBranding(evtWithoutCancellationReason), eventTypeMetadata: eventType?.metadata as EventTypeMetadata, }); } diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index dd1b5d0086c9e6..812ce867ed83d8 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -12,6 +12,7 @@ import { sendRoundRobinReassignedEmailsAndSMS, sendRoundRobinScheduledEmailsAndSMS, sendRoundRobinUpdatedEmailsAndSMS, + withHideBranding, } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; @@ -35,7 +36,8 @@ import { prisma } from "@calcom/prisma"; import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils"; import type { EventTypeMetadata, PlatformClientParams } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; +import type { TeamBrandingContext } from "@calcom/lib/branding/types"; import { handleRescheduleEventManager } from "./handleRescheduleEventManager"; import { handleWorkflowsUpdate } from "./roundRobinManualReassignment"; @@ -362,14 +364,15 @@ export const roundRobinReassignment = async ({ select: { organizationId: true }, }) )?.organizationId ?? null; - const hideBranding = await shouldHideBrandingForEvent({ + const brandingService = new BrandingApplicationService(prisma); + const hideBranding = await brandingService.computeHideBranding({ eventTypeId: eventType.id, - team: (teamForBranding as any) ?? null, - owner: { id: organizer.id, hideBranding: null } as any, + teamContext: (teamForBranding as TeamBrandingContext) ?? null, + owner: { id: organizer.id, hideBranding: null }, organizationId: organizationIdForBranding, }); (evt as any).hideBranding = hideBranding; - } catch (_) { + } catch { (evt as any).hideBranding = false; } @@ -447,7 +450,7 @@ export const roundRobinReassignment = async ({ // Send to new RR host if (emailsEnabled) { await sendRoundRobinScheduledEmailsAndSMS({ - calEvent: { ...evtWithoutCancellationReason, hideBranding: evtWithoutCancellationReason.hideBranding ?? false }, + calEvent: withHideBranding(evtWithoutCancellationReason), members: [ { ...reassignedRRHost, @@ -493,7 +496,7 @@ export const roundRobinReassignment = async ({ if (emailsEnabled) { await sendRoundRobinReassignedEmailsAndSMS({ - calEvent: { ...cancelledRRHostEvt, hideBranding: cancelledRRHostEvt.hideBranding ?? false }, + calEvent: withHideBranding(cancelledRRHostEvt), members: [ { ...previousRRHost, @@ -514,7 +517,7 @@ export const roundRobinReassignment = async ({ if (emailsEnabled && dayjs(evt.startTime).isAfter(dayjs())) { // send email with event updates to attendees await sendRoundRobinUpdatedEmailsAndSMS({ - calEvent: { ...evtWithoutCancellationReason, hideBranding: evtWithoutCancellationReason.hideBranding ?? false }, + calEvent: withHideBranding(evtWithoutCancellationReason), eventTypeMetadata: eventType?.metadata as EventTypeMetadata, }); } diff --git a/packages/lib/branding/BrandingApplicationService.ts b/packages/lib/branding/BrandingApplicationService.ts new file mode 100644 index 00000000000000..b4dd5af0818610 --- /dev/null +++ b/packages/lib/branding/BrandingApplicationService.ts @@ -0,0 +1,60 @@ +import type { PrismaClient } from "@calcom/prisma"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import logger from "@calcom/lib/logger"; +import { BrandingRepository } from "@calcom/lib/server/repository/branding/BrandingRepository"; +import type { BrandingServiceParams } from "./types"; + +export class BrandingApplicationService { + private readonly repo: BrandingRepository; + + constructor(prisma: PrismaClient) { + this.repo = new BrandingRepository(prisma); + } + + async computeHideBranding(input: BrandingServiceParams): Promise { + try { + const { eventTypeId } = input; + + const teamContext = + typeof input.teamContext !== "undefined" + ? input.teamContext + : await this.repo.getEventTypeBrandingContext(eventTypeId); + + const owner = + typeof input.owner !== "undefined" + ? input.owner + : input.ownerIdFallback + ? await this.repo.getUserBranding(input.ownerIdFallback) + : null; + + const organizationId = + typeof input.organizationId !== "undefined" + ? input.organizationId + : input.ownerIdFallback + ? await this.repo.getUserOrganizationId(input.ownerIdFallback) + : null; + + return await shouldHideBrandingForEvent({ + eventTypeId, + team: teamContext ?? null, + owner: owner ?? null, + organizationId: organizationId ?? null, + }); + } catch (error) { + logger.error("BrandingApplicationService: Failed to compute hideBranding", { + error, + eventTypeId: input.eventTypeId, + teamContext: input.teamContext, + owner: input.owner, + organizationId: input.organizationId, + ownerIdFallback: input.ownerIdFallback, + }); + // Always return false on error to ensure critical flows never fail + return false; + } + } +} + +export default BrandingApplicationService; + + diff --git a/packages/lib/branding/README.md b/packages/lib/branding/README.md new file mode 100644 index 00000000000000..a9ca1cd9f21d22 --- /dev/null +++ b/packages/lib/branding/README.md @@ -0,0 +1,51 @@ +# Branding System + +## Overview + +Branding computation centralized; email boundary enforces required `hideBranding`; repository pattern enforced. + +## Architecture + +### Core Components + +- **`BrandingApplicationService`**: Centralized business logic for computing `hideBranding` values +- **`BrandingRepository`**: Database access layer following repository pattern +- **`withHideBranding`**: Email boundary helper ensuring `hideBranding: boolean` in email calls + +### Key Principles + +1. **Centralized Computation**: All branding logic flows through `BrandingApplicationService` +2. **Repository Pattern**: Database access isolated to `BrandingRepository` +3. **Email Boundary Contract**: Email functions always receive `hideBranding: boolean` +4. **Error Resilience**: Branding computation never fails critical flows +5. **Type Safety**: Strong typing throughout with shared interfaces + +### Error Handling + +- Service handles all errors internally +- Always returns `false` on error to ensure critical flows continue +- Comprehensive error logging with context +- No try-catch blocks needed in calling code + +### Usage + +```typescript +// Simple service call - no error handling needed +const brandingService = new BrandingApplicationService(prisma); +const hideBranding = await brandingService.computeHideBranding({ + eventTypeId: 123, + teamContext: team ?? null, + owner: user ?? null, + organizationId: orgId ?? null, +}); + +// Email boundary - ensures hideBranding is always boolean +await sendScheduledEmailsAndSMS(withHideBranding(calEvent, hideBranding)); +``` + +## Files + +- `BrandingApplicationService.ts` - Main business logic +- `BrandingRepository.ts` - Database access +- `types.ts` - Shared type definitions +- `email-manager.ts` - `withHideBranding` helper diff --git a/packages/lib/branding/__tests__/BrandingApplicationService.test.ts b/packages/lib/branding/__tests__/BrandingApplicationService.test.ts new file mode 100644 index 00000000000000..b9e7c3a94a8fb1 --- /dev/null +++ b/packages/lib/branding/__tests__/BrandingApplicationService.test.ts @@ -0,0 +1,282 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import type { PrismaClient } from "@calcom/prisma"; +import type { TeamBrandingContext, UserBrandingContext, BrandingServiceParams } from "../types"; + +// Mock the shouldHideBrandingForEvent function +vi.mock("@calcom/lib/hideBranding", () => ({ + shouldHideBrandingForEvent: vi.fn(), +})); + +// Mock the BrandingRepository +vi.mock("@calcom/lib/server/repository/branding/BrandingRepository", () => ({ + BrandingRepository: vi.fn(), +})); + +import { BrandingApplicationService } from "../BrandingApplicationService"; +import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingRepository } from "@calcom/lib/server/repository/branding/BrandingRepository"; + +describe("BrandingApplicationService", () => { + let service: BrandingApplicationService; + let mockPrisma: any; + let mockRepository: any; + + beforeEach(() => { + mockPrisma = {}; + mockRepository = { + getEventTypeBrandingContext: vi.fn(), + getUserBranding: vi.fn(), + getUserOrganizationId: vi.fn(), + }; + + // Mock the repository constructor + vi.mocked(BrandingRepository).mockImplementation(() => mockRepository as any); + + service = new BrandingApplicationService(mockPrisma as PrismaClient); + vi.clearAllMocks(); + }); + + describe("computeHideBranding", () => { + const mockTeamContext: TeamBrandingContext = { + id: 1, + hideBranding: true, + parentId: 2, + parent: { hideBranding: false }, + }; + + const mockUserContext: UserBrandingContext = { + id: 1, + hideBranding: true, + }; + + it("should use provided teamContext and not fetch from repository", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + teamContext: mockTeamContext, + owner: mockUserContext, + organizationId: 456, + }; + + (shouldHideBrandingForEvent as any).mockResolvedValue(true); + + const result = await service.computeHideBranding(input); + + expect(mockRepository.getEventTypeBrandingContext).not.toHaveBeenCalled(); + expect(mockRepository.getUserBranding).not.toHaveBeenCalled(); + expect(mockRepository.getUserOrganizationId).not.toHaveBeenCalled(); + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: mockTeamContext, + owner: mockUserContext, + organizationId: 456, + }); + expect(result).toBe(true); + }); + + it("should fetch teamContext from repository when not provided", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + owner: mockUserContext, + organizationId: 456, + }; + + mockRepository.getEventTypeBrandingContext.mockResolvedValue(mockTeamContext); + (shouldHideBrandingForEvent as any).mockResolvedValue(false); + + const result = await service.computeHideBranding(input); + + expect(mockRepository.getEventTypeBrandingContext).toHaveBeenCalledWith(123); + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: mockTeamContext, + owner: mockUserContext, + organizationId: 456, + }); + expect(result).toBe(false); + }); + + it("should fetch owner from repository when not provided but ownerIdFallback is given", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + teamContext: mockTeamContext, + ownerIdFallback: 789, + organizationId: 456, + }; + + mockRepository.getUserBranding.mockResolvedValue(mockUserContext); + (shouldHideBrandingForEvent as any).mockResolvedValue(true); + + const result = await service.computeHideBranding(input); + + expect(mockRepository.getUserBranding).toHaveBeenCalledWith(789); + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: mockTeamContext, + owner: mockUserContext, + organizationId: 456, + }); + expect(result).toBe(true); + }); + + it("should fetch organizationId from repository when not provided but ownerIdFallback is given", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + teamContext: mockTeamContext, + owner: mockUserContext, + ownerIdFallback: 789, + }; + + mockRepository.getUserOrganizationId.mockResolvedValue(999); + (shouldHideBrandingForEvent as any).mockResolvedValue(false); + + const result = await service.computeHideBranding(input); + + expect(mockRepository.getUserOrganizationId).toHaveBeenCalledWith(789); + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: mockTeamContext, + owner: mockUserContext, + organizationId: 999, + }); + expect(result).toBe(false); + }); + + it("should handle null values from repository", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + ownerIdFallback: 789, + }; + + mockRepository.getEventTypeBrandingContext.mockResolvedValue(null); + mockRepository.getUserBranding.mockResolvedValue(null); + mockRepository.getUserOrganizationId.mockResolvedValue(null); + (shouldHideBrandingForEvent as any).mockResolvedValue(false); + + const result = await service.computeHideBranding(input); + + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: null, + owner: null, + organizationId: null, + }); + expect(result).toBe(false); + }); + + it("should handle team-only scenario", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + teamContext: mockTeamContext, + }; + + (shouldHideBrandingForEvent as any).mockResolvedValue(true); + + const result = await service.computeHideBranding(input); + + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: mockTeamContext, + owner: null, + organizationId: null, + }); + expect(result).toBe(true); + }); + + it("should handle owner-only scenario", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + owner: mockUserContext, + }; + + (shouldHideBrandingForEvent as any).mockResolvedValue(false); + + const result = await service.computeHideBranding(input); + + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: null, + owner: mockUserContext, + organizationId: null, + }); + expect(result).toBe(false); + }); + + it("should handle parent-org scenario", async () => { + const parentTeamContext: TeamBrandingContext = { + id: 2, + hideBranding: false, + parentId: null, + parent: null, + }; + + const input: BrandingServiceParams = { + eventTypeId: 123, + teamContext: parentTeamContext, + organizationId: 456, + }; + + (shouldHideBrandingForEvent as any).mockResolvedValue(false); + + const result = await service.computeHideBranding(input); + + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: parentTeamContext, + owner: null, + organizationId: 456, + }); + expect(result).toBe(false); + }); + + it("should handle orgId hint scenario", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + teamContext: mockTeamContext, + owner: mockUserContext, + ownerIdFallback: 789, + }; + + mockRepository.getUserOrganizationId.mockResolvedValue(999); + (shouldHideBrandingForEvent as any).mockResolvedValue(true); + + const result = await service.computeHideBranding(input); + + expect(mockRepository.getUserOrganizationId).toHaveBeenCalledWith(789); + expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ + eventTypeId: 123, + team: mockTeamContext, + owner: mockUserContext, + organizationId: 999, + }); + expect(result).toBe(true); + }); + + it("should handle error paths gracefully", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + ownerIdFallback: 789, + }; + + mockRepository.getEventTypeBrandingContext.mockRejectedValue(new Error("Database error")); + mockRepository.getUserBranding.mockRejectedValue(new Error("User not found")); + mockRepository.getUserOrganizationId.mockRejectedValue(new Error("Profile not found")); + + const result = await service.computeHideBranding(input); + expect(result).toBe(false); + }); + + it("should handle shouldHideBrandingForEvent errors", async () => { + const input: BrandingServiceParams = { + eventTypeId: 123, + teamContext: mockTeamContext, + owner: mockUserContext, + organizationId: 456, + }; + + (shouldHideBrandingForEvent as any).mockRejectedValue(new Error("Branding computation failed")); + + const result = await service.computeHideBranding(input); + expect(result).toBe(false); + }); + }); +}); diff --git a/packages/lib/branding/types.ts b/packages/lib/branding/types.ts new file mode 100644 index 00000000000000..7b74cdd82e2f78 --- /dev/null +++ b/packages/lib/branding/types.ts @@ -0,0 +1,26 @@ +/** + * Shared type definitions for branding functionality + * Used across multiple booking-related services to avoid code duplication + */ + +export interface TeamBrandingContext { + id: number; + hideBranding: boolean | null; + parentId: number | null; + parent: { + hideBranding: boolean | null; + } | null; +} + +export interface UserBrandingContext { + id: number; + hideBranding: boolean | null; +} + +export interface BrandingServiceParams { + eventTypeId: number; + teamContext?: TeamBrandingContext | null; + owner?: UserBrandingContext | null; + organizationId?: number | null; + ownerIdFallback?: number | null; +} diff --git a/packages/lib/server/repository/branding/BrandingRepository.ts b/packages/lib/server/repository/branding/BrandingRepository.ts new file mode 100644 index 00000000000000..52cef22e7cceea --- /dev/null +++ b/packages/lib/server/repository/branding/BrandingRepository.ts @@ -0,0 +1,48 @@ +import type { PrismaClient } from "@calcom/prisma"; +import type { TeamBrandingContext, UserBrandingContext } from "@calcom/lib/branding/types"; + +export class BrandingRepository { + private prisma: PrismaClient; + + constructor(prisma: PrismaClient) { + this.prisma = prisma; + } + + async getEventTypeBrandingContext(eventTypeId: number): Promise { + const result = await this.prisma.eventType.findUnique({ + where: { id: eventTypeId }, + select: { + teamId: true, + team: { + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { select: { hideBranding: true } }, + }, + }, + }, + }); + + return result?.team ?? null; + } + + async getUserBranding(userId: number): Promise { + return await this.prisma.user.findUnique({ + where: { id: userId }, + select: { id: true, hideBranding: true }, + }); + } + + async getUserOrganizationId(userId: number, orgIdHint?: number | null): Promise { + const profile = await this.prisma.profile.findFirst({ + where: { userId, organizationId: orgIdHint ?? undefined }, + select: { organizationId: true }, + }); + return profile?.organizationId ?? null; + } +} + +export default BrandingRepository; + + diff --git a/packages/lib/server/repository/branding/__tests__/BrandingRepository.test.ts b/packages/lib/server/repository/branding/__tests__/BrandingRepository.test.ts new file mode 100644 index 00000000000000..a9536d85796fc5 --- /dev/null +++ b/packages/lib/server/repository/branding/__tests__/BrandingRepository.test.ts @@ -0,0 +1,187 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import type { PrismaClient } from "@calcom/prisma"; +import { BrandingRepository } from "../BrandingRepository"; +import type { TeamBrandingContext, UserBrandingContext } from "@calcom/lib/branding/types"; + +describe("BrandingRepository", () => { + let repository: BrandingRepository; + let mockPrisma: any; + + beforeEach(() => { + mockPrisma = { + eventType: { + findUnique: vi.fn(), + }, + user: { + findUnique: vi.fn(), + }, + profile: { + findFirst: vi.fn(), + }, + }; + repository = new BrandingRepository(mockPrisma as PrismaClient); + }); + + describe("getEventTypeBrandingContext", () => { + it("should return team context when event type has a team", async () => { + const mockTeamContext: TeamBrandingContext = { + id: 1, + hideBranding: true, + parentId: 2, + parent: { hideBranding: false }, + }; + + mockPrisma.eventType.findUnique.mockResolvedValue({ + teamId: 1, + team: mockTeamContext, + }); + + const result = await repository.getEventTypeBrandingContext(123); + + expect(result).toEqual(mockTeamContext); + expect(mockPrisma.eventType.findUnique).toHaveBeenCalledWith({ + where: { id: 123 }, + select: { + teamId: true, + team: { + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { select: { hideBranding: true } }, + }, + }, + }, + }); + }); + + it("should return null when event type has no team", async () => { + mockPrisma.eventType.findUnique.mockResolvedValue({ + teamId: null, + team: null, + }); + + const result = await repository.getEventTypeBrandingContext(123); + + expect(result).toBeNull(); + }); + + it("should return null when event type is not found", async () => { + mockPrisma.eventType.findUnique.mockResolvedValue(null); + + const result = await repository.getEventTypeBrandingContext(999); + + expect(result).toBeNull(); + }); + + it("should handle team with no parent", async () => { + const mockTeamContext: TeamBrandingContext = { + id: 1, + hideBranding: true, + parentId: null, + parent: null, + }; + + mockPrisma.eventType.findUnique.mockResolvedValue({ + teamId: 1, + team: mockTeamContext, + }); + + const result = await repository.getEventTypeBrandingContext(123); + + expect(result).toEqual(mockTeamContext); + }); + }); + + describe("getUserBranding", () => { + it("should return user branding context", async () => { + const mockUser: UserBrandingContext = { + id: 1, + hideBranding: true, + }; + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + + const result = await repository.getUserBranding(123); + + expect(result).toEqual(mockUser); + expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({ + where: { id: 123 }, + select: { id: true, hideBranding: true }, + }); + }); + + it("should return null when user is not found", async () => { + mockPrisma.user.findUnique.mockResolvedValue(null); + + const result = await repository.getUserBranding(999); + + expect(result).toBeNull(); + }); + + it("should handle user with null hideBranding", async () => { + const mockUser: UserBrandingContext = { + id: 1, + hideBranding: null, + }; + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + + const result = await repository.getUserBranding(123); + + expect(result).toEqual(mockUser); + }); + }); + + describe("getUserOrganizationId", () => { + it("should return organization ID when profile exists", async () => { + mockPrisma.profile.findFirst.mockResolvedValue({ + organizationId: 456, + }); + + const result = await repository.getUserOrganizationId(123); + + expect(result).toBe(456); + expect(mockPrisma.profile.findFirst).toHaveBeenCalledWith({ + where: { userId: 123, organizationId: undefined }, + select: { organizationId: true }, + }); + }); + + it("should return organization ID with orgIdHint", async () => { + mockPrisma.profile.findFirst.mockResolvedValue({ + organizationId: 789, + }); + + const result = await repository.getUserOrganizationId(123, 789); + + expect(result).toBe(789); + expect(mockPrisma.profile.findFirst).toHaveBeenCalledWith({ + where: { userId: 123, organizationId: 789 }, + select: { organizationId: true }, + }); + }); + + it("should return null when profile is not found", async () => { + mockPrisma.profile.findFirst.mockResolvedValue(null); + + const result = await repository.getUserOrganizationId(999); + + expect(result).toBeNull(); + }); + + it("should handle null orgIdHint", async () => { + mockPrisma.profile.findFirst.mockResolvedValue({ + organizationId: 456, + }); + + const result = await repository.getUserOrganizationId(123, null); + + expect(result).toBe(456); + expect(mockPrisma.profile.findFirst).toHaveBeenCalledWith({ + where: { userId: 123, organizationId: undefined }, + select: { organizationId: true }, + }); + }); + }); +}); diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index f8f52614cc6699..08d089f29de43f 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -1,7 +1,8 @@ import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential"; import dayjs from "@calcom/dayjs"; import { sendAddGuestsEmails } from "@calcom/emails"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; +import type { TeamBrandingContext } from "@calcom/lib/branding/types"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; @@ -209,14 +210,16 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => { }) : null; const eventTypeId = booking.eventTypeId; - const hideBranding = eventTypeId - ? await shouldHideBrandingForEvent({ - eventTypeId, - team: (teamForBranding as any) ?? null, - owner: userForBranding, - organizationId: organizationIdForBranding, - }) - : false; + let hideBranding = false; + if (eventTypeId) { + const brandingService = new BrandingApplicationService(prisma); + hideBranding = await brandingService.computeHideBranding({ + eventTypeId, + teamContext: (teamForBranding as TeamBrandingContext) ?? null, + owner: userForBranding, + organizationId: organizationIdForBranding, + }); + } await sendAddGuestsEmails({ ...evt, hideBranding }, guests); } catch (err) { diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 7b0dbb2cffa879..8543563dbf3524 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -13,7 +13,7 @@ import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/ import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -357,52 +357,13 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { const eventTypeIdForBranding = booking.eventTypeId ?? null; let hideBranding = false; - let userForBranding: { id: number; hideBranding: boolean | null } | null = null; - let fullEventType: { - team: { - id: number | null; - hideBranding: boolean | null; - parentId: number | null; - parent: { hideBranding: boolean | null } | null; - } | null; - teamId: number | null; - } | null = null; if (eventTypeIdForBranding) { - fullEventType = await prisma.eventType.findUnique({ - where: { id: eventTypeIdForBranding }, - select: { - team: { - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { - select: { - hideBranding: true, - }, - }, - }, - }, - teamId: true, - }, - }); - - if (!fullEventType?.teamId && booking.userId) { - userForBranding = await prisma.user.findUnique({ - where: { id: booking.userId }, - select: { - id: true, - hideBranding: true, - }, - }); - } - - hideBranding = await shouldHideBrandingForEvent({ + const brandingService = new BrandingApplicationService(prisma); + hideBranding = await brandingService.computeHideBranding({ eventTypeId: eventTypeIdForBranding, - team: fullEventType?.team ?? null, - owner: userForBranding, organizationId: orgId ?? null, + ownerIdFallback: booking.userId ?? null, }); } diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index 55198af97f0de1..3a319fe7dd7025 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -5,7 +5,8 @@ import { getEventLocationType, OrganizerDefaultConferencingAppType } from "@calc import { getAppFromSlug } from "@calcom/app-store/utils"; import { sendLocationChangeEmailsAndSMS } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; +import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; +import type { TeamBrandingContext } from "@calcom/lib/branding/types"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { buildCalEventFromBooking } from "@calcom/lib/buildCalEventFromBooking"; import logger from "@calcom/lib/logger"; @@ -318,14 +319,16 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { : null; const eventTypeId = booking.eventTypeId; - const hideBranding = eventTypeId - ? await shouldHideBrandingForEvent({ - eventTypeId, - team: (teamForBranding as any) ?? null, - owner: userForBranding, - organizationId: organizationIdForBranding, - }) - : false; + let hideBranding = false; + if (eventTypeId) { + const brandingService = new BrandingApplicationService(prisma); + hideBranding = await brandingService.computeHideBranding({ + eventTypeId, + teamContext: (teamForBranding as TeamBrandingContext) ?? null, + owner: userForBranding, + organizationId: organizationIdForBranding, + }); + } await sendLocationChangeEmailsAndSMS( { ...evt, additionalInformation, hideBranding }, From fb9d9a4c5e778f13e83643a16d26f4164a1c9697 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 17 Oct 2025 04:47:44 +0530 Subject: [PATCH 14/58] fix repository pattern violation --- .../web/app/api/cron/bookingReminder/route.ts | 25 +- .../handleCancelBooking.integration.test.ts | 24 +- .../handleConfirmation.integration.test.ts | 24 +- .../bookings/lib/handleBookingRequested.ts | 27 +- .../bookings/lib/handleCancelBooking.ts | 56 ++-- .../bookings/lib/handleConfirmation.ts | 29 +- .../features/bookings/lib/handleNewBooking.ts | 12 +- .../handleNewBooking/getEventTypesFromDB.ts | 10 +- .../attendeeRescheduleSeatedBooking.ts | 45 +-- .../owner/combineTwoSeatedBookings.ts | 41 +-- .../owner/moveSeatedBookingToNewTimeSlot.ts | 41 +-- .../bookings/lib/payment/getBooking.ts | 10 +- .../credentials/handleDeleteCredential.ts | 6 +- ...obinManualReassignment.integration.test.ts | 32 +- .../roundRobinManualReassignment.ts | 13 +- .../ee/round-robin/roundRobinReassignment.ts | 12 +- .../branding/BrandingApplicationService.ts | 60 ---- packages/lib/branding/README.md | 51 ---- .../BrandingApplicationService.test.ts | 282 ------------------ packages/lib/branding/types.ts | 26 -- .../repository/branding/BrandingRepository.ts | 48 --- .../__tests__/BrandingRepository.test.ts | 187 ------------ .../viewer/bookings/addGuests.handler.ts | 51 +++- .../viewer/bookings/confirm.handler.ts | 32 +- .../viewer/bookings/editLocation.handler.ts | 57 +--- .../server/routers/viewer/bookings/util.ts | 28 +- 26 files changed, 294 insertions(+), 935 deletions(-) delete mode 100644 packages/lib/branding/BrandingApplicationService.ts delete mode 100644 packages/lib/branding/README.md delete mode 100644 packages/lib/branding/__tests__/BrandingApplicationService.test.ts delete mode 100644 packages/lib/branding/types.ts delete mode 100644 packages/lib/server/repository/branding/BrandingRepository.ts delete mode 100644 packages/lib/server/repository/branding/__tests__/BrandingRepository.test.ts diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 80be6e03487813..583e54d5a78ecf 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -5,16 +5,15 @@ import { NextResponse } from "next/server"; import dayjs from "@calcom/dayjs"; import { sendOrganizerRequestReminderEmail, withHideBranding } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import { BookingStatus, ReminderType } from "@calcom/prisma/enums"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; - async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); @@ -72,6 +71,7 @@ async function postHandler(request: NextRequest) { team: { select: { id: true, + parentId: true, hideBranding: true, parent: { select: { @@ -131,25 +131,13 @@ async function postHandler(request: NextRequest) { const attendeesList = await Promise.all(attendeesListPromises); const selectedDestinationCalendar = booking.destinationCalendar || user.destinationCalendar; - - const userOrganizationId = !booking.eventType?.team && booking.user - ? ( - await prisma.profile.findFirst({ - where: { - userId: booking.user.id, - }, - select: { - organizationId: true, - }, - }) - )?.organizationId - : null; + // Use pre-fetched branding data from booking query const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, - organizationId: userOrganizationId ?? null, + organizationId: booking.eventType?.team?.parentId ?? null, }); const evt: CalendarEvent = { @@ -178,7 +166,10 @@ async function postHandler(request: NextRequest) { hideBranding, }; - await sendOrganizerRequestReminderEmail(withHideBranding(evt), booking?.eventType?.metadata as EventTypeMetadata); + await sendOrganizerRequestReminderEmail( + withHideBranding(evt), + booking?.eventType?.metadata as EventTypeMetadata + ); await prisma.reminderMail.create({ data: { diff --git a/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts b/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts index 401b96cb19d6b6..395090be427dae 100644 --- a/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts +++ b/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts @@ -1,12 +1,13 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { handleCancelBooking } from "../lib/handleCancelBooking"; + +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import type { PrismaClient } from "@calcom/prisma"; -// Mock the BrandingApplicationService -vi.mock("@calcom/lib/branding/BrandingApplicationService", () => ({ - BrandingApplicationService: vi.fn().mockImplementation(() => ({ - computeHideBranding: vi.fn().mockResolvedValue(true), - })), +import { handleCancelBooking } from "../lib/handleCancelBooking"; + +// Mock the shouldHideBrandingForEvent function +vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ + shouldHideBrandingForEvent: vi.fn().mockResolvedValue(true), })); // Mock other dependencies @@ -45,7 +46,7 @@ describe("handleCancelBooking Integration", () => { it("should include hideBranding in cancellation email calls", async () => { const { sendCancelledEmailsAndSMS } = await import("@calcom/emails"); - + await handleCancelBooking({ bookingId: 1, reason: "Test cancellation", @@ -63,14 +64,11 @@ describe("handleCancelBooking Integration", () => { }); it("should handle branding computation errors gracefully", async () => { - const { BrandingApplicationService } = await import("@calcom/lib/branding/BrandingApplicationService"); - const mockService = new BrandingApplicationService(mockPrisma); - - // Mock service to return false on error - vi.mocked(mockService.computeHideBranding).mockResolvedValue(false); + // Mock shouldHideBrandingForEvent to return false on error + vi.mocked(shouldHideBrandingForEvent).mockResolvedValue(false); const { sendCancelledEmailsAndSMS } = await import("@calcom/emails"); - + await handleCancelBooking({ bookingId: 1, reason: "Test cancellation", diff --git a/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts b/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts index 05506c3eb5c3bc..05835c87d4e183 100644 --- a/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts +++ b/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts @@ -1,12 +1,13 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { handleConfirmation } from "../lib/handleConfirmation"; + +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import type { PrismaClient } from "@calcom/prisma"; -// Mock the BrandingApplicationService -vi.mock("@calcom/lib/branding/BrandingApplicationService", () => ({ - BrandingApplicationService: vi.fn().mockImplementation(() => ({ - computeHideBranding: vi.fn().mockResolvedValue(true), - })), +import { handleConfirmation } from "../lib/handleConfirmation"; + +// Mock the shouldHideBrandingForEvent function +vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ + shouldHideBrandingForEvent: vi.fn().mockResolvedValue(true), })); // Mock other dependencies @@ -46,7 +47,7 @@ describe("handleConfirmation Integration", () => { it("should include hideBranding in email calls", async () => { const { sendScheduledEmailsAndSMS } = await import("@calcom/emails"); - + const mockBooking = { id: 1, userId: 1, @@ -88,14 +89,11 @@ describe("handleConfirmation Integration", () => { }); it("should handle branding computation errors gracefully", async () => { - const { BrandingApplicationService } = await import("@calcom/lib/branding/BrandingApplicationService"); - const mockService = new BrandingApplicationService(mockPrisma); - - // Mock service to return false on error - vi.mocked(mockService.computeHideBranding).mockResolvedValue(false); + // Mock shouldHideBrandingForEvent to return false on error + vi.mocked(shouldHideBrandingForEvent).mockResolvedValue(false); const { sendScheduledEmailsAndSMS } = await import("@calcom/emails"); - + const mockBooking = { id: 1, userId: 1, diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index b35a538406155c..873f56caa2941e 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -1,10 +1,10 @@ import { sendAttendeeRequestEmailAndSMS, sendOrganizerRequestEmail } from "@calcom/emails"; import { getWebhookPayloadForBooking } from "@calcom/features/bookings/lib/getWebhookPayloadForBooking"; import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { WorkflowService } from "@calcom/lib/server/service/workflows"; @@ -33,6 +33,10 @@ export async function handleBookingRequested(args: { } | null; team?: { parentId: number | null; + hideBranding: boolean | null; + parent: { + hideBranding: boolean | null; + } | null; } | null; currency: string; hosts?: { @@ -54,6 +58,10 @@ export async function handleBookingRequested(args: { } | null; eventTypeId: number | null; userId: number | null; + user?: { + id: number; + hideBranding: boolean | null; + } | null; id: number; }; }) { @@ -63,12 +71,19 @@ export async function handleBookingRequested(args: { const eventTypeId = booking.eventType?.id ?? booking.eventTypeId ?? null; let hideBranding = false; - - if (eventTypeId) { - const brandingService = new BrandingApplicationService(prisma); - hideBranding = await brandingService.computeHideBranding({ + + if (!eventTypeId) { + log.warn("Booking missing eventTypeId, defaulting hideBranding to false", { + bookingId: booking.id, + userId: booking.userId, + }); + hideBranding = false; + } else { + hideBranding = await shouldHideBrandingForEvent({ eventTypeId, - ownerIdFallback: booking.userId ?? null, + team: booking.eventType?.team ?? null, + owner: booking.user ?? null, + organizationId: booking.eventType?.team?.parentId ?? null, }); } diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 5c0bc7b7884519..6c1fe3f078867d 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -9,7 +9,10 @@ import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation"; import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund"; +import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import { sendCancelledReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; +import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { @@ -18,17 +21,14 @@ import { } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; -import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import { HttpError } from "@calcom/lib/http-error"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { getTranslation } from "@calcom/lib/server/i18n"; -import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import prisma from "@calcom/prisma"; import type { Prisma, WorkflowReminder } from "@calcom/prisma/client"; @@ -119,7 +119,12 @@ async function handler(input: CancelBookingInput) { const isCancellationUserHost = bookingToDelete.userId == userId || bookingToDelete.user.email === cancelledBy; - if (!platformClientId && !cancellationReason?.trim() && isCancellationUserHost && !skipCancellationReasonValidation) { + if ( + !platformClientId && + !cancellationReason?.trim() && + isCancellationUserHost && + !skipCancellationReasonValidation + ) { throw new HttpError({ statusCode: 400, message: "Cancellation reason is required when you are the host", @@ -241,42 +246,27 @@ async function handler(input: CancelBookingInput) { bookingToDelete.eventType?.team?.parentId ?? ownerProfile?.organizationId ?? null ); - const userOrganizationId = !bookingToDelete.eventType?.team - ? ( - await prisma.profile.findFirst({ - where: { - userId: bookingToDelete.userId, - }, - select: { - organizationId: true, - }, - }) - )?.organizationId - : null; - + // Use existing data from bookingToDelete - no additional queries needed! let hideBranding = false; - - if (bookingToDelete.eventTypeId) { - const brandingService = new BrandingApplicationService(prisma); - hideBranding = await brandingService.computeHideBranding({ + + if (!bookingToDelete.eventTypeId) { + log.warn("Booking missing eventTypeId, defaulting hideBranding to false", { + bookingId: bookingToDelete.id, + userId: bookingToDelete.userId, + }); + hideBranding = false; + } else { + hideBranding = await shouldHideBrandingForEvent({ eventTypeId: bookingToDelete.eventTypeId, - teamContext: bookingToDelete.eventType?.team ?? null, + team: bookingToDelete.eventType?.team ?? null, owner: bookingToDelete.user ?? null, - organizationId: bookingToDelete.eventType?.team?.parentId ?? userOrganizationId ?? null, - ownerIdFallback: bookingToDelete.userId, + organizationId: bookingToDelete.eventType?.team?.parentId ?? null, }); } - log.debug("Branding configuration", { + log.debug("Computed hideBranding", { hideBranding, eventTypeId: bookingToDelete.eventTypeId, - teamHideBranding: bookingToDelete.eventType?.team?.hideBranding, - teamParentHideBranding: bookingToDelete.eventType?.team?.parent?.hideBranding, - ownerHideBranding: bookingToDelete.user?.hideBranding, - teamParentId: bookingToDelete.eventType?.team?.parentId, - userOrganizationId, - finalOrganizationId: bookingToDelete.eventType?.team?.parentId ?? userOrganizationId, - isTeamEvent: !!bookingToDelete.eventType?.team, }); const evt: CalendarEvent = { @@ -331,7 +321,7 @@ async function handler(input: CancelBookingInput) { iCalUID: bookingToDelete.iCalUID, iCalSequence: bookingToDelete.iCalSequence + 1, platformClientId, - hideBranding: hideBranding ?? false, + hideBranding, platformRescheduleUrl, platformCancelUrl, hideOrganizerEmail: bookingToDelete.eventType?.hideOrganizerEmail, diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 740a1e01e51cae..09ed4477138bba 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -9,12 +9,12 @@ import { allowDisablingHostConfirmationEmails, } from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails"; import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; @@ -55,12 +55,20 @@ export async function handleConfirmation(args: { title: string; team?: { parentId: number | null; + hideBranding: boolean | null; + parent: { + hideBranding: boolean | null; + } | null; } | null; teamId?: number | null; parentId?: number | null; parent?: { teamId: number | null; } | null; + owner?: { + id: number; + hideBranding: boolean | null; + } | null; workflows?: { workflow: Workflow; }[]; @@ -70,6 +78,10 @@ export async function handleConfirmation(args: { smsReminderNumber: string | null; userId: number | null; location: string | null; + user?: { + id: number; + hideBranding: boolean | null; + } | null; }; paid?: boolean; emailsEnabled?: boolean; @@ -108,14 +120,21 @@ export async function handleConfirmation(args: { const eventTypeId = eventType?.id ?? booking.eventTypeId ?? null; + // Use existing data from booking - no additional queries needed! let hideBranding = false; - if (eventTypeId) { - const brandingService = new BrandingApplicationService(prisma); - hideBranding = await brandingService.computeHideBranding({ + if (!eventTypeId) { + log.warn("Booking missing eventTypeId, defaulting hideBranding to false", { + bookingId: booking.id, + userId: booking.userId, + }); + hideBranding = false; + } else { + hideBranding = await shouldHideBrandingForEvent({ eventTypeId, + team: booking.eventType?.team ?? null, + owner: booking.user ?? null, organizationId: orgId ?? null, - ownerIdFallback: booking.userId ?? null, }); } diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 427611abe002c4..1ad89f72b439eb 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1,6 +1,6 @@ import short, { uuid } from "short-uuid"; import { v5 as uuidv5 } from "uuid"; -import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; + import processExternalId from "@calcom/app-store/_utils/calendars/processExternalId"; import { getPaymentAppData } from "@calcom/app-store/_utils/payments/getPaymentAppData"; import { @@ -43,6 +43,8 @@ import { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents"; import { getEventName, updateHostInEventName } from "@calcom/features/eventtypes/lib/eventNaming"; import type { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { getFullName } from "@calcom/features/form-builder/utils"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { handleAnalyticsEvents } from "@calcom/features/tasker/tasks/analytics/handleAnalyticsEvents"; import type { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { UsersRepository } from "@calcom/features/users/users.repository"; @@ -126,7 +128,6 @@ import { validateBookingTimeIsNotOutOfBounds } from "./handleNewBooking/validate import { validateEventLength } from "./handleNewBooking/validateEventLength"; import handleSeats from "./handleSeats/handleSeats"; import type { IBookingService } from "./interfaces/IBookingService"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; const translator = short(); const log = logger.getSubLogger({ prefix: ["[api] book:user"] }); @@ -1301,11 +1302,10 @@ async function handler( }); const organizerOrganizationId = organizerOrganizationProfile?.organizationId; - - const brandingService = new BrandingApplicationService(prisma); - const hideBranding = await brandingService.computeHideBranding({ + + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, - teamContext: eventType.team ?? null, + team: eventType.team ?? null, owner: organizerUser ?? null, organizationId: organizerOrganizationId ?? null, }); diff --git a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts index 8c67dd0d1cdb3b..a6820b681d0e16 100644 --- a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts +++ b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts @@ -2,9 +2,9 @@ import type { LocationObject } from "@calcom/app-store/locations"; import { workflowSelect } from "@calcom/ee/workflows/lib/getAllWorkflows"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; import type { DefaultEvent } from "@calcom/features/eventtypes/lib/defaultEvents"; +import { withSelectedCalendars } from "@calcom/features/users/repositories/UserRepository"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; -import { withSelectedCalendars } from "@calcom/features/users/repositories/UserRepository"; import { prisma } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; @@ -43,16 +43,16 @@ const getEventTypesFromDBSelect = { id: true, name: true, parentId: true, - bookingLimits: true, - includeManagedEventsInLimits: true, - rrResetInterval: true, - rrTimestampBasis: true, hideBranding: true, parent: { select: { hideBranding: true, }, }, + bookingLimits: true, + includeManagedEventsInLimits: true, + rrResetInterval: true, + rrTimestampBasis: true, }, }, bookingFields: true, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 7ffc0d0c43b905..76969c5fce488e 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -3,7 +3,7 @@ import { cloneDeep } from "lodash"; import { sendRescheduledSeatEmailAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; import type { Person, CalendarEvent } from "@calcom/types/Calendar"; @@ -23,35 +23,12 @@ const attendeeRescheduleSeatedBooking = async ( let { originalRescheduledBooking } = rescheduleSeatedBookingObject; const { organizerUser } = rescheduleSeatedBookingObject; - type TeamForBranding = { - id: number; - hideBranding: boolean | null; - parentId: number | null; - parent: { hideBranding: boolean | null } | null; - }; - - const teamForBranding: TeamForBranding | null = eventType.team?.id - ? await prisma.team.findUnique({ - where: { id: eventType.team.id }, - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { - select: { - hideBranding: true, - }, - }, - }, - }) - : null; - - const brandingService = new BrandingApplicationService(prisma); - const hideBranding = await brandingService.computeHideBranding({ + // Use pre-fetched branding data from eventType + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, - teamContext: teamForBranding ?? null, + team: eventType.team ?? null, owner: organizerUser ?? null, - ownerIdFallback: organizerUser?.id ?? null, + organizationId: eventType.team?.parentId ?? null, }); seatAttendee["language"] = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" }; @@ -84,7 +61,11 @@ const attendeeRescheduleSeatedBooking = async ( // We don't want to trigger rescheduling logic of the original booking originalRescheduledBooking = null; - await sendRescheduledSeatEmailAndSMS({ ...evt, hideBranding }, seatAttendee as Person, eventType.metadata); + await sendRescheduledSeatEmailAndSMS( + { ...evt, hideBranding }, + seatAttendee as Person, + eventType.metadata + ); return null; } @@ -126,7 +107,11 @@ const attendeeRescheduleSeatedBooking = async ( await eventManager.updateCalendarAttendees(copyEvent, newTimeSlotBooking); - await sendRescheduledSeatEmailAndSMS({ ...copyEvent, hideBranding }, seatAttendee as Person, eventType.metadata); + await sendRescheduledSeatEmailAndSMS( + { ...copyEvent, hideBranding }, + seatAttendee as Person, + eventType.metadata + ); const filteredAttendees = originalRescheduledBooking?.attendees.filter((attendee) => { return attendee.email !== bookerEmail; }); diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index fd8d4ae59fb189..67e691799f4881 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -4,10 +4,9 @@ import { uuid } from "short-uuid"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; -import type { TeamBrandingContext } from "@calcom/lib/branding/types"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -137,40 +136,22 @@ const combineTwoSeatedBookings = async ( : calendarResult?.updatedEvent?.iCalUID || undefined; if (noEmail !== true && isConfirmedByDefault) { - const teamForBranding = eventType.team?.id - ? await prisma.team.findUnique({ - where: { id: eventType.team.id }, - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { - select: { - hideBranding: true, - }, - }, - }, - }) - : null; - - const brandingService = new BrandingApplicationService(prisma); - const hideBranding = await brandingService.computeHideBranding({ + // Use pre-fetched branding data from eventType + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, - teamContext: (teamForBranding as TeamBrandingContext) ?? null, + team: eventType.team ?? null, owner: organizerUser ?? null, - ownerIdFallback: organizerUser?.id ?? null, + organizationId: eventType.team?.parentId ?? null, }); // TODO send reschedule emails to attendees of the old booking loggerWithEventDetails.debug("Emails: Sending reschedule emails - handleSeats"); await sendRescheduledEmailsAndSMS( - ( - { - ...copyEvent, - additionalNotes, - cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, - hideBranding, - } as any - ), + { + ...copyEvent, + additionalNotes, + cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, + hideBranding, + } as any, eventType.metadata ); } diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index ab0252af1b6dbd..be1ea1b86c2ab8 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -3,6 +3,7 @@ import { cloneDeep } from "lodash"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import prisma from "@calcom/prisma"; import type { AdditionalInformation, AppsStatus } from "@calcom/types/Calendar"; @@ -12,8 +13,6 @@ import { findBookingQuery } from "../../../handleNewBooking/findBookingQuery"; import { handleAppsStatus } from "../../../handleNewBooking/handleAppsStatus"; import type { createLoggerWithEventDetails } from "../../../handleNewBooking/logger"; import type { SeatedBooking, RescheduleSeatedBookingObject } from "../../types"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; -import type { TeamBrandingContext } from "@calcom/lib/branding/types"; const moveSeatedBookingToNewTimeSlot = async ( rescheduleSeatedBookingObject: RescheduleSeatedBookingObject, @@ -32,28 +31,12 @@ const moveSeatedBookingToNewTimeSlot = async ( additionalNotes, } = rescheduleSeatedBookingObject; - const teamForBranding = eventType.team?.id - ? await prisma.team.findUnique({ - where: { id: eventType.team.id }, - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { - select: { - hideBranding: true, - }, - }, - }, - }) - : null; - - const brandingService = new BrandingApplicationService(prisma); - const hideBranding = await brandingService.computeHideBranding({ + // Use pre-fetched branding data from eventType + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, - teamContext: (teamForBranding as TeamBrandingContext) ?? null, + team: eventType.team ?? null, owner: organizerUser ?? null, - ownerIdFallback: organizerUser?.id ?? null, + organizationId: eventType.team?.parentId ?? null, }); let { evt } = rescheduleSeatedBookingObject; @@ -118,14 +101,12 @@ const moveSeatedBookingToNewTimeSlot = async ( const copyEvent = cloneDeep(evt); loggerWithEventDetails.debug("Emails: Sending reschedule emails - handleSeats"); await sendRescheduledEmailsAndSMS( - ( - { - ...copyEvent, - additionalNotes, - cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, - hideBranding, - } as any - ), + { + ...copyEvent, + additionalNotes, + cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, + hideBranding, + } as any, eventType.metadata ); } diff --git a/packages/features/bookings/lib/payment/getBooking.ts b/packages/features/bookings/lib/payment/getBooking.ts index 004f57d484ee6c..3182f91f4d1df5 100644 --- a/packages/features/bookings/lib/payment/getBooking.ts +++ b/packages/features/bookings/lib/payment/getBooking.ts @@ -1,6 +1,6 @@ +import { enrichUserWithDelegationCredentials } from "@calcom/app-store/delegationCredential"; import { workflowSelect } from "@calcom/ee/workflows/lib/getAllWorkflows"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; -import { enrichUserWithDelegationCredentials } from "@calcom/app-store/delegationCredential"; import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; @@ -37,6 +37,7 @@ export async function getBooking(bookingId: number) { select: { owner: { select: { + id: true, hideBranding: true, }, }, @@ -85,6 +86,12 @@ export async function getBooking(bookingId: number) { id: true, name: true, parentId: true, + hideBranding: true, + parent: { + select: { + hideBranding: true, + }, + }, }, }, }, @@ -110,6 +117,7 @@ export async function getBooking(bookingId: number) { locale: true, destinationCalendar: true, isPlatformManaged: true, + hideBranding: true, }, }, }, diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index a3e67f67c59e7c..73001480c55223 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -11,12 +11,12 @@ import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-util import { sendCancelledEmailsAndSMS } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { deletePayment } from "@calcom/features/bookings/lib/payment/deletePayment"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/scheduleTrigger"; import { buildNonDelegationCredential } from "@calcom/lib/delegationCredential"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getTranslation } from "@calcom/lib/server/i18n"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; import { bookingMinimalSelect, prisma } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { AppCategories, BookingStatus } from "@calcom/prisma/enums"; @@ -326,14 +326,14 @@ const handleDeleteCredential = async ({ const attendeesList = await Promise.all(attendeesListPromises); const tOrganizer = await getTranslation(booking?.user?.locale ?? "en", "common"); - + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, organizationId: null, }); - + await sendCancelledEmailsAndSMS( { type: booking?.eventType?.title as string, diff --git a/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts b/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts index 6a36628487f728..dec6470f1c33d8 100644 --- a/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts +++ b/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts @@ -1,12 +1,13 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { roundRobinManualReassignment } from "../roundRobinManualReassignment"; + +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import type { PrismaClient } from "@calcom/prisma"; -// Mock the BrandingApplicationService -vi.mock("@calcom/lib/branding/BrandingApplicationService", () => ({ - BrandingApplicationService: vi.fn().mockImplementation(() => ({ - computeHideBranding: vi.fn().mockResolvedValue(true), - })), +import { roundRobinManualReassignment } from "../roundRobinManualReassignment"; + +// Mock the shouldHideBrandingForEvent function +vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ + shouldHideBrandingForEvent: vi.fn().mockResolvedValue(true), })); // Mock other dependencies @@ -53,12 +54,12 @@ describe("roundRobinManualReassignment Integration", () => { }); it("should include hideBranding in round-robin email calls", async () => { - const { + const { sendRoundRobinScheduledEmailsAndSMS, sendRoundRobinReassignedEmailsAndSMS, - sendRoundRobinUpdatedEmailsAndSMS + sendRoundRobinUpdatedEmailsAndSMS, } = await import("@calcom/emails"); - + await roundRobinManualReassignment({ bookingId: 1, newUserId: 2, @@ -95,18 +96,15 @@ describe("roundRobinManualReassignment Integration", () => { }); it("should handle branding computation errors gracefully", async () => { - const { BrandingApplicationService } = await import("@calcom/lib/branding/BrandingApplicationService"); - const mockService = new BrandingApplicationService(mockPrisma); - - // Mock service to return false on error - vi.mocked(mockService.computeHideBranding).mockResolvedValue(false); + // Mock shouldHideBrandingForEvent to return false on error + vi.mocked(shouldHideBrandingForEvent).mockResolvedValue(false); - const { + const { sendRoundRobinScheduledEmailsAndSMS, sendRoundRobinReassignedEmailsAndSMS, - sendRoundRobinUpdatedEmailsAndSMS + sendRoundRobinUpdatedEmailsAndSMS, } = await import("@calcom/emails"); - + await roundRobinManualReassignment({ bookingId: 1, newUserId: 2, diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 2b2fb78e520708..0077ee69da96f4 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -1,4 +1,3 @@ - import { cloneDeep } from "lodash"; import { enrichUserWithDelegationCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential"; @@ -15,6 +14,7 @@ import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/book import getBookingResponsesSchema from "@calcom/features/bookings/lib/getBookingResponsesSchema"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB"; +import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import AssignmentReasonRecorder, { RRReassignmentType, } from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder"; @@ -25,9 +25,9 @@ import { } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { SENDER_NAME } from "@calcom/lib/constants"; -import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import logger from "@calcom/lib/logger"; @@ -37,7 +37,6 @@ import { prisma } from "@calcom/prisma"; import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { EventTypeMetadata, PlatformClientParams } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import { handleRescheduleEventManager } from "./handleRescheduleEventManager"; import type { BookingSelectResult } from "./utils/bookingSelect"; @@ -314,15 +313,15 @@ export const roundRobinManualReassignment = async ({ conferenceCredentialId: conferenceCredentialId ?? undefined, }; - const brandingService = new BrandingApplicationService(prisma); - const hideBranding = await brandingService.computeHideBranding({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, + team: eventType.team ?? null, + owner: organizer, organizationId: orgId ?? null, - ownerIdFallback: organizer.id, }); evt.hideBranding = hideBranding; - if( hasOrganizerChanged ){ + if (hasOrganizerChanged) { // location might changed and will be new created in eventManager.create (organizer default location) evt.videoCallData = undefined; // To prevent "The requested identifier already exists" error while updating event, we need to remove iCalUID diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index 4778fc46e740e5..ed764062e072f2 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -25,8 +25,7 @@ import AssignmentReasonRecorder, { RRReassignmentType, } from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder"; import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; -import type { TeamBrandingContext } from "@calcom/lib/branding/types"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; @@ -343,12 +342,11 @@ export const roundRobinReassignment = async ({ ...(platformClientParams ? platformClientParams : {}), }; - const brandingService = new BrandingApplicationService(prisma); - const hideBranding = await brandingService.computeHideBranding({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, - teamContext: (teamForBranding as TeamBrandingContext) ?? null, - owner: { id: organizer.id, hideBranding: null }, - organizationId: organizationIdForBranding, + team: eventType.team ?? null, + owner: organizer, + organizationId: orgId ?? null, }); (evt as any).hideBranding = hideBranding; diff --git a/packages/lib/branding/BrandingApplicationService.ts b/packages/lib/branding/BrandingApplicationService.ts deleted file mode 100644 index b4dd5af0818610..00000000000000 --- a/packages/lib/branding/BrandingApplicationService.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { PrismaClient } from "@calcom/prisma"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; -import logger from "@calcom/lib/logger"; -import { BrandingRepository } from "@calcom/lib/server/repository/branding/BrandingRepository"; -import type { BrandingServiceParams } from "./types"; - -export class BrandingApplicationService { - private readonly repo: BrandingRepository; - - constructor(prisma: PrismaClient) { - this.repo = new BrandingRepository(prisma); - } - - async computeHideBranding(input: BrandingServiceParams): Promise { - try { - const { eventTypeId } = input; - - const teamContext = - typeof input.teamContext !== "undefined" - ? input.teamContext - : await this.repo.getEventTypeBrandingContext(eventTypeId); - - const owner = - typeof input.owner !== "undefined" - ? input.owner - : input.ownerIdFallback - ? await this.repo.getUserBranding(input.ownerIdFallback) - : null; - - const organizationId = - typeof input.organizationId !== "undefined" - ? input.organizationId - : input.ownerIdFallback - ? await this.repo.getUserOrganizationId(input.ownerIdFallback) - : null; - - return await shouldHideBrandingForEvent({ - eventTypeId, - team: teamContext ?? null, - owner: owner ?? null, - organizationId: organizationId ?? null, - }); - } catch (error) { - logger.error("BrandingApplicationService: Failed to compute hideBranding", { - error, - eventTypeId: input.eventTypeId, - teamContext: input.teamContext, - owner: input.owner, - organizationId: input.organizationId, - ownerIdFallback: input.ownerIdFallback, - }); - // Always return false on error to ensure critical flows never fail - return false; - } - } -} - -export default BrandingApplicationService; - - diff --git a/packages/lib/branding/README.md b/packages/lib/branding/README.md deleted file mode 100644 index a9ca1cd9f21d22..00000000000000 --- a/packages/lib/branding/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Branding System - -## Overview - -Branding computation centralized; email boundary enforces required `hideBranding`; repository pattern enforced. - -## Architecture - -### Core Components - -- **`BrandingApplicationService`**: Centralized business logic for computing `hideBranding` values -- **`BrandingRepository`**: Database access layer following repository pattern -- **`withHideBranding`**: Email boundary helper ensuring `hideBranding: boolean` in email calls - -### Key Principles - -1. **Centralized Computation**: All branding logic flows through `BrandingApplicationService` -2. **Repository Pattern**: Database access isolated to `BrandingRepository` -3. **Email Boundary Contract**: Email functions always receive `hideBranding: boolean` -4. **Error Resilience**: Branding computation never fails critical flows -5. **Type Safety**: Strong typing throughout with shared interfaces - -### Error Handling - -- Service handles all errors internally -- Always returns `false` on error to ensure critical flows continue -- Comprehensive error logging with context -- No try-catch blocks needed in calling code - -### Usage - -```typescript -// Simple service call - no error handling needed -const brandingService = new BrandingApplicationService(prisma); -const hideBranding = await brandingService.computeHideBranding({ - eventTypeId: 123, - teamContext: team ?? null, - owner: user ?? null, - organizationId: orgId ?? null, -}); - -// Email boundary - ensures hideBranding is always boolean -await sendScheduledEmailsAndSMS(withHideBranding(calEvent, hideBranding)); -``` - -## Files - -- `BrandingApplicationService.ts` - Main business logic -- `BrandingRepository.ts` - Database access -- `types.ts` - Shared type definitions -- `email-manager.ts` - `withHideBranding` helper diff --git a/packages/lib/branding/__tests__/BrandingApplicationService.test.ts b/packages/lib/branding/__tests__/BrandingApplicationService.test.ts deleted file mode 100644 index b9e7c3a94a8fb1..00000000000000 --- a/packages/lib/branding/__tests__/BrandingApplicationService.test.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; -import type { PrismaClient } from "@calcom/prisma"; -import type { TeamBrandingContext, UserBrandingContext, BrandingServiceParams } from "../types"; - -// Mock the shouldHideBrandingForEvent function -vi.mock("@calcom/lib/hideBranding", () => ({ - shouldHideBrandingForEvent: vi.fn(), -})); - -// Mock the BrandingRepository -vi.mock("@calcom/lib/server/repository/branding/BrandingRepository", () => ({ - BrandingRepository: vi.fn(), -})); - -import { BrandingApplicationService } from "../BrandingApplicationService"; -import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding"; -import { BrandingRepository } from "@calcom/lib/server/repository/branding/BrandingRepository"; - -describe("BrandingApplicationService", () => { - let service: BrandingApplicationService; - let mockPrisma: any; - let mockRepository: any; - - beforeEach(() => { - mockPrisma = {}; - mockRepository = { - getEventTypeBrandingContext: vi.fn(), - getUserBranding: vi.fn(), - getUserOrganizationId: vi.fn(), - }; - - // Mock the repository constructor - vi.mocked(BrandingRepository).mockImplementation(() => mockRepository as any); - - service = new BrandingApplicationService(mockPrisma as PrismaClient); - vi.clearAllMocks(); - }); - - describe("computeHideBranding", () => { - const mockTeamContext: TeamBrandingContext = { - id: 1, - hideBranding: true, - parentId: 2, - parent: { hideBranding: false }, - }; - - const mockUserContext: UserBrandingContext = { - id: 1, - hideBranding: true, - }; - - it("should use provided teamContext and not fetch from repository", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - teamContext: mockTeamContext, - owner: mockUserContext, - organizationId: 456, - }; - - (shouldHideBrandingForEvent as any).mockResolvedValue(true); - - const result = await service.computeHideBranding(input); - - expect(mockRepository.getEventTypeBrandingContext).not.toHaveBeenCalled(); - expect(mockRepository.getUserBranding).not.toHaveBeenCalled(); - expect(mockRepository.getUserOrganizationId).not.toHaveBeenCalled(); - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: mockTeamContext, - owner: mockUserContext, - organizationId: 456, - }); - expect(result).toBe(true); - }); - - it("should fetch teamContext from repository when not provided", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - owner: mockUserContext, - organizationId: 456, - }; - - mockRepository.getEventTypeBrandingContext.mockResolvedValue(mockTeamContext); - (shouldHideBrandingForEvent as any).mockResolvedValue(false); - - const result = await service.computeHideBranding(input); - - expect(mockRepository.getEventTypeBrandingContext).toHaveBeenCalledWith(123); - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: mockTeamContext, - owner: mockUserContext, - organizationId: 456, - }); - expect(result).toBe(false); - }); - - it("should fetch owner from repository when not provided but ownerIdFallback is given", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - teamContext: mockTeamContext, - ownerIdFallback: 789, - organizationId: 456, - }; - - mockRepository.getUserBranding.mockResolvedValue(mockUserContext); - (shouldHideBrandingForEvent as any).mockResolvedValue(true); - - const result = await service.computeHideBranding(input); - - expect(mockRepository.getUserBranding).toHaveBeenCalledWith(789); - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: mockTeamContext, - owner: mockUserContext, - organizationId: 456, - }); - expect(result).toBe(true); - }); - - it("should fetch organizationId from repository when not provided but ownerIdFallback is given", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - teamContext: mockTeamContext, - owner: mockUserContext, - ownerIdFallback: 789, - }; - - mockRepository.getUserOrganizationId.mockResolvedValue(999); - (shouldHideBrandingForEvent as any).mockResolvedValue(false); - - const result = await service.computeHideBranding(input); - - expect(mockRepository.getUserOrganizationId).toHaveBeenCalledWith(789); - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: mockTeamContext, - owner: mockUserContext, - organizationId: 999, - }); - expect(result).toBe(false); - }); - - it("should handle null values from repository", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - ownerIdFallback: 789, - }; - - mockRepository.getEventTypeBrandingContext.mockResolvedValue(null); - mockRepository.getUserBranding.mockResolvedValue(null); - mockRepository.getUserOrganizationId.mockResolvedValue(null); - (shouldHideBrandingForEvent as any).mockResolvedValue(false); - - const result = await service.computeHideBranding(input); - - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: null, - owner: null, - organizationId: null, - }); - expect(result).toBe(false); - }); - - it("should handle team-only scenario", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - teamContext: mockTeamContext, - }; - - (shouldHideBrandingForEvent as any).mockResolvedValue(true); - - const result = await service.computeHideBranding(input); - - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: mockTeamContext, - owner: null, - organizationId: null, - }); - expect(result).toBe(true); - }); - - it("should handle owner-only scenario", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - owner: mockUserContext, - }; - - (shouldHideBrandingForEvent as any).mockResolvedValue(false); - - const result = await service.computeHideBranding(input); - - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: null, - owner: mockUserContext, - organizationId: null, - }); - expect(result).toBe(false); - }); - - it("should handle parent-org scenario", async () => { - const parentTeamContext: TeamBrandingContext = { - id: 2, - hideBranding: false, - parentId: null, - parent: null, - }; - - const input: BrandingServiceParams = { - eventTypeId: 123, - teamContext: parentTeamContext, - organizationId: 456, - }; - - (shouldHideBrandingForEvent as any).mockResolvedValue(false); - - const result = await service.computeHideBranding(input); - - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: parentTeamContext, - owner: null, - organizationId: 456, - }); - expect(result).toBe(false); - }); - - it("should handle orgId hint scenario", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - teamContext: mockTeamContext, - owner: mockUserContext, - ownerIdFallback: 789, - }; - - mockRepository.getUserOrganizationId.mockResolvedValue(999); - (shouldHideBrandingForEvent as any).mockResolvedValue(true); - - const result = await service.computeHideBranding(input); - - expect(mockRepository.getUserOrganizationId).toHaveBeenCalledWith(789); - expect(shouldHideBrandingForEvent).toHaveBeenCalledWith({ - eventTypeId: 123, - team: mockTeamContext, - owner: mockUserContext, - organizationId: 999, - }); - expect(result).toBe(true); - }); - - it("should handle error paths gracefully", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - ownerIdFallback: 789, - }; - - mockRepository.getEventTypeBrandingContext.mockRejectedValue(new Error("Database error")); - mockRepository.getUserBranding.mockRejectedValue(new Error("User not found")); - mockRepository.getUserOrganizationId.mockRejectedValue(new Error("Profile not found")); - - const result = await service.computeHideBranding(input); - expect(result).toBe(false); - }); - - it("should handle shouldHideBrandingForEvent errors", async () => { - const input: BrandingServiceParams = { - eventTypeId: 123, - teamContext: mockTeamContext, - owner: mockUserContext, - organizationId: 456, - }; - - (shouldHideBrandingForEvent as any).mockRejectedValue(new Error("Branding computation failed")); - - const result = await service.computeHideBranding(input); - expect(result).toBe(false); - }); - }); -}); diff --git a/packages/lib/branding/types.ts b/packages/lib/branding/types.ts deleted file mode 100644 index 7b74cdd82e2f78..00000000000000 --- a/packages/lib/branding/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Shared type definitions for branding functionality - * Used across multiple booking-related services to avoid code duplication - */ - -export interface TeamBrandingContext { - id: number; - hideBranding: boolean | null; - parentId: number | null; - parent: { - hideBranding: boolean | null; - } | null; -} - -export interface UserBrandingContext { - id: number; - hideBranding: boolean | null; -} - -export interface BrandingServiceParams { - eventTypeId: number; - teamContext?: TeamBrandingContext | null; - owner?: UserBrandingContext | null; - organizationId?: number | null; - ownerIdFallback?: number | null; -} diff --git a/packages/lib/server/repository/branding/BrandingRepository.ts b/packages/lib/server/repository/branding/BrandingRepository.ts deleted file mode 100644 index 52cef22e7cceea..00000000000000 --- a/packages/lib/server/repository/branding/BrandingRepository.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { PrismaClient } from "@calcom/prisma"; -import type { TeamBrandingContext, UserBrandingContext } from "@calcom/lib/branding/types"; - -export class BrandingRepository { - private prisma: PrismaClient; - - constructor(prisma: PrismaClient) { - this.prisma = prisma; - } - - async getEventTypeBrandingContext(eventTypeId: number): Promise { - const result = await this.prisma.eventType.findUnique({ - where: { id: eventTypeId }, - select: { - teamId: true, - team: { - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { select: { hideBranding: true } }, - }, - }, - }, - }); - - return result?.team ?? null; - } - - async getUserBranding(userId: number): Promise { - return await this.prisma.user.findUnique({ - where: { id: userId }, - select: { id: true, hideBranding: true }, - }); - } - - async getUserOrganizationId(userId: number, orgIdHint?: number | null): Promise { - const profile = await this.prisma.profile.findFirst({ - where: { userId, organizationId: orgIdHint ?? undefined }, - select: { organizationId: true }, - }); - return profile?.organizationId ?? null; - } -} - -export default BrandingRepository; - - diff --git a/packages/lib/server/repository/branding/__tests__/BrandingRepository.test.ts b/packages/lib/server/repository/branding/__tests__/BrandingRepository.test.ts deleted file mode 100644 index a9536d85796fc5..00000000000000 --- a/packages/lib/server/repository/branding/__tests__/BrandingRepository.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; -import type { PrismaClient } from "@calcom/prisma"; -import { BrandingRepository } from "../BrandingRepository"; -import type { TeamBrandingContext, UserBrandingContext } from "@calcom/lib/branding/types"; - -describe("BrandingRepository", () => { - let repository: BrandingRepository; - let mockPrisma: any; - - beforeEach(() => { - mockPrisma = { - eventType: { - findUnique: vi.fn(), - }, - user: { - findUnique: vi.fn(), - }, - profile: { - findFirst: vi.fn(), - }, - }; - repository = new BrandingRepository(mockPrisma as PrismaClient); - }); - - describe("getEventTypeBrandingContext", () => { - it("should return team context when event type has a team", async () => { - const mockTeamContext: TeamBrandingContext = { - id: 1, - hideBranding: true, - parentId: 2, - parent: { hideBranding: false }, - }; - - mockPrisma.eventType.findUnique.mockResolvedValue({ - teamId: 1, - team: mockTeamContext, - }); - - const result = await repository.getEventTypeBrandingContext(123); - - expect(result).toEqual(mockTeamContext); - expect(mockPrisma.eventType.findUnique).toHaveBeenCalledWith({ - where: { id: 123 }, - select: { - teamId: true, - team: { - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { select: { hideBranding: true } }, - }, - }, - }, - }); - }); - - it("should return null when event type has no team", async () => { - mockPrisma.eventType.findUnique.mockResolvedValue({ - teamId: null, - team: null, - }); - - const result = await repository.getEventTypeBrandingContext(123); - - expect(result).toBeNull(); - }); - - it("should return null when event type is not found", async () => { - mockPrisma.eventType.findUnique.mockResolvedValue(null); - - const result = await repository.getEventTypeBrandingContext(999); - - expect(result).toBeNull(); - }); - - it("should handle team with no parent", async () => { - const mockTeamContext: TeamBrandingContext = { - id: 1, - hideBranding: true, - parentId: null, - parent: null, - }; - - mockPrisma.eventType.findUnique.mockResolvedValue({ - teamId: 1, - team: mockTeamContext, - }); - - const result = await repository.getEventTypeBrandingContext(123); - - expect(result).toEqual(mockTeamContext); - }); - }); - - describe("getUserBranding", () => { - it("should return user branding context", async () => { - const mockUser: UserBrandingContext = { - id: 1, - hideBranding: true, - }; - - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - - const result = await repository.getUserBranding(123); - - expect(result).toEqual(mockUser); - expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({ - where: { id: 123 }, - select: { id: true, hideBranding: true }, - }); - }); - - it("should return null when user is not found", async () => { - mockPrisma.user.findUnique.mockResolvedValue(null); - - const result = await repository.getUserBranding(999); - - expect(result).toBeNull(); - }); - - it("should handle user with null hideBranding", async () => { - const mockUser: UserBrandingContext = { - id: 1, - hideBranding: null, - }; - - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - - const result = await repository.getUserBranding(123); - - expect(result).toEqual(mockUser); - }); - }); - - describe("getUserOrganizationId", () => { - it("should return organization ID when profile exists", async () => { - mockPrisma.profile.findFirst.mockResolvedValue({ - organizationId: 456, - }); - - const result = await repository.getUserOrganizationId(123); - - expect(result).toBe(456); - expect(mockPrisma.profile.findFirst).toHaveBeenCalledWith({ - where: { userId: 123, organizationId: undefined }, - select: { organizationId: true }, - }); - }); - - it("should return organization ID with orgIdHint", async () => { - mockPrisma.profile.findFirst.mockResolvedValue({ - organizationId: 789, - }); - - const result = await repository.getUserOrganizationId(123, 789); - - expect(result).toBe(789); - expect(mockPrisma.profile.findFirst).toHaveBeenCalledWith({ - where: { userId: 123, organizationId: 789 }, - select: { organizationId: true }, - }); - }); - - it("should return null when profile is not found", async () => { - mockPrisma.profile.findFirst.mockResolvedValue(null); - - const result = await repository.getUserOrganizationId(999); - - expect(result).toBeNull(); - }); - - it("should handle null orgIdHint", async () => { - mockPrisma.profile.findFirst.mockResolvedValue({ - organizationId: 456, - }); - - const result = await repository.getUserOrganizationId(123, null); - - expect(result).toBe(456); - expect(mockPrisma.profile.findFirst).toHaveBeenCalledWith({ - where: { userId: 123, organizationId: undefined }, - select: { organizationId: true }, - }); - }); - }); -}); diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 88106165cb12f7..2a1269161104d9 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -3,9 +3,8 @@ import dayjs from "@calcom/dayjs"; import { sendAddGuestsEmails } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; -import type { TeamBrandingContext } from "@calcom/lib/branding/types"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -35,13 +34,42 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => { }, include: { attendees: true, - eventType: true, + eventType: { + include: { + team: { + select: { + id: true, + name: true, + parentId: true, + hideBranding: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }, + owner: { + select: { + id: true, + hideBranding: true, + }, + }, + }, + }, destinationCalendar: true, references: true, user: { - include: { + select: { + id: true, + username: true, + email: true, + timeZone: true, + timeFormat: true, + name: true, destinationCalendar: true, credentials: true, + hideBranding: true, }, }, }, @@ -213,11 +241,18 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => { try { const eventTypeId = booking.eventTypeId; let hideBranding = false; - if (eventTypeId) { - const brandingService = new BrandingApplicationService(prisma); - hideBranding = await brandingService.computeHideBranding({ + if (!eventTypeId) { + console.warn("Booking missing eventTypeId, defaulting hideBranding to false", { + bookingId: booking.id, + userId: booking.userId, + }); + hideBranding = false; + } else { + hideBranding = await shouldHideBrandingForEvent({ eventTypeId, - ownerIdFallback: booking.userId ?? null, + team: booking.eventType?.team ?? null, + owner: booking.user ?? null, + organizationId: null, // Will be fetched by the function if needed }); } diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 8a741038e1cfa0..db3d32fd2f8b6b 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -7,13 +7,13 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation"; import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger"; import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund"; +import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import { workflowSelect } from "@calcom/features/ee/workflows/lib/getAllWorkflows"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; -import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -73,7 +73,12 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { eventType: { select: { id: true, - owner: true, + owner: { + select: { + id: true, + hideBranding: true, + }, + }, teamId: true, recurringEvent: true, title: true, @@ -96,6 +101,12 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { id: true, name: true, parentId: true, + hideBranding: true, + parent: { + select: { + hideBranding: true, + }, + }, }, }, workflows: { @@ -126,6 +137,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { name: true, destinationCalendar: true, locale: true, + hideBranding: true, }, }, id: true, @@ -358,12 +370,18 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { const eventTypeIdForBranding = booking.eventTypeId ?? null; let hideBranding = false; - if (eventTypeIdForBranding) { - const brandingService = new BrandingApplicationService(prisma); - hideBranding = await brandingService.computeHideBranding({ + if (!eventTypeIdForBranding) { + console.warn("Booking missing eventTypeId, defaulting hideBranding to false", { + bookingId: booking.id, + userId: booking.userId, + }); + hideBranding = false; + } else { + hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventTypeIdForBranding, + team: booking.eventType?.team ?? null, + owner: booking.user ?? null, organizationId: orgId ?? null, - ownerIdFallback: booking.userId ?? null, }); } diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index 0ae7f8a6696ab1..c7c25705a50acb 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -5,16 +5,15 @@ import { getEventLocationType, OrganizerDefaultConferencingAppType } from "@calc import { getAppFromSlug } from "@calcom/app-store/utils"; import { sendLocationChangeEmailsAndSMS } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; -import { BrandingApplicationService } from "@calcom/lib/branding/BrandingApplicationService"; -import type { TeamBrandingContext } from "@calcom/lib/branding/types"; +import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { buildCalEventFromBooking } from "@calcom/lib/buildCalEventFromBooking"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { getTranslation } from "@calcom/lib/server/i18n"; -import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { CredentialRepository } from "@calcom/lib/server/repository/credential"; -import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { prisma } from "@calcom/prisma"; import type { Booking, BookingReference } from "@calcom/prisma/client"; import type { userMetadata } from "@calcom/prisma/zod-utils"; @@ -286,47 +285,21 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { }); try { - const teamForBranding = booking.eventType?.teamId - ? await prisma.team.findUnique({ - where: { id: booking.eventType.teamId }, - select: { - id: true, - hideBranding: true, - parentId: true, - parent: { - select: { - hideBranding: true, - }, - }, - }, - }) - : null; - - const organizationIdForBranding = teamForBranding?.parentId - ? teamForBranding.parentId - : ( - await prisma.profile.findFirst({ - where: { userId: booking.userId || undefined }, - select: { organizationId: true }, - }) - )?.organizationId ?? null; - - const userForBranding = !booking.eventType?.teamId - ? await prisma.user.findUnique({ - where: { id: booking.userId || 0 }, - select: { id: true, hideBranding: true }, - }) - : null; - + // Use pre-fetched branding data from booking context const eventTypeId = booking.eventTypeId; let hideBranding = false; - if (eventTypeId) { - const brandingService = new BrandingApplicationService(prisma); - hideBranding = await brandingService.computeHideBranding({ + if (!eventTypeId) { + console.warn("Booking missing eventTypeId, defaulting hideBranding to false", { + bookingId: booking.id, + userId: booking.userId, + }); + hideBranding = false; + } else { + hideBranding = await shouldHideBrandingForEvent({ eventTypeId, - teamContext: (teamForBranding as TeamBrandingContext) ?? null, - owner: userForBranding, - organizationId: organizationIdForBranding, + team: booking.eventType?.team ?? null, + owner: booking.user ?? null, + organizationId: booking.eventType?.team?.parentId ?? null, }); } diff --git a/packages/trpc/server/routers/viewer/bookings/util.ts b/packages/trpc/server/routers/viewer/bookings/util.ts index ec990144dbd8ed..5fa738656ddd2c 100644 --- a/packages/trpc/server/routers/viewer/bookings/util.ts +++ b/packages/trpc/server/routers/viewer/bookings/util.ts @@ -30,6 +30,18 @@ export const bookingsProcedure = authedProcedure id: true, name: true, parentId: true, + hideBranding: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }, + owner: { + select: { + id: true, + hideBranding: true, }, }, }, @@ -40,6 +52,7 @@ export const bookingsProcedure = authedProcedure include: { destinationCalendar: true, credentials: true, + hideBranding: true, }, }, }; @@ -102,7 +115,19 @@ export type BookingsProcedureContext = { booking: Booking & { eventType: | (EventType & { - team?: { id: number; name: string; parentId?: number | null } | null; + team?: { + id: number; + name: string; + parentId?: number | null; + hideBranding: boolean | null; + parent: { + hideBranding: boolean | null; + } | null; + } | null; + owner?: { + id: number; + hideBranding: boolean | null; + } | null; }) | null; destinationCalendar: DestinationCalendar | null; @@ -110,6 +135,7 @@ export type BookingsProcedureContext = { | (User & { destinationCalendar: DestinationCalendar | null; credentials: Credential[]; + hideBranding: boolean | null; }) | null; references: BookingReference[]; From 11d6d83d217b84108cba72fd844ce2690d5d786c Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 17 Oct 2025 05:02:51 +0530 Subject: [PATCH 15/58] type export and JSDoc --- packages/features/profile/lib/hideBranding.ts | 95 +++++++++++++++++-- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/packages/features/profile/lib/hideBranding.ts b/packages/features/profile/lib/hideBranding.ts index c951a696952f63..a1be10a2a57db2 100644 --- a/packages/features/profile/lib/hideBranding.ts +++ b/packages/features/profile/lib/hideBranding.ts @@ -7,30 +7,59 @@ import { prisma } from "@calcom/prisma"; const log = logger.getSubLogger({ name: "hideBranding" }); const teamRepository = new TeamRepository(prisma); const userRepository = new UserRepository(prisma); -type Team = { +export type TeamWithBranding = { hideBranding: boolean | null; parent: { hideBranding: boolean | null; } | null; }; -type Profile = { +export type ProfileWithBranding = { organization: { hideBranding: boolean | null; } | null; }; -type UserWithoutProfile = { +export type UserWithBranding = { id: number; hideBranding: boolean | null; }; -type UserWithProfile = UserWithoutProfile & { - profile: Profile | null; +export type UserWithProfileAndBranding = UserWithBranding & { + profile: ProfileWithBranding | null; }; +// Internal type aliases for backward compatibility +type Team = TeamWithBranding; +type Profile = ProfileWithBranding; +type UserWithoutProfile = UserWithBranding; +type UserWithProfile = UserWithProfileAndBranding; + /** - * Determines if branding should be hidden by checking entity and organization settings + * Determines if branding should be hidden by checking entity and organization settings. + * + * This function implements the branding hierarchy: organization settings override entity settings. + * If the organization has branding hidden, the entity's branding setting is ignored. + * + * @param options - Object containing entity and organization branding settings + * @param options.entityHideBranding - Whether the entity (user/team) has branding hidden + * @param options.organizationHideBranding - Whether the organization has branding hidden + * @returns boolean - true if branding should be hidden, false otherwise + * + * @example + * ```typescript + * // Organization overrides entity setting + * resolveHideBranding({ + * entityHideBranding: false, + * organizationHideBranding: true + * }); // Returns true (organization setting wins) + * + * // Entity setting is used when organization doesn't hide branding + * resolveHideBranding({ + * entityHideBranding: true, + * organizationHideBranding: false + * }); // Returns true (entity setting is used) + * ``` */ function resolveHideBranding(options: { entityHideBranding: boolean | null; @@ -80,7 +109,20 @@ export async function getHideBranding({ } /** - * Determines if branding should be hidden for an event that could be a team event or user event + * Determines if branding should be hidden for an event using pre-fetched profile data. + * + * This function is used when you already have the user's profile data and don't need to fetch it. + * It's more efficient than shouldHideBrandingForEvent when profile data is already available. + * + * @param eventTypeId - The event type ID for the event + * @param owner - User with profile data including organization.hideBranding, or null for team events + * @param team - Team with hideBranding and parent.hideBranding fields, or null for user events + * @returns boolean - true if branding should be hidden, false otherwise + * + * @note Data Requirements: + * - For team events: team.hideBranding and team.parent.hideBranding must be present + * - For user events: owner.hideBranding and owner.profile.organization.hideBranding must be present + * - Either team or owner must be provided, but not both */ export function shouldHideBrandingForEventUsingProfile({ eventTypeId, @@ -110,8 +152,43 @@ export function shouldHideBrandingForEventUsingProfile({ } /** - * A wrapper over shouldHideBrandingForEventUsingProfile that fetches the profile itself - * Use it when you don't have user's profile + * Determines if branding should be hidden for an event based on team, user, and organization settings. + * + * This function handles both team events and user events, with different data requirements for each: + * - Team events: Requires team.hideBranding and team.parent.hideBranding fields + * - User events: Requires owner.hideBranding and may fetch organization profile if needed + * + * @param eventTypeId - The event type ID for the event + * @param team - Team object with hideBranding and parent.hideBranding fields, or null for user events + * @param owner - User object with hideBranding field, or null for team events + * @param organizationId - Organization ID for profile lookup (only needed for user events without team) + * @returns Promise - true if branding should be hidden, false otherwise + * + * @example + * ```typescript + * // Team event + * const hideBranding = await shouldHideBrandingForEvent({ + * eventTypeId: 123, + * team: { hideBranding: true, parent: { hideBranding: false } }, + * owner: null, + * organizationId: null + * }); + * + * // User event + * const hideBranding = await shouldHideBrandingForEvent({ + * eventTypeId: 456, + * team: null, + * owner: { id: 789, hideBranding: false }, + * organizationId: 101 + * }); + * ``` + * + * @note Data Requirements: + * - For team events: team.hideBranding and team.parent.hideBranding must be present + * - For user events: owner.hideBranding must be present + * - organizationId is only used for user events to fetch organization profile + * - If team is provided, owner and organizationId are ignored + * - If owner is provided without team, organizationId is used to fetch profile */ export async function shouldHideBrandingForEvent({ eventTypeId, From 4f88da2cb70d6f791306ff47552c4320557563ec Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 17 Oct 2025 06:11:13 +0530 Subject: [PATCH 16/58] fix: compute hideBranding dynamically in payment handlers --- .../_utils/payments/handlePaymentSuccess.ts | 20 ++++++++++++--- .../bookings/lib/payment/handleNoShowFee.ts | 25 ++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index eb51c43597e168..350b17fde9a45e 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -1,5 +1,6 @@ import { eventTypeAppMetadataOptionalSchema } from "@calcom/app-store/zod-utils"; -import { sendScheduledEmailsAndSMS, withHideBranding } from "@calcom/emails"; +import { sendScheduledEmailsAndSMS } from "@calcom/emails"; +import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { doesBookingRequireConfirmation } from "@calcom/features/bookings/lib/doesBookingRequireConfirmation"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { handleBookingRequested } from "@calcom/features/bookings/lib/handleBookingRequested"; @@ -7,7 +8,7 @@ import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirma import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; -import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; @@ -96,7 +97,20 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number) log.debug(`handling booking request for eventId ${eventType.id}`); } } else if (areEmailsEnabled) { - await sendScheduledEmailsAndSMS(withHideBranding(evt), undefined, undefined, undefined, eventType.metadata); + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: booking.eventType?.id ?? 0, + team: booking.eventType?.team ?? null, + owner: userWithCredentials ?? null, + organizationId: booking.eventType?.team?.parentId ?? null, + }); + + await sendScheduledEmailsAndSMS( + { ...evt, hideBranding }, + undefined, + undefined, + undefined, + booking.eventType?.metadata as EventTypeMetadata + ); } throw new HttpCode({ diff --git a/packages/features/bookings/lib/payment/handleNoShowFee.ts b/packages/features/bookings/lib/payment/handleNoShowFee.ts index 98dfab009782e8..598ec95c4b2de3 100644 --- a/packages/features/bookings/lib/payment/handleNoShowFee.ts +++ b/packages/features/bookings/lib/payment/handleNoShowFee.ts @@ -1,14 +1,15 @@ -// eslint-disable-next-line + import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; -import { sendNoShowFeeChargedEmail, withHideBranding } from "@calcom/emails"; +import { sendNoShowFeeChargedEmail } from "@calcom/emails"; +import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { ErrorWithCode } from "@calcom/lib/errors"; import logger from "@calcom/lib/logger"; import { getTranslation } from "@calcom/lib/server/i18n"; import { CredentialRepository } from "@calcom/lib/server/repository/credential"; -import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository"; import { TeamRepository } from "@calcom/lib/server/repository/team"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; @@ -28,15 +29,24 @@ export const handleNoShowFee = async ({ userPrimaryEmail: string | null; userId: number | null; user?: { + id: number; email: string; name?: string | null; locale: string | null; timeZone: string; + hideBranding: boolean; } | null; eventType: { + id: number; title: string; hideOrganizerEmail: boolean; teamId: number | null; + team?: { + id: number; + hideBranding: boolean; + parentId: number | null; + parent: { hideBranding: boolean | null } | null; + } | null; metadata?: Prisma.JsonValue; } | null; attendees: { @@ -157,7 +167,14 @@ export const handleNoShowFee = async ({ throw new Error("Payment processing failed"); } - await sendNoShowFeeChargedEmail(attendee, withHideBranding(evt), eventTypeMetdata); + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: booking.eventType?.id ?? 0, + team: booking.eventType?.team ?? null, + owner: booking.user ?? null, + organizationId: booking.eventType?.team?.parentId ?? null, + }); + + await sendNoShowFeeChargedEmail(attendee, { ...evt, hideBranding }, eventTypeMetdata); return paymentData; } catch (err) { From 305774f1d54501740a4a98905b284dc48ee4ad08 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 24 Oct 2025 19:21:17 +0530 Subject: [PATCH 17/58] fix failing round robin test --- .../emails/__tests__/withHideBranding.test.ts | 90 ----------- .../handleCancelBooking.integration.test.ts | 87 ----------- .../handleConfirmation.integration.test.ts | 136 ----------------- ...obinManualReassignment.integration.test.ts | 142 ------------------ .../roundRobinManualReassignment.ts | 2 + 5 files changed, 2 insertions(+), 455 deletions(-) delete mode 100644 packages/emails/__tests__/withHideBranding.test.ts delete mode 100644 packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts delete mode 100644 packages/features/bookings/__tests__/handleConfirmation.integration.test.ts delete mode 100644 packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts diff --git a/packages/emails/__tests__/withHideBranding.test.ts b/packages/emails/__tests__/withHideBranding.test.ts deleted file mode 100644 index 3eeb0afe117ce8..00000000000000 --- a/packages/emails/__tests__/withHideBranding.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { withHideBranding } from "../email-manager"; -import type { CalendarEvent } from "@calcom/types/Calendar"; - -describe("withHideBranding", () => { - const baseEvent: CalendarEvent = { - uid: "test-uid", - title: "Test Event", - startTime: "2024-01-15T10:00:00Z", - endTime: "2024-01-15T11:00:00Z", - attendees: [], - organizer: { name: "Test Organizer", email: "test@example.com" }, - }; - - it("should add hideBranding: false when not present", () => { - const result = withHideBranding(baseEvent); - - expect(result).toEqual({ - ...baseEvent, - hideBranding: false, - }); - }); - - it("should preserve existing hideBranding value", () => { - const eventWithBranding = { ...baseEvent, hideBranding: true }; - const result = withHideBranding(eventWithBranding); - - expect(result).toEqual({ - ...baseEvent, - hideBranding: true, - }); - }); - - it("should use explicit value when provided", () => { - const result = withHideBranding(baseEvent, true); - - expect(result).toEqual({ - ...baseEvent, - hideBranding: true, - }); - }); - - it("should override existing value with explicit value", () => { - const eventWithBranding = { ...baseEvent, hideBranding: false }; - const result = withHideBranding(eventWithBranding, true); - - expect(result).toEqual({ - ...baseEvent, - hideBranding: true, - }); - }); - - it("should default to false when explicit value is undefined", () => { - const eventWithBranding = { ...baseEvent, hideBranding: true }; - const result = withHideBranding(eventWithBranding, undefined); - - expect(result).toEqual({ - ...baseEvent, - hideBranding: true, // Should preserve existing value when explicit is undefined - }); - }); - - it("should handle null hideBranding correctly", () => { - const eventWithNullBranding = { ...baseEvent, hideBranding: null }; - const result = withHideBranding(eventWithNullBranding); - - expect(result).toEqual({ - ...baseEvent, - hideBranding: false, // Should default to false when null - }); - }); - - it("should handle undefined hideBranding correctly", () => { - const eventWithUndefinedBranding = { ...baseEvent, hideBranding: undefined }; - const result = withHideBranding(eventWithUndefinedBranding); - - expect(result).toEqual({ - ...baseEvent, - hideBranding: false, // Should default to false when undefined - }); - }); - - it("should return CalendarEventWithBranding type", () => { - const result = withHideBranding(baseEvent); - - // Type check - this should compile without errors - expect(typeof result.hideBranding).toBe("boolean"); - expect(result.hideBranding).toBe(false); - }); -}); diff --git a/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts b/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts deleted file mode 100644 index 395090be427dae..00000000000000 --- a/packages/features/bookings/__tests__/handleCancelBooking.integration.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; - -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; -import type { PrismaClient } from "@calcom/prisma"; - -import { handleCancelBooking } from "../lib/handleCancelBooking"; - -// Mock the shouldHideBrandingForEvent function -vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ - shouldHideBrandingForEvent: vi.fn().mockResolvedValue(true), -})); - -// Mock other dependencies -vi.mock("@calcom/emails", () => ({ - sendCancelledEmailsAndSMS: vi.fn(), -})); - -vi.mock("@calcom/features/webhooks/lib/sendOrSchedulePayload", () => ({ - default: vi.fn(), -})); - -describe("handleCancelBooking Integration", () => { - let mockPrisma: PrismaClient; - - beforeEach(() => { - mockPrisma = { - booking: { - findUnique: vi.fn().mockResolvedValue({ - id: 1, - eventTypeId: 1, - userId: 1, - eventType: { - team: { id: 1, hideBranding: true, parentId: null, parent: null }, - }, - user: { id: 1, hideBranding: false }, - }), - update: vi.fn().mockResolvedValue({}), - }, - profile: { - findFirst: vi.fn().mockResolvedValue({ - organizationId: 123, - }), - }, - } as any; - }); - - it("should include hideBranding in cancellation email calls", async () => { - const { sendCancelledEmailsAndSMS } = await import("@calcom/emails"); - - await handleCancelBooking({ - bookingId: 1, - reason: "Test cancellation", - prisma: mockPrisma, - }); - - // Verify that sendCancelledEmailsAndSMS was called with hideBranding - expect(sendCancelledEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - hideBranding: true, - }), - expect.any(Object), - expect.any(Object) - ); - }); - - it("should handle branding computation errors gracefully", async () => { - // Mock shouldHideBrandingForEvent to return false on error - vi.mocked(shouldHideBrandingForEvent).mockResolvedValue(false); - - const { sendCancelledEmailsAndSMS } = await import("@calcom/emails"); - - await handleCancelBooking({ - bookingId: 1, - reason: "Test cancellation", - prisma: mockPrisma, - }); - - // Verify that sendCancelledEmailsAndSMS was called with hideBranding: false - expect(sendCancelledEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - hideBranding: false, - }), - expect.any(Object), - expect.any(Object) - ); - }); -}); diff --git a/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts b/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts deleted file mode 100644 index 05835c87d4e183..00000000000000 --- a/packages/features/bookings/__tests__/handleConfirmation.integration.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; - -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; -import type { PrismaClient } from "@calcom/prisma"; - -import { handleConfirmation } from "../lib/handleConfirmation"; - -// Mock the shouldHideBrandingForEvent function -vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ - shouldHideBrandingForEvent: vi.fn().mockResolvedValue(true), -})); - -// Mock other dependencies -vi.mock("@calcom/emails", () => ({ - sendScheduledEmailsAndSMS: vi.fn(), -})); - -vi.mock("@calcom/features/webhooks/lib/sendOrSchedulePayload", () => ({ - default: vi.fn(), -})); - -describe("handleConfirmation Integration", () => { - let mockPrisma: PrismaClient; - - beforeEach(() => { - mockPrisma = { - eventType: { - findUnique: vi.fn().mockResolvedValue({ - id: 1, - team: { id: 1, hideBranding: true, parentId: null, parent: null }, - teamId: 1, - }), - }, - user: { - findUnique: vi.fn().mockResolvedValue({ - id: 1, - hideBranding: false, - }), - }, - profile: { - findFirst: vi.fn().mockResolvedValue({ - organizationId: 123, - }), - }, - } as any; - }); - - it("should include hideBranding in email calls", async () => { - const { sendScheduledEmailsAndSMS } = await import("@calcom/emails"); - - const mockBooking = { - id: 1, - userId: 1, - eventTypeId: 1, - startTime: new Date("2024-01-15T10:00:00Z"), - endTime: new Date("2024-01-15T11:00:00Z"), - uid: "test-uid", - }; - - const mockEventType = { - id: 1, - team: { id: 1, hideBranding: true, parentId: null, parent: null }, - teamId: 1, - }; - - await handleConfirmation({ - user: { id: 1, username: "testuser" }, - evt: { - uid: "test-uid", - title: "Test Event", - startTime: "2024-01-15T10:00:00Z", - endTime: "2024-01-15T11:00:00Z", - attendees: [], - organizer: { name: "Test Organizer", email: "test@example.com" }, - }, - prisma: mockPrisma, - bookingId: 1, - booking: mockBooking, - eventType: mockEventType, - }); - - // Verify that sendScheduledEmailsAndSMS was called with hideBranding - expect(sendScheduledEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - hideBranding: true, - }), - expect.any(Object) - ); - }); - - it("should handle branding computation errors gracefully", async () => { - // Mock shouldHideBrandingForEvent to return false on error - vi.mocked(shouldHideBrandingForEvent).mockResolvedValue(false); - - const { sendScheduledEmailsAndSMS } = await import("@calcom/emails"); - - const mockBooking = { - id: 1, - userId: 1, - eventTypeId: 1, - startTime: new Date("2024-01-15T10:00:00Z"), - endTime: new Date("2024-01-15T11:00:00Z"), - uid: "test-uid", - }; - - const mockEventType = { - id: 1, - team: { id: 1, hideBranding: true, parentId: null, parent: null }, - teamId: 1, - }; - - await handleConfirmation({ - user: { id: 1, username: "testuser" }, - evt: { - uid: "test-uid", - title: "Test Event", - startTime: "2024-01-15T10:00:00Z", - endTime: "2024-01-15T11:00:00Z", - attendees: [], - organizer: { name: "Test Organizer", email: "test@example.com" }, - }, - prisma: mockPrisma, - bookingId: 1, - booking: mockBooking, - eventType: mockEventType, - }); - - // Verify that sendScheduledEmailsAndSMS was called with hideBranding: false - expect(sendScheduledEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - hideBranding: false, - }), - expect.any(Object) - ); - }); -}); diff --git a/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts b/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts deleted file mode 100644 index dec6470f1c33d8..00000000000000 --- a/packages/features/ee/round-robin/__tests__/roundRobinManualReassignment.integration.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; - -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; -import type { PrismaClient } from "@calcom/prisma"; - -import { roundRobinManualReassignment } from "../roundRobinManualReassignment"; - -// Mock the shouldHideBrandingForEvent function -vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ - shouldHideBrandingForEvent: vi.fn().mockResolvedValue(true), -})); - -// Mock other dependencies -vi.mock("@calcom/emails", () => ({ - sendRoundRobinScheduledEmailsAndSMS: vi.fn(), - sendRoundRobinReassignedEmailsAndSMS: vi.fn(), - sendRoundRobinUpdatedEmailsAndSMS: vi.fn(), -})); - -describe("roundRobinManualReassignment Integration", () => { - let mockPrisma: PrismaClient; - - beforeEach(() => { - mockPrisma = { - booking: { - findUnique: vi.fn().mockResolvedValue({ - id: 1, - eventTypeId: 1, - userId: 1, - eventType: { - id: 1, - teamId: 1, - team: { id: 1, hideBranding: true, parentId: null, parent: null }, - }, - user: { id: 1, hideBranding: false }, - }), - update: vi.fn().mockResolvedValue({}), - }, - user: { - findUnique: vi.fn().mockResolvedValue({ - id: 2, - email: "newuser@example.com", - }), - }, - credential: { - findMany: vi.fn().mockResolvedValue([]), - }, - profile: { - findFirst: vi.fn().mockResolvedValue({ - organizationId: 123, - }), - }, - } as any; - }); - - it("should include hideBranding in round-robin email calls", async () => { - const { - sendRoundRobinScheduledEmailsAndSMS, - sendRoundRobinReassignedEmailsAndSMS, - sendRoundRobinUpdatedEmailsAndSMS, - } = await import("@calcom/emails"); - - await roundRobinManualReassignment({ - bookingId: 1, - newUserId: 2, - prisma: mockPrisma, - }); - - // Verify that all email functions were called with hideBranding - expect(sendRoundRobinScheduledEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - calEvent: expect.objectContaining({ - hideBranding: true, - }), - }), - expect.any(Object) - ); - - expect(sendRoundRobinReassignedEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - calEvent: expect.objectContaining({ - hideBranding: true, - }), - }), - expect.any(Object) - ); - - expect(sendRoundRobinUpdatedEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - calEvent: expect.objectContaining({ - hideBranding: true, - }), - }), - expect.any(Object) - ); - }); - - it("should handle branding computation errors gracefully", async () => { - // Mock shouldHideBrandingForEvent to return false on error - vi.mocked(shouldHideBrandingForEvent).mockResolvedValue(false); - - const { - sendRoundRobinScheduledEmailsAndSMS, - sendRoundRobinReassignedEmailsAndSMS, - sendRoundRobinUpdatedEmailsAndSMS, - } = await import("@calcom/emails"); - - await roundRobinManualReassignment({ - bookingId: 1, - newUserId: 2, - prisma: mockPrisma, - }); - - // Verify that all email functions were called with hideBranding: false - expect(sendRoundRobinScheduledEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - calEvent: expect.objectContaining({ - hideBranding: false, - }), - }), - expect.any(Object) - ); - - expect(sendRoundRobinReassignedEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - calEvent: expect.objectContaining({ - hideBranding: false, - }), - }), - expect.any(Object) - ); - - expect(sendRoundRobinUpdatedEmailsAndSMS).toHaveBeenCalledWith( - expect.objectContaining({ - calEvent: expect.objectContaining({ - hideBranding: false, - }), - }), - expect.any(Object) - ); - }); -}); diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 0077ee69da96f4..8a3c5d29180cc7 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -622,3 +622,5 @@ export async function handleWorkflowsUpdate({ hideBranding: evt.hideBranding ?? false, }); } + +export default roundRobinManualReassignment; From 00c2c637e65927176c5680d6a950d57e5b69ae8f Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 24 Oct 2025 20:33:32 +0530 Subject: [PATCH 18/58] fix violation of no prisma outside of repositories rule --- .../web/app/api/cron/bookingReminder/route.ts | 4 +- ...ookings-single-view.getServerSideProps.tsx | 4 +- .../_utils/payments/handlePaymentSuccess.ts | 4 +- .../bookings/lib/handleBookingRequested.ts | 5 +- .../bookings/lib/handleCancelBooking.ts | 4 +- .../bookings/lib/handleConfirmation.ts | 4 +- .../attendeeRescheduleSeatedBooking.ts | 4 +- .../owner/combineTwoSeatedBookings.ts | 4 +- .../owner/moveSeatedBookingToNewTimeSlot.ts | 4 +- .../bookings/lib/payment/handleNoShowFee.ts | 4 +- .../lib/service/RegularBookingService.ts | 4 +- .../credentials/handleDeleteCredential.ts | 4 +- .../features/ee/payments/pages/payment.tsx | 4 +- .../roundRobinManualReassignment.ts | 4 +- .../ee/round-robin/roundRobinReassignment.ts | 6 +- .../lib/service/WorkflowService.test.ts | 3 +- .../workflows/lib/service/WorkflowService.ts | 5 +- packages/features/profile/lib/hideBranding.ts | 57 +++++++++++++++++-- .../viewer/bookings/addGuests.handler.ts | 4 +- .../viewer/bookings/confirm.handler.ts | 4 +- .../viewer/bookings/editLocation.handler.ts | 4 +- 21 files changed, 95 insertions(+), 45 deletions(-) diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 583e54d5a78ecf..933b998a50c617 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -5,7 +5,7 @@ import { NextResponse } from "next/server"; import dayjs from "@calcom/dayjs"; import { sendOrganizerRequestReminderEmail, withHideBranding } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -133,7 +133,7 @@ async function postHandler(request: NextRequest) { const selectedDestinationCalendar = booking.destinationCalendar || user.destinationCalendar; // Use pre-fetched branding data from booking query - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx index 2506f2846fd1ff..55162600ea1aea 100644 --- a/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx +++ b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx @@ -9,7 +9,7 @@ import getBookingInfo from "@calcom/features/bookings/lib/getBookingInfo"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { getDefaultEvent } from "@calcom/features/eventtypes/lib/defaultEvents"; import { getBrandingForEventType } from "@calcom/features/profile/lib/getBranding"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat"; @@ -246,7 +246,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username, hideBranding: isPlatformBooking ? true - : await shouldHideBrandingForEvent({ + : await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType?.parent?.team ?? eventType?.team, owner: eventType.users[0] ?? null, diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index 350b17fde9a45e..0b087b308b805c 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -8,7 +8,7 @@ import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirma import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; @@ -97,7 +97,7 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number) log.debug(`handling booking request for eventId ${eventType.id}`); } } else if (areEmailsEnabled) { - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, owner: userWithCredentials ?? null, diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index c258cef0a4a867..fd99be3b92284e 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -2,13 +2,12 @@ import { sendAttendeeRequestEmailAndSMS, sendOrganizerRequestEmail } from "@calc import { getWebhookPayloadForBooking } from "@calcom/features/bookings/lib/getWebhookPayloadForBooking"; import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; 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 prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { WebhookTriggerEvents, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; @@ -79,7 +78,7 @@ export async function handleBookingRequested(args: { }); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEvent({ + hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index c64ca300e34f12..71d93db2d1f58c 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -12,7 +12,7 @@ import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/proc import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import { sendCancelledReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { @@ -255,7 +255,7 @@ async function handler(input: CancelBookingInput) { }); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEvent({ + hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: bookingToDelete.eventTypeId, team: bookingToDelete.eventType?.team ?? null, owner: bookingToDelete.user ?? null, diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 0ca19f1a268309..de17016228463f 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -10,7 +10,7 @@ import { } from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails"; import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; @@ -130,7 +130,7 @@ export async function handleConfirmation(args: { }); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEvent({ + hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 76969c5fce488e..f7661fe6c86073 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -3,7 +3,7 @@ import { cloneDeep } from "lodash"; import { sendRescheduledSeatEmailAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; import type { Person, CalendarEvent } from "@calcom/types/Calendar"; @@ -24,7 +24,7 @@ const attendeeRescheduleSeatedBooking = async ( const { organizerUser } = rescheduleSeatedBookingObject; // Use pre-fetched branding data from eventType - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index 67e691799f4881..dd128311200895 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -4,7 +4,7 @@ import { uuid } from "short-uuid"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; @@ -137,7 +137,7 @@ const combineTwoSeatedBookings = async ( if (noEmail !== true && isConfirmedByDefault) { // Use pre-fetched branding data from eventType - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index be1ea1b86c2ab8..e21007a93afc15 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -3,7 +3,7 @@ import { cloneDeep } from "lodash"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import prisma from "@calcom/prisma"; import type { AdditionalInformation, AppsStatus } from "@calcom/types/Calendar"; @@ -32,7 +32,7 @@ const moveSeatedBookingToNewTimeSlot = async ( } = rescheduleSeatedBookingObject; // Use pre-fetched branding data from eventType - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, diff --git a/packages/features/bookings/lib/payment/handleNoShowFee.ts b/packages/features/bookings/lib/payment/handleNoShowFee.ts index d5498171be079c..a0ba7cabaa4b12 100644 --- a/packages/features/bookings/lib/payment/handleNoShowFee.ts +++ b/packages/features/bookings/lib/payment/handleNoShowFee.ts @@ -5,7 +5,7 @@ import { sendNoShowFeeChargedEmail } from "@calcom/emails"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { ErrorWithCode } from "@calcom/lib/errors"; import logger from "@calcom/lib/logger"; @@ -166,7 +166,7 @@ export const handleNoShowFee = async ({ throw new Error("Payment processing failed"); } - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/features/bookings/lib/service/RegularBookingService.ts b/packages/features/bookings/lib/service/RegularBookingService.ts index bc720b931273b4..7181edf134c813 100644 --- a/packages/features/bookings/lib/service/RegularBookingService.ts +++ b/packages/features/bookings/lib/service/RegularBookingService.ts @@ -44,7 +44,7 @@ import { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents"; import { getEventName, updateHostInEventName } from "@calcom/features/eventtypes/lib/eventNaming"; import { getFullName } from "@calcom/features/form-builder/utils"; import type { HashedLinkService } from "@calcom/features/hashedLink/lib/service/HashedLinkService"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { handleAnalyticsEvents } from "@calcom/features/tasker/tasks/analytics/handleAnalyticsEvents"; import type { UserRepository } from "@calcom/features/users/repositories/UserRepository"; @@ -1299,7 +1299,7 @@ async function handler( const organizerOrganizationId = organizerOrganizationProfile?.organizationId; - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 2ba93e03172918..044ef2ebbc0a09 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -11,7 +11,7 @@ import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-util import { sendCancelledEmailsAndSMS } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { deletePayment } from "@calcom/features/bookings/lib/payment/deletePayment"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/scheduleTrigger"; import { buildNonDelegationCredential } from "@calcom/lib/delegationCredential"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; @@ -328,7 +328,7 @@ const handleDeleteCredential = async ({ const attendeesList = await Promise.all(attendeesListPromises); const tOrganizer = await getTranslation(booking?.user?.locale ?? "en", "common"); - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/features/ee/payments/pages/payment.tsx b/packages/features/ee/payments/pages/payment.tsx index f3e086fdb3d0c0..b1b9121fbf7557 100644 --- a/packages/features/ee/payments/pages/payment.tsx +++ b/packages/features/ee/payments/pages/payment.tsx @@ -3,7 +3,7 @@ import { z } from "zod"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getClientSecretFromPayment } from "@calcom/features/ee/payments/pages/getClientSecretFromPayment"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; import { paymentDataSelect } from "@calcom/prisma/selects/payment"; @@ -55,7 +55,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const profile = { name: eventType.team?.name || user?.name || null, theme: (!eventType.team?.name && user?.theme) || null, - hideBranding: await shouldHideBrandingForEvent({ + hideBranding: await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team, owner: eventType.users[0] ?? null, diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 8a3c5d29180cc7..c38b29c60e311b 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -25,7 +25,7 @@ import { } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { SENDER_NAME } from "@calcom/lib/constants"; import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService"; @@ -313,7 +313,7 @@ export const roundRobinManualReassignment = async ({ conferenceCredentialId: conferenceCredentialId ?? undefined, }; - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizer, diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index ed764062e072f2..e0e397af2cffc6 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -25,7 +25,7 @@ import AssignmentReasonRecorder, { RRReassignmentType, } from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder"; import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; @@ -342,13 +342,13 @@ export const roundRobinReassignment = async ({ ...(platformClientParams ? platformClientParams : {}), }; - const hideBranding = await shouldHideBrandingForEvent({ + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizer, organizationId: orgId ?? null, }); - (evt as any).hideBranding = hideBranding; + evt.hideBranding = hideBranding; if (hasOrganizerChanged) { // location might changed and will be new created in eventManager.create (organizer default location) diff --git a/packages/features/ee/workflows/lib/service/WorkflowService.test.ts b/packages/features/ee/workflows/lib/service/WorkflowService.test.ts index 04c6a03bd68bdb..1608de7699c174 100644 --- a/packages/features/ee/workflows/lib/service/WorkflowService.test.ts +++ b/packages/features/ee/workflows/lib/service/WorkflowService.test.ts @@ -13,9 +13,10 @@ vi.mock("@calcom/features/tasker"); const mockScheduleWorkflowReminders = vi.mocked(scheduleWorkflowReminders); const mockTasker = vi.mocked(tasker); -// Mock the getHideBranding function to return false +// Mock the hideBranding functions to return false vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ getHideBranding: vi.fn().mockResolvedValue(false), + getHideBrandingWithPrisma: vi.fn().mockResolvedValue(false), })); describe("WorkflowService.scheduleFormWorkflows", () => { diff --git a/packages/features/ee/workflows/lib/service/WorkflowService.ts b/packages/features/ee/workflows/lib/service/WorkflowService.ts index c57529e9078ea3..b9dcbfa0ebcc1e 100644 --- a/packages/features/ee/workflows/lib/service/WorkflowService.ts +++ b/packages/features/ee/workflows/lib/service/WorkflowService.ts @@ -6,7 +6,7 @@ import type { timeUnitLowerCase } from "@calcom/ee/workflows/lib/reminders/smsRe import type { Workflow } from "@calcom/ee/workflows/lib/types"; import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; -import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; +import { getHideBrandingWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { tasker } from "@calcom/features/tasker"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { prisma } from "@calcom/prisma"; @@ -121,9 +121,10 @@ export class WorkflowService { } } - const hideBranding = await getHideBranding({ + const hideBranding = await getHideBrandingWithPrisma({ userId: form.userId, teamId: form.teamId ?? undefined, + prisma, }); await scheduleWorkflowReminders({ diff --git a/packages/features/profile/lib/hideBranding.ts b/packages/features/profile/lib/hideBranding.ts index bfe44c99734246..e25b443dd8a10e 100644 --- a/packages/features/profile/lib/hideBranding.ts +++ b/packages/features/profile/lib/hideBranding.ts @@ -2,11 +2,9 @@ import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepos import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import logger from "@calcom/lib/logger"; -import { prisma } from "@calcom/prisma"; +import { PrismaClient } from "@calcom/prisma"; const log = logger.getSubLogger({ name: "hideBranding" }); -const teamRepository = new TeamRepository(prisma); -const userRepository = new UserRepository(prisma); export type TeamWithBranding = { hideBranding: boolean | null; parent: { @@ -31,7 +29,6 @@ export type UserWithProfileAndBranding = UserWithBranding & { // Internal type aliases for backward compatibility type Team = TeamWithBranding; -type Profile = ProfileWithBranding; type UserWithoutProfile = UserWithBranding; type UserWithProfile = UserWithProfileAndBranding; @@ -79,9 +76,13 @@ function resolveHideBranding(options: { export async function getHideBranding({ userId, teamId, + teamRepository, + userRepository, }: { userId?: number; teamId?: number; + teamRepository: TeamRepository; + userRepository: UserRepository; }): Promise { if (teamId) { // Get team data with parent organization @@ -256,3 +257,51 @@ export function shouldHideBrandingForUserEvent({ eventTypeId, }); } + +/** + * Convenience function that creates repositories with a PrismaClient instance + * This maintains backward compatibility for existing code + */ +export async function getHideBrandingWithPrisma({ + userId, + teamId, + prisma, +}: { + userId?: number; + teamId?: number; + prisma: PrismaClient; +}): Promise { + const teamRepository = new TeamRepository(prisma); + const userRepository = new UserRepository(prisma); + + return getHideBranding({ + userId, + teamId, + teamRepository, + userRepository, + }); +} + +/** + * Convenience function that creates repositories with a PrismaClient instance + * This maintains backward compatibility for existing code + */ +export async function shouldHideBrandingForEventWithPrisma({ + eventTypeId, + team, + owner, + organizationId, +}: { + eventTypeId: number; + team: Team | null; + owner: UserWithoutProfile | null; + organizationId: number | null; +}) { + // ProfileRepository is a static class, so we don't need to instantiate it + return shouldHideBrandingForEvent({ + eventTypeId, + team, + owner, + organizationId, + }); +} diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 2a1269161104d9..3d9304d87cf05d 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -3,7 +3,7 @@ import dayjs from "@calcom/dayjs"; import { sendAddGuestsEmails } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; @@ -248,7 +248,7 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => { }); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEvent({ + hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index b047b5e554f5f8..7d26ce29a21d17 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -10,7 +10,7 @@ import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/proc import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import { workflowSelect } from "@calcom/features/ee/workflows/lib/getAllWorkflows"; import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; @@ -377,7 +377,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { }); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEvent({ + hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventTypeIdForBranding, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index 0f51e2739c392c..b754166bff575c 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -7,7 +7,7 @@ import { sendLocationChangeEmailsAndSMS } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { buildCalEventFromBooking } from "@calcom/lib/buildCalEventFromBooking"; @@ -295,7 +295,7 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { }); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEvent({ + hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, From c411cc789a275b48f33d336cde805077dc820f9e Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 27 Oct 2025 02:44:18 +0530 Subject: [PATCH 19/58] update orgId passing logic and logger --- .../bookings/lib/getBookingToDelete.ts | 1 + .../bookings/lib/handleCancelBooking.ts | 18 +++++----- .../bookings/lib/handleConfirmation.ts | 6 +--- .../bookings/lib/payment/handleNoShowFee.ts | 12 ++++++- .../repositories/BookingRepository.ts | 28 +++++++++++++++ .../credentials/handleDeleteCredential.ts | 18 ++++++++-- .../loggedInViewer/connectAndJoin.handler.ts | 36 +++++++++++++++++-- .../viewer/bookings/addGuests.handler.ts | 9 +++-- .../viewer/bookings/confirm.handler.ts | 5 +-- .../viewer/bookings/editLocation.handler.ts | 11 +++--- packages/types/Calendar.d.ts | 2 +- 11 files changed, 112 insertions(+), 34 deletions(-) diff --git a/packages/features/bookings/lib/getBookingToDelete.ts b/packages/features/bookings/lib/getBookingToDelete.ts index a68a2c9f2e444f..2753946b02a069 100644 --- a/packages/features/bookings/lib/getBookingToDelete.ts +++ b/packages/features/bookings/lib/getBookingToDelete.ts @@ -24,6 +24,7 @@ export async function getBookingToDelete(id: number | undefined, uid: string | u destinationCalendar: true, locale: true, hideBranding: true, + organizationId: true, }, }, location: true, diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 71d93db2d1f58c..9a7158e95369c3 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -241,25 +241,27 @@ async function handler(input: CancelBookingInput) { }, }); - const bookerUrl = await getBookerBaseUrl( - bookingToDelete.eventType?.team?.parentId ?? ownerProfile?.organizationId ?? null - ); + const organizationId = + bookingToDelete.eventType?.team?.parentId ?? + bookingToDelete.eventType?.parent?.teamId ?? + bookingToDelete.user?.organizationId ?? + ownerProfile?.organizationId ?? + null; + + const bookerUrl = await getBookerBaseUrl(organizationId); // Use existing data from bookingToDelete - no additional queries needed! let hideBranding = false; if (!bookingToDelete.eventTypeId) { - log.warn("Booking missing eventTypeId, defaulting hideBranding to false", { - bookingId: bookingToDelete.id, - userId: bookingToDelete.userId, - }); + log.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: bookingToDelete.eventTypeId, team: bookingToDelete.eventType?.team ?? null, owner: bookingToDelete.user ?? null, - organizationId: bookingToDelete.eventType?.team?.parentId ?? null, + organizationId: organizationId, }); } diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index de17016228463f..c189eac516cee3 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -124,10 +124,7 @@ export async function handleConfirmation(args: { let hideBranding = false; if (!eventTypeId) { - log.warn("Booking missing eventTypeId, defaulting hideBranding to false", { - bookingId: booking.id, - userId: booking.userId, - }); + log.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { hideBranding = await shouldHideBrandingForEventWithPrisma({ @@ -153,7 +150,6 @@ export async function handleConfirmation(args: { metadata.entryPoints = results[0].createdEvent?.entryPoints; } try { - const eventType = booking.eventType; let isHostConfirmationEmailsDisabled = false; let isAttendeeConfirmationEmailDisabled = false; diff --git a/packages/features/bookings/lib/payment/handleNoShowFee.ts b/packages/features/bookings/lib/payment/handleNoShowFee.ts index a0ba7cabaa4b12..d2f3c5e7747f78 100644 --- a/packages/features/bookings/lib/payment/handleNoShowFee.ts +++ b/packages/features/bookings/lib/payment/handleNoShowFee.ts @@ -35,6 +35,7 @@ export const handleNoShowFee = async ({ locale: string | null; timeZone: string; hideBranding: boolean; + organizationId: number | null; } | null; eventType: { title: string; @@ -46,6 +47,9 @@ export const handleNoShowFee = async ({ parentId: number | null; parent: { hideBranding: boolean | null } | null; } | null; + parent?: { + teamId: number | null; + } | null; metadata?: Prisma.JsonValue; } | null; attendees: { @@ -166,11 +170,17 @@ export const handleNoShowFee = async ({ throw new Error("Payment processing failed"); } + const organizationId = + booking.eventType?.team?.parentId ?? + booking.eventType?.parent?.teamId ?? + booking.user?.organizationId ?? + null; + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, - organizationId: booking.eventType?.team?.parentId ?? null, + organizationId: organizationId, }); await sendNoShowFeeChargedEmail(attendee, { ...evt, hideBranding }, eventTypeMetdata); diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index aad9ba1595817c..d32e614836bc12 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1156,6 +1156,34 @@ export class BookingRepository { hideOrganizerEmail: true, teamId: true, metadata: true, + team: { + select: { + id: true, + hideBranding: true, + parentId: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }, + parent: { + select: { + teamId: true, + }, + }, + }, + }, + user: { + select: { + id: true, + email: true, + name: true, + locale: true, + timeZone: true, + hideBranding: true, + organizationId: true, }, }, payment: { diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 044ef2ebbc0a09..6f6d76d416cf62 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -239,6 +239,7 @@ const handleDeleteCredential = async ({ destinationCalendar: true, locale: true, hideBranding: true, + organizationId: true, }, }, location: true, @@ -265,6 +266,7 @@ const handleDeleteCredential = async ({ id: true, name: true, hideBranding: true, + parentId: true, parent: { select: { hideBranding: true, @@ -272,6 +274,12 @@ const handleDeleteCredential = async ({ }, }, }, + parent: { + // ✅ Add this entire block + select: { + teamId: true, + }, + }, metadata: true, }, }, @@ -328,11 +336,17 @@ const handleDeleteCredential = async ({ const attendeesList = await Promise.all(attendeesListPromises); const tOrganizer = await getTranslation(booking?.user?.locale ?? "en", "common"); + const organizationId = + booking.eventType?.team?.parentId ?? + booking.eventType?.parent?.teamId ?? + booking.user?.organizationId ?? + null; + const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, - owner: booking.user ?? null, - organizationId: null, + owner: booking.eventType?.team ? null : booking.user ?? null, + organizationId: organizationId, }); await sendCancelledEmailsAndSMS( diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts index 67648d47349e2c..7ac5447680f861 100644 --- a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts @@ -1,6 +1,7 @@ import { sendScheduledEmailsAndSMS } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { scheduleNoShowTriggers } from "@calcom/features/bookings/lib/handleNewBooking/scheduleNoShowTriggers"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { getTranslation } from "@calcom/lib/server/i18n"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; @@ -115,7 +116,12 @@ export const Handler = async ({ ctx, input }: Options) => { eventType: { select: { id: true, - owner: true, + owner: { + select: { + id: true, + hideBranding: true, + }, + }, teamId: true, title: true, slug: true, @@ -135,6 +141,18 @@ export const Handler = async ({ ctx, input }: Options) => { select: { id: true, name: true, + parentId: true, + hideBranding: true, + parent: { + select: { + hideBranding: true, + }, + }, + }, + }, + parent: { + select: { + teamId: true, }, }, }, @@ -186,10 +204,24 @@ export const Handler = async ({ ctx, input }: Options) => { const attendeesList = await Promise.all(attendeesListPromises); + const organizationId = + updatedBooking.eventType?.team?.parentId ?? + updatedBooking.eventType?.parent?.teamId ?? + user.organization.id ?? + null; + + const hideBranding = await shouldHideBrandingForEventWithPrisma({ + eventTypeId: updatedBooking.eventTypeId ?? 0, + team: updatedBooking.eventType?.team ?? null, + owner: updatedBooking.eventType?.team ? null : updatedBooking.eventType?.owner ?? null, + organizationId: organizationId, + }); + const evt: CalendarEvent = { type: updatedBooking?.eventType?.slug as string, title: updatedBooking.title, description: updatedBooking.description, + hideBranding: hideBranding, ...getCalEventResponses({ bookingFields: eventType?.bookingFields ?? null, booking: updatedBooking, @@ -227,7 +259,7 @@ export const Handler = async ({ ctx, input }: Options) => { await sendScheduledEmailsAndSMS( { ...evt, - hideBranding: evt.hideBranding ?? false, + hideBranding: hideBranding, }, undefined, false, diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 3d9304d87cf05d..66c95e699de5f4 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -70,6 +70,7 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => { destinationCalendar: true, credentials: true, hideBranding: true, + organizationId: true, }, }, }, @@ -242,17 +243,15 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => { const eventTypeId = booking.eventTypeId; let hideBranding = false; if (!eventTypeId) { - console.warn("Booking missing eventTypeId, defaulting hideBranding to false", { - bookingId: booking.id, - userId: booking.userId, - }); + console.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { + const organizationId = booking.eventType?.team?.parentId ?? booking.user?.organizationId ?? null; hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, - organizationId: null, // Will be fetched by the function if needed + organizationId: organizationId, }); } diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 7d26ce29a21d17..7a58d489df2a9e 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -371,10 +371,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { let hideBranding = false; if (!eventTypeIdForBranding) { - console.warn("Booking missing eventTypeId, defaulting hideBranding to false", { - bookingId: booking.id, - userId: booking.userId, - }); + console.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { hideBranding = await shouldHideBrandingForEventWithPrisma({ diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index b754166bff575c..a8d8a8a06f83d7 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -8,6 +8,7 @@ import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { buildCalEventFromBooking } from "@calcom/lib/buildCalEventFromBooking"; @@ -251,6 +252,7 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { const { booking, user: loggedInUser } = ctx; const organizer = await new UserRepository(prisma).findByIdOrThrow({ id: booking.userId || 0 }); + const ownerProfile = await ProfileRepository.findFirstForUserId({ userId: booking.userId || 0 }); const newLocationInEvtFormat = await getLocationInEvtFormatOrThrow({ location: newLocation, @@ -289,17 +291,14 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { const eventTypeId = booking.eventTypeId; let hideBranding = false; if (!eventTypeId) { - console.warn("Booking missing eventTypeId, defaulting hideBranding to false", { - bookingId: booking.id, - userId: booking.userId, - }); + logger.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, - organizationId: booking.eventType?.team?.parentId ?? null, + organizationId: booking.eventType?.team?.parentId ?? ownerProfile?.organizationId ?? null, }); } @@ -308,7 +307,7 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { booking?.eventType?.metadata as EventTypeMetadata ); } catch (error) { - console.log("Error sending LocationChangeEmails", safeStringify(error)); + logger.error("Error sending LocationChangeEmails", safeStringify(error)); } return { message: "Location updated" }; diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts index dc91dbdf4891e8..d77eaa3f270b31 100644 --- a/packages/types/Calendar.d.ts +++ b/packages/types/Calendar.d.ts @@ -193,7 +193,7 @@ export interface CalendarEvent { rejectionReason?: string | null; hideCalendarNotes?: boolean; hideCalendarEventDetails?: boolean; - hideBranding?: boolean + hideBranding?: boolean; recurrence?: string; recurringEvent?: RecurringEvent | null; eventTypeId?: number | null; From 1d301ba42f7dd9a72a27041dad6bcb4eca8b889f Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 27 Oct 2025 04:07:18 +0530 Subject: [PATCH 20/58] address cubics comments --- .../bookings/lib/BookingEmailSmsHandler.ts | 12 ++++++------ .../features/bookings/lib/handleCancelBooking.ts | 1 - .../features/bookings/lib/payment/getBooking.ts | 1 + .../bookings/lib/payment/handleNoShowFee.ts | 6 +----- packages/features/conferencing/lib/videoClient.ts | 4 ++-- .../features/credentials/handleDeleteCredential.ts | 6 +----- packages/features/ee/payments/api/webhook.ts | 14 ++++++++++++-- .../loggedInViewer/connectAndJoin.handler.ts | 6 +----- .../trpc/server/routers/viewer/bookings/util.ts | 1 - 9 files changed, 24 insertions(+), 27 deletions(-) diff --git a/packages/features/bookings/lib/BookingEmailSmsHandler.ts b/packages/features/bookings/lib/BookingEmailSmsHandler.ts index 6761f29baab1cf..265af7176338fb 100644 --- a/packages/features/bookings/lib/BookingEmailSmsHandler.ts +++ b/packages/features/bookings/lib/BookingEmailSmsHandler.ts @@ -132,7 +132,7 @@ export class BookingEmailSmsHandler { additionalInformation, additionalNotes, cancellationReason: `$RCH$${rescheduleReason || ""}`, - }), + }, true), metadata ); } @@ -228,17 +228,17 @@ export class BookingEmailSmsHandler { try { await Promise.all([ sendRoundRobinRescheduledEmailsAndSMS( - withHideBranding({ ...copyEventAdditionalInfo, iCalUID }), + withHideBranding({ ...copyEventAdditionalInfo, iCalUID }, true), rescheduledMembers, metadata ), sendRoundRobinScheduledEmailsAndSMS({ - calEvent: withHideBranding(copyEventAdditionalInfo), + calEvent: withHideBranding(copyEventAdditionalInfo, true), members: newBookedMembers, eventTypeMetadata: metadata, }), sendRoundRobinCancelledEmailsAndSMS( - withHideBranding(cancelledRRHostEvt), + withHideBranding(cancelledRRHostEvt, true), cancelledMembers, metadata, reassignedTo @@ -282,7 +282,7 @@ export class BookingEmailSmsHandler { try { await sendScheduledEmailsAndSMS( - withHideBranding({ ...evt, additionalInformation, additionalNotes, customInputs }), + withHideBranding({ ...evt, additionalInformation, additionalNotes, customInputs }, true), eventNameObject, isHostConfirmationEmailsDisabled, isAttendeeConfirmationEmailDisabled, @@ -312,7 +312,7 @@ export class BookingEmailSmsHandler { safeStringify({ calEvent: getPiiFreeCalendarEvent(evt) }) ); - const eventWithNotes = withHideBranding({ ...evt, additionalNotes }); + const eventWithNotes = withHideBranding({ ...evt, additionalNotes }, true); try { await Promise.all([ diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 9a7158e95369c3..12894191c21839 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -243,7 +243,6 @@ async function handler(input: CancelBookingInput) { const organizationId = bookingToDelete.eventType?.team?.parentId ?? - bookingToDelete.eventType?.parent?.teamId ?? bookingToDelete.user?.organizationId ?? ownerProfile?.organizationId ?? null; diff --git a/packages/features/bookings/lib/payment/getBooking.ts b/packages/features/bookings/lib/payment/getBooking.ts index 3182f91f4d1df5..6b34a75eb8ca24 100644 --- a/packages/features/bookings/lib/payment/getBooking.ts +++ b/packages/features/bookings/lib/payment/getBooking.ts @@ -118,6 +118,7 @@ export async function getBooking(bookingId: number) { destinationCalendar: true, isPlatformManaged: true, hideBranding: true, + organizationId: true, }, }, }, diff --git a/packages/features/bookings/lib/payment/handleNoShowFee.ts b/packages/features/bookings/lib/payment/handleNoShowFee.ts index d2f3c5e7747f78..e40f656962876d 100644 --- a/packages/features/bookings/lib/payment/handleNoShowFee.ts +++ b/packages/features/bookings/lib/payment/handleNoShowFee.ts @@ -170,11 +170,7 @@ export const handleNoShowFee = async ({ throw new Error("Payment processing failed"); } - const organizationId = - booking.eventType?.team?.parentId ?? - booking.eventType?.parent?.teamId ?? - booking.user?.organizationId ?? - null; + const organizationId = booking.eventType?.team?.parentId ?? booking.user?.organizationId ?? null; const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventTypeId ?? 0, diff --git a/packages/features/conferencing/lib/videoClient.ts b/packages/features/conferencing/lib/videoClient.ts index 9025ea2353ab44..4848cf7edad21f 100644 --- a/packages/features/conferencing/lib/videoClient.ts +++ b/packages/features/conferencing/lib/videoClient.ts @@ -80,7 +80,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv returnObject = { ...returnObject, createdEvent: createdMeeting, success: true }; log.debug("created Meeting", safeStringify(returnObject)); } catch (err) { - await sendBrokenIntegrationEmail(withHideBranding(calEvent), "video"); + await sendBrokenIntegrationEmail(withHideBranding(calEvent, true), "video"); log.error( "createMeeting failed", safeStringify(err), @@ -109,7 +109,7 @@ const updateMeeting = async ( const canCallUpdateMeeting = !!(credential && bookingRef); const updatedMeeting = canCallUpdateMeeting ? await firstVideoAdapter?.updateMeeting(bookingRef, calEvent).catch(async (e) => { - await sendBrokenIntegrationEmail(withHideBranding(calEvent), "video"); + await sendBrokenIntegrationEmail(withHideBranding(calEvent, true), "video"); log.error("updateMeeting failed", e, calEvent); success = false; return undefined; diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 6f6d76d416cf62..f3d1018ad5c7b1 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -336,11 +336,7 @@ const handleDeleteCredential = async ({ const attendeesList = await Promise.all(attendeesListPromises); const tOrganizer = await getTranslation(booking?.user?.locale ?? "en", "common"); - const organizationId = - booking.eventType?.team?.parentId ?? - booking.eventType?.parent?.teamId ?? - booking.user?.organizationId ?? - null; + const organizationId = booking.eventType?.team?.parentId ?? booking.user?.organizationId ?? null; const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventTypeId ?? 0, diff --git a/packages/features/ee/payments/api/webhook.ts b/packages/features/ee/payments/api/webhook.ts index ba2adb40b5e315..03d5076b3f6faa 100644 --- a/packages/features/ee/payments/api/webhook.ts +++ b/packages/features/ee/payments/api/webhook.ts @@ -5,6 +5,7 @@ import type Stripe from "stripe"; import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import { sendAttendeeRequestEmailAndSMS, sendOrganizerRequestEmail } from "@calcom/emails"; +import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { doesBookingRequireConfirmation } from "@calcom/features/bookings/lib/doesBookingRequireConfirmation"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation"; @@ -12,7 +13,7 @@ import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; import stripe from "@calcom/features/ee/payments/server/stripe"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; -import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { IS_PRODUCTION } from "@calcom/lib/constants"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; @@ -128,7 +129,16 @@ const handleSetupSuccess = async (event: Stripe.Event) => { platformClientParams: platformOAuthClient ? getPlatformParams(platformOAuthClient) : undefined, }); } else if (areEmailsEnabled) { - const evtWithBranding = { ...evt, hideBranding: evt.hideBranding ?? false }; + const organizationId = booking.eventType?.team?.parentId ?? user.organizationId ?? null; + + const hideBranding = await shouldHideBrandingForEventWithPrisma({ + eventTypeId: booking.eventTypeId ?? 0, + team: booking.eventType?.team ?? null, + owner: booking.eventType?.team ? null : user ?? null, + organizationId: organizationId, + }); + + const evtWithBranding = { ...evt, hideBranding }; await sendOrganizerRequestEmail(evtWithBranding, eventType.metadata); await sendAttendeeRequestEmailAndSMS(evtWithBranding, evt.attendees[0], eventType.metadata); } diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts index 7ac5447680f861..db8b39f837ef36 100644 --- a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts @@ -204,11 +204,7 @@ export const Handler = async ({ ctx, input }: Options) => { const attendeesList = await Promise.all(attendeesListPromises); - const organizationId = - updatedBooking.eventType?.team?.parentId ?? - updatedBooking.eventType?.parent?.teamId ?? - user.organization.id ?? - null; + const organizationId = updatedBooking.eventType?.team?.parentId ?? user.organization.id ?? null; const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: updatedBooking.eventTypeId ?? 0, diff --git a/packages/trpc/server/routers/viewer/bookings/util.ts b/packages/trpc/server/routers/viewer/bookings/util.ts index 5fa738656ddd2c..e417a40607c4dc 100644 --- a/packages/trpc/server/routers/viewer/bookings/util.ts +++ b/packages/trpc/server/routers/viewer/bookings/util.ts @@ -52,7 +52,6 @@ export const bookingsProcedure = authedProcedure include: { destinationCalendar: true, credentials: true, - hideBranding: true, }, }, }; From a108677c1e5a9413f476638116e8a8bdc0a256fb Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 27 Oct 2025 04:51:06 +0530 Subject: [PATCH 21/58] use proper types instead of any --- .../reschedule/owner/combineTwoSeatedBookings.ts | 9 ++++----- .../reschedule/owner/moveSeatedBookingToNewTimeSlot.ts | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index dd128311200895..c296053881b299 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports + import { cloneDeep } from "lodash"; import { uuid } from "short-uuid"; @@ -136,7 +136,6 @@ const combineTwoSeatedBookings = async ( : calendarResult?.updatedEvent?.iCalUID || undefined; if (noEmail !== true && isConfirmedByDefault) { - // Use pre-fetched branding data from eventType const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, @@ -148,10 +147,10 @@ const combineTwoSeatedBookings = async ( await sendRescheduledEmailsAndSMS( { ...copyEvent, - additionalNotes, - cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, + additionalNotes, // Resets back to the additionalNote input and not the override value + cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, // Removable code prefix to differentiate cancellation from rescheduling for email hideBranding, - } as any, + }, eventType.metadata ); } diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index e21007a93afc15..99140641f3a5f8 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports + import { cloneDeep } from "lodash"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; @@ -31,7 +31,6 @@ const moveSeatedBookingToNewTimeSlot = async ( additionalNotes, } = rescheduleSeatedBookingObject; - // Use pre-fetched branding data from eventType const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, @@ -103,10 +102,10 @@ const moveSeatedBookingToNewTimeSlot = async ( await sendRescheduledEmailsAndSMS( { ...copyEvent, - additionalNotes, - cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, + additionalNotes, // Resets back to the additionalNote input and not the override value + cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, // Removable code prefix to differentiate cancellation from rescheduling for email hideBranding, - } as any, + }, eventType.metadata ); } From 89b9f2b451f44c63a5cbc2c341b7a3240b8241ba Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 27 Oct 2025 05:25:23 +0530 Subject: [PATCH 22/58] remove comments --- apps/web/app/api/cron/bookingReminder/route.ts | 1 - packages/features/bookings/lib/handleCancelBooking.ts | 1 - packages/features/bookings/lib/handleConfirmation.ts | 1 - .../reschedule/attendee/attendeeRescheduleSeatedBooking.ts | 3 +-- .../handleSeats/reschedule/owner/combineTwoSeatedBookings.ts | 1 - packages/features/credentials/handleDeleteCredential.ts | 1 - .../server/routers/viewer/bookings/editLocation.handler.ts | 1 - 7 files changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 933b998a50c617..8491e3609e2032 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -132,7 +132,6 @@ async function postHandler(request: NextRequest) { const attendeesList = await Promise.all(attendeesListPromises); const selectedDestinationCalendar = booking.destinationCalendar || user.destinationCalendar; - // Use pre-fetched branding data from booking query const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 12894191c21839..27cb450dac8862 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -249,7 +249,6 @@ async function handler(input: CancelBookingInput) { const bookerUrl = await getBookerBaseUrl(organizationId); - // Use existing data from bookingToDelete - no additional queries needed! let hideBranding = false; if (!bookingToDelete.eventTypeId) { diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index c189eac516cee3..2712301d3f6216 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -120,7 +120,6 @@ export async function handleConfirmation(args: { const eventTypeId = eventType?.id ?? booking.eventTypeId ?? null; - // Use existing data from booking - no additional queries needed! let hideBranding = false; if (!eventTypeId) { diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index f7661fe6c86073..f4ea4be2e40be8 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports + import { cloneDeep } from "lodash"; import { sendRescheduledSeatEmailAndSMS } from "@calcom/emails"; @@ -23,7 +23,6 @@ const attendeeRescheduleSeatedBooking = async ( let { originalRescheduledBooking } = rescheduleSeatedBookingObject; const { organizerUser } = rescheduleSeatedBookingObject; - // Use pre-fetched branding data from eventType const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventType.id, team: eventType.team ?? null, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index c296053881b299..0142c6ad57257e 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -1,4 +1,3 @@ - import { cloneDeep } from "lodash"; import { uuid } from "short-uuid"; diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index f3d1018ad5c7b1..1dc9d31f6c595d 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -275,7 +275,6 @@ const handleDeleteCredential = async ({ }, }, parent: { - // ✅ Add this entire block select: { teamId: true, }, diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index a8d8a8a06f83d7..f2071d06a54346 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -287,7 +287,6 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { }); try { - // Use pre-fetched branding data from booking context const eventTypeId = booking.eventTypeId; let hideBranding = false; if (!eventTypeId) { From 3c6b2372c5764858505ded6149419ea01aab519c Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Wed, 5 Nov 2025 20:55:31 +0530 Subject: [PATCH 23/58] use prisma from repository --- .../repositories/eventTypeRepository.ts | 14 ++++ .../viewer/bookings/addGuests.handler.ts | 72 ++++++------------- 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/packages/features/eventtypes/repositories/eventTypeRepository.ts b/packages/features/eventtypes/repositories/eventTypeRepository.ts index 2e2e5da67415e7..93a7150b2f82da 100644 --- a/packages/features/eventtypes/repositories/eventTypeRepository.ts +++ b/packages/features/eventtypes/repositories/eventTypeRepository.ts @@ -1527,4 +1527,18 @@ export class EventTypeRepository { }, }); } + + async findOwnerHideBranding({ eventTypeId }: { eventTypeId: number }) { + return this.prismaClient.eventType.findUnique({ + where: { id: eventTypeId }, + select: { + owner: { + select: { + id: true, + hideBranding: true, + }, + }, + }, + }); + } } diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index db5a873cc71947..dacfdecf742e59 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -4,6 +4,8 @@ import dayjs from "@calcom/dayjs"; import { BookingEmailSmsHandler } from "@calcom/features/bookings/lib/BookingEmailSmsHandler"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; +import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; +import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/EventTypeRepository"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; @@ -87,69 +89,41 @@ async function getBooking(bookingId: number) { throw new TRPCError({ code: "NOT_FOUND", message: "booking_not_found" }); } - // Fetch additional data needed for branding logic - // The repository method doesn't include eventType.team, eventType.owner, or user.organizationId/hideBranding - const [eventTypeWithTeam, userWithBranding] = await Promise.all([ - booking.eventTypeId - ? prisma.eventType.findUnique({ - where: { id: booking.eventTypeId }, - select: { - id: true, - team: { - select: { - id: true, - name: true, - parentId: true, - hideBranding: true, - parent: { - select: { - id: true, - hideBranding: true, - }, - }, - }, - }, - owner: { - select: { - id: true, - hideBranding: true, - }, - }, - }, - }) + // Fetch additional data needed for branding logic via repositories + const teamRepository = new TeamRepository(prisma); + const userRepository = new UserRepository(prisma); + const eventTypeRepository = new EventTypeRepository(prisma); + + const [teamBranding, userWithBranding, ownerBranding] = await Promise.all([ + booking.eventType?.teamId + ? teamRepository.findTeamWithParentHideBranding({ teamId: booking.eventType.teamId }) : Promise.resolve(null), booking.userId - ? prisma.user.findUnique({ - where: { id: booking.userId }, - select: { - id: true, - organizationId: true, - hideBranding: true, - }, - }) + ? userRepository.findUserWithHideBranding({ userId: booking.userId }) + : Promise.resolve(null), + booking.eventTypeId + ? eventTypeRepository.findOwnerHideBranding({ eventTypeId: booking.eventTypeId }) : Promise.resolve(null), ]); // Attach fetched data to booking object with proper type handling - if (eventTypeWithTeam && booking.eventType) { + if (teamBranding && booking.eventType) { ( booking.eventType as typeof booking.eventType & { - team: typeof eventTypeWithTeam.team; - owner: typeof eventTypeWithTeam.owner; + team: typeof teamBranding; } - ).team = eventTypeWithTeam.team; + ).team = teamBranding; + } + + if (ownerBranding && booking.eventType) { ( booking.eventType as typeof booking.eventType & { - team: typeof eventTypeWithTeam.team; - owner: typeof eventTypeWithTeam.owner; + owner: NonNullable["owner"] | null; } - ).owner = eventTypeWithTeam.owner; + ).owner = ownerBranding.owner ?? null; } if (userWithBranding && booking.user) { - ( - booking.user as typeof booking.user & { organizationId: number | null; hideBranding: boolean | null } - ).organizationId = userWithBranding.organizationId; ( booking.user as typeof booking.user & { organizationId: number | null; hideBranding: boolean | null } ).hideBranding = userWithBranding.hideBranding; @@ -427,7 +401,7 @@ async function sendGuestNotifications( hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team, - owner: user ?? null, + owner: eventType?.owner ?? null, organizationId: organizationId, }); } From 5b68ef4e32b5429aedfbb1e59ee39bd643d1e503 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 6 Nov 2025 01:05:48 +0530 Subject: [PATCH 24/58] address cubics comment --- .../features/bookings/lib/handleBookingRequested.ts | 5 +---- .../lib/handleSeats/cancel/cancelAttendeeSeat.ts | 12 +++++++++++- .../attendee/attendeeRescheduleSeatedBooking.ts | 6 ++++-- .../reschedule/owner/combineTwoSeatedBookings.ts | 5 ++++- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index fd99be3b92284e..614da2a6ef455a 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -72,10 +72,7 @@ export async function handleBookingRequested(args: { let hideBranding = false; if (!eventTypeId) { - log.warn("Booking missing eventTypeId, defaulting hideBranding to false", { - bookingId: booking.id, - userId: booking.userId, - }); + log.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { hideBranding = await shouldHideBrandingForEventWithPrisma({ diff --git a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts index 8166bdb3633ad9..a8f01913e4483b 100644 --- a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts +++ b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts @@ -117,6 +117,7 @@ async function cancelAttendeeSeat( try { await Promise.all(integrationsToUpdate); } catch (error) { + logger.error(`Error updating integrations for event: ${evt.bookingId}, bookingUid: ${evt.uid}`, safeStringify(error)); // Shouldn't stop code execution if integrations fail // as integrations was already updated } @@ -124,7 +125,16 @@ async function cancelAttendeeSeat( const tAttendees = await getTranslation(attendee.locale ?? "en", "common"); await sendCancelledSeatEmailsAndSMS( - { ...evt, hideBranding: !!(bookingToDelete.eventType?.team?.hideBranding || bookingToDelete.eventType?.team?.parent?.hideBranding || bookingToDelete.user?.hideBranding) }, + { + ...evt, + hideBranding: + evt.hideBranding || + !!( + bookingToDelete.eventType?.team?.hideBranding || + bookingToDelete.eventType?.team?.parent?.hideBranding || + bookingToDelete.user?.hideBranding + ), + }, { ...attendee, language: { translate: tAttendees, locale: attendee.locale ?? "en" }, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index f4ea4be2e40be8..ffffa3cd4b18c9 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -1,4 +1,3 @@ - import { cloneDeep } from "lodash"; import { sendRescheduledSeatEmailAndSMS } from "@calcom/emails"; @@ -27,7 +26,10 @@ const attendeeRescheduleSeatedBooking = async ( eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, - organizationId: eventType.team?.parentId ?? null, + organizationId: + eventType.team?.parentId ?? + (organizerUser as { organizationId?: number | null })?.organizationId ?? + null, }); seatAttendee["language"] = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" }; diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index 0142c6ad57257e..4e7e2da26cba3a 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -139,7 +139,10 @@ const combineTwoSeatedBookings = async ( eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, - organizationId: eventType.team?.parentId ?? null, + organizationId: + eventType.team?.parentId ?? + (organizerUser as { organizationId?: number | null })?.organizationId ?? + null, }); // TODO send reschedule emails to attendees of the old booking loggerWithEventDetails.debug("Emails: Sending reschedule emails - handleSeats"); From 1ad1fbfe522ec669bb288f25e0c9852fed5a1f45 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 6 Nov 2025 01:37:07 +0530 Subject: [PATCH 25/58] address cubics comment --- .../reschedule/owner/moveSeatedBookingToNewTimeSlot.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index 99140641f3a5f8..99f8ffb7a7100f 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -1,4 +1,3 @@ - import { cloneDeep } from "lodash"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; @@ -35,7 +34,10 @@ const moveSeatedBookingToNewTimeSlot = async ( eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, - organizationId: eventType.team?.parentId ?? null, + organizationId: + eventType.team?.parentId ?? + (organizerUser as { organizationId?: number | null })?.organizationId ?? + null, }); let { evt } = rescheduleSeatedBookingObject; From 4686bf6d718743bde45832fc066efe16d79d9b5e Mon Sep 17 00:00:00 2001 From: Udit Takkar Date: Fri, 7 Nov 2025 19:51:06 +0530 Subject: [PATCH 26/58] chore: trigger again --- .../trpc/server/routers/viewer/bookings/addGuests.handler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index dacfdecf742e59..037e363264b2fd 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -34,7 +34,6 @@ type AddGuestsOptions = { input: TAddGuestsInputSchema; emailsEnabled?: boolean; }; - type Booking = NonNullable>>; type OrganizerData = Awaited>; From 790410d1e18cd3daf8447ccaec1ed3004e451f2d Mon Sep 17 00:00:00 2001 From: Udit Takkar Date: Fri, 7 Nov 2025 20:05:22 +0530 Subject: [PATCH 27/58] fix: type errro --- .../trpc/server/routers/viewer/bookings/addGuests.handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 037e363264b2fd..956a894a42f776 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -5,7 +5,7 @@ import { BookingEmailSmsHandler } from "@calcom/features/bookings/lib/BookingEma import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; -import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/EventTypeRepository"; +import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; From 8b9f34f1c90b2d1dd6bac80082cdd816cffa19e8 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 10 Nov 2025 13:35:18 +0530 Subject: [PATCH 28/58] added new method findByIdIncludeDestinationCalendarAndBranding and removed unnenessary logs --- .../bookings/lib/handleCancelBooking.ts | 10 +-- .../repositories/BookingRepository.ts | 42 ++++++++++ .../viewer/bookings/addGuests.handler.ts | 84 +++---------------- .../viewer/bookings/confirm.handler.ts | 5 +- .../viewer/bookings/editLocation.handler.ts | 5 +- 5 files changed, 58 insertions(+), 88 deletions(-) diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index bcd21df061225a..a5ba89d06d6980 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -262,10 +262,7 @@ async function handler(input: CancelBookingInput) { let hideBranding = false; - if (!bookingToDelete.eventTypeId) { - log.warn("Booking missing eventTypeId, defaulting hideBranding to false"); - hideBranding = false; - } else { + if (bookingToDelete.eventTypeId) { hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: bookingToDelete.eventTypeId, team: bookingToDelete.eventType?.team ?? null, @@ -274,11 +271,6 @@ async function handler(input: CancelBookingInput) { }); } - log.debug("Computed hideBranding", { - hideBranding, - eventTypeId: bookingToDelete.eventTypeId, - }); - const evt: CalendarEvent = { bookerUrl, title: bookingToDelete?.title, diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index e667a41ef6f5bf..2e128468079078 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1385,6 +1385,48 @@ export class BookingRepository { }); } + async findByIdIncludeDestinationCalendarAndBranding(bookingId: number) { + return await this.prismaClient.booking.findUnique({ + where: { + id: bookingId, + }, + include: { + attendees: true, + destinationCalendar: true, + references: true, + eventType: { + include: { + team: { + select: { + id: true, + parentId: true, + hideBranding: true, + parent: { + select: { + id: true, + hideBranding: true, + }, + }, + }, + }, + owner: { + select: { + id: true, + hideBranding: true, + }, + }, + }, + }, + user: { + include: { + destinationCalendar: true, + credentials: true, + }, + }, + }, + }); + } + async updateBookingAttendees({ bookingId, newAttendees, diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 956a894a42f776..b4e4216a178d7d 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -4,8 +4,6 @@ import dayjs from "@calcom/dayjs"; import { BookingEmailSmsHandler } from "@calcom/features/bookings/lib/BookingEmailSmsHandler"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; -import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; -import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; @@ -34,7 +32,9 @@ type AddGuestsOptions = { input: TAddGuestsInputSchema; emailsEnabled?: boolean; }; -type Booking = NonNullable>>; +type Booking = NonNullable< + Awaited> +>; type OrganizerData = Awaited>; export const addGuestsHandler = async ({ ctx, input, emailsEnabled = true }: AddGuestsOptions) => { @@ -82,52 +82,12 @@ export const addGuestsHandler = async ({ ctx, input, emailsEnabled = true }: Add async function getBooking(bookingId: number) { const bookingRepository = new BookingRepository(prisma); - const booking = await bookingRepository.findByIdIncludeDestinationCalendar(bookingId); + const booking = await bookingRepository.findByIdIncludeDestinationCalendarAndBranding(bookingId); if (!booking || !booking.user) { throw new TRPCError({ code: "NOT_FOUND", message: "booking_not_found" }); } - // Fetch additional data needed for branding logic via repositories - const teamRepository = new TeamRepository(prisma); - const userRepository = new UserRepository(prisma); - const eventTypeRepository = new EventTypeRepository(prisma); - - const [teamBranding, userWithBranding, ownerBranding] = await Promise.all([ - booking.eventType?.teamId - ? teamRepository.findTeamWithParentHideBranding({ teamId: booking.eventType.teamId }) - : Promise.resolve(null), - booking.userId - ? userRepository.findUserWithHideBranding({ userId: booking.userId }) - : Promise.resolve(null), - booking.eventTypeId - ? eventTypeRepository.findOwnerHideBranding({ eventTypeId: booking.eventTypeId }) - : Promise.resolve(null), - ]); - - // Attach fetched data to booking object with proper type handling - if (teamBranding && booking.eventType) { - ( - booking.eventType as typeof booking.eventType & { - team: typeof teamBranding; - } - ).team = teamBranding; - } - - if (ownerBranding && booking.eventType) { - ( - booking.eventType as typeof booking.eventType & { - owner: NonNullable["owner"] | null; - } - ).owner = ownerBranding.owner ?? null; - } - - if (userWithBranding && booking.user) { - ( - booking.user as typeof booking.user & { organizationId: number | null; hideBranding: boolean | null } - ).hideBranding = userWithBranding.hideBranding; - } - return booking; } @@ -369,39 +329,21 @@ async function sendGuestNotifications( ): Promise { const eventTypeId = booking.eventTypeId; let hideBranding = false; - if (!eventTypeId) { - logger.warn("Booking missing eventTypeId, defaulting hideBranding to false"); - hideBranding = false; - } else { - // Type assertion needed because repository doesn't include team/owner in type - const eventType = booking.eventType as - | (typeof booking.eventType & { - team?: { - parentId: number | null; - hideBranding: boolean | null; - parent: { hideBranding: boolean | null } | null; - } | null; - owner?: { id: number; hideBranding: boolean | null } | null; - }) - | null; - const user = booking.user as - | (typeof booking.user & { - organizationId: number | null; - }) - | null; - const organizationId = eventType?.team?.parentId ?? user?.organizationId ?? null; - // Convert team to match TeamWithBranding type (parent must be object or null, not undefined) - const team = eventType?.team + if (eventTypeId) { + const team = booking.eventType?.team ? { - hideBranding: eventType.team.hideBranding, - parent: eventType.team.parent ?? null, + hideBranding: booking.eventType.team.hideBranding, + parent: booking.eventType.team.parent ?? null, } : null; + + const organizationId = booking.eventType?.team?.parentId ?? null; + hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team, - owner: eventType?.owner ?? null, - organizationId: organizationId, + owner: booking.eventType?.owner ?? null, + organizationId, }); } diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index f58ec9de121950..55af6705c649dd 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -371,10 +371,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { const eventTypeIdForBranding = booking.eventTypeId ?? null; let hideBranding = false; - if (!eventTypeIdForBranding) { - console.warn("Booking missing eventTypeId, defaulting hideBranding to false"); - hideBranding = false; - } else { + if (eventTypeIdForBranding) { hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: eventTypeIdForBranding, team: booking.eventType?.team ?? null, diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index f2071d06a54346..a9fe928714f51e 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -289,10 +289,7 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { try { const eventTypeId = booking.eventTypeId; let hideBranding = false; - if (!eventTypeId) { - logger.warn("Booking missing eventTypeId, defaulting hideBranding to false"); - hideBranding = false; - } else { + if (eventTypeId) { hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, From 52edc3fed88436983ee472e3d9594db6608345bd Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 10 Nov 2025 15:17:50 +0530 Subject: [PATCH 29/58] address cubics comments --- .../_utils/payments/handlePaymentSuccess.ts | 2 +- .../repositories/BookingRepository.ts | 41 ++++++++++++++++--- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index 0b087b308b805c..9b6e7e77ba3c66 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -101,7 +101,7 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number) eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, owner: userWithCredentials ?? null, - organizationId: booking.eventType?.team?.parentId ?? null, + organizationId: booking.eventType?.team?.parentId ?? userWithCredentials?.organizationId ?? null, }); await sendScheduledEmailsAndSMS( diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index 2e128468079078..bab1913f391cec 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -5,7 +5,7 @@ import type { Prisma } from "@calcom/prisma/client"; import type { Booking } from "@calcom/prisma/client"; import { RRTimestampBasis, BookingStatus } from "@calcom/prisma/enums"; import { bookingMinimalSelect } from "@calcom/prisma/selects/booking"; -import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import { credentialForCalendarServiceSelect, safeCredentialSelect } from "@calcom/prisma/selects/credential"; export type FormResponse = Record< // Field ID @@ -610,6 +610,13 @@ export class BookingRepository { name: true, email: true, username: true, + locale: true, + hideBranding: true, + organizationId: true, + destinationCalendar: true, + credentials: { + select: safeCredentialSelect, + }, }, }, references: { @@ -642,7 +649,9 @@ export class BookingRepository { uid: true, user: { select: { - credentials: true, + credentials: { + select: safeCredentialSelect, + }, }, }, references: { @@ -1376,9 +1385,19 @@ export class BookingRepository { destinationCalendar: true, references: true, user: { - include: { + select: { + id: true, + email: true, + name: true, + timeZone: true, + locale: true, + organizationId: true, + hideBranding: true, + username: true, destinationCalendar: true, - credentials: true, + credentials: { + select: safeCredentialSelect, + }, }, }, }, @@ -1418,9 +1437,19 @@ export class BookingRepository { }, }, user: { - include: { + select: { + id: true, + email: true, + name: true, + timeZone: true, + locale: true, + organizationId: true, + hideBranding: true, + username: true, destinationCalendar: true, - credentials: true, + credentials: { + select: safeCredentialSelect, + }, }, }, }, From 8ac988441f605bf26740e466519fe4f17119343a Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 11 Nov 2025 15:58:28 +0530 Subject: [PATCH 30/58] use correct organization resolution for both team and user events --- .../routers/viewer/bookings/addGuests.handler.ts | 14 +++++++++++--- .../viewer/bookings/editLocation.handler.ts | 13 ++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index b4e4216a178d7d..b472124539312e 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -8,6 +8,8 @@ import { PermissionCheckService } from "@calcom/features/pbac/services/permissio import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import logger from "@calcom/lib/logger"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -22,7 +24,7 @@ import { TRPCError } from "@trpc/server"; import type { TrpcSessionUser } from "../../../types"; import type { TAddGuestsInputSchema } from "./addGuests.schema"; -type TUser = Pick, "id" | "email" | "organizationId"> & +type TUser = Pick, "id" | "email"> & Partial, "profile">>; type AddGuestsOptions = { @@ -337,13 +339,19 @@ async function sendGuestNotifications( } : null; - const organizationId = booking.eventType?.team?.parentId ?? null; + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: booking.eventType?.teamId ?? null }, + parentId: booking?.eventType?.parentId ?? null, + }, + }); + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team, owner: booking.eventType?.owner ?? null, - organizationId, + organizationId: orgId ?? null, }); } diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index a9fe928714f51e..a8d883ec232e07 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -8,10 +8,11 @@ import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; -import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { buildCalEventFromBooking } from "@calcom/lib/buildCalEventFromBooking"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -252,7 +253,6 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { const { booking, user: loggedInUser } = ctx; const organizer = await new UserRepository(prisma).findByIdOrThrow({ id: booking.userId || 0 }); - const ownerProfile = await ProfileRepository.findFirstForUserId({ userId: booking.userId || 0 }); const newLocationInEvtFormat = await getLocationInEvtFormatOrThrow({ location: newLocation, @@ -290,11 +290,18 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { const eventTypeId = booking.eventTypeId; let hideBranding = false; if (eventTypeId) { + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: booking.eventType?.teamId ?? null }, + parentId: booking?.eventType?.parentId ?? null, + }, + }); + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, - organizationId: booking.eventType?.team?.parentId ?? ownerProfile?.organizationId ?? null, + organizationId: orgId ?? null, }); } From ac55d970e5714c640bd4f19e143a8b7ea207d473 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 11 Nov 2025 16:49:50 +0530 Subject: [PATCH 31/58] use getOrgIdFromMemberOrTeamId to compute orgId in other files --- .../_utils/payments/handlePaymentSuccess.ts | 11 ++++++++++- .../bookings/lib/handleBookingRequested.ts | 11 ++++++++++- .../loggedInViewer/connectAndJoin.handler.ts | 15 +++++++++++---- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index 9b6e7e77ba3c66..cf95f0f558699a 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -11,6 +11,8 @@ import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-c import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -97,11 +99,18 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number) log.debug(`handling booking request for eventId ${eventType.id}`); } } else if (areEmailsEnabled) { + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: booking.eventType?.teamId ?? null }, + parentId: booking?.eventType?.parentId ?? null, + }, + }); + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, owner: userWithCredentials ?? null, - organizationId: booking.eventType?.team?.parentId ?? userWithCredentials?.organizationId ?? null, + organizationId: orgId ?? null, }); await sendScheduledEmailsAndSMS( diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index 614da2a6ef455a..48df8c97d18d55 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -6,6 +6,7 @@ import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/l 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"; import type { Prisma } from "@calcom/prisma/client"; @@ -24,6 +25,7 @@ export async function handleBookingRequested(args: { booking: { smsReminderNumber: string | null; eventType: { + parentId?: number | null; workflows: { workflow: Workflow; }[]; @@ -75,11 +77,18 @@ export async function handleBookingRequested(args: { log.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: booking.eventType?.teamId ?? null }, + parentId: booking?.eventType?.parentId ?? null, + }, + }); + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, - organizationId: booking.eventType?.team?.parentId ?? null, + organizationId: orgId ?? null, }); } diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts index db8b39f837ef36..1bfecef350ad48 100644 --- a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts @@ -3,6 +3,8 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR import { scheduleNoShowTriggers } from "@calcom/features/bookings/lib/handleNewBooking/scheduleNoShowTriggers"; import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { getTranslation } from "@calcom/lib/server/i18n"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { prisma } from "@calcom/prisma"; @@ -204,13 +206,18 @@ export const Handler = async ({ ctx, input }: Options) => { const attendeesList = await Promise.all(attendeesListPromises); - const organizationId = updatedBooking.eventType?.team?.parentId ?? user.organization.id ?? null; - + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: updatedBooking.eventType?.teamId ?? null }, + parentId: updatedBooking?.eventType?.parentId ?? null, + }, + }); + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: updatedBooking.userId, teamId }); const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: updatedBooking.eventTypeId ?? 0, team: updatedBooking.eventType?.team ?? null, owner: updatedBooking.eventType?.team ? null : updatedBooking.eventType?.owner ?? null, - organizationId: organizationId, + organizationId: orgId ?? null, }); const evt: CalendarEvent = { @@ -241,7 +248,7 @@ export const Handler = async ({ ctx, input }: Options) => { eventTypeId: eventType?.id, videoCallData, customReplyToEmail: eventType?.customReplyToEmail, - team: !!updatedBooking.eventType?.team + team: updatedBooking.eventType?.team ? { name: updatedBooking.eventType.team.name, id: updatedBooking.eventType.team.id, From 2665fe0ead88581b186dbe93a0e0d205bd6c56ee Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 11 Nov 2025 16:53:41 +0530 Subject: [PATCH 32/58] use helped function instead of manually computing hidebranding --- .../handleSeats/cancel/cancelAttendeeSeat.ts | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts index a8f01913e4483b..11abe6a160290d 100644 --- a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts +++ b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts @@ -4,9 +4,12 @@ import { getDelegationCredentialOrFindRegularCredential } from "@calcom/app-stor import { sendCancelledSeatEmailsAndSMS } from "@calcom/emails"; import { updateMeeting } from "@calcom/features/conferencing/lib/videoClient"; import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; +import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { getRichDescription } from "@calcom/lib/CalEventParser"; +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"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -117,7 +120,10 @@ async function cancelAttendeeSeat( try { await Promise.all(integrationsToUpdate); } catch (error) { - logger.error(`Error updating integrations for event: ${evt.bookingId}, bookingUid: ${evt.uid}`, safeStringify(error)); + logger.error( + `Error updating integrations for event: ${evt.bookingId}, bookingUid: ${evt.uid}`, + safeStringify(error) + ); // Shouldn't stop code execution if integrations fail // as integrations was already updated } @@ -129,11 +135,25 @@ async function cancelAttendeeSeat( ...evt, hideBranding: evt.hideBranding || - !!( - bookingToDelete.eventType?.team?.hideBranding || - bookingToDelete.eventType?.team?.parent?.hideBranding || - bookingToDelete.user?.hideBranding - ), + (await (async () => { + if (!bookingToDelete.eventTypeId) return false; + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: bookingToDelete.eventType?.team?.id ?? null }, + parentId: bookingToDelete?.eventType?.parentId ?? null, + }, + }); + const orgId = await getOrgIdFromMemberOrTeamId({ + memberId: bookingToDelete.userId, + teamId, + }); + return await shouldHideBrandingForEventWithPrisma({ + eventTypeId: bookingToDelete.eventTypeId, + team: bookingToDelete.eventType?.team ?? null, + owner: bookingToDelete.user ?? null, + organizationId: orgId ?? null, + }); + })()), }, { ...attendee, From 1fc9525eb1bb2496023ad39f080e28d964feaecd Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 11 Nov 2025 19:29:39 +0530 Subject: [PATCH 33/58] use select instead of include, correctly compute orgId in reminder emails, dont log sensitive info --- apps/web/app/api/cron/bookingReminder/route.ts | 16 +++++++++++++++- .../features/bookings/lib/handleCancelBooking.ts | 1 - .../lib/handleSeats/cancel/cancelAttendeeSeat.ts | 5 +---- .../bookings/repositories/BookingRepository.ts | 12 +++++++++++- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 8491e3609e2032..165ccd401339ea 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -6,6 +6,8 @@ import dayjs from "@calcom/dayjs"; import { sendOrganizerRequestReminderEmail, withHideBranding } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -59,12 +61,14 @@ async function postHandler(request: NextRequest) { destinationCalendar: true, isPlatformManaged: true, hideBranding: true, + organizationId: true, platformOAuthClients: { select: { id: true, areEmailsEnabled: true } }, }, }, eventType: { select: { id: true, + parentId: true, recurringEvent: true, bookingFields: true, metadata: true, @@ -132,11 +136,21 @@ async function postHandler(request: NextRequest) { const attendeesList = await Promise.all(attendeesListPromises); const selectedDestinationCalendar = booking.destinationCalendar || user.destinationCalendar; + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: booking.eventType?.team?.id ?? null }, + parentId: booking.eventType?.parentId ?? null, + }, + }); + const orgId = await getOrgIdFromMemberOrTeamId({ + memberId: booking.user?.id ?? null, + teamId, + }); const hideBranding = await shouldHideBrandingForEventWithPrisma({ eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, - organizationId: booking.eventType?.team?.parentId ?? null, + organizationId: orgId ?? null, }); const evt: CalendarEvent = { diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index a5ba89d06d6980..e86c13f72bc517 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -606,7 +606,6 @@ async function handler(input: CancelBookingInput) { if (!platformClientId || (platformClientId && arePlatformEmailsEnabled)) { log.debug("Sending cancellation emails with branding config", { hideBranding: evt.hideBranding, - platformClientId, arePlatformEmailsEnabled, }); diff --git a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts index 11abe6a160290d..ae4fa8bd73fcfc 100644 --- a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts +++ b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts @@ -120,10 +120,7 @@ async function cancelAttendeeSeat( try { await Promise.all(integrationsToUpdate); } catch (error) { - logger.error( - `Error updating integrations for event: ${evt.bookingId}, bookingUid: ${evt.uid}`, - safeStringify(error) - ); + logger.error("Error updating integrations for event", safeStringify(error)); // Shouldn't stop code execution if integrations fail // as integrations was already updated } diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index bab1913f391cec..492149e0160ab0 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1414,7 +1414,17 @@ export class BookingRepository { destinationCalendar: true, references: true, eventType: { - include: { + select: { + id: true, + title: true, + bookingFields: true, + recurringEvent: true, + seatsPerTimeSlot: true, + seatsShowAttendees: true, + customReplyToEmail: true, + schedulingType: true, + metadata: true, + teamId: true, team: { select: { id: true, From e1ddfa4e0faf7ecbceefe1187448b6bbf547263e Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 13 Nov 2025 19:33:17 +0530 Subject: [PATCH 34/58] resolved merge conflicts --- packages/emails/email-manager.ts | 268 +------------------------------ 1 file changed, 6 insertions(+), 262 deletions(-) diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts index 86850462962f83..023b32a0c4aa59 100644 --- a/packages/emails/email-manager.ts +++ b/packages/emails/email-manager.ts @@ -12,12 +12,6 @@ import { withReporting } from "@calcom/lib/sentryWrapper"; import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { CalendarEvent, Person } from "@calcom/types/Calendar"; -type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; - -export function withHideBranding(calEvent: CalendarEvent, explicit?: boolean): CalendarEventWithBranding { - return { ...calEvent, hideBranding: explicit ?? calEvent.hideBranding ?? false }; -} - import AwaitingPaymentSMS from "../sms/attendee/awaiting-payment-sms"; import CancelledSeatSMS from "../sms/attendee/cancelled-seat-sms"; import EventCancelledSMS from "../sms/attendee/event-cancelled-sms"; @@ -49,6 +43,12 @@ import OrganizerRequestedToRescheduleEmail from "./templates/organizer-requested import OrganizerRescheduledEmail from "./templates/organizer-rescheduled-email"; import OrganizerScheduledEmail from "./templates/organizer-scheduled-email"; +type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; + +export function withHideBranding(calEvent: CalendarEvent, explicit?: boolean): CalendarEventWithBranding { + return { ...calEvent, hideBranding: explicit ?? calEvent.hideBranding ?? false }; +} + type EventTypeMetadata = z.infer; const sendEmail = (prepare: () => BaseEmail) => { @@ -540,53 +540,6 @@ export const sendAwaitingPaymentEmailAndSMS = async ( await awaitingPaymentSMS.sendSMSToAttendees(); }; -export const sendOrganizerPaymentRefundFailedEmail = async (calEvent: CalendarEventWithBranding) => { - const emailsToSend: Promise[] = []; - emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent }))); - - if (calEvent.team?.members) { - for (const teamMember of calEvent.team.members) { - emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent, teamMember }))); - } - } - - await Promise.all(emailsToSend); -}; - -export const sendPasswordResetEmail = async (passwordResetEvent: PasswordReset) => { - await sendEmail(() => new ForgotPasswordEmail(passwordResetEvent)); -}; - -export const sendTeamInviteEmail = async (teamInviteEvent: TeamInvite) => { - await sendEmail(() => new TeamInviteEmail(teamInviteEvent)); -}; - -export const sendCustomWorkflowEmail = async (emailData: WorkflowEmailData) => { - await sendEmail(() => new WorkflowEmail(emailData)); -}; - -export const sendOrganizationCreationEmail = async (organizationCreationEvent: OrganizationCreation) => { - await sendEmail(() => new OrganizationCreationEmail(organizationCreationEvent)); -}; - -export const sendOrganizationAdminNoSlotsNotification = async ( - orgInviteEvent: OrganizationAdminNoSlotsEmailInput -) => { - await sendEmail(() => new OrganizationAdminNoSlotsEmail(orgInviteEvent)); -}; - -export const sendEmailVerificationLink = async (verificationInput: EmailVerifyLink) => { - await sendEmail(() => new AccountVerifyEmail(verificationInput)); -}; - -export const sendEmailVerificationCode = async (verificationInput: EmailVerifyCode) => { - await sendEmail(() => new AttendeeVerifyEmail(verificationInput)); -}; - -export const sendChangeOfEmailVerificationLink = async (verificationInput: ChangeOfEmailVerifyLink) => { - await sendEmail(() => new ChangeOfEmailVerifyEmail(verificationInput)); -}; - export const sendRequestRescheduleEmailAndSMS = async ( calEvent: CalendarEvent, metadata: { rescheduleLink: string }, @@ -706,212 +659,3 @@ export const sendAddGuestsEmailsAndSMS = async (args: { await Promise.all(emailsAndSMSToSend); }; - -export const sendFeedbackEmail = async (feedback: Feedback) => { - await sendEmail(() => new FeedbackEmail(feedback)); -}; - -export const sendBrokenIntegrationEmail = async ( - evt: CalendarEventWithBranding, - type: "video" | "calendar" -) => { - const calendarEvent = formatCalEvent(evt); - await sendEmail(() => new BrokenIntegrationEmail(calendarEvent, type)); -}; - -export const sendDisabledAppEmail = async ({ - email, - appName, - appType, - t, - title = undefined, - eventTypeId = undefined, -}: { - email: string; - appName: string; - appType: string[]; - t: TFunction; - title?: string; - eventTypeId?: number; -}) => { - await sendEmail(() => new DisabledAppEmail(email, appName, appType, t, title, eventTypeId)); -}; - -export const sendSlugReplacementEmail = async ({ - email, - name, - teamName, - t, - slug, -}: { - email: string; - name: string; - teamName: string | null; - t: TFunction; - slug: string; -}) => { - await sendEmail(() => new SlugReplacementEmail(email, name, teamName, slug, t)); -}; - -export const sendNoShowFeeChargedEmail = async ( - attendee: Person, - evt: CalendarEventWithBranding, - eventTypeMetadata?: EventTypeMetadata -) => { - if (eventTypeDisableAttendeeEmail(eventTypeMetadata)) return; - await sendEmail(() => new NoShowFeeChargedEmail(evt, attendee)); -}; - -export const sendDailyVideoRecordingEmails = async ( - calEvent: CalendarEventWithBranding, - downloadLink: string -) => { - const calendarEvent = formatCalEvent(calEvent); - const emailsToSend: Promise[] = []; - - emailsToSend.push( - sendEmail(() => new OrganizerDailyVideoDownloadRecordingEmail(calendarEvent, downloadLink)) - ); - - for (const attendee of calendarEvent.attendees) { - emailsToSend.push( - sendEmail(() => new AttendeeDailyVideoDownloadRecordingEmail(calendarEvent, attendee, downloadLink)) - ); - } - await Promise.all(emailsToSend); -}; - -export const sendDailyVideoTranscriptEmails = async ( - calEvent: CalendarEventWithBranding, - transcripts: string[] -) => { - const emailsToSend: Promise[] = []; - - emailsToSend.push(sendEmail(() => new OrganizerDailyVideoDownloadTranscriptEmail(calEvent, transcripts))); - - for (const attendee of calEvent.attendees) { - emailsToSend.push( - sendEmail(() => new AttendeeDailyVideoDownloadTranscriptEmail(calEvent, attendee, transcripts)) - ); - } - await Promise.all(emailsToSend); -}; - -export const sendOrganizationEmailVerification = async (sendOrgInput: OrganizationEmailVerify) => { - await sendEmail(() => new OrganizationEmailVerification(sendOrgInput)); -}; - -export const sendMonthlyDigestEmails = async (eventData: MonthlyDigestEmailData) => { - await sendEmail(() => new MonthlyDigestEmail(eventData)); -}; - -export const sendAdminOrganizationNotification = async (input: OrganizationNotification) => { - await sendEmail(() => new AdminOrganizationNotification(input)); -}; - -export const sendBookingRedirectNotification = async (bookingRedirect: IBookingRedirect) => { - await sendEmail(() => new BookingRedirectEmailNotification(bookingRedirect)); -}; - -export const sendCreditBalanceLowWarningEmails = async (input: { - team?: { - name: string | null; - id: number; - adminAndOwners: { - id: number; - name: string | null; - email: string; - t: TFunction; - }[]; - }; - user?: { - id: number; - name: string | null; - email: string; - t: TFunction; - }; - balance: number; - creditFor?: CreditUsageType; -}) => { - const { team, balance, user, creditFor } = input; - if ((!team || !team.adminAndOwners.length) && !user) return; - - if (team) { - const emailsToSend: Promise[] = []; - - for (const admin of team.adminAndOwners) { - emailsToSend.push( - sendEmail(() => new CreditBalanceLowWarningEmail({ user: admin, balance, team, creditFor })) - ); - } - - await Promise.all(emailsToSend); - } - - if (user) { - await sendEmail(() => new CreditBalanceLowWarningEmail({ user, balance, creditFor })); - } -}; - -export const sendCreditBalanceLimitReachedEmails = async ({ - team, - user, - creditFor, -}: { - team?: { - name: string; - id: number; - adminAndOwners: { - id: number; - name: string | null; - email: string; - t: TFunction; - }[]; - }; - user?: { - id: number; - name: string | null; - email: string; - t: TFunction; - }; - creditFor?: CreditUsageType; -}) => { - if ((!team || !team.adminAndOwners.length) && !user) return; - - if (team) { - const emailsToSend: Promise[] = []; - - for (const admin of team.adminAndOwners) { - emailsToSend.push( - sendEmail(() => new CreditBalanceLimitReachedEmail({ user: admin, team, creditFor })) - ); - } - await Promise.all(emailsToSend); - } - - if (user) { - await sendEmail(() => new CreditBalanceLimitReachedEmail({ user, creditFor })); - } -}; - -export const sendDelegationCredentialDisabledEmail = async ({ - recipientEmail, - recipientName, - calendarAppName, - conferencingAppName, -}: { - recipientEmail: string; - recipientName?: string; - calendarAppName: string; - conferencingAppName: string; -}) => { - await sendEmail( - () => - new DelegationCredentialDisabledEmail({ - recipientEmail, - recipientName, - calendarAppName, - conferencingAppName, - }) - ); -}; From 982ca538f11eceff976cca1f24c7e5b241f9a69c Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 13 Nov 2025 21:48:50 +0530 Subject: [PATCH 35/58] fix failing type check --- packages/features/bookings/repositories/BookingRepository.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index 492149e0160ab0..ae9f08a379e549 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1417,11 +1417,13 @@ export class BookingRepository { select: { id: true, title: true, + parentId: true, bookingFields: true, recurringEvent: true, seatsPerTimeSlot: true, seatsShowAttendees: true, customReplyToEmail: true, + hideOrganizerEmail: true, schedulingType: true, metadata: true, teamId: true, From a55a3215a821a709b4123a19b420b2ac0e29aaa7 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 17 Nov 2025 14:13:39 +0530 Subject: [PATCH 36/58] repository pattern and update method name --- .../web/app/api/cron/bookingReminder/route.ts | 4 +- ...ookings-single-view.getServerSideProps.tsx | 4 +- .../_utils/payments/handlePaymentSuccess.ts | 4 +- .../bookings/lib/handleBookingRequested.ts | 4 +- .../bookings/lib/handleCancelBooking.ts | 4 +- .../bookings/lib/handleConfirmation.ts | 4 +- .../attendeeRescheduleSeatedBooking.ts | 4 +- .../owner/combineTwoSeatedBookings.ts | 4 +- .../owner/moveSeatedBookingToNewTimeSlot.ts | 4 +- .../bookings/lib/payment/handleNoShowFee.ts | 4 +- .../lib/service/RegularBookingService.ts | 4 +- .../credentials/handleDeleteCredential.ts | 4 +- packages/features/ee/payments/api/webhook.ts | 4 +- .../features/ee/payments/pages/payment.tsx | 4 +- .../roundRobinManualReassignment.ts | 4 +- .../ee/round-robin/roundRobinReassignment.ts | 4 +- .../lib/service/WorkflowService.test.ts | 7 ++- .../workflows/lib/service/WorkflowService.ts | 6 +-- packages/features/profile/lib/hideBranding.ts | 48 ------------------- .../repositories/BrandingRepository.ts | 28 +++++++++++ .../loggedInViewer/connectAndJoin.handler.ts | 6 +-- .../viewer/bookings/addGuests.handler.ts | 4 +- .../viewer/bookings/confirm.handler.ts | 4 +- .../viewer/bookings/editLocation.handler.ts | 4 +- 24 files changed, 78 insertions(+), 93 deletions(-) create mode 100644 packages/features/profile/repositories/BrandingRepository.ts diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 8491e3609e2032..73d5657655cd64 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -5,7 +5,7 @@ import { NextResponse } from "next/server"; import dayjs from "@calcom/dayjs"; import { sendOrganizerRequestReminderEmail, withHideBranding } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -132,7 +132,7 @@ async function postHandler(request: NextRequest) { const attendeesList = await Promise.all(attendeesListPromises); const selectedDestinationCalendar = booking.destinationCalendar || user.destinationCalendar; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx index 7fa856c7c18263..8c07035aa62b75 100644 --- a/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx +++ b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx @@ -10,7 +10,7 @@ import { BookingRepository } from "@calcom/features/bookings/repositories/Bookin import { isTeamMember } from "@calcom/features/ee/teams/lib/queries"; import { getDefaultEvent } from "@calcom/features/eventtypes/lib/defaultEvents"; import { getBrandingForEventType } from "@calcom/features/profile/lib/getBranding"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat"; @@ -250,7 +250,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username, hideBranding: isPlatformBooking ? true - : await shouldHideBrandingForEventWithPrisma({ + : await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: eventType?.parent?.team ?? eventType?.team, owner: eventType.users[0] ?? null, diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index 0b087b308b805c..350b17fde9a45e 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -8,7 +8,7 @@ import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirma import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; @@ -97,7 +97,7 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number) log.debug(`handling booking request for eventId ${eventType.id}`); } } else if (areEmailsEnabled) { - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventType?.id ?? 0, team: booking.eventType?.team ?? null, owner: userWithCredentials ?? null, diff --git a/packages/features/bookings/lib/handleBookingRequested.ts b/packages/features/bookings/lib/handleBookingRequested.ts index 614da2a6ef455a..19417f314091c4 100644 --- a/packages/features/bookings/lib/handleBookingRequested.ts +++ b/packages/features/bookings/lib/handleBookingRequested.ts @@ -2,7 +2,7 @@ import { sendAttendeeRequestEmailAndSMS, sendOrganizerRequestEmail } from "@calc import { getWebhookPayloadForBooking } from "@calcom/features/bookings/lib/getWebhookPayloadForBooking"; import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; @@ -75,7 +75,7 @@ export async function handleBookingRequested(args: { log.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEventWithPrisma({ + hideBranding = await shouldHideBrandingForEvent({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index bcd21df061225a..47d98ea871bbba 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -12,7 +12,7 @@ import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/proc import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import { sendCancelledReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { @@ -266,7 +266,7 @@ async function handler(input: CancelBookingInput) { log.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEventWithPrisma({ + hideBranding = await shouldHideBrandingForEvent({ eventTypeId: bookingToDelete.eventTypeId, team: bookingToDelete.eventType?.team ?? null, owner: bookingToDelete.user ?? null, diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 2712301d3f6216..1deea2edeb990e 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -10,7 +10,7 @@ import { } from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails"; import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; @@ -126,7 +126,7 @@ export async function handleConfirmation(args: { log.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEventWithPrisma({ + hideBranding = await shouldHideBrandingForEvent({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index ffffa3cd4b18c9..f9756fc4e626c2 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -2,7 +2,7 @@ import { cloneDeep } from "lodash"; import { sendRescheduledSeatEmailAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; import type { Person, CalendarEvent } from "@calcom/types/Calendar"; @@ -22,7 +22,7 @@ const attendeeRescheduleSeatedBooking = async ( let { originalRescheduledBooking } = rescheduleSeatedBookingObject; const { organizerUser } = rescheduleSeatedBookingObject; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index 4e7e2da26cba3a..f023a87697ce78 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -3,7 +3,7 @@ import { uuid } from "short-uuid"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; @@ -135,7 +135,7 @@ const combineTwoSeatedBookings = async ( : calendarResult?.updatedEvent?.iCalUID || undefined; if (noEmail !== true && isConfirmedByDefault) { - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index 99f8ffb7a7100f..772c1c07c8f357 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -2,7 +2,7 @@ import { cloneDeep } from "lodash"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails"; import type EventManager from "@calcom/features/bookings/lib/EventManager"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import prisma from "@calcom/prisma"; import type { AdditionalInformation, AppsStatus } from "@calcom/types/Calendar"; @@ -30,7 +30,7 @@ const moveSeatedBookingToNewTimeSlot = async ( additionalNotes, } = rescheduleSeatedBookingObject; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, diff --git a/packages/features/bookings/lib/payment/handleNoShowFee.ts b/packages/features/bookings/lib/payment/handleNoShowFee.ts index e40f656962876d..98508371aa9934 100644 --- a/packages/features/bookings/lib/payment/handleNoShowFee.ts +++ b/packages/features/bookings/lib/payment/handleNoShowFee.ts @@ -5,7 +5,7 @@ import { sendNoShowFeeChargedEmail } from "@calcom/emails"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { ErrorWithCode } from "@calcom/lib/errors"; import logger from "@calcom/lib/logger"; @@ -172,7 +172,7 @@ export const handleNoShowFee = async ({ const organizationId = booking.eventType?.team?.parentId ?? booking.user?.organizationId ?? null; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/features/bookings/lib/service/RegularBookingService.ts b/packages/features/bookings/lib/service/RegularBookingService.ts index c1afb9c0c2bee9..110a39f1c56e25 100644 --- a/packages/features/bookings/lib/service/RegularBookingService.ts +++ b/packages/features/bookings/lib/service/RegularBookingService.ts @@ -44,7 +44,7 @@ import { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents"; import { getEventName, updateHostInEventName } from "@calcom/features/eventtypes/lib/eventNaming"; import { getFullName } from "@calcom/features/form-builder/utils"; import type { HashedLinkService } from "@calcom/features/hashedLink/lib/service/HashedLinkService"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { handleAnalyticsEvents } from "@calcom/features/tasker/tasks/analytics/handleAnalyticsEvents"; import type { UserRepository } from "@calcom/features/users/repositories/UserRepository"; @@ -1286,7 +1286,7 @@ async function handler( const organizerOrganizationId = organizerOrganizationProfile?.organizationId; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizerUser ?? null, diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 1dc9d31f6c595d..65bbc4c9f67384 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -11,7 +11,7 @@ import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-util import { sendCancelledEmailsAndSMS } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { deletePayment } from "@calcom/features/bookings/lib/payment/deletePayment"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/scheduleTrigger"; import { buildNonDelegationCredential } from "@calcom/lib/delegationCredential"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; @@ -337,7 +337,7 @@ const handleDeleteCredential = async ({ const organizationId = booking.eventType?.team?.parentId ?? booking.user?.organizationId ?? null; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, owner: booking.eventType?.team ? null : booking.user ?? null, diff --git a/packages/features/ee/payments/api/webhook.ts b/packages/features/ee/payments/api/webhook.ts index 03d5076b3f6faa..3da42a559ff0bc 100644 --- a/packages/features/ee/payments/api/webhook.ts +++ b/packages/features/ee/payments/api/webhook.ts @@ -13,7 +13,7 @@ import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; import stripe from "@calcom/features/ee/payments/server/stripe"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { IS_PRODUCTION } from "@calcom/lib/constants"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; @@ -131,7 +131,7 @@ const handleSetupSuccess = async (event: Stripe.Event) => { } else if (areEmailsEnabled) { const organizationId = booking.eventType?.team?.parentId ?? user.organizationId ?? null; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, owner: booking.eventType?.team ? null : user ?? null, diff --git a/packages/features/ee/payments/pages/payment.tsx b/packages/features/ee/payments/pages/payment.tsx index b1b9121fbf7557..f3e086fdb3d0c0 100644 --- a/packages/features/ee/payments/pages/payment.tsx +++ b/packages/features/ee/payments/pages/payment.tsx @@ -3,7 +3,7 @@ import { z } from "zod"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getClientSecretFromPayment } from "@calcom/features/ee/payments/pages/getClientSecretFromPayment"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; import { paymentDataSelect } from "@calcom/prisma/selects/payment"; @@ -55,7 +55,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const profile = { name: eventType.team?.name || user?.name || null, theme: (!eventType.team?.name && user?.theme) || null, - hideBranding: await shouldHideBrandingForEventWithPrisma({ + hideBranding: await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: eventType.team, owner: eventType.users[0] ?? null, diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index a66c08cd2996ee..2539e0e2b650f9 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -22,7 +22,7 @@ import { BookingLocationService } from "@calcom/features/ee/round-robin/lib/book import { scheduleEmailReminder, deleteScheduledEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { SENDER_NAME } from "@calcom/lib/constants"; import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService"; @@ -315,7 +315,7 @@ export const roundRobinManualReassignment = async ({ conferenceCredentialId: conferenceCredentialId ?? undefined, }; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizer, diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index 304e8783f818ac..a711f15ca459fb 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -27,7 +27,7 @@ import AssignmentReasonRecorder, { RRReassignmentType, } from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder"; import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; @@ -345,7 +345,7 @@ export const roundRobinReassignment = async ({ ...(platformClientParams ? platformClientParams : {}), }; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventType.id, team: eventType.team ?? null, owner: organizer, diff --git a/packages/features/ee/workflows/lib/service/WorkflowService.test.ts b/packages/features/ee/workflows/lib/service/WorkflowService.test.ts index 1608de7699c174..60317569d22f4f 100644 --- a/packages/features/ee/workflows/lib/service/WorkflowService.test.ts +++ b/packages/features/ee/workflows/lib/service/WorkflowService.test.ts @@ -16,7 +16,12 @@ const mockTasker = vi.mocked(tasker); // Mock the hideBranding functions to return false vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ getHideBranding: vi.fn().mockResolvedValue(false), - getHideBrandingWithPrisma: vi.fn().mockResolvedValue(false), +})); + +vi.mock("@calcom/features/profile/repositories/BrandingRepository", () => ({ + BrandingRepository: vi.fn().mockImplementation(() => ({ + getHideBrandingByIds: vi.fn().mockResolvedValue(false), + })), })); describe("WorkflowService.scheduleFormWorkflows", () => { diff --git a/packages/features/ee/workflows/lib/service/WorkflowService.ts b/packages/features/ee/workflows/lib/service/WorkflowService.ts index b9dcbfa0ebcc1e..26e3f3d34cca43 100644 --- a/packages/features/ee/workflows/lib/service/WorkflowService.ts +++ b/packages/features/ee/workflows/lib/service/WorkflowService.ts @@ -6,7 +6,7 @@ import type { timeUnitLowerCase } from "@calcom/ee/workflows/lib/reminders/smsRe import type { Workflow } from "@calcom/ee/workflows/lib/types"; import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; -import { getHideBrandingWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { BrandingRepository } from "@calcom/features/profile/repositories/BrandingRepository"; import { tasker } from "@calcom/features/tasker"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { prisma } from "@calcom/prisma"; @@ -121,10 +121,10 @@ export class WorkflowService { } } - const hideBranding = await getHideBrandingWithPrisma({ + const brandingRepository = new BrandingRepository(prisma); + const hideBranding = await brandingRepository.getHideBrandingByIds({ userId: form.userId, teamId: form.teamId ?? undefined, - prisma, }); await scheduleWorkflowReminders({ diff --git a/packages/features/profile/lib/hideBranding.ts b/packages/features/profile/lib/hideBranding.ts index e25b443dd8a10e..045a13105db8c3 100644 --- a/packages/features/profile/lib/hideBranding.ts +++ b/packages/features/profile/lib/hideBranding.ts @@ -2,7 +2,6 @@ import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepos import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import logger from "@calcom/lib/logger"; -import { PrismaClient } from "@calcom/prisma"; const log = logger.getSubLogger({ name: "hideBranding" }); export type TeamWithBranding = { @@ -258,50 +257,3 @@ export function shouldHideBrandingForUserEvent({ }); } -/** - * Convenience function that creates repositories with a PrismaClient instance - * This maintains backward compatibility for existing code - */ -export async function getHideBrandingWithPrisma({ - userId, - teamId, - prisma, -}: { - userId?: number; - teamId?: number; - prisma: PrismaClient; -}): Promise { - const teamRepository = new TeamRepository(prisma); - const userRepository = new UserRepository(prisma); - - return getHideBranding({ - userId, - teamId, - teamRepository, - userRepository, - }); -} - -/** - * Convenience function that creates repositories with a PrismaClient instance - * This maintains backward compatibility for existing code - */ -export async function shouldHideBrandingForEventWithPrisma({ - eventTypeId, - team, - owner, - organizationId, -}: { - eventTypeId: number; - team: Team | null; - owner: UserWithoutProfile | null; - organizationId: number | null; -}) { - // ProfileRepository is a static class, so we don't need to instantiate it - return shouldHideBrandingForEvent({ - eventTypeId, - team, - owner, - organizationId, - }); -} diff --git a/packages/features/profile/repositories/BrandingRepository.ts b/packages/features/profile/repositories/BrandingRepository.ts new file mode 100644 index 00000000000000..7d1829f5c5a83b --- /dev/null +++ b/packages/features/profile/repositories/BrandingRepository.ts @@ -0,0 +1,28 @@ +import type { PrismaClient } from "@calcom/prisma"; + +import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; +import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; +import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; + +/** + * Repository for branding-related database operations + */ +export class BrandingRepository { + constructor(private prisma: PrismaClient) {} + + /** + * Get hideBranding value for a user or team by their IDs + */ + async getHideBrandingByIds({ userId, teamId }: { userId?: number; teamId?: number }): Promise { + const teamRepository = new TeamRepository(this.prisma); + const userRepository = new UserRepository(this.prisma); + + return getHideBranding({ + userId, + teamId, + teamRepository, + userRepository, + }); + } +} + diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts index db8b39f837ef36..9c853cae38c2f3 100644 --- a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts @@ -1,7 +1,7 @@ import { sendScheduledEmailsAndSMS } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { scheduleNoShowTriggers } from "@calcom/features/bookings/lib/handleNewBooking/scheduleNoShowTriggers"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { getTranslation } from "@calcom/lib/server/i18n"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; @@ -206,7 +206,7 @@ export const Handler = async ({ ctx, input }: Options) => { const organizationId = updatedBooking.eventType?.team?.parentId ?? user.organization.id ?? null; - const hideBranding = await shouldHideBrandingForEventWithPrisma({ + const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: updatedBooking.eventTypeId ?? 0, team: updatedBooking.eventType?.team ?? null, owner: updatedBooking.eventType?.team ? null : updatedBooking.eventType?.owner ?? null, @@ -241,7 +241,7 @@ export const Handler = async ({ ctx, input }: Options) => { eventTypeId: eventType?.id, videoCallData, customReplyToEmail: eventType?.customReplyToEmail, - team: !!updatedBooking.eventType?.team + team: updatedBooking.eventType?.team ? { name: updatedBooking.eventType.team.name, id: updatedBooking.eventType.team.id, diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 037e363264b2fd..f092abde90b62d 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -7,7 +7,7 @@ import { BookingRepository } from "@calcom/features/bookings/repositories/Bookin import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/EventTypeRepository"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; @@ -397,7 +397,7 @@ async function sendGuestNotifications( parent: eventType.team.parent ?? null, } : null; - hideBranding = await shouldHideBrandingForEventWithPrisma({ + hideBranding = await shouldHideBrandingForEvent({ eventTypeId, team, owner: eventType?.owner ?? null, diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index f58ec9de121950..c21160640a96a1 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -10,7 +10,7 @@ import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/proc import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; import { workflowSelect } from "@calcom/features/ee/workflows/lib/getAllWorkflows"; import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; @@ -375,7 +375,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { console.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEventWithPrisma({ + hideBranding = await shouldHideBrandingForEvent({ eventTypeId: eventTypeIdForBranding, team: booking.eventType?.team ?? null, owner: booking.user ?? null, diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index f2071d06a54346..2c659d2046521e 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -7,7 +7,7 @@ import { sendLocationChangeEmailsAndSMS } from "@calcom/emails"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; @@ -293,7 +293,7 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) { logger.warn("Booking missing eventTypeId, defaulting hideBranding to false"); hideBranding = false; } else { - hideBranding = await shouldHideBrandingForEventWithPrisma({ + hideBranding = await shouldHideBrandingForEvent({ eventTypeId, team: booking.eventType?.team ?? null, owner: booking.user ?? null, From a1ea8b8bf376e49876cbfefdcf8e2c1dc63176c9 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Mon, 17 Nov 2025 16:48:01 +0530 Subject: [PATCH 37/58] update one missed file change --- .../bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts index 0b21ccef0be44d..74cfd65e436139 100644 --- a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts +++ b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts @@ -4,7 +4,7 @@ import { getDelegationCredentialOrFindRegularCredential } from "@calcom/app-stor import { sendCancelledSeatEmailsAndSMS } from "@calcom/emails/email-manager"; import { updateMeeting } from "@calcom/features/conferencing/lib/videoClient"; import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; -import { shouldHideBrandingForEventWithPrisma } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { getRichDescription } from "@calcom/lib/CalEventParser"; @@ -144,7 +144,7 @@ async function cancelAttendeeSeat( memberId: bookingToDelete.userId, teamId, }); - return await shouldHideBrandingForEventWithPrisma({ + return await shouldHideBrandingForEvent({ eventTypeId: bookingToDelete.eventTypeId, team: bookingToDelete.eventType?.team ?? null, owner: bookingToDelete.user ?? null, From aeaa5fbd6f05918236c65f4ba062bce65b5af9c2 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 21 Nov 2025 16:39:18 +0530 Subject: [PATCH 38/58] fix: use dynamic hideBranding flag instead of hardcoded true in email handlers - Replace all withHideBranding(..., true) calls with evt.hideBranding ?? false - Ensures branding visibility respects user/team/organization subscription settings - Fixes regression where all emails would hide branding regardless of settings - Updated BookingEmailSmsHandler.ts (5 occurrences) and videoClient.ts (2 occurrences) --- .../bookings/lib/BookingEmailSmsHandler.ts | 28 +++++++++++-------- .../features/conferencing/lib/videoClient.ts | 4 +-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/features/bookings/lib/BookingEmailSmsHandler.ts b/packages/features/bookings/lib/BookingEmailSmsHandler.ts index 9c6cc7cf83515b..2393277ad4b050 100644 --- a/packages/features/bookings/lib/BookingEmailSmsHandler.ts +++ b/packages/features/bookings/lib/BookingEmailSmsHandler.ts @@ -132,12 +132,15 @@ export class BookingEmailSmsHandler { } = data; await sendRescheduledEmailsAndSMS( - withHideBranding({ - ...evt, - additionalInformation, - additionalNotes, - cancellationReason: `$RCH$${rescheduleReason || ""}`, - }, true), + withHideBranding( + { + ...evt, + additionalInformation, + additionalNotes, + cancellationReason: `$RCH$${rescheduleReason || ""}`, + }, + evt.hideBranding ?? false + ), metadata ); } @@ -233,17 +236,17 @@ export class BookingEmailSmsHandler { try { await Promise.all([ sendRoundRobinRescheduledEmailsAndSMS( - withHideBranding({ ...copyEventAdditionalInfo, iCalUID }, true), + withHideBranding({ ...copyEventAdditionalInfo, iCalUID }, evt.hideBranding ?? false), rescheduledMembers, metadata ), sendRoundRobinScheduledEmailsAndSMS({ - calEvent: withHideBranding(copyEventAdditionalInfo, true), + calEvent: withHideBranding(copyEventAdditionalInfo, evt.hideBranding ?? false), members: newBookedMembers, eventTypeMetadata: metadata, }), sendRoundRobinCancelledEmailsAndSMS( - withHideBranding(cancelledRRHostEvt, true), + withHideBranding(cancelledRRHostEvt, evt.hideBranding ?? false), cancelledMembers, metadata, reassignedTo @@ -287,7 +290,10 @@ export class BookingEmailSmsHandler { try { await sendScheduledEmailsAndSMS( - withHideBranding({ ...evt, additionalInformation, additionalNotes, customInputs }, true), + withHideBranding( + { ...evt, additionalInformation, additionalNotes, customInputs }, + evt.hideBranding ?? false + ), eventNameObject, isHostConfirmationEmailsDisabled, isAttendeeConfirmationEmailDisabled, @@ -317,7 +323,7 @@ export class BookingEmailSmsHandler { safeStringify({ calEvent: getPiiFreeCalendarEvent(evt) }) ); - const eventWithNotes = withHideBranding({ ...evt, additionalNotes }, true); + const eventWithNotes = withHideBranding({ ...evt, additionalNotes }, evt.hideBranding ?? false); try { await Promise.all([ diff --git a/packages/features/conferencing/lib/videoClient.ts b/packages/features/conferencing/lib/videoClient.ts index 9f24d75aa556c9..f6a45680fa7be0 100644 --- a/packages/features/conferencing/lib/videoClient.ts +++ b/packages/features/conferencing/lib/videoClient.ts @@ -81,7 +81,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv returnObject = { ...returnObject, createdEvent: createdMeeting, success: true }; log.debug("created Meeting", safeStringify(returnObject)); } catch (err) { - await sendBrokenIntegrationEmail(withHideBranding(calEvent, true), "video"); + await sendBrokenIntegrationEmail(withHideBranding(calEvent, calEvent.hideBranding ?? false), "video"); log.error( "createMeeting failed", safeStringify(err), @@ -110,7 +110,7 @@ const updateMeeting = async ( const canCallUpdateMeeting = !!(credential && bookingRef); const updatedMeeting = canCallUpdateMeeting ? await firstVideoAdapter?.updateMeeting(bookingRef, calEvent).catch(async (e) => { - await sendBrokenIntegrationEmail(withHideBranding(calEvent, true), "video"); + await sendBrokenIntegrationEmail(withHideBranding(calEvent, calEvent.hideBranding ?? false), "video"); log.error("updateMeeting failed", e, calEvent); success = false; return undefined; From e126e0e04e8fa2d4cd3f828f4e77e60a59057d2b Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 21 Nov 2025 16:40:59 +0530 Subject: [PATCH 39/58] perf: remove unused eventType.parent select in connectAndJoin handler - Remove unnecessary parent.teamId select that was never used in the handler - Reduces database query complexity by eliminating extra relation lookup - Keeps only required selects: parentId and team.parent.hideBranding - Follows 'only select what you need' best practice for optimal performance --- .../routers/loggedInViewer/connectAndJoin.handler.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts index f485840d90994d..cc276acfc71fb5 100644 --- a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts @@ -2,9 +2,9 @@ import { sendScheduledEmailsAndSMS } from "@calcom/emails/email-manager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { scheduleNoShowTriggers } from "@calcom/features/bookings/lib/handleNewBooking/scheduleNoShowTriggers"; import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; -import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; +import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { getTranslation } from "@calcom/lib/server/i18n"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { prisma } from "@calcom/prisma"; @@ -152,11 +152,6 @@ export const Handler = async ({ ctx, input }: Options) => { }, }, }, - parent: { - select: { - teamId: true, - }, - }, }, }, location: true, From 8cc3a5ee0646a822f4b639500011f15ca2a067c9 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 21 Nov 2025 16:42:48 +0530 Subject: [PATCH 40/58] refactor: replace include with select in findByIdIncludeDestinationCalendarAndBranding - Convert attendees from include to select (only id, email, name, timeZone, locale) - Convert references from include to select (only video call related fields) - Add explicit booking field selects to avoid pulling all columns - Reduces PII exposure and improves query performance - Follows repository pattern policy of 'select what you need' --- .../repositories/BookingRepository.ts | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index ae9f08a379e549..cf17a95c7c0224 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1409,10 +1409,38 @@ export class BookingRepository { where: { id: bookingId, }, - include: { - attendees: true, + select: { + id: true, + uid: true, + title: true, + description: true, + startTime: true, + endTime: true, + location: true, + userId: true, + eventTypeId: true, + responses: true, + iCalUID: true, + attendees: { + select: { + id: true, + email: true, + name: true, + timeZone: true, + locale: true, + }, + }, destinationCalendar: true, - references: true, + references: { + select: { + id: true, + type: true, + uid: true, + meetingId: true, + meetingPassword: true, + meetingUrl: true, + }, + }, eventType: { select: { id: true, From dc8de6abc3f0bface53c2f63453f17a08fbf7f8c Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 21 Nov 2025 16:44:14 +0530 Subject: [PATCH 41/58] perf: remove unused eventType.parent select in handleDeleteCredential - Remove unnecessary parent.teamId select that was never used in the handler - Only team.parentId and team.parent.hideBranding are actually needed - Reduces Prisma payload size and improves query performance - Follows 'only select what you need' best practice --- packages/features/credentials/handleDeleteCredential.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index c0b254798d0a8e..b159567438ed9c 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -274,11 +274,6 @@ const handleDeleteCredential = async ({ }, }, }, - parent: { - select: { - teamId: true, - }, - }, metadata: true, }, }, From 280e8691508d35d3ac54c798bb4a758a07ec054d Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 21 Nov 2025 17:12:56 +0530 Subject: [PATCH 42/58] fix: add missing userPrimaryEmail field to BookingRepository select - Add userPrimaryEmail: true to findByIdIncludeDestinationCalendarAndBranding - Prevents regression where organizer email would fall back to user record - Ensures existing bookings preserve their stored organizer address --- packages/features/bookings/repositories/BookingRepository.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index cf17a95c7c0224..c07de2f35dfe83 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1421,6 +1421,7 @@ export class BookingRepository { eventTypeId: true, responses: true, iCalUID: true, + userPrimaryEmail: true, attendees: { select: { id: true, From b63f1ed2cbdd0c1804c067c993dda5f0cc9f650c Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 18 Dec 2025 14:11:09 +0530 Subject: [PATCH 43/58] fix failing tests and type check --- .../bookings/repositories/BookingRepository.ts | 13 +++++-------- .../ManagedEventManualReassignmentService.ts | 11 ++++++----- .../roundRobinManualReassignment.test.ts | 6 +++--- .../workflows/lib/service/EmailWorkflowService.ts | 5 +++++ 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index 3ce9be014df23e..e15cc264fe9b57 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1498,14 +1498,6 @@ export class BookingRepository { timeZone: true, hideBranding: true, organizationId: true, - }, - }, - user: { - select: { - email: true, - name: true, - timeZone: true, - locale: true, profiles: { select: { organizationId: true, @@ -1751,6 +1743,11 @@ export class BookingRepository { credentials: { select: safeCredentialSelect, }, + profiles: { + select: { + organizationId: true, + }, + }, }, }, }, diff --git a/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts b/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts index ba8e12cc5220d2..ab5d1acd86e329 100644 --- a/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts +++ b/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts @@ -1,9 +1,10 @@ import { eventTypeAppMetadataOptionalSchema } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; import { - sendReassignedEmailsAndSMS, + sendRoundRobinReassignedEmailsAndSMS, sendReassignedScheduledEmailsAndSMS, sendReassignedUpdatedEmailsAndSMS, + withHideBranding, } from "@calcom/emails/email-manager"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; @@ -730,7 +731,7 @@ export class ManagedEventManualReassignmentService { calEvent.additionalInformation = additionalInformation; await sendReassignedScheduledEmailsAndSMS({ - calEvent, + calEvent: withHideBranding(calEvent), members: [ { ...newUser, @@ -763,8 +764,8 @@ export class ManagedEventManualReassignmentService { }, }; - await sendReassignedEmailsAndSMS({ - calEvent: cancelledCalEvent, + await sendRoundRobinReassignedEmailsAndSMS({ + calEvent: withHideBranding(cancelledCalEvent), members: [ { ...originalUser, @@ -782,7 +783,7 @@ export class ManagedEventManualReassignmentService { if (dayjs(calEvent.startTime).isAfter(dayjs())) { await sendReassignedUpdatedEmailsAndSMS({ - calEvent, + calEvent: withHideBranding(calEvent), eventTypeMetadata, }); logger.info("Sent update emails to attendees"); diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts index 7603e346ef1529..295a9124ef7319 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts @@ -465,9 +465,9 @@ describe("roundRobinManualReassignment test", () => { const roundRobinManualReassignment = (await import("./roundRobinManualReassignment")).default; await mockEventManagerReschedule(); - const sendReassignedEmailsAndSMSSpy = vi.spyOn( + const sendRoundRobinReassignedEmailsAndSMSSpy = vi.spyOn( await import("@calcom/emails/email-manager"), - "sendReassignedEmailsAndSMS" + "sendRoundRobinReassignedEmailsAndSMS" ); const testDestinationCalendar = createTestDestinationCalendar(); @@ -523,7 +523,7 @@ describe("roundRobinManualReassignment test", () => { reassignedById: 1, }); - expect(sendReassignedEmailsAndSMSSpy).toHaveBeenCalledTimes(1); + expect(sendRoundRobinReassignedEmailsAndSMSSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts b/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts index 73cc6e223093d3..6949d5ab6a5eb9 100644 --- a/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts +++ b/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts @@ -6,6 +6,7 @@ import { sendCustomWorkflowEmail } from "@calcom/emails/workflow-email-service"; import type { BookingSeatRepository } from "@calcom/features/bookings/repositories/BookingSeatRepository"; import type { Workflow, WorkflowStep } from "@calcom/features/ee/workflows/lib/types"; import { preprocessNameFieldDataWithVariant } from "@calcom/features/form-builder/utils"; +import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; import { getSubmitterEmail } from "@calcom/features/tasker/tasks/triggerFormSubmittedNoEvent/formSubmissionValidation"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; @@ -86,9 +87,13 @@ export class EmailWorkflowService { creditCheckFn, }); + const teamRepository = new TeamRepository(prisma); + const userRepository = new UserRepository(prisma); const hideBranding = await getHideBranding({ userId: workflow.userId ?? undefined, teamId: workflow.teamId ?? undefined, + teamRepository, + userRepository, }); const emailWorkflowContentParams = await this.generateParametersToBuildEmailWorkflowContent({ From 5b32395f881c9a9fe7ccd3edabbc30fec0daf165 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 18 Dec 2025 14:46:34 +0530 Subject: [PATCH 44/58] fix failing tests --- .../managedEventManualReassignment.integration-test.ts | 2 +- .../reassignment/managedEventReassignment.integration-test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/features/ee/managed-event-types/reassignment/managedEventManualReassignment.integration-test.ts b/packages/features/ee/managed-event-types/reassignment/managedEventManualReassignment.integration-test.ts index 69b8480c7f65ee..80d188404fc2cf 100644 --- a/packages/features/ee/managed-event-types/reassignment/managedEventManualReassignment.integration-test.ts +++ b/packages/features/ee/managed-event-types/reassignment/managedEventManualReassignment.integration-test.ts @@ -28,7 +28,7 @@ const mockEventManager = async () => { const mockEmails = async () => { const emails = await import("@calcom/emails/email-manager"); vi.spyOn(emails, "sendReassignedScheduledEmailsAndSMS").mockResolvedValue(undefined); - vi.spyOn(emails, "sendReassignedEmailsAndSMS").mockResolvedValue(undefined); + vi.spyOn(emails, "sendRoundRobinReassignedEmailsAndSMS").mockResolvedValue(undefined); vi.spyOn(emails, "sendReassignedUpdatedEmailsAndSMS").mockResolvedValue(undefined); }; diff --git a/packages/features/ee/managed-event-types/reassignment/managedEventReassignment.integration-test.ts b/packages/features/ee/managed-event-types/reassignment/managedEventReassignment.integration-test.ts index fb687988902131..27881dce504c0a 100644 --- a/packages/features/ee/managed-event-types/reassignment/managedEventReassignment.integration-test.ts +++ b/packages/features/ee/managed-event-types/reassignment/managedEventReassignment.integration-test.ts @@ -28,7 +28,7 @@ const mockEventManager = async () => { const mockEmails = async () => { const emails = await import("@calcom/emails/email-manager"); vi.spyOn(emails, "sendReassignedScheduledEmailsAndSMS").mockResolvedValue(undefined); - vi.spyOn(emails, "sendReassignedEmailsAndSMS").mockResolvedValue(undefined); + vi.spyOn(emails, "sendRoundRobinReassignedEmailsAndSMS").mockResolvedValue(undefined); vi.spyOn(emails, "sendReassignedUpdatedEmailsAndSMS").mockResolvedValue(undefined); }; From ad490ca68d133530296dfded9c1ff7a9255d58f6 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Thu, 18 Dec 2025 15:15:10 +0530 Subject: [PATCH 45/58] fix failing tests --- .../managedEventManualReassignment.integration-test.ts | 2 +- .../reassignment/managedEventReassignment.integration-test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/features/ee/managed-event-types/reassignment/managedEventManualReassignment.integration-test.ts b/packages/features/ee/managed-event-types/reassignment/managedEventManualReassignment.integration-test.ts index 80d188404fc2cf..35399a7f035c6e 100644 --- a/packages/features/ee/managed-event-types/reassignment/managedEventManualReassignment.integration-test.ts +++ b/packages/features/ee/managed-event-types/reassignment/managedEventManualReassignment.integration-test.ts @@ -465,7 +465,7 @@ describe("managedEventManualReassignment - Integration Tests", () => { // Verify email functions were called expect(emails.sendReassignedScheduledEmailsAndSMS).toHaveBeenCalledTimes(1); - expect(emails.sendReassignedEmailsAndSMS).toHaveBeenCalledTimes(1); + expect(emails.sendRoundRobinReassignedEmailsAndSMS).toHaveBeenCalledTimes(1); expect(emails.sendReassignedUpdatedEmailsAndSMS).toHaveBeenCalledTimes(1); const newBooking = await prisma.booking.findFirst({ diff --git a/packages/features/ee/managed-event-types/reassignment/managedEventReassignment.integration-test.ts b/packages/features/ee/managed-event-types/reassignment/managedEventReassignment.integration-test.ts index 27881dce504c0a..2611d774c90b7f 100644 --- a/packages/features/ee/managed-event-types/reassignment/managedEventReassignment.integration-test.ts +++ b/packages/features/ee/managed-event-types/reassignment/managedEventReassignment.integration-test.ts @@ -387,7 +387,7 @@ describe("managedEventReassignment - Integration Tests", () => { }); expect(emails.sendReassignedScheduledEmailsAndSMS).toHaveBeenCalledTimes(1); - expect(emails.sendReassignedEmailsAndSMS).toHaveBeenCalledTimes(1); + expect(emails.sendRoundRobinReassignedEmailsAndSMS).toHaveBeenCalledTimes(1); expect(emails.sendReassignedUpdatedEmailsAndSMS).toHaveBeenCalledTimes(1); const newBooking = await prisma.booking.findFirst({ From 86c02d6adb9c3acdf0a5ce2c88ca546577a8d842 Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sun, 4 Jan 2026 14:59:36 +0530 Subject: [PATCH 46/58] chore: update locales --- apps/web/public/static/locales/en/common.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 5824d4067b640c..a5f2718e9df67e 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -18,6 +18,7 @@ "reset_password_subject": "{{appName}}: Reset password instructions", "verify_email_subject": "{{appName}}: Verify your account", "verify_email_subject_verifying_email": "{{appName}}: Verify your email", + "hide_branding_verify_email_subject": "Action Required: Verify your email", "check_your_email": "Check your email", "old_email_address": "Old Email", "new_email_address": "New Email", From 59b4cb687aab2e4cb264358e28336a49f2d06947 Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sun, 4 Jan 2026 15:04:49 +0530 Subject: [PATCH 47/58] fix: hide branding should be respected when sending verify email subject --- packages/emails/lib/types/email-types.ts | 1 + .../src/templates/VerifyEmailByCode.tsx | 49 ++++++++++---- .../emails/templates/attendee-verify-email.ts | 23 +++++-- packages/features/auth/lib/verifyEmail.ts | 64 +++++++++++++++---- 4 files changed, 106 insertions(+), 31 deletions(-) diff --git a/packages/emails/lib/types/email-types.ts b/packages/emails/lib/types/email-types.ts index f8e48dffaa632a..c2d43e94e3a04d 100644 --- a/packages/emails/lib/types/email-types.ts +++ b/packages/emails/lib/types/email-types.ts @@ -20,4 +20,5 @@ export type EmailVerifyCode = { }; verificationEmailCode: string; isVerifyingEmail?: boolean; + hideBranding?: boolean; }; diff --git a/packages/emails/src/templates/VerifyEmailByCode.tsx b/packages/emails/src/templates/VerifyEmailByCode.tsx index 1def5f664601dd..91b5c44b8bbbbb 100644 --- a/packages/emails/src/templates/VerifyEmailByCode.tsx +++ b/packages/emails/src/templates/VerifyEmailByCode.tsx @@ -1,4 +1,8 @@ -import { APP_NAME, SENDER_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants"; +import { + APP_NAME, + SENDER_NAME, + SUPPORT_MAIL_ADDRESS, +} from "@calcom/lib/constants"; import type { EmailVerifyCode } from "../../lib/types/email-types"; import { BaseEmailHtml } from "../components"; @@ -8,15 +12,23 @@ export const VerifyEmailByCode = ( ) => { return ( + hideLogo={props.hideBranding} + subject={props.language( + `verify_email_subject${ + props.isVerifyingEmail ? "_verifying_email" : "" + }`, + { + appName: APP_NAME, + } + )} + >

+ }} + > <>{props.language("verify_email_email_header")}

@@ -34,14 +46,25 @@ export const VerifyEmailByCode = (

<> - {props.language("happy_scheduling")},
- - <>{props.language("the_calcom_team", { companyName: SENDER_NAME })} - + {props.hideBranding ? ( + <> {props.language("happy_scheduling")}! + ) : ( + <> + {props.language("happy_scheduling")},
+ + <> + {props.language("the_calcom_team", { + companyName: SENDER_NAME, + })} + + + + )}

diff --git a/packages/emails/templates/attendee-verify-email.ts b/packages/emails/templates/attendee-verify-email.ts index be15cff0518a70..0395c7112ab1e8 100644 --- a/packages/emails/templates/attendee-verify-email.ts +++ b/packages/emails/templates/attendee-verify-email.ts @@ -20,7 +20,11 @@ export default class AttendeeVerifyEmail extends BaseEmail { to: `${this.verifyAccountInput.user.name} <${this.verifyAccountInput.user.email}>`, from: `${EMAIL_FROM_NAME} <${this.getMailerOptions().from}>`, subject: this.verifyAccountInput.language( - `verify_email_subject${this.verifyAccountInput.isVerifyingEmail ? "_verifying_email" : ""}`, + this.verifyAccountInput.hideBranding + ? `hide_branding_verify_email_subject` + : `verify_email_subject${ + this.verifyAccountInput.isVerifyingEmail ? "_verifying_email" : "" + }`, { appName: APP_NAME, } @@ -33,17 +37,22 @@ export default class AttendeeVerifyEmail extends BaseEmail { protected getTextBody(): string { return ` ${this.verifyAccountInput.language( - `verify_email_subject${this.verifyAccountInput.isVerifyingEmail ? "_verifying_email" : ""}`, + `verify_email_subject${ + this.verifyAccountInput.isVerifyingEmail ? "_verifying_email" : "" + }`, { appName: APP_NAME } )} ${this.verifyAccountInput.language("verify_email_email_header")} -${this.verifyAccountInput.language("hi_user_name", { name: this.verifyAccountInput.user.name })}, +${this.verifyAccountInput.language("hi_user_name", { + name: this.verifyAccountInput.user.name, +})}, ${this.verifyAccountInput.language("verify_email_by_code_email_body")} ${this.verifyAccountInput.verificationEmailCode} -${this.verifyAccountInput.language("happy_scheduling")} ${this.verifyAccountInput.language( - "the_calcom_team", - { companyName: COMPANY_NAME } - )} +${this.verifyAccountInput.language( + "happy_scheduling" +)} ${this.verifyAccountInput.language("the_calcom_team", { + companyName: COMPANY_NAME, + })} `.replace(/(<([^>]+)>)/gi, ""); } } diff --git a/packages/features/auth/lib/verifyEmail.ts b/packages/features/auth/lib/verifyEmail.ts index 7c14a771014023..efecd3a3834e70 100644 --- a/packages/features/auth/lib/verifyEmail.ts +++ b/packages/features/auth/lib/verifyEmail.ts @@ -1,20 +1,23 @@ -import { randomBytes, createHash } from "crypto"; -import { totp } from "otplib"; - +import process from "node:process"; import { + sendChangeOfEmailVerificationLink, sendEmailVerificationCode, sendEmailVerificationLink, - sendChangeOfEmailVerificationLink, } from "@calcom/emails/auth-email-service"; +import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { FeaturesRepository } from "@calcom/features/flags/features.repository"; +import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; +import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { sentrySpan } from "@calcom/features/watchlist/lib/telemetry"; import { checkIfEmailIsBlockedInWatchlistController } from "@calcom/features/watchlist/operations/check-if-email-in-watchlist.controller"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { WEBAPP_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; -import { hashEmail } from "@calcom/lib/server/PiiHasher"; import { getTranslation } from "@calcom/lib/server/i18n"; +import { hashEmail } from "@calcom/lib/server/PiiHasher"; import { prisma } from "@calcom/prisma"; +import { createHash, randomBytes } from "crypto"; +import { totp } from "otplib"; const log = logger.getSubLogger({ prefix: [`[[Auth] `] }); @@ -37,14 +40,23 @@ export const sendEmailVerification = async ({ const token = randomBytes(32).toString("hex"); const translation = await getTranslation(language ?? "en", "common"); const featuresRepository = new FeaturesRepository(prisma); - const emailVerification = await featuresRepository.checkIfFeatureIsEnabledGlobally("email-verification"); + const emailVerification = + await featuresRepository.checkIfFeatureIsEnabledGlobally( + "email-verification" + ); if (!emailVerification) { log.warn("Email verification is disabled - Skipping"); return { ok: true, skipped: true }; } - if (await checkIfEmailIsBlockedInWatchlistController({ email, organizationId: null, span: sentrySpan })) { + if ( + await checkIfEmailIsBlockedInWatchlistController({ + email, + organizationId: null, + span: sentrySpan, + }) + ) { log.warn("Email is blocked - not sending verification email", email); return { ok: false, skipped: false }; } @@ -91,7 +103,13 @@ export const sendEmailVerificationByCode = async ({ username, isVerifyingEmail, }: VerifyEmailType) => { - if (await checkIfEmailIsBlockedInWatchlistController({ email, organizationId: null, span: sentrySpan })) { + if ( + await checkIfEmailIsBlockedInWatchlistController({ + email, + organizationId: null, + span: sentrySpan, + }) + ) { log.warn("Email is blocked - not sending verification email", email); return { ok: false, skipped: false }; } @@ -104,6 +122,20 @@ export const sendEmailVerificationByCode = async ({ totp.options = { step: 900 }; const code = totp.generate(secret); + const userRepository = new UserRepository(prisma); + const user = await userRepository.findByEmail({ email }); + + let hideBranding = false; + if (user) { + const teamRepository = new TeamRepository(prisma); + + hideBranding = await getHideBranding({ + userId: user.id, + userRepository, + teamRepository, + }); + } + await sendEmailVerificationCode({ language: translation, verificationEmailCode: code, @@ -112,6 +144,7 @@ export const sendEmailVerificationByCode = async ({ name: username, }, isVerifyingEmail, + hideBranding, }); return { ok: true, skipped: false }; @@ -126,11 +159,17 @@ interface ChangeOfEmail { language?: string; } -export const sendChangeOfEmailVerification = async ({ user, language }: ChangeOfEmail) => { +export const sendChangeOfEmailVerification = async ({ + user, + language, +}: ChangeOfEmail) => { const token = randomBytes(32).toString("hex"); const translation = await getTranslation(language ?? "en", "common"); const featuresRepository = new FeaturesRepository(prisma); - const emailVerification = await featuresRepository.checkIfFeatureIsEnabledGlobally("email-verification"); + const emailVerification = + await featuresRepository.checkIfFeatureIsEnabledGlobally( + "email-verification" + ); if (!emailVerification) { log.warn("Email verification is disabled - Skipping"); @@ -144,7 +183,10 @@ export const sendChangeOfEmailVerification = async ({ user, language }: ChangeOf span: sentrySpan, }) ) { - log.warn("Email is blocked - not sending verification email", user.emailFrom); + log.warn( + "Email is blocked - not sending verification email", + user.emailFrom + ); return { ok: false, skipped: false }; } From e1e38d623bde16e007a674154cc3ab5e3554da13 Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sun, 4 Jan 2026 15:12:54 +0530 Subject: [PATCH 48/58] fix: bad imports --- packages/features/auth/lib/verifyEmail.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/features/auth/lib/verifyEmail.ts b/packages/features/auth/lib/verifyEmail.ts index 7443ba45568cc7..7a527612e5932b 100644 --- a/packages/features/auth/lib/verifyEmail.ts +++ b/packages/features/auth/lib/verifyEmail.ts @@ -2,24 +2,22 @@ import { randomBytes, createHash } from "node:crypto"; import { totp } from "otplib"; import { - sendChangeOfEmailVerificationLink, sendEmailVerificationCode, sendEmailVerificationLink, + sendChangeOfEmailVerificationLink, } from "@calcom/emails/auth-email-service"; -import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { FeaturesRepository } from "@calcom/features/flags/features.repository"; -import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; -import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { sentrySpan } from "@calcom/features/watchlist/lib/telemetry"; import { checkIfEmailIsBlockedInWatchlistController } from "@calcom/features/watchlist/operations/check-if-email-in-watchlist.controller"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { WEBAPP_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; -import { getTranslation } from "@calcom/lib/server/i18n"; import { hashEmail } from "@calcom/lib/server/PiiHasher"; +import { getTranslation } from "@calcom/lib/server/i18n"; import { prisma } from "@calcom/prisma"; -import { createHash, randomBytes } from "crypto"; -import { totp } from "otplib"; +import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; +import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; +import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; const log = logger.getSubLogger({ prefix: [`[[Auth] `] }); From 15c57a10f566d5444f4e93071c66c0b7ede77205 Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sun, 4 Jan 2026 15:25:38 +0530 Subject: [PATCH 49/58] chore: implement cubic feedback --- packages/emails/templates/attendee-verify-email.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/emails/templates/attendee-verify-email.ts b/packages/emails/templates/attendee-verify-email.ts index 0395c7112ab1e8..18ed87049ae7f5 100644 --- a/packages/emails/templates/attendee-verify-email.ts +++ b/packages/emails/templates/attendee-verify-email.ts @@ -37,9 +37,11 @@ export default class AttendeeVerifyEmail extends BaseEmail { protected getTextBody(): string { return ` ${this.verifyAccountInput.language( - `verify_email_subject${ - this.verifyAccountInput.isVerifyingEmail ? "_verifying_email" : "" - }`, + this.verifyAccountInput.hideBranding + ? `hide_branding_verify_email_subject` + : `verify_email_subject${ + this.verifyAccountInput.isVerifyingEmail ? "_verifying_email" : "" + }`, { appName: APP_NAME } )} ${this.verifyAccountInput.language("verify_email_email_header")} From 87df808c9a952a0d41710031f15b2eef23224a0e Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Mon, 5 Jan 2026 13:20:53 +0530 Subject: [PATCH 50/58] fixup: implement correct logic for booker email verification for hide branding --- .../atoms.verification.controller.ts | 35 ++++-- .../inputs/send-verification-email.input.ts | 13 +- .../services/verification-atom.service.ts | 58 +++++++-- packages/features/auth/lib/verifyEmail.ts | 25 +++- .../Booker/components/hooks/useVerifyEmail.ts | 35 ++++-- .../repositories/eventTypeRepository.ts | 119 ++++++++++++++---- .../platform/atoms/hooks/useVerifyEmail.ts | 29 ++++- .../server/routers/viewer/auth/_router.tsx | 90 +++++++------ .../auth/sendVerifyEmailCode.handler.ts | 10 +- .../viewer/auth/sendVerifyEmailCode.schema.ts | 15 ++- 10 files changed, 313 insertions(+), 116 deletions(-) diff --git a/apps/api/v2/src/modules/atoms/controllers/atoms.verification.controller.ts b/apps/api/v2/src/modules/atoms/controllers/atoms.verification.controller.ts index 9d17044cded206..3b8ea386cd4a8c 100644 --- a/apps/api/v2/src/modules/atoms/controllers/atoms.verification.controller.ts +++ b/apps/api/v2/src/modules/atoms/controllers/atoms.verification.controller.ts @@ -24,7 +24,10 @@ import { Get, Query, } from "@nestjs/common"; -import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger"; +import { + ApiTags as DocsTags, + ApiExcludeController as DocsExcludeController, +} from "@nestjs/swagger"; import { SUCCESS_STATUS } from "@calcom/platform-constants"; import { ApiResponse } from "@calcom/platform-types"; @@ -41,7 +44,12 @@ export class AtomsVerificationController { @Post("/verification/email/send-code") @Version(VERSION_NEUTRAL) @HttpCode(HttpStatus.OK) - @Throttle({ limit: 3, ttl: 60000, blockDuration: 60000, name: "atoms_verification_email_send_code" }) + @Throttle({ + limit: 3, + ttl: 60000, + blockDuration: 60000, + name: "atoms_verification_email_send_code", + }) async sendEmailVerificationCode( @Body() body: SendVerificationEmailInput ): Promise { @@ -50,6 +58,7 @@ export class AtomsVerificationController { username: body.username, language: body.language, isVerifyingEmail: body.isVerifyingEmail, + eventTypeId: body.eventTypeId, }); return { @@ -64,10 +73,11 @@ export class AtomsVerificationController { async checkEmailVerificationRequired( @Query() query: CheckEmailVerificationRequiredParams ): Promise> { - const required = await this.verificationService.checkEmailVerificationRequired({ - email: query.email, - userSessionEmail: query.userSessionEmail, - }); + const required = + await this.verificationService.checkEmailVerificationRequired({ + email: query.email, + userSessionEmail: query.userSessionEmail, + }); return { data: required, @@ -78,7 +88,9 @@ export class AtomsVerificationController { @Post("/verification/email/verify-code") @Version(VERSION_NEUTRAL) @HttpCode(HttpStatus.OK) - async verifyEmailCode(@Body() body: VerifyEmailCodeInput): Promise { + async verifyEmailCode( + @Body() body: VerifyEmailCodeInput + ): Promise { await this.verificationService.verifyEmailCodeUnAuthenticated({ email: body.email, code: body.code, @@ -98,10 +110,11 @@ export class AtomsVerificationController { @Body() body: VerifyEmailCodeInput, @GetUser() user: UserWithProfile ): Promise { - const verified = await this.verificationService.verifyEmailCodeAuthenticated(user, { - email: body.email, - code: body.code, - }); + const verified = + await this.verificationService.verifyEmailCodeAuthenticated(user, { + email: body.email, + code: body.code, + }); return { data: { verified }, diff --git a/apps/api/v2/src/modules/atoms/inputs/send-verification-email.input.ts b/apps/api/v2/src/modules/atoms/inputs/send-verification-email.input.ts index 2082fa94cece3d..098878ebda0a89 100644 --- a/apps/api/v2/src/modules/atoms/inputs/send-verification-email.input.ts +++ b/apps/api/v2/src/modules/atoms/inputs/send-verification-email.input.ts @@ -1,5 +1,11 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { IsEmail, IsOptional, IsString, IsBoolean } from "class-validator"; +import { + IsEmail, + IsOptional, + IsString, + IsBoolean, + IsNumber, +} from "class-validator"; export class SendVerificationEmailInput { @ApiProperty({ example: "user@example.com" }) @@ -20,4 +26,9 @@ export class SendVerificationEmailInput { @IsOptional() @IsBoolean() isVerifyingEmail?: boolean; + + @ApiPropertyOptional({ example: 123 }) + @IsOptional() + @IsNumber() + eventTypeId?: number; } diff --git a/apps/api/v2/src/modules/atoms/services/verification-atom.service.ts b/apps/api/v2/src/modules/atoms/services/verification-atom.service.ts index 4c7c1ddb0603c2..ded1fb196a2112 100644 --- a/apps/api/v2/src/modules/atoms/services/verification-atom.service.ts +++ b/apps/api/v2/src/modules/atoms/services/verification-atom.service.ts @@ -5,7 +5,11 @@ import { SendVerificationEmailInput } from "@/modules/atoms/inputs/send-verifica import { VerifyEmailCodeInput } from "@/modules/atoms/inputs/verify-email-code.input"; import { UserWithProfile } from "@/modules/users/users.repository"; import { UsersRepository } from "@/modules/users/users.repository"; -import { Injectable, BadRequestException, UnauthorizedException } from "@nestjs/common"; +import { + Injectable, + BadRequestException, + UnauthorizedException, +} from "@nestjs/common"; import { verifyCodeUnAuthenticated, @@ -21,7 +25,9 @@ export class VerificationAtomsService { private readonly usersRepository: UsersRepository ) {} - async checkEmailVerificationRequired(input: CheckEmailVerificationRequiredParams) { + async checkEmailVerificationRequired( + input: CheckEmailVerificationRequiredParams + ) { return await checkEmailVerificationRequired(input); } @@ -41,7 +47,10 @@ export class VerificationAtomsService { } } - async verifyEmailCodeAuthenticated(user: UserWithProfile, input: VerifyEmailCodeInput) { + async verifyEmailCodeAuthenticated( + user: UserWithProfile, + input: VerifyEmailCodeInput + ) { try { return await verifyCodeAuthenticated({ user, @@ -54,7 +63,9 @@ export class VerificationAtomsService { throw new BadRequestException("Invalid verification code"); } if (error.message === "BAD_REQUEST") { - throw new BadRequestException("Email, code, and user ID are required"); + throw new BadRequestException( + "Email, code, and user ID are required" + ); } } throw new UnauthorizedException("Verification failed"); @@ -67,28 +78,35 @@ export class VerificationAtomsService { username: input.username, language: input.language, isVerifyingEmail: input.isVerifyingEmail, + eventTypeId: input.eventTypeId, }); } async getVerifiedEmails(input: GetVerifiedEmailsInput): Promise { const { userId, userEmail, teamId } = input; - const userEmailWithoutOauthClientId = this.removeClientIdFromEmail(userEmail); + const userEmailWithoutOauthClientId = + this.removeClientIdFromEmail(userEmail); if (teamId) { const verifiedEmails: string[] = []; - const teamMembers = await this.usersRepository.getUserEmailsVerifiedForTeam(teamId); + const teamMembers = + await this.usersRepository.getUserEmailsVerifiedForTeam(teamId); if (teamMembers.length === 0) { return verifiedEmails; } teamMembers.forEach((member) => { - const memberEmailWithoutOauthClientId = this.removeClientIdFromEmail(member.email); + const memberEmailWithoutOauthClientId = this.removeClientIdFromEmail( + member.email + ); verifiedEmails.push(memberEmailWithoutOauthClientId); member.secondaryEmails.forEach((secondaryEmail) => { - verifiedEmails.push(this.removeClientIdFromEmail(secondaryEmail.email)); + verifiedEmails.push( + this.removeClientIdFromEmail(secondaryEmail.email) + ); }); }); @@ -97,9 +115,14 @@ export class VerificationAtomsService { let verifiedEmails = [userEmailWithoutOauthClientId]; - const secondaryEmails = await this.atomsSecondaryEmailsRepository.getSecondaryEmailsVerified(userId); + const secondaryEmails = + await this.atomsSecondaryEmailsRepository.getSecondaryEmailsVerified( + userId + ); verifiedEmails = verifiedEmails.concat( - secondaryEmails.map((secondaryEmail) => this.removeClientIdFromEmail(secondaryEmail.email)) + secondaryEmails.map((secondaryEmail) => + this.removeClientIdFromEmail(secondaryEmail.email) + ) ); return verifiedEmails; @@ -115,8 +138,14 @@ export class VerificationAtomsService { email: string; }): Promise { const existingSecondaryEmail = - await this.atomsSecondaryEmailsRepository.getExistingSecondaryEmailByUserAndEmail(userId, email); - const alreadyExistingEmail = await this.atomsSecondaryEmailsRepository.getExistingSecondaryEmail(email); + await this.atomsSecondaryEmailsRepository.getExistingSecondaryEmailByUserAndEmail( + userId, + email + ); + const alreadyExistingEmail = + await this.atomsSecondaryEmailsRepository.getExistingSecondaryEmail( + email + ); if (alreadyExistingEmail) { throw new BadRequestException("Email already exists"); @@ -126,7 +155,10 @@ export class VerificationAtomsService { return true; } - await this.atomsSecondaryEmailsRepository.addSecondaryEmailVerified(userId, email); + await this.atomsSecondaryEmailsRepository.addSecondaryEmailVerified( + userId, + email + ); return true; } diff --git a/packages/features/auth/lib/verifyEmail.ts b/packages/features/auth/lib/verifyEmail.ts index 7a527612e5932b..8b12ee7a3ce692 100644 --- a/packages/features/auth/lib/verifyEmail.ts +++ b/packages/features/auth/lib/verifyEmail.ts @@ -6,6 +6,7 @@ import { sendEmailVerificationLink, sendChangeOfEmailVerificationLink, } from "@calcom/emails/auth-email-service"; +import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository"; import { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { sentrySpan } from "@calcom/features/watchlist/lib/telemetry"; import { checkIfEmailIsBlockedInWatchlistController } from "@calcom/features/watchlist/operations/check-if-email-in-watchlist.controller"; @@ -18,6 +19,7 @@ import { prisma } from "@calcom/prisma"; import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; +import { EventRepository } from "@calcom/features/eventtypes/repositories/EventRepository"; const log = logger.getSubLogger({ prefix: [`[[Auth] `] }); @@ -28,6 +30,7 @@ interface VerifyEmailType { secondaryEmailId?: number; isVerifyingEmail?: boolean; isPlatform?: boolean; + eventTypeId?: number; } export const sendEmailVerification = async ({ @@ -102,6 +105,7 @@ export const sendEmailVerificationByCode = async ({ language, username, isVerifyingEmail, + eventTypeId, }: VerifyEmailType) => { if ( await checkIfEmailIsBlockedInWatchlistController({ @@ -122,15 +126,26 @@ export const sendEmailVerificationByCode = async ({ totp.options = { step: 900 }; const code = totp.generate(secret); - const userRepository = new UserRepository(prisma); - const user = await userRepository.findByEmail({ email }); - let hideBranding = false; - if (user) { + if (eventTypeId) { + const userRepository = new UserRepository(prisma); const teamRepository = new TeamRepository(prisma); + const eventTypeRepository = new EventTypeRepository(prisma); + + let teamId: number | undefined; + let userId: number | undefined; + + const eventType = + await eventTypeRepository.findByIdIncludeHostsAndTeamMembers({ + id: eventTypeId, + }); + + teamId = eventType?.teamId ?? undefined; + userId = eventType?.userId ?? undefined; hideBranding = await getHideBranding({ - userId: user.id, + userId, + teamId, userRepository, teamRepository, }); diff --git a/packages/features/bookings/Booker/components/hooks/useVerifyEmail.ts b/packages/features/bookings/Booker/components/hooks/useVerifyEmail.ts index eada0222d3cfb2..5d66e750dda3a7 100644 --- a/packages/features/bookings/Booker/components/hooks/useVerifyEmail.ts +++ b/packages/features/bookings/Booker/components/hooks/useVerifyEmail.ts @@ -20,23 +20,28 @@ export const useVerifyEmail = ({ requiresBookerEmailVerification, onVerifyEmail, }: IUseVerifyEmailProps) => { - const [isEmailVerificationModalVisible, setEmailVerificationModalVisible] = useState(false); + const [isEmailVerificationModalVisible, setEmailVerificationModalVisible] = + useState(false); const verifiedEmail = useBookerStore((state) => state.verifiedEmail); const setVerifiedEmail = useBookerStore((state) => state.setVerifiedEmail); - const isRescheduling = useBookerStore((state) => Boolean(state.rescheduleUid && state.bookingData)); + const isRescheduling = useBookerStore((state) => + Boolean(state.rescheduleUid && state.bookingData) + ); + const eventTypeId = useBookerStore((state) => state.eventId); const debouncedEmail = useDebounce(email, 600); const { data: session } = useSession(); const { t, i18n } = useLocale(); - const sendEmailVerificationByCodeMutation = trpc.viewer.auth.sendVerifyEmailCode.useMutation({ - onSuccess: () => { - setEmailVerificationModalVisible(true); - showToast(t("email_sent"), "success"); - }, - onError: () => { - showToast(t("email_not_sent"), "error"); - }, - }); + const sendEmailVerificationByCodeMutation = + trpc.viewer.auth.sendVerifyEmailCode.useMutation({ + onSuccess: () => { + setEmailVerificationModalVisible(true); + showToast(t("email_sent"), "success"); + }, + onError: () => { + showToast(t("email_not_sent"), "error"); + }, + }); const { data: isEmailVerificationRequired } = trpc.viewer.public.checkIfUserEmailVerificationRequired.useQuery( @@ -56,10 +61,12 @@ export const useVerifyEmail = ({ email, username: typeof name === "string" ? name : name?.firstName, language: i18n.language || "en", + eventTypeId: eventTypeId ?? undefined, }); }; - const isVerificationCodeSending = sendEmailVerificationByCodeMutation.isPending; + const isVerificationCodeSending = + sendEmailVerificationByCodeMutation.isPending; const renderConfirmNotVerifyEmailButtonCond = isRescheduling || @@ -71,7 +78,9 @@ export const useVerifyEmail = ({ isEmailVerificationModalVisible, setEmailVerificationModalVisible, setVerifiedEmail, - renderConfirmNotVerifyEmailButtonCond: Boolean(renderConfirmNotVerifyEmailButtonCond), + renderConfirmNotVerifyEmailButtonCond: Boolean( + renderConfirmNotVerifyEmailButtonCond + ), isVerificationCodeSending, }; }; diff --git a/packages/features/eventtypes/repositories/eventTypeRepository.ts b/packages/features/eventtypes/repositories/eventTypeRepository.ts index 62c189a872011b..12cc187044fda6 100644 --- a/packages/features/eventtypes/repositories/eventTypeRepository.ts +++ b/packages/features/eventtypes/repositories/eventTypeRepository.ts @@ -1,5 +1,8 @@ import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository"; -import { LookupTarget, ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; +import { + LookupTarget, + ProfileRepository, +} from "@calcom/features/profile/repositories/ProfileRepository"; import type { UserWithLegacySelectedCalendars } from "@calcom/features/users/repositories/UserRepository"; import { withSelectedCalendars } from "@calcom/features/users/repositories/UserRepository"; import { ErrorCode } from "@calcom/lib/errorCodes"; @@ -8,12 +11,18 @@ import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { eventTypeSelect } from "@calcom/lib/server/eventTypeSelect"; import type { PrismaClient } from "@calcom/prisma"; -import { availabilityUserSelect, userSelect as userSelectWithSelectedCalendars } from "@calcom/prisma"; +import { + availabilityUserSelect, + userSelect as userSelectWithSelectedCalendars, +} from "@calcom/prisma"; import type { EventType as PrismaEventType } from "@calcom/prisma/client"; import type { Prisma } from "@calcom/prisma/client"; import { MembershipRole } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; -import { EventTypeMetaDataSchema, rrSegmentQueryValueSchema } from "@calcom/prisma/zod-utils"; +import { + EventTypeMetaDataSchema, + rrSegmentQueryValueSchema, +} from "@calcom/prisma/zod-utils"; import type { Ensure } from "@calcom/types/utils"; const log = logger.getSubLogger({ prefix: ["repository/eventType"] }); @@ -42,7 +51,9 @@ type IEventType = Ensure< "title" | "slug" | "length" >; -type UserWithSelectedCalendars = { +type UserWithSelectedCalendars< + TSelectedCalendar extends { eventTypeId: number | null } +> = { allSelectedCalendars: TSelectedCalendar[]; }; @@ -62,9 +73,11 @@ const userSelect = { timeZone: true, } satisfies Prisma.UserSelect; -function hostsWithSelectedCalendars( - hosts: HostWithLegacySelectedCalendars[] -) { +function hostsWithSelectedCalendars< + TSelectedCalendar extends { eventTypeId: number | null }, + THost, + TUser +>(hosts: HostWithLegacySelectedCalendars[]) { return hosts.map((host) => ({ ...host, user: withSelectedCalendars(host.user), @@ -484,7 +497,11 @@ export class EventTypeRepository { }, }); - if (!teamMembership) throw new ErrorWithCode(ErrorCode.Unauthorized, "User is not a member of this team"); + if (!teamMembership) + throw new ErrorWithCode( + ErrorCode.Unauthorized, + "User is not a member of this team" + ); return await this.prismaClient.eventType.findMany({ where: { @@ -521,7 +538,11 @@ export class EventTypeRepository { return await this.prismaClient.eventType.findUnique({ where: { id, - OR: [{ userId }, { hosts: { some: { userId } } }, { users: { some: { id: userId } } }], + OR: [ + { userId }, + { hosts: { some: { userId } } }, + { users: { some: { id: userId } } }, + ], }, }); } @@ -810,7 +831,10 @@ export class EventTypeRepository { }, }, { - AND: [{ teamId: { not: null } }, { teamId: { in: userTeamIds } }], + AND: [ + { teamId: { not: null } }, + { teamId: { in: userTeamIds } }, + ], }, { userId: userId, @@ -826,7 +850,13 @@ export class EventTypeRepository { }); } - async findByIdForOrgAdmin({ id, organizationId }: { id: number; organizationId: number }) { + async findByIdForOrgAdmin({ + id, + organizationId, + }: { + id: number; + organizationId: number; + }) { const userSelect = { name: true, avatarUrl: true, @@ -1095,7 +1125,10 @@ export class EventTypeRepository { } satisfies Prisma.EventTypeSelect; const orgUserEventTypeQuery = { - AND: [{ userId: { not: null } }, { owner: { profiles: { some: { organizationId } } } }], + AND: [ + { userId: { not: null } }, + { owner: { profiles: { some: { organizationId } } } }, + ], }; const orgTeamEventTypeQuery = { AND: [{ teamId: { not: null } }, { team: { parentId: organizationId } }], @@ -1122,7 +1155,15 @@ export class EventTypeRepository { }); } - async findFirstEventTypeId({ slug, teamId, userId }: { slug: string; teamId?: number; userId?: number }) { + async findFirstEventTypeId({ + slug, + teamId, + userId, + }: { + slug: string; + teamId?: number; + userId?: number; + }) { // Use compound unique keys when available for optimal performance // Note: teamId and userId are mutually exclusive - never both provided if (teamId) { @@ -1247,7 +1288,11 @@ export class EventTypeRepository { }); } - async findAllByTeamIdIncludeManagedEventTypes({ teamId }: { teamId?: number }) { + async findAllByTeamIdIncludeManagedEventTypes({ + teamId, + }: { + teamId?: number; + }) { return await this.prismaClient.eventType.findMany({ where: { OR: [ @@ -1400,18 +1445,24 @@ export class EventTypeRepository { hosts: hostsWithSelectedCalendars(eventType.hosts), users: usersWithSelectedCalendars(eventType.users), metadata: EventTypeMetaDataSchema.parse(eventType.metadata), - rrSegmentQueryValue: rrSegmentQueryValueSchema.parse(eventType.rrSegmentQueryValue), + rrSegmentQueryValue: rrSegmentQueryValueSchema.parse( + eventType.rrSegmentQueryValue + ), }; } - static getSelectedCalendarsFromUser({ + static getSelectedCalendarsFromUser< + TSelectedCalendar extends { eventTypeId: number | null } + >({ user, eventTypeId, }: { user: UserWithSelectedCalendars; eventTypeId: number; }) { - return user.allSelectedCalendars.filter((calendar) => calendar.eventTypeId === eventTypeId); + return user.allSelectedCalendars.filter( + (calendar) => calendar.eventTypeId === eventTypeId + ); } async findByIdForUserAvailability({ id }: { id: number }) { @@ -1515,7 +1566,10 @@ export class EventTypeRepository { }); } - async findEventTypesWithoutChildren(eventTypeIds: number[], teamId?: number | null) { + async findEventTypesWithoutChildren( + eventTypeIds: number[], + teamId?: number | null + ) { return await this.prismaClient.eventType.findMany({ where: { id: { @@ -1534,7 +1588,11 @@ export class EventTypeRepository { }); } - async findAllIncludingChildrenByUserId({ userId }: { userId: number | null }) { + async findAllIncludingChildrenByUserId({ + userId, + }: { + userId: number | null; + }) { if (userId === null) { return []; } @@ -1600,15 +1658,21 @@ export class EventTypeRepository { id: true, parentId: true, userId: true, + teamId: true, }, }); } - async findManyChildEventTypes(parentId: number, excludeUserId?: number | null) { + async findManyChildEventTypes( + parentId: number, + excludeUserId?: number | null + ) { return this.prismaClient.eventType.findMany({ where: { parentId, - ...(excludeUserId !== undefined ? { userId: { not: excludeUserId } } : {}), + ...(excludeUserId !== undefined + ? { userId: { not: excludeUserId } } + : {}), }, select: { id: true, @@ -1661,8 +1725,12 @@ export class EventTypeRepository { ? { owner: { OR: [ - { name: { contains: searchTerm, mode: "insensitive" as const } }, - { email: { contains: searchTerm, mode: "insensitive" as const } }, + { + name: { contains: searchTerm, mode: "insensitive" as const }, + }, + { + email: { contains: searchTerm, mode: "insensitive" as const }, + }, ], }, } @@ -1850,7 +1918,10 @@ export class EventTypeRepository { } if (isMember) { - eventTypeWhereConditional["OR"] = [{ userId: user.id }, { users: { some: { id: user.id } } }]; + eventTypeWhereConditional["OR"] = [ + { userId: user.id }, + { users: { some: { id: user.id } } }, + ]; // @TODO this is not working as expected // hosts: { some: { id: user.id } }, } diff --git a/packages/platform/atoms/hooks/useVerifyEmail.ts b/packages/platform/atoms/hooks/useVerifyEmail.ts index 7b687c322bb80e..1aeca71ec143dd 100644 --- a/packages/platform/atoms/hooks/useVerifyEmail.ts +++ b/packages/platform/atoms/hooks/useVerifyEmail.ts @@ -5,7 +5,11 @@ import { useState } from "react"; import { useBookerStore } from "@calcom/features/bookings/Booker/store"; import { useDebounce } from "@calcom/lib/hooks/useDebounce"; import { SUCCESS_STATUS } from "@calcom/platform-constants"; -import type { ApiResponse, ApiErrorResponse, ApiSuccessResponseWithoutData } from "@calcom/platform-types"; +import type { + ApiResponse, + ApiErrorResponse, + ApiSuccessResponseWithoutData, +} from "@calcom/platform-types"; import { useMe } from "../hooks/useMe"; import http from "../lib/http"; @@ -22,6 +26,7 @@ export type UseVerifyEmailReturnType = ReturnType; interface RequestEmailVerificationInput { email: string; username?: string; + eventTypeId?: number; } export const useVerifyEmail = ({ @@ -30,10 +35,14 @@ export const useVerifyEmail = ({ requiresBookerEmailVerification, onVerifyEmail, }: IUseVerifyEmailProps) => { - const [isEmailVerificationModalVisible, setEmailVerificationModalVisible] = useState(false); + const [isEmailVerificationModalVisible, setEmailVerificationModalVisible] = + useState(false); const verifiedEmail = useBookerStore((state) => state.verifiedEmail); const setVerifiedEmail = useBookerStore((state) => state.setVerifiedEmail); - const isRescheduling = useBookerStore((state) => Boolean(state.rescheduleUid && state.bookingData)); + const isRescheduling = useBookerStore((state) => + Boolean(state.rescheduleUid && state.bookingData) + ); + const eventTypeId = useBookerStore((state) => state.eventId); const debouncedEmail = useDebounce(email, 600); const { data: user } = useMe(); @@ -64,12 +73,17 @@ export const useVerifyEmail = ({ >({ mutationFn: (props: RequestEmailVerificationInput) => { return http - .post>("/atoms/verification/email/send-code", props) + .post>( + "/atoms/verification/email/send-code", + props + ) .then((res) => { if (res.data.status === SUCCESS_STATUS) { return res.data; } - throw new Error(res.data.error?.message || "Failed to send verification email"); + throw new Error( + res.data.error?.message || "Failed to send verification email" + ); }); }, onSuccess: () => { @@ -85,6 +99,7 @@ export const useVerifyEmail = ({ sendEmailVerificationMutation.mutate({ email, username: typeof name === "string" ? name : name?.firstName, + eventTypeId: eventTypeId ?? undefined, }); }; @@ -100,7 +115,9 @@ export const useVerifyEmail = ({ isEmailVerificationModalVisible, setEmailVerificationModalVisible, setVerifiedEmail, - renderConfirmNotVerifyEmailButtonCond: Boolean(renderConfirmNotVerifyEmailButtonCond), + renderConfirmNotVerifyEmailButtonCond: Boolean( + renderConfirmNotVerifyEmailButtonCond + ), isVerificationCodeSending, }; }; diff --git a/packages/trpc/server/routers/viewer/auth/_router.tsx b/packages/trpc/server/routers/viewer/auth/_router.tsx index 860c33bd9a3d31..55828b99804e49 100644 --- a/packages/trpc/server/routers/viewer/auth/_router.tsx +++ b/packages/trpc/server/routers/viewer/auth/_router.tsx @@ -19,52 +19,72 @@ type AuthRouterHandlerCache = { }; export const authRouter = router({ - changePassword: authedProcedure.input(ZChangePasswordInputSchema).mutation(async ({ input, ctx }) => { - const { changePasswordHandler } = await import("./changePassword.handler"); + changePassword: authedProcedure + .input(ZChangePasswordInputSchema) + .mutation(async ({ input, ctx }) => { + const { changePasswordHandler } = await import( + "./changePassword.handler" + ); - return changePasswordHandler({ - ctx, - input, - }); - }), + return changePasswordHandler({ + ctx, + input, + }); + }), - verifyPassword: authedProcedure.input(ZVerifyPasswordInputSchema).mutation(async ({ input, ctx }) => { - const { verifyPasswordHandler } = await import("./verifyPassword.handler"); + verifyPassword: authedProcedure + .input(ZVerifyPasswordInputSchema) + .mutation(async ({ input, ctx }) => { + const { verifyPasswordHandler } = await import( + "./verifyPassword.handler" + ); - return verifyPasswordHandler({ - ctx, - input, - }); - }), + return verifyPasswordHandler({ + ctx, + input, + }); + }), - verifyCodeUnAuthenticated: publicProcedure.input(ZVerifyCodeInputSchema).mutation(async ({ input }) => { - const { verifyCodeUnAuthenticatedHandler } = await import("./verifyCodeUnAuthenticated.handler"); + verifyCodeUnAuthenticated: publicProcedure + .input(ZVerifyCodeInputSchema) + .mutation(async ({ input }) => { + const { verifyCodeUnAuthenticatedHandler } = await import( + "./verifyCodeUnAuthenticated.handler" + ); - return verifyCodeUnAuthenticatedHandler({ - input, - }); - }), + return verifyCodeUnAuthenticatedHandler({ + input, + }); + }), - sendVerifyEmailCode: publicProcedure.input(ZSendVerifyEmailCodeSchema).mutation(async ({ input, ctx }) => { - const { sendVerifyEmailCodeHandler } = await import("./sendVerifyEmailCode.handler"); + sendVerifyEmailCode: publicProcedure + .input(ZSendVerifyEmailCodeSchema) + .mutation(async ({ input, ctx }) => { + const { sendVerifyEmailCodeHandler } = await import( + "./sendVerifyEmailCode.handler" + ); - return sendVerifyEmailCodeHandler({ - input, - req: ctx.req, - }); - }), + return sendVerifyEmailCodeHandler({ + input, + req: ctx.req, + }); + }), - resendVerifyEmail: authedProcedure.input(ZResendVerifyEmailSchema).mutation(async ({ input, ctx }) => { - const { resendVerifyEmail } = await import("./resendVerifyEmail.handler"); + resendVerifyEmail: authedProcedure + .input(ZResendVerifyEmailSchema) + .mutation(async ({ input, ctx }) => { + const { resendVerifyEmail } = await import("./resendVerifyEmail.handler"); - return resendVerifyEmail({ - input, - ctx, - }); - }), + return resendVerifyEmail({ + input, + ctx, + }); + }), createAccountPassword: authedProcedure.mutation(async ({ ctx }) => { - const { createAccountPasswordHandler } = await import("./createAccountPassword.handler"); + const { createAccountPasswordHandler } = await import( + "./createAccountPassword.handler" + ); return createAccountPasswordHandler({ ctx, diff --git a/packages/trpc/server/routers/viewer/auth/sendVerifyEmailCode.handler.ts b/packages/trpc/server/routers/viewer/auth/sendVerifyEmailCode.handler.ts index b7d0bd76375704..f25ea51815e425 100644 --- a/packages/trpc/server/routers/viewer/auth/sendVerifyEmailCode.handler.ts +++ b/packages/trpc/server/routers/viewer/auth/sendVerifyEmailCode.handler.ts @@ -13,8 +13,13 @@ type SendVerifyEmailCode = { req: TRPCContext["req"] | undefined; }; -export const sendVerifyEmailCodeHandler = async ({ input, req }: SendVerifyEmailCode) => { - const identifier = req ? piiHasher.hash(getIP(req as NextApiRequest)) : hashEmail(input.email); +export const sendVerifyEmailCodeHandler = async ({ + input, + req, +}: SendVerifyEmailCode) => { + const identifier = req + ? piiHasher.hash(getIP(req as NextApiRequest)) + : hashEmail(input.email); return sendVerifyEmailCode({ input, identifier }); }; @@ -35,5 +40,6 @@ export const sendVerifyEmailCode = async ({ username: input.username, language: input.language, isVerifyingEmail: input.isVerifyingEmail, + eventTypeId: input.eventTypeId, }); }; diff --git a/packages/trpc/server/routers/viewer/auth/sendVerifyEmailCode.schema.ts b/packages/trpc/server/routers/viewer/auth/sendVerifyEmailCode.schema.ts index 714272a6c280d0..5f823cac74a0f3 100644 --- a/packages/trpc/server/routers/viewer/auth/sendVerifyEmailCode.schema.ts +++ b/packages/trpc/server/routers/viewer/auth/sendVerifyEmailCode.schema.ts @@ -5,11 +5,14 @@ export type TSendVerifyEmailCodeSchema = { username?: string; language: string; isVerifyingEmail?: boolean; + eventTypeId?: number; }; -export const ZSendVerifyEmailCodeSchema: z.ZodType = z.object({ - email: z.string().min(1), - username: z.string().optional(), - language: z.string(), - isVerifyingEmail: z.boolean().optional(), -}); +export const ZSendVerifyEmailCodeSchema: z.ZodType = + z.object({ + email: z.string().min(1), + username: z.string().optional(), + language: z.string(), + isVerifyingEmail: z.boolean().optional(), + eventTypeId: z.number().optional(), + }); From b36bab4f8e76a7397db2943200693e3123514a12 Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sat, 17 Jan 2026 02:37:28 +0530 Subject: [PATCH 51/58] chore: implement PR feedback --- packages/features/ee/payments/api/webhook.ts | 27 ++++++++++++------- packages/features/profile/lib/hideBranding.ts | 23 +++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/features/ee/payments/api/webhook.ts b/packages/features/ee/payments/api/webhook.ts index fcb84bde83947e..a9e78b924aff9b 100644 --- a/packages/features/ee/payments/api/webhook.ts +++ b/packages/features/ee/payments/api/webhook.ts @@ -157,8 +157,14 @@ const handleSetupSuccess = async ( // If the card information was already captured in the same customer. Delete the previous payment method if (!requiresConfirmation) { - const apps = eventTypeAppMetadataOptionalSchema.parse(eventType?.metadata?.apps); - const actor = getAppActor({ appSlug: "stripe", bookingId: booking.id, apps }); + const apps = eventTypeAppMetadataOptionalSchema.parse( + eventType?.metadata?.apps + ); + const actor = getAppActor({ + appSlug: "stripe", + bookingId: booking.id, + apps, + }); await handleConfirmation({ user: { ...user, credentials: allCredentials }, evt, @@ -174,7 +180,8 @@ const handleSetupSuccess = async ( actor, }); } else if (areEmailsEnabled) { - const organizationId = booking.eventType?.team?.parentId ?? user.organizationId ?? null; + const organizationId = + booking.eventType?.team?.parentId ?? user.organizationId ?? null; const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventTypeId ?? 0, @@ -184,12 +191,14 @@ const handleSetupSuccess = async ( }); const evtWithBranding = { ...evt, hideBranding }; - await sendOrganizerRequestEmail(evtWithBranding, eventType.metadata); - await sendAttendeeRequestEmailAndSMS( - evtWithBranding, - evt.attendees[0], - eventType.metadata - ); + await Promise.all([ + sendOrganizerRequestEmail(evtWithBranding, eventType.metadata), + sendAttendeeRequestEmailAndSMS( + evtWithBranding, + evt.attendees[0], + eventType.metadata + ), + ]); } }; diff --git a/packages/features/profile/lib/hideBranding.ts b/packages/features/profile/lib/hideBranding.ts index 045a13105db8c3..e21c2d06498b41 100644 --- a/packages/features/profile/lib/hideBranding.ts +++ b/packages/features/profile/lib/hideBranding.ts @@ -11,26 +11,21 @@ export type TeamWithBranding = { } | null; }; -export type ProfileWithBranding = { +type ProfileWithBranding = { organization: { hideBranding: boolean | null; } | null; }; -export type UserWithBranding = { +type UserWithBranding = { id: number; hideBranding: boolean | null; }; -export type UserWithProfileAndBranding = UserWithBranding & { +type UserWithProfileAndBranding = UserWithBranding & { profile: ProfileWithBranding | null; }; -// Internal type aliases for backward compatibility -type Team = TeamWithBranding; -type UserWithoutProfile = UserWithBranding; -type UserWithProfile = UserWithProfileAndBranding; - /** * Determines if branding should be hidden by checking entity and organization settings. * @@ -129,8 +124,8 @@ export function shouldHideBrandingForEventUsingProfile({ owner, team, }: { - owner: UserWithProfile | null; - team: Team | null; + owner: UserWithProfileAndBranding | null; + team: TeamWithBranding | null; eventTypeId: number; }) { let hideBranding; @@ -197,8 +192,8 @@ export async function shouldHideBrandingForEvent({ organizationId, }: { eventTypeId: number; - team: Team | null; - owner: UserWithoutProfile | null; + team: TeamWithBranding | null; + owner: UserWithBranding | null; organizationId: number | null; }) { let ownerProfile = null; @@ -232,7 +227,7 @@ export async function shouldHideBrandingForEvent({ /** * A convenience wrapper for shouldHideBrandingForEventUsingProfile to use for Team events */ -export function shouldHideBrandingForTeamEvent({ eventTypeId, team }: { eventTypeId: number; team: Team }) { +export function shouldHideBrandingForTeamEvent({ eventTypeId, team }: { eventTypeId: number; team: TeamWithBranding }) { return shouldHideBrandingForEventUsingProfile({ owner: null, team, @@ -248,7 +243,7 @@ export function shouldHideBrandingForUserEvent({ owner, }: { eventTypeId: number; - owner: UserWithProfile; + owner: UserWithProfileAndBranding; }) { return shouldHideBrandingForEventUsingProfile({ owner, From cd32d98853c5ed5c98c6da61ee83f3a5837eea10 Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sun, 18 Jan 2026 00:46:34 +0530 Subject: [PATCH 52/58] fix: type check --- packages/features/bookings/lib/handleConfirmation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 8ee37c40ee7f89..db43c70e5510f3 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -29,10 +29,11 @@ import { distributedTracing } from "@calcom/lib/tracing/factory"; import type { PrismaClient } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import type { SchedulingType } from "@calcom/prisma/enums"; -import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums"; +import { BookingStatus, WebhookTriggerEvents, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { PlatformClientParams } from "@calcom/prisma/zod-utils"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar"; +import logger from "@calcom/lib/logger"; import { v4 as uuidv4 } from "uuid"; import { getCalEventResponses } from "./getCalEventResponses"; @@ -169,6 +170,8 @@ export async function handleConfirmation(args: { const metadata: AdditionalInformation = {}; const workflows = await getAllWorkflowsFromEventType(eventType, booking.userId); + const log = logger.getSubLogger({ prefix: ["[handleConfirmation]"] }); + const teamId = await getTeamIdFromEventType({ eventType: { team: { id: eventType?.teamId ?? null }, From f6a126863107ac5db61999bf698d64bf438fc6ab Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sun, 18 Jan 2026 01:24:26 +0530 Subject: [PATCH 53/58] fix: unit tests --- .../ee/workflows/lib/service/WorkflowService.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/features/ee/workflows/lib/service/WorkflowService.test.ts b/packages/features/ee/workflows/lib/service/WorkflowService.test.ts index 448c6914b21303..e88578c81b9e27 100644 --- a/packages/features/ee/workflows/lib/service/WorkflowService.test.ts +++ b/packages/features/ee/workflows/lib/service/WorkflowService.test.ts @@ -18,11 +18,13 @@ vi.mock("@calcom/features/profile/lib/hideBranding", () => ({ getHideBranding: vi.fn().mockResolvedValue(false), })); -vi.mock("@calcom/features/profile/repositories/BrandingRepository", () => ({ - BrandingRepository: vi.fn().mockImplementation(() => ({ - getHideBrandingByIds: vi.fn().mockResolvedValue(false), - })), -})); +vi.mock("@calcom/features/profile/repositories/BrandingRepository", () => { + return { + BrandingRepository: class { + getHideBrandingByIds = vi.fn().mockResolvedValue(false); + }, + }; +}); const mockWorkflowReminderCreate = vi.fn(); vi.mock("@calcom/features/ee/workflows/repositories/WorkflowReminderRepository", () => ({ From de672658b1212ade8bd22f2be5db1caccdb291b1 Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sun, 18 Jan 2026 12:59:10 +0530 Subject: [PATCH 54/58] chore: shift withHideBranding fn from email-manager to hideBranding --- .../web/app/api/cron/bookingReminder/route.ts | 4 ++-- packages/emails/email-manager.ts | 7 +------ .../bookings/lib/BookingEmailSmsHandler.ts | 2 +- .../bookings/lib/handleCancelBooking.ts | 3 ++- .../features/conferencing/lib/videoClient.ts | 2 +- .../ManagedEventManualReassignmentService.ts | 2 +- .../roundRobinManualReassignment.ts | 2 +- .../ee/round-robin/roundRobinReassignment.ts | 2 +- packages/features/profile/lib/hideBranding.ts | 19 +++++++++++++++++++ 9 files changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 15481c8a6ea708..2f1ff6893a2661 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -3,9 +3,9 @@ import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import dayjs from "@calcom/dayjs"; -import { sendOrganizerRequestReminderEmail, withHideBranding } from "@calcom/emails/email-manager"; +import { sendOrganizerRequestReminderEmail } from "@calcom/emails/email-manager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; -import { shouldHideBrandingForEvent } from "@calcom/features/profile/lib/hideBranding"; +import { shouldHideBrandingForEvent, withHideBranding } from "@calcom/features/profile/lib/hideBranding"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts index 421e6a361ee529..04ad2bd4b3e599 100644 --- a/packages/emails/email-manager.ts +++ b/packages/emails/email-manager.ts @@ -6,6 +6,7 @@ import type BaseEmail from "@calcom/emails/templates/_base-email"; import type { EventNameObjectType } from "@calcom/features/eventtypes/lib/eventNaming"; import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming"; import { OrganizationSettingsRepository } from "@calcom/features/organizations/repositories/OrganizationSettingsRepository"; +import type { CalendarEventWithBranding } from "@calcom/features/profile/lib/hideBranding"; import { formatCalEvent } from "@calcom/lib/formatCalendarEvent"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -46,12 +47,6 @@ import OrganizerRequestedToRescheduleEmail from "./templates/organizer-requested import OrganizerRescheduledEmail from "./templates/organizer-rescheduled-email"; import OrganizerScheduledEmail from "./templates/organizer-scheduled-email"; -type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; - -export function withHideBranding(calEvent: CalendarEvent, explicit?: boolean): CalendarEventWithBranding { - return { ...calEvent, hideBranding: explicit ?? calEvent.hideBranding ?? false }; -} - type EventTypeMetadata = z.infer; const sendEmail = (prepare: () => BaseEmail) => { diff --git a/packages/features/bookings/lib/BookingEmailSmsHandler.ts b/packages/features/bookings/lib/BookingEmailSmsHandler.ts index 960e5b97ca5d4e..81f1007eece1b1 100644 --- a/packages/features/bookings/lib/BookingEmailSmsHandler.ts +++ b/packages/features/bookings/lib/BookingEmailSmsHandler.ts @@ -7,8 +7,8 @@ import { allowDisablingAttendeeConfirmationEmails, } from "@calcom/ee/workflows/lib/allowDisablingStandardEmails"; import type { Workflow as WorkflowType } from "@calcom/ee/workflows/lib/types"; -import { withHideBranding } from "@calcom/emails/email-manager"; import type { BookingType } from "@calcom/features/bookings/lib/handleNewBooking/originalRescheduledBookingUtils"; +import { withHideBranding } from "@calcom/features/profile/lib/hideBranding"; import type { EventNameObjectType } from "@calcom/features/eventtypes/lib/eventNaming"; import { getPiiFreeCalendarEvent } from "@calcom/lib/piiFreeData"; import { safeStringify } from "@calcom/lib/safeStringify"; diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 27f87772eead71..8a14ef55726639 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -5,8 +5,9 @@ import { DailyLocationType } from "@calcom/app-store/constants"; import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; -import { sendCancelledEmailsAndSMS, withHideBranding } from "@calcom/emails/email-manager"; +import { sendCancelledEmailsAndSMS } from "@calcom/emails/email-manager"; import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; +import { withHideBranding } from "@calcom/features/profile/lib/hideBranding"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; diff --git a/packages/features/conferencing/lib/videoClient.ts b/packages/features/conferencing/lib/videoClient.ts index dc841be2e2de9a..97982569ce249f 100644 --- a/packages/features/conferencing/lib/videoClient.ts +++ b/packages/features/conferencing/lib/videoClient.ts @@ -4,8 +4,8 @@ import { v5 as uuidv5 } from "uuid"; import { DailyLocationType } from "@calcom/app-store/constants"; import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys"; import { getVideoAdapters } from "@calcom/app-store/getVideoAdapters"; -import { withHideBranding } from "@calcom/emails/email-manager"; import { sendBrokenIntegrationEmail } from "@calcom/emails/integration-email-service"; +import { withHideBranding } from "@calcom/features/profile/lib/hideBranding"; import { getUid } from "@calcom/lib/CalEventParser"; import { CAL_VIDEO, CAL_VIDEO_TYPE } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; diff --git a/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts b/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts index 0f09e9d831ce46..d869b39ad227ba 100644 --- a/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts +++ b/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts @@ -4,8 +4,8 @@ import { sendRoundRobinReassignedEmailsAndSMS, sendReassignedScheduledEmailsAndSMS, sendReassignedUpdatedEmailsAndSMS, - withHideBranding, } from "@calcom/emails/email-manager"; +import { withHideBranding } from "@calcom/features/profile/lib/hideBranding"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB"; diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index c9fa9a86a71a57..e65d439dbe1485 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -7,8 +7,8 @@ import { sendRoundRobinReassignedEmailsAndSMS, sendReassignedScheduledEmailsAndSMS, sendReassignedUpdatedEmailsAndSMS, - withHideBranding, } from "@calcom/emails/email-manager"; +import { withHideBranding } from "@calcom/features/profile/lib/hideBranding"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index c9a6c5d193b462..8dc7dbe51cf2b3 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -11,8 +11,8 @@ import { sendRoundRobinReassignedEmailsAndSMS, sendReassignedScheduledEmailsAndSMS, sendReassignedUpdatedEmailsAndSMS, - withHideBranding, } from "@calcom/emails/email-manager"; +import { withHideBranding } from "@calcom/features/profile/lib/hideBranding"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; diff --git a/packages/features/profile/lib/hideBranding.ts b/packages/features/profile/lib/hideBranding.ts index e21c2d06498b41..de19531325b779 100644 --- a/packages/features/profile/lib/hideBranding.ts +++ b/packages/features/profile/lib/hideBranding.ts @@ -2,6 +2,7 @@ import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepos import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import logger from "@calcom/lib/logger"; +import type { CalendarEvent } from "@calcom/types/Calendar"; const log = logger.getSubLogger({ name: "hideBranding" }); export type TeamWithBranding = { @@ -252,3 +253,21 @@ export function shouldHideBrandingForUserEvent({ }); } +/** + * A CalendarEvent with hideBranding explicitly set as a boolean. + * Used when preparing calendar events for email sending. + */ +export type CalendarEventWithBranding = CalendarEvent & { hideBranding: boolean }; + +/** + * Transforms a CalendarEvent to include an explicit hideBranding boolean value. + * + * @param calEvent - The calendar event to transform + * @param explicit - Optional explicit value for hideBranding. If provided, this value is used. + * Otherwise falls back to calEvent.hideBranding, then to false. + * @returns CalendarEvent with hideBranding guaranteed to be a boolean + */ +export function withHideBranding(calEvent: CalendarEvent, explicit?: boolean): CalendarEventWithBranding { + return { ...calEvent, hideBranding: explicit ?? calEvent.hideBranding ?? false }; +} + From 6d21283d769e1229908a395529b40b78e7c8dc0d Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Sun, 18 Jan 2026 15:01:39 +0530 Subject: [PATCH 55/58] chore: implement cubic feedback --- .../inputs/send-verification-email.input.ts | 10 +---- packages/features/auth/lib/verifyEmail.ts | 45 +++++++------------ .../credentials/handleDeleteCredential.ts | 35 ++++++++------- 3 files changed, 38 insertions(+), 52 deletions(-) diff --git a/apps/api/v2/src/modules/atoms/inputs/send-verification-email.input.ts b/apps/api/v2/src/modules/atoms/inputs/send-verification-email.input.ts index 098878ebda0a89..bcb843779ab605 100644 --- a/apps/api/v2/src/modules/atoms/inputs/send-verification-email.input.ts +++ b/apps/api/v2/src/modules/atoms/inputs/send-verification-email.input.ts @@ -1,11 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { - IsEmail, - IsOptional, - IsString, - IsBoolean, - IsNumber, -} from "class-validator"; +import { IsBoolean, IsEmail, IsInt, IsOptional, IsString } from "class-validator"; export class SendVerificationEmailInput { @ApiProperty({ example: "user@example.com" }) @@ -29,6 +23,6 @@ export class SendVerificationEmailInput { @ApiPropertyOptional({ example: 123 }) @IsOptional() - @IsNumber() + @IsInt() eventTypeId?: number; } diff --git a/packages/features/auth/lib/verifyEmail.ts b/packages/features/auth/lib/verifyEmail.ts index 8b12ee7a3ce692..c8bc2312f80cc1 100644 --- a/packages/features/auth/lib/verifyEmail.ts +++ b/packages/features/auth/lib/verifyEmail.ts @@ -1,25 +1,25 @@ -import { randomBytes, createHash } from "node:crypto"; -import { totp } from "otplib"; - +import { createHash, randomBytes } from "node:crypto"; +import process from "node:process"; import { + sendChangeOfEmailVerificationLink, sendEmailVerificationCode, sendEmailVerificationLink, - sendChangeOfEmailVerificationLink, } from "@calcom/emails/auth-email-service"; +import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; +import { EventRepository } from "@calcom/features/eventtypes/repositories/EventRepository"; import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository"; import { FeaturesRepository } from "@calcom/features/flags/features.repository"; +import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; +import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { sentrySpan } from "@calcom/features/watchlist/lib/telemetry"; import { checkIfEmailIsBlockedInWatchlistController } from "@calcom/features/watchlist/operations/check-if-email-in-watchlist.controller"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { WEBAPP_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; -import { hashEmail } from "@calcom/lib/server/PiiHasher"; import { getTranslation } from "@calcom/lib/server/i18n"; +import { hashEmail } from "@calcom/lib/server/PiiHasher"; import { prisma } from "@calcom/prisma"; -import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; -import { getHideBranding } from "@calcom/features/profile/lib/hideBranding"; -import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; -import { EventRepository } from "@calcom/features/eventtypes/repositories/EventRepository"; +import { totp } from "otplib"; const log = logger.getSubLogger({ prefix: [`[[Auth] `] }); @@ -43,10 +43,7 @@ export const sendEmailVerification = async ({ const token = randomBytes(32).toString("hex"); const translation = await getTranslation(language ?? "en", "common"); const featuresRepository = new FeaturesRepository(prisma); - const emailVerification = - await featuresRepository.checkIfFeatureIsEnabledGlobally( - "email-verification" - ); + const emailVerification = await featuresRepository.checkIfFeatureIsEnabledGlobally("email-verification"); if (!emailVerification) { log.warn("Email verification is disabled - Skipping"); @@ -135,10 +132,9 @@ export const sendEmailVerificationByCode = async ({ let teamId: number | undefined; let userId: number | undefined; - const eventType = - await eventTypeRepository.findByIdIncludeHostsAndTeamMembers({ - id: eventTypeId, - }); + const eventType = await eventTypeRepository.findByIdIncludeHostsAndTeamMembers({ + id: eventTypeId, + }); teamId = eventType?.teamId ?? undefined; userId = eventType?.userId ?? undefined; @@ -174,17 +170,11 @@ interface ChangeOfEmail { language?: string; } -export const sendChangeOfEmailVerification = async ({ - user, - language, -}: ChangeOfEmail) => { +export const sendChangeOfEmailVerification = async ({ user, language }: ChangeOfEmail) => { const token = randomBytes(32).toString("hex"); const translation = await getTranslation(language ?? "en", "common"); const featuresRepository = new FeaturesRepository(prisma); - const emailVerification = - await featuresRepository.checkIfFeatureIsEnabledGlobally( - "email-verification" - ); + const emailVerification = await featuresRepository.checkIfFeatureIsEnabledGlobally("email-verification"); if (!emailVerification) { log.warn("Email verification is disabled - Skipping"); @@ -198,10 +188,7 @@ export const sendChangeOfEmailVerification = async ({ span: sentrySpan, }) ) { - log.warn( - "Email is blocked - not sending verification email", - user.emailFrom - ); + log.warn("Email is blocked - not sending verification email"); return { ok: false, skipped: false }; } diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 09b3724fa53b11..91b91b9567141f 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -1,13 +1,11 @@ -import z from "zod"; - import { getCalendar } from "@calcom/app-store/_utils/getCalendar"; import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; import { DailyLocationType } from "@calcom/app-store/locations"; import { type EventTypeAppMetadataSchema, eventTypeAppMetadataOptionalSchema, + eventTypeMetaDataSchemaWithTypedApps, } from "@calcom/app-store/zod-utils"; -import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import { sendCancelledEmailsAndSMS } from "@calcom/emails/email-manager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { deletePayment } from "@calcom/features/bookings/lib/payment/deletePayment"; @@ -22,8 +20,8 @@ import type { Prisma } from "@calcom/prisma/client"; import { AppCategories, BookingStatus } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; -import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; -import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils"; +import { EventTypeMetaDataSchema, userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils"; +import z from "zod"; type App = { slug: string; @@ -349,12 +347,16 @@ const handleDeleteCredential = async ({ const attendeesList = await Promise.all(attendeesListPromises); const tOrganizer = await getTranslation(booking?.user?.locale ?? "en", "common"); - const organizationId = booking.eventType?.team?.parentId ?? booking.user?.organizationId ?? null; + const organizationId = + booking.eventType?.team?.parentId ?? + booking.user?.organizationId ?? + booking.user?.profiles?.[0]?.organizationId ?? + null; const hideBranding = await shouldHideBrandingForEvent({ eventTypeId: booking.eventTypeId ?? 0, team: booking.eventType?.team ?? null, - owner: booking.eventType?.team ? null : booking.user ?? null, + owner: booking.eventType?.team ? null : (booking.user ?? null), organizationId: organizationId, }); @@ -383,8 +385,8 @@ const handleDeleteCredential = async ({ destinationCalendar: booking.destinationCalendar ? [booking.destinationCalendar] : booking.user?.destinationCalendar - ? [booking.user?.destinationCalendar] - : [], + ? [booking.user?.destinationCalendar] + : [], cancellationReason: "Payment method removed by organizer", seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot, seatsShowAttendees: booking.eventType?.seatsShowAttendees, @@ -514,12 +516,15 @@ const removeAppFromEventTypeMetadata = ( } ) => { const appMetadata = eventTypeMetadata?.apps - ? Object.entries(eventTypeMetadata.apps).reduce((filteredApps, [appName, appData]) => { - if (appName !== appSlugToDelete) { - filteredApps[appName as keyof typeof eventTypeMetadata.apps] = appData; - } - return filteredApps; - }, {} as z.infer) + ? Object.entries(eventTypeMetadata.apps).reduce( + (filteredApps, [appName, appData]) => { + if (appName !== appSlugToDelete) { + filteredApps[appName as keyof typeof eventTypeMetadata.apps] = appData; + } + return filteredApps; + }, + {} as z.infer + ) : {}; return appMetadata; From f5cd5f8a4ed56627360ee98e2f303920650ce761 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:14:08 +0000 Subject: [PATCH 56/58] fix: revert formatting changes to improve PR readability Co-Authored-By: rajiv@cal.com --- .../reschedule/attendee/attendeeRescheduleSeatedBooking.ts | 1 + .../lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts | 1 + .../reschedule/owner/moveSeatedBookingToNewTimeSlot.ts | 1 + .../trpc/server/routers/viewer/bookings/addGuests.handler.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 9c0daf09a3e5f5..91e5b23a384265 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports import { cloneDeep } from "lodash"; import { sendRescheduledSeatEmailAndSMS } from "@calcom/emails/email-manager"; diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index eb76676cb80f65..f43c79b000e18d 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports import { cloneDeep } from "lodash"; import { uuid } from "short-uuid"; diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index 03544e98ce901f..0f165e9116ccf8 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports import { cloneDeep } from "lodash"; import { sendRescheduledEmailsAndSMS } from "@calcom/emails/email-manager"; diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 9858007e5bb59f..a24f719389b991 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -41,6 +41,7 @@ type AddGuestsOptions = { type Booking = NonNullable< Awaited> >; + type OrganizerData = Awaited>; export const addGuestsHandler = async ({ From 489f07780cfb220d4e0ced18b5cc6068246725df Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:22:01 +0000 Subject: [PATCH 57/58] refactor: remove unused findByIdIncludeDestinationCalendar method Co-Authored-By: rajiv@cal.com --- .../repositories/BookingRepository.ts | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index 1fff4ab605509a..8068aec1dc9ade 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1619,41 +1619,6 @@ async updateMany({ where, data }: { where: BookingWhereInput; data: BookingUpdat }); } - async findByIdIncludeDestinationCalendar(bookingId: number) { - return await this.prismaClient.booking.findUnique({ - where: { - id: bookingId, - }, - include: { - attendees: true, - eventType: true, - destinationCalendar: true, - references: true, - user: { - select: { - id: true, - email: true, - name: true, - timeZone: true, - locale: true, - organizationId: true, - hideBranding: true, - username: true, - destinationCalendar: true, - credentials: { - select: safeCredentialSelect, - }, - profiles: { - select: { - organizationId: true, - }, - }, - }, - }, - }, - }); - } - async findByIdForReassignment(bookingId: number) { return await this.prismaClient.booking.findUnique({ where: { From a506fe6b36681038e0db287234448418ca5c1bd8 Mon Sep 17 00:00:00 2001 From: Ryukemeister Date: Wed, 21 Jan 2026 17:14:46 +0530 Subject: [PATCH 58/58] chore: implement cubic feedback --- .../ManagedEventManualReassignmentService.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts b/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts index d869b39ad227ba..a524910cc86634 100644 --- a/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts +++ b/packages/features/ee/managed-event-types/reassignment/services/ManagedEventManualReassignmentService.ts @@ -5,7 +5,10 @@ import { sendReassignedScheduledEmailsAndSMS, sendReassignedUpdatedEmailsAndSMS, } from "@calcom/emails/email-manager"; -import { withHideBranding } from "@calcom/features/profile/lib/hideBranding"; +import { + withHideBranding, + shouldHideBrandingForEvent, +} from "@calcom/features/profile/lib/hideBranding"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB"; @@ -213,6 +216,7 @@ export class ManagedEventManualReassignmentService { videoCallData, additionalInformation, reassignReason, + orgId, logger: reassignLogger, }); } @@ -661,6 +665,7 @@ export class ManagedEventManualReassignmentService { videoCallData, additionalInformation, reassignReason, + orgId, logger, }: { newBooking: ManagedEventReassignmentCreatedBooking; @@ -674,11 +679,21 @@ export class ManagedEventManualReassignmentService { videoCallData: CalendarEvent["videoCallData"]; additionalInformation: AdditionalInformation; reassignReason?: string; + orgId: number | null; logger: ReturnType; }) { try { const eventTypeMetadata = targetEventTypeDetails.metadata as EventTypeMetadata | undefined; + const hideBranding = await shouldHideBrandingForEvent({ + eventTypeId: targetEventTypeDetails.id, + team: targetEventTypeDetails.team ?? null, + owner: targetEventTypeDetails.owner + ? { id: newUser.id, hideBranding: targetEventTypeDetails.owner.hideBranding } + : null, + organizationId: orgId, + }); + const bookerUrlForEmail = await getBookerBaseUrl(targetEventTypeDetails.team?.parentId ?? null); const attendeesForEmail = newBooking.attendees.map( @@ -732,7 +747,7 @@ export class ManagedEventManualReassignmentService { calEvent.additionalInformation = additionalInformation; await sendReassignedScheduledEmailsAndSMS({ - calEvent: withHideBranding(calEvent), + calEvent: withHideBranding(calEvent, hideBranding), members: [ { ...newUser, @@ -766,7 +781,7 @@ export class ManagedEventManualReassignmentService { }; await sendRoundRobinReassignedEmailsAndSMS({ - calEvent: withHideBranding(cancelledCalEvent), + calEvent: withHideBranding(cancelledCalEvent, hideBranding), members: [ { ...originalUser, @@ -784,7 +799,7 @@ export class ManagedEventManualReassignmentService { if (dayjs(calEvent.startTime).isAfter(dayjs())) { await sendReassignedUpdatedEmailsAndSMS({ - calEvent: withHideBranding(calEvent), + calEvent: withHideBranding(calEvent, hideBranding), eventTypeMetadata, showAttendees: !!targetEventTypeDetails.seatsShowAttendees, });
-
`} - /> -
- -