diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts b/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts index ff1f25c63e..3ed1af1e63 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts @@ -26,6 +26,7 @@ import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/ import { InstantBookingModule } from "@/lib/modules/instant-booking.module"; import { RecurringBookingModule } from "@/lib/modules/recurring-booking.module"; import { RegularBookingModule } from "@/lib/modules/regular-booking.module"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; import { AppsRepository } from "@/modules/apps/apps.repository"; import { BillingModule } from "@/modules/billing/billing.module"; @@ -105,6 +106,7 @@ import { BookingEventHandlerModule } from "@/lib/modules/booking-event-handler.m CalVideoOutputService, BookingPbacGuard, BookingLocationService_2024_08_13, + PrismaFeaturesRepository, ], controllers: [ BookingsController_2024_08_13, diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts index 44318b530c..a9eff5ebb8 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts @@ -10,6 +10,7 @@ import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/reposito import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service"; import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/input.service"; import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service"; import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy"; import { EventTypeAccessService } from "@/modules/event-types/services/event-type-access.service"; @@ -26,7 +27,8 @@ export class BookingLocationService_2024_08_13 { private readonly inputService: InputBookingsService_2024_08_13, private readonly eventTypesRepository: EventTypesRepository_2024_06_14, private readonly eventTypeAccessService: EventTypeAccessService, - private readonly bookingEventHandlerService: BookingEventHandlerService + private readonly bookingEventHandlerService: BookingEventHandlerService, + private readonly featuresRepository: PrismaFeaturesRepository ) {} async updateBookingLocation( @@ -99,10 +101,15 @@ export class BookingLocationService_2024_08_13 { responses: updatedBookingResponses, }); + const organizationId = existingBookingHost.organizationId ?? null; + const isBookingAuditEnabled = organizationId + ? await this.featuresRepository.checkIfTeamHasFeature(organizationId, "booking-audit") + : false; + await this.bookingEventHandlerService.onLocationChanged({ bookingUid: existingBooking.uid, actor: makeUserActor(user.uuid), - organizationId: existingBookingHost.organizationId ?? null, + organizationId, source: "API_V2", auditData: { location: { @@ -110,6 +117,7 @@ export class BookingLocationService_2024_08_13 { new: bookingLocation, }, }, + isBookingAuditEnabled, }); return this.bookingsService.getBooking(updatedBooking.uid, user); diff --git a/apps/api/v2/src/lib/modules/recurring-booking.module.ts b/apps/api/v2/src/lib/modules/recurring-booking.module.ts index b3849bdd30..9a0a8614a4 100644 --- a/apps/api/v2/src/lib/modules/recurring-booking.module.ts +++ b/apps/api/v2/src/lib/modules/recurring-booking.module.ts @@ -7,8 +7,10 @@ import { Scope } from "@nestjs/common"; import { BookingAuditProducerService } from "@/lib/services/booking-audit-producer.service"; import { HashedLinkService } from "@/lib/services/hashed-link.service"; import { TaskerService } from "@/lib/services/tasker.service"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; +import { PrismaWorkerModule } from "@/modules/prisma/prisma-worker.module"; @Module({ - imports: [RegularBookingModule], + imports: [RegularBookingModule, PrismaWorkerModule], providers: [ RecurringBookingService, BookingEventHandlerService, @@ -24,6 +26,8 @@ import { TaskerService } from "@/lib/services/tasker.service"; BookingAuditProducerService, TaskerService, /** Required by BookingEventHandlerService - Ends **/ + /** Required by RecurringBookingService **/ + PrismaFeaturesRepository, ], exports: [RecurringBookingService], }) diff --git a/apps/api/v2/src/lib/services/recurring-booking.service.ts b/apps/api/v2/src/lib/services/recurring-booking.service.ts index 79888bdf6e..ffa418a9f8 100644 --- a/apps/api/v2/src/lib/services/recurring-booking.service.ts +++ b/apps/api/v2/src/lib/services/recurring-booking.service.ts @@ -1,5 +1,6 @@ import { RegularBookingService } from "@/lib/services/regular-booking.service"; import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; import { Injectable } from "@nestjs/common"; import { RecurringBookingService as BaseRecurringBookingService } from "@calcom/platform-libraries/bookings"; @@ -8,11 +9,13 @@ import { RecurringBookingService as BaseRecurringBookingService } from "@calcom/ export class RecurringBookingService extends BaseRecurringBookingService { constructor( regularBookingService: RegularBookingService, - bookingEventHandler: BookingEventHandlerService + bookingEventHandler: BookingEventHandlerService, + featuresRepository: PrismaFeaturesRepository ) { super({ regularBookingService, bookingEventHandler, + featuresRepository, }); } } diff --git a/packages/features/booking-audit/lib/service/BookingAuditProducerService.interface.ts b/packages/features/booking-audit/lib/service/BookingAuditProducerService.interface.ts index 239d6d0d1b..1a4aa0c232 100644 --- a/packages/features/booking-audit/lib/service/BookingAuditProducerService.interface.ts +++ b/packages/features/booking-audit/lib/service/BookingAuditProducerService.interface.ts @@ -38,6 +38,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueRescheduledAudit(params: { @@ -48,6 +49,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueAcceptedAudit(params: { @@ -58,6 +60,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueCancelledAudit(params: { @@ -68,6 +71,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueRescheduleRequestedAudit(params: { @@ -78,6 +82,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueAttendeeAddedAudit(params: { @@ -88,6 +93,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueNoShowUpdatedAudit(params: { @@ -98,6 +104,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueRejectedAudit(params: { @@ -108,6 +115,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueAttendeeRemovedAudit(params: { @@ -118,6 +126,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueReassignmentAudit(params: { @@ -128,6 +137,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueLocationChangedAudit(params: { @@ -138,6 +148,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueSeatBookedAudit(params: { @@ -148,6 +159,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueSeatRescheduledAudit(params: { @@ -158,11 +170,9 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; - /** - * Queues a bulk accepted audit task for multiple bookings - */ queueBulkAcceptedAudit(params: { bookings: Array<{ bookingUid: string; @@ -173,11 +183,9 @@ export interface BookingAuditProducerService { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; - /** - * Queues a bulk cancelled audit task for multiple bookings - */ queueBulkCancelledAudit(params: { bookings: Array<{ bookingUid: string; @@ -188,6 +196,7 @@ export interface BookingAuditProducerService { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueBulkCreatedAudit(params: { @@ -199,6 +208,7 @@ export interface BookingAuditProducerService { organizationId: number | null; source: ActionSource; operationId?: string | null; + isBookingAuditEnabled: boolean; }): Promise; queueBulkRescheduledAudit(params: { @@ -210,6 +220,7 @@ export interface BookingAuditProducerService { organizationId: number | null; source: ActionSource; operationId?: string | null; + isBookingAuditEnabled: boolean; }): Promise; queueBulkRejectedAudit(params: { @@ -222,5 +233,6 @@ export interface BookingAuditProducerService { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; } diff --git a/packages/features/booking-audit/lib/service/BookingAuditTaskerProducerService.ts b/packages/features/booking-audit/lib/service/BookingAuditTaskerProducerService.ts index c800a5c9c5..669c362dac 100644 --- a/packages/features/booking-audit/lib/service/BookingAuditTaskerProducerService.ts +++ b/packages/features/booking-audit/lib/service/BookingAuditTaskerProducerService.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import { v4 as uuidv4 } from "uuid"; import type { Tasker } from "@calcom/features/tasker/tasker"; -import { IS_PRODUCTION } from "@calcom/lib/constants"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { ISimpleLogger } from "@calcom/features/di/shared/services/logger.service"; @@ -89,6 +88,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe * Internal helper to queue audit task to Tasker * @param params.action - Must be a valid BookingAuditAction value (TYPE from action services are string-typed) * @param params.operationId - Optional operation ID for correlating bulk operations. If null, will be auto-generated. + * @param params.isBookingAuditEnabled - Flag indicating if booking audit is enabled for the organization. + * When false, skips queueing. */ private async queueTask(params: { bookingUid: string; @@ -99,12 +100,18 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe operationId?: string | null; data: unknown; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { // Skip queueing for non-organization bookings if (params.organizationId === null) { return; } - if (IS_PRODUCTION) { + // Skip queueing if booking audit is disabled for this organization + if (!params.isBookingAuditEnabled) { + this.log.debug( + `Skipping ${params.action} audit: booking-audit feature is disabled for organization`, + { organizationId: params.organizationId, bookingUid: params.bookingUid, action: params.action } + ); return; } try { @@ -138,6 +145,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -152,6 +161,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -166,6 +177,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -180,6 +193,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -194,6 +209,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -208,6 +225,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -223,6 +242,7 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -237,6 +257,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -251,6 +273,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -265,6 +289,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -279,6 +305,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -293,6 +321,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -307,6 +337,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -325,12 +357,18 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { // Skip queueing for non-organization bookings if (params.organizationId === null) { return; } - if (IS_PRODUCTION) { + // Skip queueing if booking audit is disabled for this organization + if (!params.isBookingAuditEnabled) { + this.log.debug( + `Skipping bulk ${params.action} audit: booking-audit feature is disabled for organization`, + { organizationId: params.organizationId, bookingCount: params.bookings.length, action: params.action } + ); return; } try { @@ -365,6 +403,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, @@ -381,6 +421,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, @@ -397,6 +439,7 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, @@ -413,6 +456,7 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, @@ -429,6 +473,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, diff --git a/packages/features/bookings/di/RecurringBookingService.module.ts b/packages/features/bookings/di/RecurringBookingService.module.ts index 06f1f69a8a..bbcb1977ae 100644 --- a/packages/features/bookings/di/RecurringBookingService.module.ts +++ b/packages/features/bookings/di/RecurringBookingService.module.ts @@ -1,5 +1,6 @@ import { RecurringBookingService } from "@calcom/features/bookings/lib/service/RecurringBookingService"; import { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di"; +import { moduleLoader as featuresRepositoryModuleLoader } from "@calcom/features/di/modules/FeaturesRepository"; import { DI_TOKENS } from "@calcom/features/di/tokens"; import { moduleLoader as regularBookingServiceModuleLoader } from "./RegularBookingService.module"; @@ -17,6 +18,7 @@ const loadModule = bindModuleToClassOnToken({ depsMap: { regularBookingService: regularBookingServiceModuleLoader, bookingEventHandler: bookingEventHandlerModuleLoader, + featuresRepository: featuresRepositoryModuleLoader, }, }); diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 8f4bcfbd7a..92f278c543 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -13,6 +13,7 @@ import { import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { BookingReferenceRepository } from "@calcom/features/bookingReference/repositories/BookingReferenceRepository"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; 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"; @@ -273,6 +274,11 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: organizerUserId, teamId }); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + const subscriberOptions: GetSubscriberOptions = { userId: organizerUserId, eventTypeId: bookingToDelete.eventTypeId as number, @@ -536,6 +542,7 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) { organizationId: orgId ?? null, operationId, source: actionSource, + isBookingAuditEnabled, }); } else { if (bookingToDelete?.eventType?.seatsPerTimeSlot) { @@ -570,6 +577,7 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) { new: BookingStatus.CANCELLED, }, }, + isBookingAuditEnabled, }); if (bookingToDelete.payment.some((payment) => payment.paymentOption === "ON_BOOKING")) { diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index f12a7dacf5..8f7bf89ffd 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -4,6 +4,7 @@ import { sendScheduledEmailsAndSMS } from "@calcom/emails/email-manager"; import type { Actor } from "@calcom/features/booking-audit/lib/dto/types"; import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import type { EventManagerUser } from "@calcom/features/bookings/lib/EventManager"; import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { CreditService } from "@calcom/features/ee/billing/credit-service"; @@ -43,6 +44,7 @@ async function fireBookingAcceptedEvent({ organizationId, actionSource, acceptedBookings, + isBookingAuditEnabled, tracingLogger, }: { actor: Actor; @@ -52,6 +54,7 @@ async function fireBookingAcceptedEvent({ uid: string; oldStatus: BookingStatus; }[]; + isBookingAuditEnabled: boolean; tracingLogger: ISimpleLogger; }) { try { @@ -69,6 +72,7 @@ async function fireBookingAcceptedEvent({ organizationId, operationId, source: actionSource, + isBookingAuditEnabled, }); } else if (acceptedBookings.length === 1) { const acceptedBooking = acceptedBookings[0]; @@ -80,6 +84,7 @@ async function fireBookingAcceptedEvent({ status: { old: acceptedBooking.oldStatus, new: BookingStatus.ACCEPTED }, }, source: actionSource, + isBookingAuditEnabled, }); } } catch (error) { @@ -410,6 +415,11 @@ export async function handleConfirmation(args: { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + const bookerUrl = await getBookerBaseUrl(orgId ?? null); await fireBookingAcceptedEvent({ @@ -417,6 +427,7 @@ export async function handleConfirmation(args: { acceptedBookings, organizationId: orgId ?? null, actionSource, + isBookingAuditEnabled, tracingLogger, }); diff --git a/packages/features/bookings/lib/handleSeats/handleSeats.ts b/packages/features/bookings/lib/handleSeats/handleSeats.ts index 62b696d63a..7c1153a449 100644 --- a/packages/features/bookings/lib/handleSeats/handleSeats.ts +++ b/packages/features/bookings/lib/handleSeats/handleSeats.ts @@ -3,6 +3,7 @@ import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhoo import { CreditService } from "@calcom/features/ee/billing/credit-service"; import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; import type { EventPayloadType } from "@calcom/features/webhooks/lib/sendPayload"; +import type { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; @@ -28,6 +29,7 @@ const fireBookingEvents = async ({ rescheduledBy, actionSource, actorUserUuid, + isBookingAuditEnabled, deps, }: { previousSeatedBooking: SeatedBooking; @@ -40,6 +42,7 @@ const fireBookingEvents = async ({ rescheduledBy: string | undefined; actionSource: ActionSource; actorUserUuid: string | null; + isBookingAuditEnabled: boolean; deps: { bookingEventHandler: BookingEventHandlerService; logger: ISimpleLogger; @@ -104,6 +107,7 @@ const fireBookingEvents = async ({ }, }, source: actionSource, + isBookingAuditEnabled, }); } else { await deps.bookingEventHandler.onSeatBooked({ @@ -118,6 +122,7 @@ const fireBookingEvents = async ({ endTime: previousSeatedBooking.endTime.getTime(), }, source: actionSource, + isBookingAuditEnabled, }); } } catch (error) { @@ -125,7 +130,10 @@ const fireBookingEvents = async ({ } }; -const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => { +const handleSeats = async ( + newSeatedBookingObject: NewSeatedBookingObject, + featuresRepository: FeaturesRepository +) => { const { eventType, reqBodyUser, @@ -221,6 +229,10 @@ const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => { // If the resultBooking is defined we should trigger workflows else, trigger in handleNewBooking if (resultBooking) { + const isBookingAuditEnabled = organizationId + ? await featuresRepository.checkIfTeamHasFeature(organizationId, "booking-audit") + : false; + await fireBookingEvents({ previousSeatedBooking: seatedBooking, newBooking: resultBooking, @@ -232,6 +244,7 @@ const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => { rescheduledBy, actionSource, actorUserUuid: reqUserUuid ?? null, + isBookingAuditEnabled, deps: { bookingEventHandler, logger: loggerWithEventDetails }, }); const metadata = { diff --git a/packages/features/bookings/lib/onBookingEvents/BookingEventHandlerService.ts b/packages/features/bookings/lib/onBookingEvents/BookingEventHandlerService.ts index 2999afb04c..24d27fa883 100644 --- a/packages/features/bookings/lib/onBookingEvents/BookingEventHandlerService.ts +++ b/packages/features/bookings/lib/onBookingEvents/BookingEventHandlerService.ts @@ -32,6 +32,7 @@ interface OnBookingCreatedParams { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; } interface OnBookingRescheduledParams { @@ -41,6 +42,7 @@ interface OnBookingRescheduledParams { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; } interface BaseBookingEventParams { @@ -51,6 +53,7 @@ interface BaseBookingEventParams { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; } type OnBookingAcceptedParams = BaseBookingEventParams; @@ -75,7 +78,7 @@ export class BookingEventHandlerService { } async onBookingCreated(params: OnBookingCreatedParams) { - const { payload, actor, auditData, source, operationId, context } = params; + const { payload, actor, auditData, source, operationId, context, isBookingAuditEnabled } = params; this.log.debug("onBookingCreated", safeStringify(payload)); if (payload.config.isDryRun) { return; @@ -89,11 +92,12 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onBookingRescheduled(params: OnBookingRescheduledParams) { - const { payload, actor, auditData, source, operationId, context } = params; + const { payload, actor, auditData, source, operationId, context, isBookingAuditEnabled } = params; this.log.debug("onBookingRescheduled", safeStringify(payload)); if (payload.config.isDryRun) { return; @@ -109,6 +113,7 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } @@ -142,7 +147,8 @@ export class BookingEventHandlerService { } async onBookingAccepted(params: OnBookingAcceptedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueAcceptedAudit({ bookingUid, actor, @@ -151,11 +157,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onBookingCancelled(params: OnBookingCancelledParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueCancelledAudit({ bookingUid, actor, @@ -164,11 +172,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onRescheduleRequested(params: OnRescheduleRequestedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueRescheduleRequestedAudit({ bookingUid, actor, @@ -177,11 +187,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onAttendeeAdded(params: OnAttendeeAddedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueAttendeeAddedAudit({ bookingUid, actor, @@ -190,11 +202,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onNoShowUpdated(params: OnNoShowUpdatedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueNoShowUpdatedAudit({ bookingUid, actor, @@ -203,11 +217,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onBookingRejected(params: OnBookingRejectedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueRejectedAudit({ bookingUid, actor, @@ -216,11 +232,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onAttendeeRemoved(params: OnAttendeeRemovedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueAttendeeRemovedAudit({ bookingUid, actor, @@ -229,11 +247,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onReassignment(params: OnReassignmentParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueReassignmentAudit({ bookingUid, actor, @@ -242,12 +262,14 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onLocationChanged(params: OnLocationChangedParams) { try { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueLocationChangedAudit({ bookingUid, actor, @@ -256,6 +278,7 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } catch (error) { this.log.error("Error while onLocationChanged", safeStringify(error)); @@ -263,7 +286,8 @@ export class BookingEventHandlerService { } async onSeatBooked(params: OnSeatBookedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueSeatBookedAudit({ bookingUid, actor, @@ -272,11 +296,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onSeatRescheduled(params: OnSeatRescheduledParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueSeatRescheduledAudit({ bookingUid, actor, @@ -285,13 +311,10 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } - /** - * Handles bulk booking acceptance for recurring bookings - * Creates a single task that will be processed to create multiple audit logs atomically - */ async onBulkBookingsAccepted(params: { bookings: Array<{ bookingUid: string; @@ -302,8 +325,9 @@ export class BookingEventHandlerService { operationId?: string | null; source: ActionSource; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source, context } = params; + const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkAcceptedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -314,13 +338,10 @@ export class BookingEventHandlerService { source, operationId, context, + isBookingAuditEnabled, }); } - /** - * Handles bulk booking cancellation for recurring bookings - * Creates a single task that will be processed to create multiple audit logs atomically - */ async onBulkBookingsCancelled(params: { bookings: Array<{ bookingUid: string; @@ -331,8 +352,9 @@ export class BookingEventHandlerService { operationId?: string | null; source: ActionSource; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source, context } = params; + const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkCancelledAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -343,6 +365,7 @@ export class BookingEventHandlerService { source, operationId, context, + isBookingAuditEnabled, }); } @@ -355,8 +378,9 @@ export class BookingEventHandlerService { organizationId: number | null; operationId?: string | null; source: ActionSource; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source } = params; + const { bookings, actor, organizationId, operationId, source, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkCreatedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -366,6 +390,7 @@ export class BookingEventHandlerService { organizationId, source, operationId, + isBookingAuditEnabled, }); } @@ -378,8 +403,9 @@ export class BookingEventHandlerService { organizationId: number | null; operationId?: string | null; source: ActionSource; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source } = params; + const { bookings, actor, organizationId, operationId, source, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkRescheduledAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -389,6 +415,7 @@ export class BookingEventHandlerService { organizationId, source, operationId, + isBookingAuditEnabled, }); } @@ -402,8 +429,9 @@ export class BookingEventHandlerService { operationId?: string | null; source: ActionSource; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source, context } = params; + const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkRejectedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -414,6 +442,7 @@ export class BookingEventHandlerService { source, operationId, context, + isBookingAuditEnabled, }); } } diff --git a/packages/features/bookings/lib/service/RecurringBookingService.ts b/packages/features/bookings/lib/service/RecurringBookingService.ts index 0ae1e0b36b..3fcf92aef8 100644 --- a/packages/features/bookings/lib/service/RecurringBookingService.ts +++ b/packages/features/bookings/lib/service/RecurringBookingService.ts @@ -1,5 +1,6 @@ import type { CreateBookingMeta, CreateRecurringBookingData } from "@calcom/features/bookings/lib/dto/types"; import type { BookingResponse } from "@calcom/features/bookings/types"; +import type { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { CreationSource, SchedulingType } from "@calcom/prisma/enums"; import type { AppsStatus } from "@calcom/types/Calendar"; import { v4 as uuidv4 } from "uuid"; @@ -156,6 +157,7 @@ export const handleNewRecurringBooking = async function ( export interface IRecurringBookingServiceDependencies { regularBookingService: RegularBookingService; bookingEventHandler: BookingEventHandlerService; + featuresRepository: FeaturesRepository; } /** @@ -199,6 +201,10 @@ export class RecurringBookingService implements IBookingService { const bookerName = bookerAttendee?.name || ""; const bookerEmail = bookerAttendee?.email || ""; + const isBookingAuditEnabled = eventOrganizationId + ? await this.deps.featuresRepository.checkIfTeamHasFeature(eventOrganizationId, "booking-audit") + : false; + const rescheduledByAttendeeId = firstCreatedBooking.attendees?.find( (attendee) => attendee.email === rescheduledBy )?.id; @@ -255,6 +261,7 @@ export class RecurringBookingService implements IBookingService { organizationId: eventOrganizationId, operationId, source: actionSource, + isBookingAuditEnabled, }); } } else { @@ -271,6 +278,7 @@ export class RecurringBookingService implements IBookingService { organizationId: eventOrganizationId, operationId, source: actionSource, + isBookingAuditEnabled, }); } } diff --git a/packages/features/bookings/lib/service/RegularBookingService.ts b/packages/features/bookings/lib/service/RegularBookingService.ts index f65c43873a..156f19ad48 100644 --- a/packages/features/bookings/lib/service/RegularBookingService.ts +++ b/packages/features/bookings/lib/service/RegularBookingService.ts @@ -1769,46 +1769,49 @@ async function handler( // For seats, if the booking already exists then we want to add the new attendee to the existing booking if (eventType.seatsPerTimeSlot) { - const newBooking = await handleSeats({ - rescheduleUid, - reqBookingUid: reqBody.bookingUid, - eventType, - evt: { ...evt, seatsPerTimeSlot: eventType.seatsPerTimeSlot, bookerUrl }, - invitee, - allCredentials, - organizerUser, - originalRescheduledBooking, - bookerEmail, - bookerPhoneNumber, - tAttendees, - bookingSeat, - reqUserId: input.userId, - reqUserUuid: userUuid, - rescheduleReason, - reqBodyUser: reqBody.user, - noEmail, - isConfirmedByDefault, - additionalNotes, - reqAppsStatus, - attendeeLanguage, - paymentAppData, - fullName, - smsReminderNumber, - eventTypeInfo, - uid, - eventTypeId, - reqBodyMetadata: reqBody.metadata, - subscriberOptions, - eventTrigger, - responses, - workflows, - rescheduledBy: reqBody.rescheduledBy, - isDryRun, - organizationId: eventOrganizationId, - actionSource, - traceContext, - deps, - }); + const newBooking = await handleSeats( + { + rescheduleUid, + reqBookingUid: reqBody.bookingUid, + eventType, + evt: { ...evt, seatsPerTimeSlot: eventType.seatsPerTimeSlot, bookerUrl }, + invitee, + allCredentials, + organizerUser, + originalRescheduledBooking, + bookerEmail, + bookerPhoneNumber, + tAttendees, + bookingSeat, + reqUserId: input.userId, + reqUserUuid: userUuid, + rescheduleReason, + reqBodyUser: reqBody.user, + noEmail, + isConfirmedByDefault, + additionalNotes, + reqAppsStatus, + attendeeLanguage, + paymentAppData, + fullName, + smsReminderNumber, + eventTypeInfo, + uid, + eventTypeId, + reqBodyMetadata: reqBody.metadata, + subscriberOptions, + eventTrigger, + responses, + workflows, + rescheduledBy: reqBody.rescheduledBy, + isDryRun, + organizationId: eventOrganizationId, + actionSource, + traceContext, + deps, + }, + deps.featuresRepository + ); if (newBooking) { const bookingResponse = { @@ -2463,6 +2466,15 @@ async function handler( } : undefined; + // Query feature flags in parallel before firing booking events + // TODO: We should support checkIfOrgHasFeatures - Bulk plus orgId check, that would be more efficient. + const [isBookingEmailSmsTaskerEnabled, isBookingAuditEnabled] = orgId + ? await Promise.all([ + deps.featuresRepository.checkIfTeamHasFeature(orgId, "booking-email-sms-tasker"), + deps.featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit"), + ]) + : [false, false]; + await this.fireBookingEvents({ booking: { ...booking, @@ -2482,6 +2494,7 @@ async function handler( isRecurringBooking: !!input.bookingData.allRecurringDates, attendeeSeatId: evt.attendeeSeatId ?? null, tracingLogger, + isBookingAuditEnabled, }); const webhookLocation = metadata?.videoCallUrl || evt.location; @@ -2840,29 +2853,21 @@ async function handler( // Unused until we deploy to trigger.dev production // for now we only enable for cal.com org and we keep our current email system // cal.com org members will see emails in double while we test - if (ENABLE_ASYNC_TASKER && !noEmail) { + if (ENABLE_ASYNC_TASKER && !noEmail && isBookingEmailSmsTaskerEnabled) { try { - if (orgId) { - const hasTeamFeature = await deps.featuresRepository.checkIfTeamHasFeature( - orgId, - "booking-email-sms-tasker" - ); - if (hasTeamFeature) { - await deps.bookingEmailAndSmsTasker.send({ - action: bookingEmailsAndSmsTaskerAction, - schedulingType: evtWithMetadata.eventType.schedulingType, - payload: { - bookingId: booking.id, - conferenceCredentialId, - platformClientId, - platformRescheduleUrl, - platformCancelUrl, - platformBookingUrl, - isRescheduledByBooker: reqBody.rescheduledBy === bookerEmail, - }, - }); - } - } + await deps.bookingEmailAndSmsTasker.send({ + action: bookingEmailsAndSmsTaskerAction, + schedulingType: evtWithMetadata.eventType.schedulingType, + payload: { + bookingId: booking.id, + conferenceCredentialId, + platformClientId, + platformRescheduleUrl, + platformCancelUrl, + platformBookingUrl, + isRescheduledByBooker: reqBody.rescheduledBy === bookerEmail, + }, + }); } catch (err) { tracingLogger.error("bookingEmailAndSmsTasker error:", err); } @@ -2922,6 +2927,7 @@ export class RegularBookingService implements IBookingService { isRecurringBooking, attendeeSeatId, tracingLogger, + isBookingAuditEnabled, }: { booking: { id: number; @@ -2947,6 +2953,7 @@ export class RegularBookingService implements IBookingService { isRecurringBooking: boolean; tracingLogger: ReturnType; attendeeSeatId: string | null; + isBookingAuditEnabled: boolean; }) { try { const bookingCreatedPayload = buildBookingCreatedPayload({ @@ -2999,6 +3006,7 @@ export class RegularBookingService implements IBookingService { }), source: actionSource, operationId: null, + isBookingAuditEnabled, }); } else { await bookingEventHandler.onBookingCreated({ @@ -3007,6 +3015,7 @@ export class RegularBookingService implements IBookingService { auditData: buildBookingCreatedAuditData({ booking, attendeeSeatId }), source: actionSource, operationId: null, + isBookingAuditEnabled, }); } } diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts index a0bd9454fd..ce2061a712 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts @@ -1148,6 +1148,7 @@ describe("roundRobinManualReassignment - Audit Data Verification", () => { reassignmentReason: "Test reassignment reason", reassignmentType: "manual", }, + isBookingAuditEnabled: false, }); }); @@ -1227,6 +1228,7 @@ describe("roundRobinManualReassignment - Audit Data Verification", () => { expect(callArgs.actor).toEqual({ identifiedBy: "user", userUuid: reassigningUser.uuid }); expect(callArgs.organizationId).toBe(null); expect(callArgs.source).toBe("WEBAPP"); + expect(callArgs.isBookingAuditEnabled).toBe(false); // organizerUuid should NOT be included when organizer hasn't changed (fixed host scenario) expect(callArgs.auditData.organizerUuid).toBeUndefined(); expect(callArgs.auditData.reassignmentType).toBe("manual"); diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 61c2abcafd..20dfe81586 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -9,6 +9,7 @@ import { import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; 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"; @@ -256,6 +257,10 @@ export const roundRobinManualReassignment = async ({ } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; await bookingEventHandlerService.onReassignment({ bookingUid: booking.uid, @@ -280,6 +285,7 @@ export const roundRobinManualReassignment = async ({ reassignmentReason: reassignReason ?? null, reassignmentType: "manual", }, + isBookingAuditEnabled, }); // When organizer hasn't changed, still extract conferenceCredentialId from event type locations diff --git a/packages/features/ee/round-robin/roundRobinReassignment.test.ts b/packages/features/ee/round-robin/roundRobinReassignment.test.ts index 8be59a6f71..1bfa51f4ee 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.test.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.test.ts @@ -603,6 +603,7 @@ describe("roundRobinReassignment - Audit Data Verification", () => { reassignmentReason: null, reassignmentType: "roundRobin", }, + isBookingAuditEnabled: false, }); }); @@ -690,6 +691,7 @@ describe("roundRobinReassignment - Audit Data Verification", () => { expect(callArgs.actor).toEqual({ identifiedBy: "user", userUuid: reassigningUser.uuid }); expect(callArgs.organizationId).toBe(null); expect(callArgs.source).toBe("WEBAPP"); + expect(callArgs.isBookingAuditEnabled).toBe(false); // organizerUuid should NOT be included when organizer hasn't changed (fixed host scenario) expect(callArgs.auditData.organizerUuid).toBeUndefined(); expect(callArgs.auditData.reassignmentType).toBe("roundRobin"); diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index 85d9b4905d..d32c4e05ba 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -13,6 +13,7 @@ import { import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; 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"; @@ -294,6 +295,11 @@ export const roundRobinReassignment = async ({ } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + await bookingEventHandlerService.onReassignment({ bookingUid: booking.uid, actor: makeUserActor(reassignedByUuid), @@ -317,6 +323,7 @@ export const roundRobinReassignment = async ({ reassignmentReason: null, reassignmentType: "roundRobin", }, + isBookingAuditEnabled, }); roundRobinReassignLogger.info(`Successfully reassigned to user ${reassignedRRHost.id}`); diff --git a/packages/features/handleMarkNoShow.ts b/packages/features/handleMarkNoShow.ts index 00ff932a57..fc33276b0c 100644 --- a/packages/features/handleMarkNoShow.ts +++ b/packages/features/handleMarkNoShow.ts @@ -8,6 +8,7 @@ import { } from "@calcom/features/booking-audit/lib/makeActor"; import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import { AttendeeRepository } from "@calcom/features/bookings/repositories/AttendeeRepository"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { BookingAccessService } from "@calcom/features/bookings/services/BookingAccessService"; @@ -209,6 +210,10 @@ async function fireNoShowUpdated({ } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; const isSomethingChanged = auditData.host || (auditData.attendeesNoShow && auditData.attendeesNoShow.length > 0); if (isSomethingChanged) { @@ -218,6 +223,7 @@ async function fireNoShowUpdated({ organizationId: orgId ?? null, source: actionSource, auditData, + isBookingAuditEnabled, }); } } diff --git a/packages/features/tasker/tasks/triggerNoShow/common.ts b/packages/features/tasker/tasks/triggerNoShow/common.ts index 8f8ee30ea8..0a1dce68fe 100644 --- a/packages/features/tasker/tasks/triggerNoShow/common.ts +++ b/packages/features/tasker/tasks/triggerNoShow/common.ts @@ -2,6 +2,7 @@ import dayjs from "@calcom/dayjs"; import type { NoShowUpdatedAuditData } from "@calcom/features/booking-audit/lib/actions/NoShowUpdatedAuditActionService"; import { makeSystemActor } from "@calcom/features/booking-audit/lib/makeActor"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import type { Host } from "@calcom/features/bookings/lib/getHostsAndGuests"; import { getHostsAndGuests } from "@calcom/features/bookings/lib/getHostsAndGuests"; import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; @@ -176,6 +177,11 @@ export const fireNoShowUpdatedEvent = async ({ }); const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + await bookingEventHandlerService.onNoShowUpdated({ bookingUid: booking.uid, // This action is taken by the scheduled tasker job, so we use the system actor @@ -193,6 +199,7 @@ export const fireNoShowUpdatedEvent = async ({ : {}), ...(hasAttendeesNoShow ? { attendeesNoShow: attendeesNoShowAudit } : {}), }, + isBookingAuditEnabled, }); } catch (error) { log.error("Error logging audit for automatic no-show", error); diff --git a/packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts b/packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts index 265f03581b..a3e801c1a5 100644 --- a/packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts +++ b/packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts @@ -33,6 +33,12 @@ vi.mock("@calcom/features/bookings/di/BookingEventHandlerService.container", () }), })); +vi.mock("@calcom/features/di/containers/FeaturesRepository", () => ({ + getFeaturesRepository: vi.fn().mockReturnValue({ + checkIfTeamHasFeature: vi.fn().mockResolvedValue(false), + }), +})); + const timeout = process.env.CI ? 5000 : 20000; const EMPTY_MEETING_SESSIONS = { @@ -57,6 +63,7 @@ type ExpectNoShowAuditParams = { }; }>; }; + isBookingAuditEnabled: boolean; }; function expectNoShowAuditToBeDone(expected: ExpectNoShowAuditParams): void { @@ -240,6 +247,7 @@ describe("Trigger Guest No Show:", () => { { attendeeEmail: "guest@example.com", noShow: { new: true, old: false } }, ], }, + isBookingAuditEnabled: false, }); }, timeout @@ -442,6 +450,7 @@ describe("Trigger Guest No Show:", () => { { attendeeEmail: "guest@example.com", noShow: { new: true, old: false } }, ], }, + isBookingAuditEnabled: false, }); }, timeout @@ -683,6 +692,7 @@ describe("Trigger Guest No Show:", () => { { attendeeEmail: "guest@example.com", noShow: { new: true, old: false } }, ], }, + isBookingAuditEnabled: false, }); }, timeout diff --git a/packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts b/packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts index c738843949..480b951d2c 100644 --- a/packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts +++ b/packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts @@ -33,6 +33,12 @@ vi.mock("@calcom/features/bookings/di/BookingEventHandlerService.container", () }), })); +vi.mock("@calcom/features/di/containers/FeaturesRepository", () => ({ + getFeaturesRepository: vi.fn().mockReturnValue({ + checkIfTeamHasFeature: vi.fn().mockResolvedValue(false), + }), +})); + const timeout = process.env.CI ? 5000 : 20000; const EMPTY_MEETING_SESSIONS = { @@ -64,6 +70,7 @@ type ExpectNoShowAuditParams = { }; }>; }; + isBookingAuditEnabled: boolean; }; function expectNoShowAuditToBeDone(expected: ExpectNoShowAuditParams): void { @@ -224,6 +231,7 @@ describe("Trigger Host No Show:", () => { auditData: { host: { userUuid: expect.any(String), noShow: { old: false, new: true } }, }, + isBookingAuditEnabled: false, }); }, timeout @@ -401,6 +409,7 @@ describe("Trigger Host No Show:", () => { auditData: { host: { userUuid: expect.any(String), noShow: { old: false, new: true } }, }, + isBookingAuditEnabled: false, }); }, timeout @@ -618,6 +627,7 @@ describe("Trigger Host No Show:", () => { auditData: { host: { userUuid: expect.any(String), noShow: { old: false, new: true } }, }, + isBookingAuditEnabled: false, }); }, timeout @@ -782,6 +792,7 @@ describe("Trigger Host No Show:", () => { auditData: { host: { userUuid: expect.any(String), noShow: { old: false, new: true } }, }, + isBookingAuditEnabled: false, }); }, timeout diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 6b8b560ddc..abda8a55ad 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -4,6 +4,7 @@ import dayjs from "@calcom/dayjs"; import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import { BookingEmailSmsHandler } from "@calcom/features/bookings/lib/BookingEmailSmsHandler"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; @@ -88,14 +89,21 @@ export const addGuestsHandler = async ({ } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const organizationId = user.organizationId ?? null; + const isBookingAuditEnabled = organizationId + ? await featuresRepository.checkIfTeamHasFeature(organizationId, "booking-audit") + : false; + await bookingEventHandlerService.onAttendeeAdded({ bookingUid: booking.uid, actor: makeUserActor(user.uuid), - organizationId: user.organizationId ?? null, + organizationId, source: actionSource, auditData: { added: uniqueGuestEmails, }, + isBookingAuditEnabled, }); return { message: "Guests added" }; diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 70f544e96f..511805852a 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -3,6 +3,7 @@ import type { LocationObject } from "@calcom/app-store/locations"; import { getLocationValueForDB } from "@calcom/app-store/locations"; import { sendDeclinedEmailsAndSMS } from "@calcom/emails/email-manager"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getAssignmentReasonCategory } from "@calcom/features/bookings/lib/getAssignmentReasonCategory"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; @@ -55,6 +56,7 @@ async function fireRejectionEvent({ actionSource, rejectedBookings, rejectionReason, + isBookingAuditEnabled, tracingLogger, }: { actor: Actor; @@ -65,6 +67,7 @@ async function fireRejectionEvent({ uid: string; oldStatus: BookingStatus; }[]; + isBookingAuditEnabled: boolean; tracingLogger: ISimpleLogger; }): Promise { try { @@ -83,6 +86,7 @@ async function fireRejectionEvent({ organizationId, operationId, source: actionSource, + isBookingAuditEnabled, }); } else if (rejectedBookings.length === 1) { const booking = rejectedBookings[0]; @@ -95,6 +99,7 @@ async function fireRejectionEvent({ status: { old: booking.oldStatus, new: BookingStatus.REJECTED }, }, source: actionSource, + isBookingAuditEnabled, }); } } catch (error) { @@ -495,12 +500,18 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + await fireRejectionEvent({ actor, actionSource, organizationId: orgId ?? null, rejectionReason: rejectionReason ?? null, rejectedBookings, + isBookingAuditEnabled, tracingLogger: log, }); diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index bf2345654b..94d8bad248 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -5,6 +5,7 @@ import { sendLocationChangeEmailsAndSMS } from "@calcom/emails/email-manager"; import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; @@ -315,6 +316,11 @@ export async function editLocationHandler({ ctx, input, actionSource }: EditLoca } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = organizationId + ? await featuresRepository.checkIfTeamHasFeature(organizationId, "booking-audit") + : false; + await bookingEventHandlerService.onLocationChanged({ bookingUid: booking.uid, actor: makeUserActor(loggedInUser.uuid), @@ -326,6 +332,7 @@ export async function editLocationHandler({ ctx, input, actionSource }: EditLoca new: updatedLocation, }, }, + isBookingAuditEnabled, }); return { message: "Location updated" }; diff --git a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts index 96437685e6..6d46e8dc87 100644 --- a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts @@ -8,6 +8,7 @@ import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails/email-manager"; import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { deleteMeeting } from "@calcom/features/conferencing/lib/videoClient"; @@ -315,6 +316,11 @@ export const requestRescheduleHandler = async ({ ctx, input, source }: RequestRe await Promise.all(promises); const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + await bookingEventHandlerService.onRescheduleRequested({ bookingUid: bookingToReschedule.uid, actor: makeUserActor(user.uuid), @@ -324,5 +330,6 @@ export const requestRescheduleHandler = async ({ ctx, input, source }: RequestRe rescheduleReason: cancellationReason ?? null, rescheduledRequestedBy: user.email, }, + isBookingAuditEnabled, }); };