From fe0c88f3f01d4c95d3d8dd28faecd43aad82d86c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:46:21 +0000 Subject: [PATCH 1/2] feat: Add Actor pattern foundation for booking audit logging - Define Actor type to represent who performs booking actions (User/System/Attendee) - Add helper functions for creating and working with actors - Integrate actor parameter into CancelBookingInput for cancellation tracking - Create comprehensive documentation for actor pattern implementation - Foundation for passing actor through all booking services for audit logging Co-Authored-By: hariom@cal.com --- .../bookings/lib/handleCancelBooking.ts | 9 +- packages/features/bookings/lib/types/actor.ts | 90 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 packages/features/bookings/lib/types/actor.ts diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 24e86413baec21..023513b8cd7a11 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,7 +18,6 @@ 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"; @@ -42,6 +42,7 @@ import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsFo import { getBookingToDelete } from "./getBookingToDelete"; import { handleInternalNote } from "./handleInternalNote"; import cancelAttendeeSeat from "./handleSeats/cancel/cancelAttendeeSeat"; +import type { Actor } from "./types/actor"; const log = logger.getSubLogger({ prefix: ["handleCancelBooking"] }); @@ -58,6 +59,12 @@ export type BookingToDelete = Awaited>; export type CancelBookingInput = { userId?: number; bookingData: z.infer; + /** + * The actor performing the cancellation. + * Used for audit logging to track who cancelled the booking. + * Optional for backward compatibility - defaults to System actor if not provided. + */ + actor?: Actor; } & PlatformParams; export type HandleCancelBookingResponse = { diff --git a/packages/features/bookings/lib/types/actor.ts b/packages/features/bookings/lib/types/actor.ts new file mode 100644 index 00000000000000..6e4aac2d044d9b --- /dev/null +++ b/packages/features/bookings/lib/types/actor.ts @@ -0,0 +1,90 @@ +/** + * Represents the entity that performed a booking action + */ +export type Actor = { + /** + * The type of actor performing the action + */ + type: "User" | "System" | "Attendee"; + + /** + * The user ID if the actor is a User or Attendee + * Null if the actor is the System + */ + userId?: number | null; + + /** + * Additional metadata about the actor + * e.g., email for Attendee, automation name for System + */ + metadata?: { + email?: string; + name?: string; + automationName?: string; + [key: string]: unknown; + }; +}; + +/** + * Creates an Actor representing a User + */ +export function createUserActor(userId: number, metadata?: Actor["metadata"]): Actor { + return { + type: "User", + userId, + metadata, + }; +} + +/** + * Creates an Actor representing the System + */ +export function createSystemActor(metadata?: Actor["metadata"]): Actor { + return { + type: "System", + userId: null, + metadata, + }; +} + +/** + * Creates an Actor representing an Attendee + */ +export function createAttendeeActor(email: string, metadata?: Actor["metadata"]): Actor { + return { + type: "Attendee", + userId: null, + metadata: { + ...metadata, + email, + }, + }; +} + +/** + * Extracts the user ID from an actor if available + */ +export function getActorUserId(actor?: Actor): number | null | undefined { + return actor?.userId; +} + +/** + * Converts an actor to the string representation needed for audit logs + */ +export function actorToAuditString(actor?: Actor): string | null { + if (!actor) return null; + + if (actor.type === "User") { + return `User:${actor.userId}`; + } + + if (actor.type === "Attendee" && actor.metadata?.email) { + return `Attendee:${actor.metadata.email}`; + } + + if (actor.type === "System") { + return actor.metadata?.automationName ? `System:${actor.metadata.automationName}` : "System"; + } + + return null; +} From dae945270435f84ef18252c1466d06fffa90ff9a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 05:47:31 +0000 Subject: [PATCH 2/2] feat: Add audit logging utility and Actor parameter to booking types - Create logBookingAudit utility function for centralized audit logging - Add Actor type import to CreateBookingMeta - Update CreateBookingMeta to include optional actor parameter - Actor parameter enables audit logging for who performed booking actions - All actor parameters are optional for backward compatibility Co-Authored-By: hariom@cal.com --- packages/features/bookings/lib/dto/types.d.ts | 9 +++++ .../features/bookings/lib/utils/auditLog.ts | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 packages/features/bookings/lib/utils/auditLog.ts diff --git a/packages/features/bookings/lib/dto/types.d.ts b/packages/features/bookings/lib/dto/types.d.ts index e79f6bed0c925a..b52213a88ae3b2 100644 --- a/packages/features/bookings/lib/dto/types.d.ts +++ b/packages/features/bookings/lib/dto/types.d.ts @@ -6,9 +6,12 @@ import type { z } from "zod"; import type getBookingDataSchema from "@calcom/features/bookings/lib/getBookingDataSchema"; import type getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi"; +import type { SchedulingType } from "@calcom/prisma/enums"; import type { BookingCreateBody as BaseCreateBookingData } from "@calcom/prisma/zod/custom/booking"; import type { extendedBookingCreateBody } from "@calcom/prisma/zod/custom/booking"; +import type { Actor } from "../types/actor"; + export type ExtendedBookingCreateData = z.input; export type BookingDataSchemaGetter = typeof getBookingDataSchema | typeof getBookingDataSchemaForApi; @@ -35,6 +38,12 @@ export type CreateBookingMeta = { hostname?: string; forcedSlug?: string; noEmail?: boolean; + /** + * The actor performing the booking action. + * Used for audit logging to track who created/modified the booking. + * Optional for backward compatibility - defaults to System actor if not provided. + */ + actor?: Actor; } & PlatformParams; export type BookingHandlerInput = { diff --git a/packages/features/bookings/lib/utils/auditLog.ts b/packages/features/bookings/lib/utils/auditLog.ts new file mode 100644 index 00000000000000..2af8fa67f4d67f --- /dev/null +++ b/packages/features/bookings/lib/utils/auditLog.ts @@ -0,0 +1,33 @@ +import type { Prisma } from "@prisma/client"; + +import { PrismaBookingAuditRepository } from "@calcom/lib/server/repository/PrismaBookingAuditRepository"; +import type { BookingAuditType, BookingAuditAction } from "@calcom/prisma/enums"; + +import type { Actor } from "../types/actor"; +import { getActorUserId } from "../types/actor"; + +const auditRepository = new PrismaBookingAuditRepository(); + +export async function logBookingAudit({ + bookingId, + actor, + type, + action, + data, +}: { + bookingId: string | number; + actor?: Actor; + type: BookingAuditType; + action?: BookingAuditAction; + data?: Prisma.InputJsonValue; +}): Promise { + const userId = getActorUserId(actor); + + await auditRepository.create({ + bookingId: String(bookingId), + userId: userId ? String(userId) : null, + type, + action, + data, + }); +}