Files
cal.diy/packages/features/booking-audit/lib/actions/RescheduleRequestedAuditActionService.ts
T
Hariom Balhara f3a37886e3 chore: Integrate reschedule request booking audit (#26527)
## What does this PR do?

Adds audit logging for the reschedule request flow. When an organizer requests an attendee to reschedule a booking, this action is now logged to the booking audit system.

This is part of the booking audit integration effort, following the pattern established in PR #26046 (booking creation/rescheduling audit) and PR #26458 (cancellation audit).

## Changes

- Added audit logging call to `requestReschedule.handler.ts` using `BookingEventHandlerService.onRescheduleRequested()`
- Captures audit data:
  - `rescheduleReason`: The reason provided for requesting the reschedule (nullable string)
  - `rescheduledRequestedBy`: Email of the user who requested the reschedule
- Uses `makeUserActor(user.uuid)` to identify the actor performing the action
- Added required `source` parameter for action source tracking (passed as `"WEBAPP"` from tRPC router)
- Updated `RescheduleRequestedAuditActionService` schema to use simpler field structure
- Updated test helper `getTrpcHandlerData` to pass `source: "WEBAPP"` parameter

## Updates since last revision

- Changed `source` parameter from optional with default to required - tests now explicitly pass `source: "WEBAPP"` instead of relying on a default value

## 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 - follows established audit pattern with existing infrastructure tests.

## How should this be tested?

1. Log in as a user with a booking
2. Navigate to the booking and click "Request Reschedule"
3. Provide a reschedule reason and submit
4. Verify the audit log is created with the correct data (requires access to audit log storage/database)

The audit logging follows the same pattern as other booking audit integrations, so if those are working, this should work as well.

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have checked if my changes generate no new warnings

---

**Link to Devin run**: https://app.devin.ai/sessions/ae84d093c4594a5d89b88c45639a6a06
**Requested by**: @hariombalhara

## Human Review Checklist

- [ ] Verify the audit data schema matches `RescheduleRequestedAuditActionService` expectations (fields: `rescheduleReason`, `rescheduledRequestedBy`)
- [ ] Confirm the schema change from change-based fields (`{ old, new }`) to simple nullable strings is intentional
- [ ] Confirm `user.uuid` is available in the tRPC session context
- [ ] Note: API v2 does not have a reschedule request endpoint, so no API v2 changes are needed
2026-01-09 14:03:01 +05:30

85 lines
2.7 KiB
TypeScript

import { z } from "zod";
import { AuditActionServiceHelper } from "./AuditActionServiceHelper";
import type {
IAuditActionService,
TranslationWithParams,
GetDisplayTitleParams,
GetDisplayJsonParams,
} from "./IAuditActionService";
/**
* Reschedule Requested Audit Action Service
* Handles RESCHEDULE_REQUESTED action with per-action versioning
*/
// Module-level because it is passed to IAuditActionService type outside the class scope
const fieldsSchemaV1 = z.object({
rescheduleReason: z.string().nullable(),
rescheduledRequestedBy: z.string().nullable(),
});
export class RescheduleRequestedAuditActionService implements IAuditActionService {
readonly VERSION = 1;
public static readonly TYPE = "RESCHEDULE_REQUESTED" 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 = RescheduleRequestedAuditActionService.dataSchemaV1;
// Union of all versions
public static readonly storedFieldsSchema = RescheduleRequestedAuditActionService.fieldsSchemaV1;
private helper: AuditActionServiceHelper<
typeof RescheduleRequestedAuditActionService.latestFieldsSchema,
typeof RescheduleRequestedAuditActionService.storedDataSchema
>;
constructor() {
this.helper = new AuditActionServiceHelper({
latestVersion: this.VERSION,
latestFieldsSchema: RescheduleRequestedAuditActionService.latestFieldsSchema,
storedDataSchema: RescheduleRequestedAuditActionService.storedDataSchema,
});
}
getVersionedData(fields: unknown) {
return this.helper.getVersionedData(fields);
}
parseStored(data: unknown) {
return this.helper.parseStored(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 };
}
async getDisplayTitle(_: GetDisplayTitleParams): Promise<TranslationWithParams> {
return { key: "booking_audit_action.reschedule_requested" };
}
getDisplayJson({ storedData }: GetDisplayJsonParams): RescheduleRequestedAuditDisplayData {
const { fields } = this.parseStored(storedData);
return {
reason: fields.rescheduleReason ?? null,
requestedBy: fields.rescheduledRequestedBy ?? null,
};
}
}
export type RescheduleRequestedAuditData = z.infer<typeof fieldsSchemaV1>;
export type RescheduleRequestedAuditDisplayData = {
reason: string | null;
requestedBy: string | null;
};