import type { BookingAuditProducerService } from "@calcom/features/booking-audit/lib/service/BookingAuditProducerService.interface"; import type { AcceptedAuditData } from "@calcom/features/booking-audit/lib/actions/AcceptedAuditActionService"; import type { CancelledAuditData } from "@calcom/features/booking-audit/lib/actions/CancelledAuditActionService"; import type { RejectedAuditData } from "@calcom/features/booking-audit/lib/actions/RejectedAuditActionService"; import type { RescheduleRequestedAuditData } from "@calcom/features/booking-audit/lib/actions/RescheduleRequestedAuditActionService"; import type { AttendeeAddedAuditData } from "@calcom/features/booking-audit/lib/actions/AttendeeAddedAuditActionService"; import type { AttendeeRemovedAuditData } from "@calcom/features/booking-audit/lib/actions/AttendeeRemovedAuditActionService"; import type { ReassignmentAuditData } from "@calcom/features/booking-audit/lib/actions/ReassignmentAuditActionService"; import type { LocationChangedAuditData } from "@calcom/features/booking-audit/lib/actions/LocationChangedAuditActionService"; import type { NoShowUpdatedAuditData } from "@calcom/features/booking-audit/lib/actions/NoShowUpdatedAuditActionService"; import type { SeatBookedAuditData } from "@calcom/features/booking-audit/lib/actions/SeatBookedAuditActionService"; import type { SeatRescheduledAuditData } from "@calcom/features/booking-audit/lib/actions/SeatRescheduledAuditActionService"; import type { CreatedAuditData } from "@calcom/features/booking-audit/lib/actions/CreatedAuditActionService"; import type { RescheduledAuditData } from "@calcom/features/booking-audit/lib/actions/RescheduledAuditActionService"; import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import type { HashedLinkService } from "@calcom/features/hashedLink/lib/service/HashedLinkService"; import type { ISimpleLogger } from "@calcom/features/di/shared/services/logger.service"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { Actor, BookingAuditContext } from "@calcom/features/booking-audit/lib/dto/types"; import type { BookingCreatedPayload, BookingRescheduledPayload } from "./types"; interface BookingEventHandlerDeps { log: ISimpleLogger; hashedLinkService: HashedLinkService; bookingAuditProducerService: BookingAuditProducerService; } interface OnBookingCreatedParams { payload: BookingCreatedPayload; actor: Actor; auditData: CreatedAuditData; source: ActionSource; operationId?: string | null; context?: BookingAuditContext; isBookingAuditEnabled: boolean; } interface OnBookingRescheduledParams { payload: BookingRescheduledPayload; actor: Actor; auditData: RescheduledAuditData; source: ActionSource; operationId?: string | null; context?: BookingAuditContext; isBookingAuditEnabled: boolean; } interface BaseBookingEventParams { bookingUid: string; actor: Actor; organizationId: number | null; auditData: TAuditData; source: ActionSource; operationId?: string | null; context?: BookingAuditContext; isBookingAuditEnabled: boolean; } type OnBookingAcceptedParams = BaseBookingEventParams; type OnBookingCancelledParams = BaseBookingEventParams; type OnRescheduleRequestedParams = BaseBookingEventParams; type OnAttendeeAddedParams = BaseBookingEventParams; type OnNoShowUpdatedParams = BaseBookingEventParams; type OnBookingRejectedParams = BaseBookingEventParams; type OnAttendeeRemovedParams = BaseBookingEventParams; type OnReassignmentParams = BaseBookingEventParams; type OnLocationChangedParams = BaseBookingEventParams; type OnSeatBookedParams = BaseBookingEventParams; type OnSeatRescheduledParams = BaseBookingEventParams; export class BookingEventHandlerService { private readonly log: BookingEventHandlerDeps["log"]; private readonly bookingAuditProducerService: BookingEventHandlerDeps["bookingAuditProducerService"]; constructor(private readonly deps: BookingEventHandlerDeps) { this.log = deps.log; this.bookingAuditProducerService = deps.bookingAuditProducerService; } async onBookingCreated(params: OnBookingCreatedParams) { const { payload, actor, auditData, source, operationId, context, isBookingAuditEnabled } = params; this.log.debug("onBookingCreated", safeStringify(payload)); if (payload.config.isDryRun) { return; } await this.onBookingCreatedOrRescheduled(payload); await this.deps.bookingAuditProducerService.queueCreatedAudit({ bookingUid: payload.booking.uid, actor, organizationId: payload.organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onBookingRescheduled(params: OnBookingRescheduledParams) { const { payload, actor, auditData, source, operationId, context, isBookingAuditEnabled } = params; this.log.debug("onBookingRescheduled", safeStringify(payload)); if (payload.config.isDryRun) { return; } await this.onBookingCreatedOrRescheduled(payload); await this.bookingAuditProducerService.queueRescheduledAudit({ // In case of rescheduled booking, we send old booking uid because the action took place on that booking only bookingUid: payload.oldBooking.uid, actor, organizationId: payload.organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } /** * Handles common tasks that need to be executed in both booking created and rescheduled events * A dedicated place because there are many tasks that need to be executed in both events. */ private async onBookingCreatedOrRescheduled(payload: BookingCreatedPayload | BookingRescheduledPayload) { const results = await Promise.allSettled([ // TODO: Migrate other post-booking tasks here, to execute them in parallel, without affecting each other this.updatePrivateLinkUsage(payload.bookingFormData.hashedLink), ]); results.forEach((result) => { if (result.status === "rejected") { this.log.error( "Error while executing onBookingCreatedOrRescheduled task", safeStringify(result.reason) ); } }); } private async updatePrivateLinkUsage(hashedLink: string | null) { try { if (hashedLink) { await this.deps.hashedLinkService.validateAndIncrementUsage(hashedLink); } } catch (error) { this.log.error("Error while updating hashed link", safeStringify(error)); } } async onBookingAccepted(params: OnBookingAcceptedParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueAcceptedAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onBookingCancelled(params: OnBookingCancelledParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueCancelledAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onRescheduleRequested(params: OnRescheduleRequestedParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueRescheduleRequestedAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onAttendeeAdded(params: OnAttendeeAddedParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueAttendeeAddedAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onNoShowUpdated(params: OnNoShowUpdatedParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueNoShowUpdatedAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onBookingRejected(params: OnBookingRejectedParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueRejectedAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onAttendeeRemoved(params: OnAttendeeRemovedParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueAttendeeRemovedAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onReassignment(params: OnReassignmentParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueReassignmentAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onLocationChanged(params: OnLocationChangedParams) { try { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueLocationChangedAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } catch (error) { this.log.error("Error while onLocationChanged", safeStringify(error)); } } async onSeatBooked(params: OnSeatBookedParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueSeatBookedAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onSeatRescheduled(params: OnSeatRescheduledParams) { const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled, } = params; await this.bookingAuditProducerService.queueSeatRescheduledAudit({ bookingUid, actor, organizationId, source, operationId, data: auditData, context, isBookingAuditEnabled, }); } async onBulkBookingsAccepted(params: { bookings: Array<{ bookingUid: string; auditData: AcceptedAuditData; }>; actor: Actor; organizationId: number | null; operationId?: string | null; source: ActionSource; context?: BookingAuditContext; isBookingAuditEnabled: boolean; }) { const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkAcceptedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, data: booking.auditData, })), actor, organizationId, source, operationId, context, isBookingAuditEnabled, }); } async onBulkBookingsCancelled(params: { bookings: Array<{ bookingUid: string; auditData: CancelledAuditData; }>; actor: Actor; organizationId: number | null; operationId?: string | null; source: ActionSource; context?: BookingAuditContext; isBookingAuditEnabled: boolean; }) { const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkCancelledAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, data: booking.auditData, })), actor, organizationId, source, operationId, context, isBookingAuditEnabled, }); } async onBulkBookingsCreated(params: { bookings: Array<{ bookingUid: string; auditData: CreatedAuditData; }>; actor: Actor; organizationId: number | null; operationId?: string | null; source: ActionSource; context?: BookingAuditContext; isBookingAuditEnabled: boolean; }) { const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkCreatedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, data: booking.auditData, })), actor, organizationId, source, operationId, context, isBookingAuditEnabled, }); } async onBulkBookingsRescheduled(params: { bookings: Array<{ bookingUid: string; auditData: RescheduledAuditData; }>; actor: Actor; organizationId: number | null; operationId?: string | null; source: ActionSource; context?: BookingAuditContext; isBookingAuditEnabled: boolean; }) { const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkRescheduledAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, data: booking.auditData, })), actor, organizationId, source, operationId, context, isBookingAuditEnabled, }); } async onBulkBookingsRejected(params: { bookings: Array<{ bookingUid: string; auditData: RejectedAuditData; }>; actor: Actor; organizationId: number | null; operationId?: string | null; source: ActionSource; context?: BookingAuditContext; isBookingAuditEnabled: boolean; }) { const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkRejectedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, data: booking.auditData, })), actor, organizationId, source, operationId, context, isBookingAuditEnabled, }); } }