chore: Integrate edit location booking audit (#26569)

## What does this PR do?

Integrates booking audit logging for the edit location functionality, following the pattern established in PR #26046 (booking creation/rescheduling audit).

This PR adds audit logging when a booking's location is changed through:
1. **Web app** (tRPC handler): `packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts`
2. **API v2**: `apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts`

### Changes:
- Added `actionSource` as a **required** parameter to `editLocationHandler` (no fallback)
- Added optional `userUuid` parameter (defaults to logged-in user's uuid)
- Added `ValidActionSource` type that excludes "UNKNOWN" for client-facing APIs
- Captures old location before update for audit data
- Calls `BookingEventHandlerService.onLocationChanged()` after successful location update
- Web app uses `actionSource: "WEBAPP"`, API v2 uses `actionSource: "API_V2"`
- Updated router to explicitly pass `actionSource: "WEBAPP"`
- Updated test to pass `actionSource: "WEBAPP"`
- **API v2**: Uses NestJS dependency injection pattern with `BookingEventHandlerService` injected via constructor

### Updates since last revision:
- **Created `BookingEventHandlerModule`** (`apps/api/v2/src/lib/modules/booking-event-handler.module.ts`) to encapsulate `BookingEventHandlerService` and its dependencies (Logger, TaskerService, HashedLinkService, BookingAuditProducerService)
- Updated both bookings modules (2024-04-15 and 2024-08-13) to import `BookingEventHandlerModule` instead of listing individual providers
- This reduces code duplication and makes dependency management cleaner

## Mandatory Tasks (DO NOT REMOVE)

- [x] I have self-reviewed the code (A decent size PR without self-review might be rejected).
- [x] I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). N/A - no documentation changes needed.
- [x] I confirm automated tests are in place that prove my fix is effective or that my feature works. N/A - using existing audit infrastructure that is already tested.

## How should this be tested?

1. Update a booking's location through the web app
2. Update a booking's location through API v2
3. Verify audit logs are created with:
   - Correct `bookingUid`
   - Correct `actor` (user who made the change)
   - Correct `source` ("WEBAPP" or "API_V2")
   - Correct `auditData.location.old` and `auditData.location.new` values

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have checked if my changes generate no new warnings

## Human Review Checklist

- [ ] Verify all callers of `editLocationHandler` pass `actionSource` (router updated, test updated)
- [ ] Verify `organizationId` derivation is correct in both handlers (tRPC uses `booking.user?.profiles?.[0]?.organizationId`, API v2 uses `existingBookingHost.organizationId`)
- [ ] Confirm audit call placement after location update is intentional (audit failures would fail the operation even though location was already updated)
- [ ] Note: API v2 has its own implementation and does NOT reuse `editLocationHandler` - this is correct
- [ ] Verify `BookingEventHandlerModule` properly exports `BookingEventHandlerService` and is imported in both bookings modules
- [ ] Verify the `updateBookingLocationInDb` return value (`{ updatedLocation }`) is destructured and used correctly for audit data
- [ ] Verify API v2 uses `bookingLocation` for audit `new` value, while tRPC uses `updatedLocation` from DB update

---


Link to Devin run: https://app.devin.ai/sessions/fd1d439779674050a26ea3fa7d799943
Requested by: @hariombalhara
This commit is contained in:
Hariom Balhara
2026-01-20 18:40:48 +05:30
committed by GitHub
parent 39ae54d682
commit 7ec8601bd6
10 changed files with 326 additions and 82 deletions
@@ -27,6 +27,7 @@ import { TokensModule } from "@/modules/tokens/tokens.module";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
import { BookingEventHandlerModule } from "@/lib/modules/booking-event-handler.module";
@Module({
imports: [
@@ -43,6 +44,7 @@ import { Module } from "@nestjs/common";
RegularBookingModule,
RecurringBookingModule,
InstantBookingModule,
BookingEventHandlerModule,
],
providers: [
TokensRepository,
@@ -50,6 +50,7 @@ import { TokensModule } from "@/modules/tokens/tokens.module";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
import { BookingEventHandlerModule } from "@/lib/modules/booking-event-handler.module";
@Module({
imports: [
@@ -71,6 +72,7 @@ import { Module } from "@nestjs/common";
RegularBookingModule,
RecurringBookingModule,
InstantBookingModule,
BookingEventHandlerModule,
],
providers: [
TokensRepository,
@@ -1,18 +1,19 @@
import { makeUserActor } from "@calcom/platform-libraries/bookings";
import type {
BookingInputLocation_2024_08_13,
UpdateBookingInputLocation_2024_08_13,
UpdateBookingLocationInput_2024_08_13,
} from "@calcom/platform-types";
import { Booking } from "@calcom/prisma/client";
import { ForbiddenException, Injectable, Logger, NotFoundException } from "@nestjs/common";
import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/repositories/bookings.repository";
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 { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.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";
import { UsersRepository } from "@/modules/users/users.repository";
import { Injectable, Logger, NotFoundException, ForbiddenException } from "@nestjs/common";
import type {
UpdateBookingLocationInput_2024_08_13,
BookingInputLocation_2024_08_13,
UpdateBookingInputLocation_2024_08_13,
} from "@calcom/platform-types";
import { Booking } from "@calcom/prisma/client";
@Injectable()
export class BookingLocationService_2024_08_13 {
@@ -24,7 +25,8 @@ export class BookingLocationService_2024_08_13 {
private readonly usersRepository: UsersRepository,
private readonly inputService: InputBookingsService_2024_08_13,
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
private readonly eventTypeAccessService: EventTypeAccessService
private readonly eventTypeAccessService: EventTypeAccessService,
private readonly bookingEventHandlerService: BookingEventHandlerService
) {}
async updateBookingLocation(
@@ -66,6 +68,7 @@ export class BookingLocationService_2024_08_13 {
user: ApiAuthGuardUser
) {
const bookingUid = existingBooking.uid;
const oldLocation = existingBooking.location;
const bookingLocation = this.getLocationValue(inputLocation) ?? existingBooking.location;
if (!existingBooking.userId) {
@@ -96,6 +99,19 @@ export class BookingLocationService_2024_08_13 {
responses: updatedBookingResponses,
});
await this.bookingEventHandlerService.onLocationChanged({
bookingUid: existingBooking.uid,
actor: makeUserActor(user.uuid),
organizationId: existingBookingHost.organizationId ?? null,
source: "API_V2",
auditData: {
location: {
old: oldLocation,
new: bookingLocation,
},
},
});
return this.bookingsService.getBooking(updatedBooking.uid, user);
}
@@ -0,0 +1,24 @@
import { Logger } from "@/lib/logger.bridge";
import { BookingAuditProducerService } from "@/lib/services/booking-audit-producer.service";
import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service";
import { HashedLinkService } from "@/lib/services/hashed-link.service";
import { TaskerService } from "@/lib/services/tasker.service";
import { Module, Scope } from "@nestjs/common";
@Module({
providers: [
{
provide: Logger,
useFactory: () => {
return new Logger();
},
scope: Scope.TRANSIENT,
},
TaskerService,
HashedLinkService,
BookingAuditProducerService,
BookingEventHandlerService,
],
exports: [BookingEventHandlerService],
})
export class BookingEventHandlerModule {}
@@ -3,7 +3,11 @@ import { z } from "zod";
import { getHumanReadableLocationValue } from "@calcom/app-store/locations";
import { StringChangeSchema } from "../common/changeSchemas";
import { AuditActionServiceHelper } from "./AuditActionServiceHelper";
import type { IAuditActionService, TranslationWithParams, GetDisplayTitleParams } from "./IAuditActionService";
import type {
IAuditActionService,
TranslationWithParams,
GetDisplayTitleParams,
} from "./IAuditActionService";
import { getTranslation } from "@calcom/lib/server/i18n";
/**
* Location Changed Audit Action Service
@@ -12,67 +16,67 @@ import { getTranslation } from "@calcom/lib/server/i18n";
// Module-level because it is passed to IAuditActionService type outside the class scope
const fieldsSchemaV1 = z.object({
location: StringChangeSchema,
location: StringChangeSchema,
});
export class LocationChangedAuditActionService implements IAuditActionService {
readonly VERSION = 1;
public static readonly TYPE = "LOCATION_CHANGED" as const;
private static dataSchemaV1 = z.object({
version: z.literal(1),
fields: fieldsSchemaV1,
readonly VERSION = 1;
public static readonly TYPE = "LOCATION_CHANGED" as const;
private static dataSchemaV1 = z.object({
version: z.literal(1),
fields: fieldsSchemaV1,
});
private static fieldsSchemaV1 = fieldsSchemaV1;
public static readonly latestFieldsSchema = fieldsSchemaV1;
// Union of all versions
public static readonly storedDataSchema = LocationChangedAuditActionService.dataSchemaV1;
// Union of all versions
public static readonly storedFieldsSchema = LocationChangedAuditActionService.fieldsSchemaV1;
private helper: AuditActionServiceHelper<
typeof LocationChangedAuditActionService.latestFieldsSchema,
typeof LocationChangedAuditActionService.storedDataSchema
>;
constructor() {
this.helper = new AuditActionServiceHelper({
latestVersion: this.VERSION,
latestFieldsSchema: LocationChangedAuditActionService.latestFieldsSchema,
storedDataSchema: LocationChangedAuditActionService.storedDataSchema,
});
private static fieldsSchemaV1 = fieldsSchemaV1;
public static readonly latestFieldsSchema = fieldsSchemaV1;
// Union of all versions
public static readonly storedDataSchema = LocationChangedAuditActionService.dataSchemaV1;
// Union of all versions
public static readonly storedFieldsSchema = LocationChangedAuditActionService.fieldsSchemaV1;
private helper: AuditActionServiceHelper<
typeof LocationChangedAuditActionService.latestFieldsSchema,
typeof LocationChangedAuditActionService.storedDataSchema
>;
}
constructor() {
this.helper = new AuditActionServiceHelper({
latestVersion: this.VERSION,
latestFieldsSchema: LocationChangedAuditActionService.latestFieldsSchema,
storedDataSchema: LocationChangedAuditActionService.storedDataSchema,
});
}
getVersionedData(fields: unknown) {
return this.helper.getVersionedData(fields);
}
getVersionedData(fields: unknown) {
return this.helper.getVersionedData(fields);
}
parseStored(data: unknown) {
return this.helper.parseStored(data);
}
parseStored(data: unknown) {
return this.helper.parseStored(data);
}
getVersion(data: unknown): number {
return this.helper.getVersion(data);
}
getVersion(data: unknown): number {
return this.helper.getVersion(data);
}
migrateToLatest(data: unknown) {
// V1-only: validate and return as-is (no migration needed)
const validated = fieldsSchemaV1.parse(data);
return { isMigrated: false, latestData: validated };
}
migrateToLatest(data: unknown) {
// V1-only: validate and return as-is (no migration needed)
const validated = fieldsSchemaV1.parse(data);
return { isMigrated: false, latestData: validated };
}
async getDisplayTitle({ storedData }: GetDisplayTitleParams): Promise<TranslationWithParams> {
const { fields } = this.parseStored(storedData);
// TODO: Ideally we want to translate the location label to the user's locale
// We currently don't accept requesting user's translate fn here, fix it later.
const t = await getTranslation("en", "common");
async getDisplayTitle({ storedData }: GetDisplayTitleParams): Promise<TranslationWithParams> {
const { fields } = this.parseStored(storedData);
// TODO: Ideally we want to translate the location label to the user's locale
// We currently don't accept requesting user's translate fn here, fix it later.
const t = await getTranslation("en", "common");
const fromLocation = getHumanReadableLocationValue(fields.location.old, t);
const toLocation = getHumanReadableLocationValue(fields.location.new, t);
const fromLocation = getHumanReadableLocationValue(fields.location.old, t);
const toLocation = getHumanReadableLocationValue(fields.location.new, t);
return {
key: "booking_audit_action.location_changed_from_to",
params: { fromLocation, toLocation },
};
}
return {
key: "booking_audit_action.location_changed_from_to",
params: { fromLocation, toLocation },
};
}
}
export type LocationChangedAuditData = z.infer<typeof fieldsSchemaV1>;
@@ -0,0 +1,173 @@
import { describe, expect, it, beforeEach, afterEach } from "vitest";
import type { BookingAuditTaskConsumer } from "../BookingAuditTaskConsumer";
import type { BookingAuditViewerService } from "../BookingAuditViewerService";
import { makeUserActor } from "../../makeActor";
import { getBookingAuditTaskConsumer } from "../../../di/BookingAuditTaskConsumer.container";
import { getBookingAuditViewerService } from "../../../di/BookingAuditViewerService.container";
import {
createTestUser,
createTestOrganization,
createTestMembership,
createTestEventType,
createTestBooking,
enableFeatureForOrganization,
cleanupTestData,
} from "./integration-utils";
import { BookingStatus } from "@calcom/prisma/enums";
describe("Location Changed Action Integration", () => {
let bookingAuditTaskConsumer: BookingAuditTaskConsumer;
let bookingAuditViewerService: BookingAuditViewerService;
let testData: {
owner: { id: number; uuid: string; email: string };
attendee: { id: number; email: string };
organization: { id: number };
eventType: { id: number };
booking: { uid: string; startTime: Date; endTime: Date; status: BookingStatus };
};
beforeEach(async () => {
bookingAuditTaskConsumer = getBookingAuditTaskConsumer();
bookingAuditViewerService = getBookingAuditViewerService();
// Setup basic test data using Test Data Builder pattern
const owner = await createTestUser({ name: "Location Audit User" });
const organization = await createTestOrganization();
await createTestMembership(owner.id, organization.id);
await enableFeatureForOrganization(organization.id, "booking-audit");
const eventType = await createTestEventType(owner.id);
const attendee = await createTestUser({ name: "Test Attendee" });
const booking = await createTestBooking(owner.id, eventType.id, {
attendees: [
{
email: attendee.email,
name: attendee.name || "Test Attendee",
timeZone: "UTC",
},
],
});
testData = {
owner: { id: owner.id, uuid: owner.uuid, email: owner.email },
attendee: { id: attendee.id, email: attendee.email },
organization: { id: organization.id },
eventType: { id: eventType.id },
booking: {
uid: booking.uid,
startTime: booking.startTime,
endTime: booking.endTime,
status: booking.status,
},
};
});
afterEach(async () => {
if (!testData) return;
await cleanupTestData({
bookingUid: testData.booking?.uid,
userUuids: testData.owner?.uuid ? [testData.owner.uuid] : [],
attendeeEmails: testData.attendee?.email ? [testData.attendee.email] : [],
eventTypeId: testData.eventType?.id,
organizationId: testData.organization?.id,
userIds: [testData.owner?.id, testData.attendee?.id].filter((id): id is number => id !== undefined),
featureSlug: "booking-audit",
});
});
describe("when location is changed", () => {
it("should create audit record and retrieve it with correct location data", async () => {
const actor = makeUserActor(testData.owner.uuid);
const operationId = `op-${Date.now()}`;
// Simulate LOCATION_CHANGED action
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "LOCATION_CHANGED",
source: "WEBAPP",
operationId,
data: {
location: {
old: "Zoom",
new: "Google Meet",
},
},
timestamp: Date.now(),
});
// Retrieve logs
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.bookingUid).toBe(testData.booking.uid);
// Find the specific log we just created
const auditLog = result.auditLogs.find((log) => log.operationId === operationId);
expect(auditLog).toBeDefined();
if (!auditLog) throw new Error("Audit log not found");
expect(auditLog.action).toBe("LOCATION_CHANGED");
// The type identifier from the service
expect(auditLog.type).toBe("RECORD_UPDATED");
expect(auditLog.displayJson).toBeNull();
// Verify the title params are correct
expect(auditLog.actionDisplayTitle).toBeDefined();
expect(auditLog.actionDisplayTitle.key).toBe("booking_audit_action.location_changed_from_to");
expect(auditLog.actionDisplayTitle.params).toEqual({
fromLocation: "Zoom",
toLocation: "Google Meet",
});
});
it("should handle location change where old value was null", async () => {
const actor = makeUserActor(testData.owner.uuid);
const operationId = `op-initial-${Date.now()}`;
// Simulate setting location for the first time (old is null)
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "LOCATION_CHANGED",
source: "WEBAPP",
operationId,
data: {
location: {
old: null,
new: "In Person",
},
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
const auditLog = result.auditLogs.find((log) => log.operationId === operationId);
expect(auditLog).toBeDefined();
expect(auditLog!.displayJson).toBeNull();
expect(auditLog!.actionDisplayTitle.params).toEqual({
fromLocation: "No location defined",
toLocation: "In Person",
});
});
});
});
@@ -248,16 +248,20 @@ export class BookingEventHandlerService {
}
async onLocationChanged(params: OnLocationChangedParams) {
const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params;
await this.bookingAuditProducerService.queueLocationChangedAudit({
bookingUid,
actor,
organizationId,
source,
operationId,
data: auditData,
context,
});
try {
const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params;
await this.bookingAuditProducerService.queueLocationChangedAudit({
bookingUid,
actor,
organizationId,
source,
operationId,
data: auditData,
context,
});
} catch (error) {
this.log.error("Error while onLocationChanged", safeStringify(error));
}
}
async onAttendeeNoShowUpdated(params: OnAttendeeNoShowUpdatedParams) {
@@ -8,9 +8,9 @@ import { ZConfirmInputSchema } from "./confirm.schema";
import { ZEditLocationInputSchema } from "./editLocation.schema";
import { ZFindInputSchema } from "./find.schema";
import { ZGetInputSchema } from "./get.schema";
import { ZGetBookingHistoryInputSchema } from "./getBookingHistory.schema";
import { ZGetBookingAttendeesInputSchema } from "./getBookingAttendees.schema";
import { ZGetBookingDetailsInputSchema } from "./getBookingDetails.schema";
import { ZGetBookingHistoryInputSchema } from "./getBookingHistory.schema";
import { ZInstantBookingInputSchema } from "./getInstantBookingLocation.schema";
import { ZReportBookingInputSchema } from "./reportBooking.schema";
import { ZRequestRescheduleInputSchema } from "./requestReschedule.schema";
@@ -42,6 +42,7 @@ export const bookingsRouter = router({
return editLocationHandler({
ctx,
input,
actionSource: "WEBAPP",
});
}),
@@ -4,7 +4,7 @@
// NOTE: All imports except vitest are deferred to inside the skipped describe blocks
// to prevent module loading side effects during test collection (which can cause
// "Closing rpc while fetch was pending" errors from watchlist module imports)
import { describe, expect, test, vi, beforeEach } from "vitest";
import { beforeEach, describe, expect, test, vi } from "vitest";
describe.skip("getLocationForOrganizerDefaultConferencingAppInEvtFormat", () => {
const mockTranslate = vi.fn((key: string) => key);
@@ -224,7 +224,7 @@ describe.skip("editLocation.handler", () => {
input: {
newLocation: "conferencing",
},
currentUserId: updatedOrganizer.id,
actionSource: "WEBAPP",
});
const updatedBooking = await prisma.booking.findFirstOrThrow({
@@ -1,28 +1,28 @@
import type { z } from "zod";
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential";
import { getEventLocationType, OrganizerDefaultConferencingAppType } from "@calcom/app-store/locations";
import { getAppFromSlug } from "@calcom/app-store/utils";
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 EventManager from "@calcom/features/bookings/lib/EventManager";
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
import { CredentialAccessService } from "@calcom/features/credentials/services/CredentialAccessService";
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
import { buildCalEventFromBooking } from "@calcom/lib/buildCalEventFromBooking";
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { getTranslation } from "@calcom/lib/server/i18n";
import { prisma } from "@calcom/prisma";
import type { Booking, BookingReference } from "@calcom/prisma/client";
import type { userMetadata } from "@calcom/prisma/zod-utils";
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import type { EventTypeMetadata, userMetadata } from "@calcom/prisma/zod-utils";
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
import type { PartialReference } from "@calcom/types/EventManager";
import type { Ensure } from "@calcom/types/utils";
import { TRPCError } from "@trpc/server";
import type { z } from "zod";
import type { TrpcSessionUser } from "../../../types";
import type { TEditLocationInputSchema } from "./editLocation.schema";
@@ -34,6 +34,7 @@ type EditLocationOptions = {
user: NonNullable<TrpcSessionUser>;
} & BookingsProcedureContext;
input: TEditLocationInputSchema;
actionSource: ValidActionSource;
};
type UserMetadata = z.infer<typeof userMetadata>;
@@ -121,6 +122,7 @@ async function updateBookingLocationInDb({
iCalSequence: (evt.iCalSequence || 0) + 1,
},
});
return { updatedLocation: evt.location };
}
async function getAllCredentialsIncludeServiceAccountKey({
@@ -257,10 +259,12 @@ export function getLocationForOrganizerDefaultConferencingAppInEvtFormat({
return appLink;
}
export async function editLocationHandler({ ctx, input }: EditLocationOptions) {
export async function editLocationHandler({ ctx, input, actionSource }: EditLocationOptions) {
const { newLocation, credentialId: conferenceCredentialId } = input;
const { booking, user: loggedInUser } = ctx;
const oldLocation = booking.location;
const organizer = await new UserRepository(prisma).findByIdOrThrow({ id: booking.userId || 0 });
const organizationId = booking.user?.profiles?.[0]?.organizationId ?? null;
@@ -295,7 +299,7 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) {
const additionalInformation = extractAdditionalInformation(updatedResult.results[0]);
await updateBookingLocationInDb({
const { updatedLocation } = await updateBookingLocationInDb({
booking,
evt: { ...evt, additionalInformation },
references: updatedResult.referencesToCreate,
@@ -310,5 +314,19 @@ export async function editLocationHandler({ ctx, input }: EditLocationOptions) {
logger.error("Error sending LocationChangeEmails", safeStringify(error));
}
const bookingEventHandlerService = getBookingEventHandlerService();
await bookingEventHandlerService.onLocationChanged({
bookingUid: booking.uid,
actor: makeUserActor(loggedInUser.uuid),
organizationId,
source: actionSource,
auditData: {
location: {
old: oldLocation,
new: updatedLocation,
},
},
});
return { message: "Location updated" };
}