Skip to content

chore: [Booking Cancellation Refactor - 2] Inject repositories and use them instead of Prisma in cancellation flow#3

Open
tomerqodo wants to merge 2 commits into
coderabbit_full_base_chore_booking_cancellation_refactor_-_2_inject_repositories_and_use_them_instead_of_prisma_in_cancellation_flow_pr3from
coderabbit_full_head_chore_booking_cancellation_refactor_-_2_inject_repositories_and_use_them_instead_of_prisma_in_cancellation_flow_pr3
Open

chore: [Booking Cancellation Refactor - 2] Inject repositories and use them instead of Prisma in cancellation flow#3
tomerqodo wants to merge 2 commits into
coderabbit_full_base_chore_booking_cancellation_refactor_-_2_inject_repositories_and_use_them_instead_of_prisma_in_cancellation_flow_pr3from
coderabbit_full_head_chore_booking_cancellation_refactor_-_2_inject_repositories_and_use_them_instead_of_prisma_in_cancellation_flow_pr3

Conversation

@tomerqodo

@tomerqodo tomerqodo commented Jan 29, 2026

Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#3

Summary by CodeRabbit

  • Refactor

    • Restructured booking cancellation system with improved data access patterns and dependency management for better code maintainability.
  • Tests

    • Added comprehensive test coverage for booking cancellation scenarios, including seated events and recurring bookings.

@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR refactors the booking cancellation system to use repository-based dependency injection instead of direct Prisma client dependencies. It introduces five new repository classes, adds repository interface contracts, extends existing repositories with bulk and combined operations, updates the BookingCancelService and cancellation handler to use repositories, and adds comprehensive test coverage for seating and cancellation scenarios.

Changes

Cohort / File(s) Summary
DI Infrastructure
packages/features/bookings/di/tokens.ts, packages/features/di/tokens.ts, packages/features/bookings/di/BookingReferenceRepository.module.ts, packages/features/bookings/di/BookingAttendeeRepository.module.ts, packages/features/users/di/Profile.module.ts
New DI tokens and module loaders for BOOKING_REFERENCE_REPOSITORY, BOOKING_ATTENDEE_REPOSITORY, PROFILE_REPOSITORY and their corresponding modules; wired to Prisma module dependencies.
New Prisma Repository Implementations
apps/api/v2/src/lib/repositories/prisma-booking-attendee.repository.ts, apps/api/v2/src/lib/repositories/prisma-booking-reference.repository.ts, apps/api/v2/src/lib/repositories/prisma-profile.repository.ts, packages/features/bookings/repositories/PrismaBookingAttendeeRepository.ts
New injectable repository classes extending base repositories and implementing repository interfaces; each accepts PrismaClient or PrismaWriteService and provides data access methods.
Repository Interfaces and DTOs
packages/features/bookings/lib/dto/IBookingAttendeeRepository.ts, packages/lib/server/repository/dto/IBookingReferenceRepository.ts, packages/lib/server/repository/dto/IBookingRepository.ts, packages/lib/server/repository/dto/IProfileRepository.ts
New interfaces defining repository contracts: IBookingAttendeeRepository.deleteManyByBookingId, IBookingReferenceRepository.updateManyByBookingId, IBookingRepository bulk operations, IProfileRepository.findFirstByUserId.
Enhanced Existing Repositories
packages/features/bookings/repositories/BookingRepository.ts, packages/features/profile/repositories/ProfileRepository.ts, packages/features/users/repositories/UserRepository.ts, packages/features/bookingReference/repositories/BookingReferenceRepository.ts
BookingRepository adds updateMany, update, updateIncludeWorkflowRemindersAndReferences, findManyIncludeWorkflowRemindersAndReferences; ProfileRepository adds DI support and findFirstByUserId; BookingReferenceRepository adds updateManyByBookingId; UserRepository removes locale from userSelect.
Service and Handler Refactoring
apps/api/v2/src/lib/services/booking-cancel.service.ts, packages/features/bookings/lib/handleCancelBooking.ts
BookingCancelService constructor changes from PrismaWriteService to five repository dependencies; handler now accepts Dependencies and replaces direct Prisma calls with repository method invocations throughout cancellation workflow.
Module Configuration
apps/api/v2/src/lib/modules/booking-cancel.module.ts, packages/features/bookings/di/BookingCancelService.module.ts
BookingCancelModule adds five repository providers; BookingCancelService.module.ts wires repository module loaders and updates dependency mapping.
Test Coverage
packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts
Extensive new test cases for seating behavior (seatsPerTimeSlot), multiple cancellation scopes (allRemainingBookings, cancelSubsequentBookings), and booking reference cleanup scenarios.
Public Exports
packages/platform/libraries/repositories.ts
Adds barrel exports for BookingReferenceRepository (as PrismaBookingReferenceRepository), PrismaBookingAttendeeRepository, and ProfileRepository (as PrismaProfileRepository).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Repositories spring forth with DI's embrace,
No more raw Prisma scattered all over the place!
Booking cancellations now flow clean and bright,
With injection of deps, everything's right.
Tests bloom for seating, a garden of care—
This refactor's a hop to a cleaner lair! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: refactoring the booking cancellation flow to inject repositories instead of directly using Prisma.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch coderabbit_full_head_chore_booking_cancellation_refactor_-_2_inject_repositories_and_use_them_instead_of_prisma_in_cancellation_flow_pr3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tomerqodo

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@tomerqodo

Copy link
Copy Markdown
Author

@coderabbitai review

1 similar comment
@tomerqodo

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hussam789

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Feb 2, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/features/bookings/lib/handleCancelBooking.ts`:
- Around line 513-520: The fetch uses bookingToDelete.startTime while the update
used the computed gte cutoff, so you may retrieve bookings that weren't updated;
change the call to
bookingRepository.findManyIncludeWorkflowRemindersAndReferences to use the same
gte variable used in the updateMany (the gte computed earlier based on
cancelSubsequentBookings) instead of bookingToDelete.startTime so both
updateMany and the subsequent fetch operate on the identical date filter
(referencing bookingRepository.updateMany,
bookingRepository.findManyIncludeWorkflowRemindersAndReferences, the gte
variable, bookingToDelete, cancelSubsequentBookings and allRemainingBookings).

In `@packages/features/bookings/repositories/BookingRepository.ts`:
- Around line 1502-1507: The updateMany implementation in BookingRepository does
not return the result expected by IBookingRepository; change the method so it
returns the Promise<{ count: number }>` produced by
prismaClient.booking.updateMany(...)—i.e., call and return the value from
`this.prismaClient.booking.updateMany({ where, data })` (and ensure the method
signature still matches `async updateMany({ where, data }: { where:
BookingWhereInput; data: BookingUpdateData }): Promise<{ count: number }>`).
🧹 Nitpick comments (10)
packages/lib/server/repository/dto/IBookingReferenceRepository.ts (1)

6-16: Consider completing the interface with existing methods.

Line 7 has a placeholder comment // ... Add existing methods as well here. The BookingReferenceRepository class has static methods like findDailyVideoReferenceByRoomName and replaceBookingReferences that aren't reflected in this interface. If these are intentionally excluded (e.g., static methods not part of the instance contract), consider removing or updating the comment to clarify the design decision.

packages/features/bookingReference/repositories/BookingReferenceRepository.ts (1)

18-22: Consider simplifying constructor parameter and addressing static method inconsistency.

Two observations:

  1. The deps object wrapper is unnecessary when there's only one dependency. Consider simplifying:
-  private prismaClient: PrismaClient;
-  constructor(private deps: { prismaClient: PrismaClient }) {
-    this.prismaClient = deps.prismaClient;
-  }
+  constructor(private prismaClient: PrismaClient) {}
  1. Static methods (lines 24-66) still use the global prisma import while instance methods use the injected prismaClient. This creates inconsistent data access patterns and makes static methods harder to test. Consider migrating static methods to instance methods in a follow-up refactor.
packages/lib/server/repository/dto/IBookingRepository.ts (1)

1-1: Use type-only import for Booking.

Since Booking is only used as a type annotation (line 50), prefer a type-only import to ensure it's erased at compile time and avoid potential bundling issues.

Suggested fix
-import { Booking } from "@calcom/prisma/client";
+import type { Booking } from "@calcom/prisma/client";
packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts (3)

1163-1282: Consider verifying that all recurring bookings were actually cancelled.

The test sets up two recurring booking instances but only asserts on the result object (result.success, result.bookingId). It doesn't verify that the second booking (id: idOfBookingToBeCancelled + 1) was also cancelled in the database.

💡 Suggested enhancement to verify all bookings were cancelled
     expect(result.success).toBe(true);
     expect(result.onlyRemovedAttendee).toBe(false);
     expect(result.bookingId).toBe(idOfBookingToBeCancelled);
+
+    // Verify both recurring bookings were cancelled
+    const { prisma } = await import("@calcom/prisma");
+    const cancelledBookings = await prisma.booking.findMany({
+      where: { recurringEventId },
+      select: { id: true, status: true },
+    });
+    expect(cancelledBookings.every(b => b.status === BookingStatus.CANCELLED)).toBe(true);
   });

1284-1403: Same consideration: verify subsequent bookings were actually cancelled.

Similar to the allRemainingBookings test, this test doesn't verify that the subsequent booking was cancelled in the database. The test only checks the result object properties.


1405-1516: Consider asserting that booking references were cleaned up.

The test name suggests it should verify "booking reference cleanup during cancellation", but the assertions only check the result object. Consider querying the database to verify the references were actually marked as deleted or handled appropriately.

💡 Suggested enhancement to verify reference cleanup
     expect(result.success).toBe(true);
     expect(result.onlyRemovedAttendee).toBe(false);
     expect(result.bookingId).toBe(idOfBookingToBeCancelled);
+
+    // Verify booking references were cleaned up
+    const { prisma } = await import("@calcom/prisma");
+    const references = await prisma.bookingReference.findMany({
+      where: { bookingId: idOfBookingToBeCancelled },
+    });
+    // Assert references were marked as deleted or handled appropriately
+    expect(references.every(ref => ref.deleted !== null)).toBe(true);
   });
packages/features/bookings/di/BookingCancelService.module.ts (1)

2-7: Import path style inconsistency.

Line 2 uses a relative import path (../lib/handleCancelBooking) while lines 3-7 use absolute @calcom aliased paths. Consider using a consistent import style throughout the file.

♻️ Suggested fix for consistency
-import { BookingCancelService } from "../lib/handleCancelBooking";
+import { BookingCancelService } from "@calcom/features/bookings/lib/handleCancelBooking";
packages/features/profile/repositories/ProfileRepository.ts (1)

1034-1041: Consolidate duplicate profile lookup methods during DI transition.

Both findFirstByUserId (instance method) and findFirstForUserId (static method) perform identical queries; the only difference is the Prisma client source. The instance method is the intended API (defined in IProfileRepository interface), while the static method is legacy code being phased out (evidenced by the TODO in RegularBookingService.ts). Complete the transition by removing the static method and updating the remaining call sites in onboardingUtils.ts and RegularBookingService.ts to use the instance method via dependency injection.

packages/features/bookings/lib/handleCancelBooking.ts (2)

89-95: Consider using repository interfaces instead of concrete types.

The Dependencies type uses concrete repository classes. For improved testability and flexibility, consider using the repository interfaces (e.g., IBookingRepository, IProfileRepository, IBookingReferenceRepository) that appear to exist in the codebase based on the PR context.


703-709: Consider consolidating duplicate type definitions.

BookingCancelServiceDependencies is identical to the Dependencies type defined at Lines 89-95. Consider reusing a single type to avoid duplication and potential drift.

♻️ Proposed fix
-type BookingCancelServiceDependencies = {
-  userRepository: UserRepository;
-  bookingRepository: BookingRepository;
-  profileRepository: ProfileRepository;
-  bookingReferenceRepository: BookingReferenceRepository;
-  attendeeRepository: PrismaBookingAttendeeRepository;
-};
+type BookingCancelServiceDependencies = Dependencies;

Comment on lines +513 to 520
const allUpdatedBookings = await bookingRepository.findManyIncludeWorkflowRemindersAndReferences({
where: {
recurringEventId: bookingToDelete.recurringEventId,
startTime: {
gte: new Date(),
},
},
select: {
id: true,
startTime: true,
endTime: true,
references: {
select: {
uid: true,
type: true,
externalCalendarId: true,
credentialId: true,
},
gte: bookingToDelete.startTime,
},
workflowReminders: true,
uid: true,
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential mismatch between update and fetch date filters.

The updateMany at Line 500 uses the gte variable (which may be new Date() when cancelSubsequentBookings is false), but findManyIncludeWorkflowRemindersAndReferences at Line 517 always uses bookingToDelete.startTime. This could result in fetching bookings that weren't actually updated when allRemainingBookings is true but cancelSubsequentBookings is false.

Consider using the same gte variable for consistency:

🐛 Proposed fix
     const allUpdatedBookings = await bookingRepository.findManyIncludeWorkflowRemindersAndReferences({
       where: {
         recurringEventId: bookingToDelete.recurringEventId,
         startTime: {
-          gte: bookingToDelete.startTime,
+          gte,
         },
       },
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allUpdatedBookings = await bookingRepository.findManyIncludeWorkflowRemindersAndReferences({
where: {
recurringEventId: bookingToDelete.recurringEventId,
startTime: {
gte: new Date(),
},
},
select: {
id: true,
startTime: true,
endTime: true,
references: {
select: {
uid: true,
type: true,
externalCalendarId: true,
credentialId: true,
},
gte: bookingToDelete.startTime,
},
workflowReminders: true,
uid: true,
},
});
const allUpdatedBookings = await bookingRepository.findManyIncludeWorkflowRemindersAndReferences({
where: {
recurringEventId: bookingToDelete.recurringEventId,
startTime: {
gte,
},
},
});
🤖 Prompt for AI Agents
In `@packages/features/bookings/lib/handleCancelBooking.ts` around lines 513 -
520, The fetch uses bookingToDelete.startTime while the update used the computed
gte cutoff, so you may retrieve bookings that weren't updated; change the call
to bookingRepository.findManyIncludeWorkflowRemindersAndReferences to use the
same gte variable used in the updateMany (the gte computed earlier based on
cancelSubsequentBookings) instead of bookingToDelete.startTime so both
updateMany and the subsequent fetch operate on the identical date filter
(referencing bookingRepository.updateMany,
bookingRepository.findManyIncludeWorkflowRemindersAndReferences, the gte
variable, bookingToDelete, cancelSubsequentBookings and allRemainingBookings).

Comment on lines +1502 to +1507
async updateMany({ where, data }: { where: BookingWhereInput; data: BookingUpdateData }) {
await this.prismaClient.booking.updateMany({
where: where,
data,
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing return statement - method doesn't fulfill interface contract.

The IBookingRepository interface declares updateMany as returning Promise<{ count: number }>, but this implementation doesn't return the result from prismaClient.booking.updateMany(). Callers expecting the count will receive undefined.

🐛 Proposed fix
 async updateMany({ where, data }: { where: BookingWhereInput; data: BookingUpdateData }) {
-    await this.prismaClient.booking.updateMany({
+    const result = await this.prismaClient.booking.updateMany({
       where: where,
       data,
     });
+    return { count: result.count };
   }
🤖 Prompt for AI Agents
In `@packages/features/bookings/repositories/BookingRepository.ts` around lines
1502 - 1507, The updateMany implementation in BookingRepository does not return
the result expected by IBookingRepository; change the method so it returns the
Promise<{ count: number }>` produced by
prismaClient.booking.updateMany(...)—i.e., call and return the value from
`this.prismaClient.booking.updateMany({ where, data })` (and ensure the method
signature still matches `async updateMany({ where, data }: { where:
BookingWhereInput; data: BookingUpdateData }): Promise<{ count: number }>`).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants