Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
307dce2
refactor: reserved slot cookie constant and retrieval helper
supalarry Oct 22, 2025
896d42e
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Oct 27, 2025
3f7b8e0
chore: extract uid from req
supalarry Oct 27, 2025
e1f4cc0
chore: pass reservedSlotUid from api layer to handlers
supalarry Oct 27, 2025
7e8818f
chore: pass reservedSlotUid from frontend to handler in request.body
supalarry Oct 27, 2025
7aa2ada
refactor: replace useSlotReservationId with booker context property
supalarry Oct 27, 2025
e6dad22
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Oct 28, 2025
47330d3
feat: handle booking with reservedSlotUid
supalarry Oct 28, 2025
98b08ac
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Oct 30, 2025
71b148e
docs: booking types this is enabled for
supalarry Oct 30, 2025
ecc4804
refactor: disable this for team event types
supalarry Oct 30, 2025
9ff6196
test: booker web, embed and atom has reservedSlotUid in body
supalarry Oct 31, 2025
8c53155
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Oct 31, 2025
96c7e6e
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Oct 31, 2025
fb7d90c
test: api handling reservedSlotUid
supalarry Oct 31, 2025
3c703c4
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Oct 31, 2025
1fc31bc
fix: e2e test booking times
supalarry Oct 31, 2025
2ecdd4d
refactor: centralize date, allow slot if non-existing/no slots reserved
supalarry Nov 3, 2025
8b46345
fix: bookings controller passing reservedSlotUid
supalarry Nov 3, 2025
30732ba
refactor: error messages
supalarry Nov 3, 2025
19fbb29
refactor: web booker error message
supalarry Nov 3, 2025
83c1531
test: recurring events
supalarry Nov 3, 2025
f4c211c
fix: tests
supalarry Nov 3, 2025
ed81321
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Nov 3, 2025
6d19c36
fix: merging main
supalarry Nov 3, 2025
14b30e3
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Nov 18, 2025
ccf96c1
chore: remove console log
supalarry Nov 18, 2025
6832445
chore: implement feedback for recurring events
supalarry Nov 18, 2025
0aaf0ac
refactor: instant booking use passed prisma client
supalarry Nov 18, 2025
1e96c53
refactor: for recurring extract from first element
supalarry Nov 18, 2025
4689a78
refactor: createBookingWithReservedSlots use prisma client in deps
supalarry Nov 19, 2025
a4ae662
update docs
supalarry Nov 20, 2025
91f65eb
more in depth test for booker e2e
supalarry Nov 20, 2025
9ec65c0
fix: e2e
supalarry Nov 20, 2025
71d1e69
fix: e2e
supalarry Nov 20, 2025
cea9dc0
fix: e2e
supalarry Nov 20, 2025
c2cd427
fix: e2e
supalarry Nov 20, 2025
dd36c1f
fix: e2e
supalarry Nov 20, 2025
d17562a
fix: e2e
supalarry Nov 20, 2025
ae73fb1
fix: e2e
supalarry Nov 20, 2025
90551ec
fix: e2e
supalarry Nov 21, 2025
07d9bb8
fix: e2e
supalarry Nov 21, 2025
1346d40
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Nov 21, 2025
f0d86b3
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Nov 21, 2025
32e61b3
more e2e
supalarry Nov 21, 2025
3d7a228
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Nov 26, 2025
31c389a
refactor: move slots functions to slots repository
supalarry Nov 27, 2025
79df00a
refactor: pass booking repository to instant booking service
supalarry Nov 27, 2025
7eac213
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Nov 27, 2025
6e5326c
refactor: pass routing form responses repository to createBooking
supalarry Nov 27, 2025
ce7283a
refactor: pass booking repository to createBooking
supalarry Nov 27, 2025
61d6e33
fix: RegularBookingService dependency injection
supalarry Nov 27, 2025
6b1fd32
refactor: instant service create booking via repository
supalarry Nov 27, 2025
3612599
refactor: tx prisma is always passed
supalarry Nov 27, 2025
bc23f0a
fix: instant booking module di
supalarry Nov 27, 2025
71b62e4
fix: don't cut off miliseconds when booking normal booking
supalarry Nov 28, 2025
a9b43d1
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Nov 28, 2025
76011d6
revert: no need for reserve slot logic in instant
supalarry Nov 28, 2025
ea4f69e
fix: instant booking docs and finish main merge
supalarry Nov 28, 2025
6d4ec83
Revert "fix: don't cut off miliseconds when booking normal booking"
supalarry Nov 28, 2025
8e26734
Reapply "fix: don't cut off miliseconds when booking normal booking"
supalarry Nov 28, 2025
f9b86d7
fix: new controller error handler
supalarry Nov 28, 2025
4b8e0a1
fix: e2e
supalarry Nov 28, 2025
0e33cb5
fix: v1 api test
supalarry Nov 28, 2025
ae333f9
fix: v1 api test
supalarry Nov 28, 2025
0bf4d7e
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Nov 28, 2025
54f65a4
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 1, 2025
c109317
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 2, 2025
6fc9f56
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 5, 2025
590210b
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 9, 2025
cadaa41
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 10, 2025
bdaf240
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 10, 2025
bf64190
refactor: dont check slot, book and delete slot in transaction
supalarry Dec 10, 2025
2a52de8
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 10, 2025
5a07234
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 11, 2025
e9b5991
feat: handle reservedSlotUid when rescheduling
supalarry Dec 11, 2025
369380c
fix: e2e tests
supalarry Dec 11, 2025
d208a70
fix: e2e
supalarry Dec 11, 2025
0a69659
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 11, 2025
4600585
fix: e2e
supalarry Dec 11, 2025
87b9ba6
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 12, 2025
937f873
fix: e2e
supalarry Dec 12, 2025
668c20d
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 15, 2025
11f0aab
chore: finish main merge
supalarry Dec 15, 2025
faede5a
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 15, 2025
663ce3d
chore: finish main merge
supalarry Dec 15, 2025
1a367ac
Merge branch 'main' into respect-reserved-slot-when-booking
supalarry Dec 17, 2025
e600891
refactor: isTeamEvent determine based on teamId
supalarry Dec 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions apps/api/v1/test/lib/bookings/_post.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock";
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, expect, test, vi, beforeEach } from "vitest";
import { describe, expect, test, vi, beforeEach, type Mock } from "vitest";

import dayjs from "@calcom/dayjs";
import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB";
Expand Down Expand Up @@ -67,8 +67,15 @@ vi.mock("@calcom/features/webhooks/lib/sendOrSchedulePayload", () => ({

const mockFindOriginalRescheduledBooking = vi.fn();
vi.mock("@calcom/features/bookings/repositories/BookingRepository", () => ({
BookingRepository: vi.fn().mockImplementation(() => ({
BookingRepository: vi.fn().mockImplementation((prismaClient) => ({
findOriginalRescheduledBooking: mockFindOriginalRescheduledBooking,
create: vi.fn().mockImplementation((args, tx) => {
return (tx || prismaClient).booking.create(args);
}),
update: vi.fn().mockImplementation((args, tx) => {
return (tx || prismaClient).booking.update(args);
}),
getValidBookingFromEventTypeForAttendee: vi.fn().mockResolvedValue(null),
})),
}));

Expand Down Expand Up @@ -166,7 +173,7 @@ vi.mock("@calcom/features/ee/workflows/lib/getAllWorkflowsFromEventType", () =>
}));

vi.mock("@calcom/lib/server/i18n", () => {
const mockT = (key: string, options?: any) => {
const mockT = (key: string, options?: Record<string, unknown>) => {
if (key === "event_between_users") {
return `${options?.eventName} between ${options?.host} and ${options?.attendeeName}`;
}
Expand Down Expand Up @@ -236,12 +243,12 @@ describe("POST /api/bookings", () => {
vi.clearAllMocks();
mockFindOriginalRescheduledBooking.mockResolvedValue(null);

(getEventTypesFromDB as any).mockResolvedValue(mockEventTypeData.eventType);
(getEventTypesFromDB as Mock).mockResolvedValue(mockEventTypeData.eventType);
});

describe("Errors", () => {
test("Missing required data", async () => {
(getEventTypesFromDB as any).mockRejectedValue(new Error(ErrorCode.RequestBodyInvalid));
(getEventTypesFromDB as Mock).mockRejectedValue(new Error(ErrorCode.RequestBodyInvalid));

const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
Expand All @@ -259,7 +266,7 @@ describe("POST /api/bookings", () => {
});

test("Invalid eventTypeId", async () => {
(getEventTypesFromDB as any).mockRejectedValue(new Error(ErrorCode.EventTypeNotFound));
(getEventTypesFromDB as Mock).mockRejectedValue(new Error(ErrorCode.EventTypeNotFound));

const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
Expand Down Expand Up @@ -439,10 +446,11 @@ describe("POST /api/bookings", () => {
oneTimePassword: null,
creationSource: "API_V1",
});
prismaMock.booking.create.mockResolvedValue(mockBooking);
prismaMock.$transaction.mockImplementation(async (callback) => {
const mockTx = {
const _mockTx = {
booking: {
create: prismaMock.booking.create.mockResolvedValue(mockBooking),
create: prismaMock.booking.create,
update: vi.fn().mockResolvedValue({}),
},
app_RoutingForms_FormResponse: {
Expand Down Expand Up @@ -516,10 +524,13 @@ describe("POST /api/bookings", () => {
oneTimePassword: null,
fromReschedule: "original-booking-uid",
});

prismaMock.booking.create.mockResolvedValue(mockBooking);

prismaMock.$transaction.mockImplementation(async (callback) => {
const mockTx = {
const _mockTx = {
booking: {
create: prismaMock.booking.create.mockResolvedValue(mockBooking),
create: prismaMock.booking.create,
update: vi.fn().mockResolvedValue({}),
},
app_RoutingForms_FormResponse: {
Expand Down Expand Up @@ -580,10 +591,11 @@ describe("POST /api/bookings", () => {
oneTimePassword: null,
creationSource: "API_V1",
});
prismaMock.booking.create.mockResolvedValue(mockBooking);
prismaMock.$transaction.mockImplementation(async (callback) => {
const mockTx = {
const _mockTx = {
booking: {
create: prismaMock.booking.create.mockResolvedValue(mockBooking),
create: prismaMock.booking.create,
update: vi.fn().mockResolvedValue({}),
},
app_RoutingForms_FormResponse: {
Expand Down Expand Up @@ -639,7 +651,7 @@ describe("POST /api/bookings", () => {
users: [buildUser()],
});

const mockBookings = Array.from(Array(12).keys()).map((i) =>
const _mockBookings = Array.from(Array(12).keys()).map((i) =>
buildBooking({ id: i + 1, uid: `recurring-booking-${i}` })
);

Expand Down Expand Up @@ -698,7 +710,7 @@ describe("POST /api/bookings", () => {
});

const createdAt = new Date();
const mockBookings = Array.from(Array(12).keys()).map((i) =>
const _mockBookings = Array.from(Array(12).keys()).map((i) =>
buildBooking({ id: i + 1, uid: `webhook-booking-${i}`, createdAt })
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import {
} from "@calcom/platform-libraries";
import { CreationSource } from "@calcom/platform-libraries";
import { type InstantBookingCreateResult } from "@calcom/platform-libraries/bookings";
import { getReservedSlotUidFromRequest } from "@calcom/platform-libraries/slots";
import {
GetBookingsInput_2024_04_15,
CancelBookingInput_2024_04_15,
Expand Down Expand Up @@ -208,6 +209,7 @@ export class BookingsController_2024_04_15 {
await this.checkBookingRequiresAuthentication(req, body.eventTypeId, body.rescheduleUid);

const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl, isEmbed);
const reservedSlotUid = getReservedSlotUidFromRequest(req);
const booking = await this.regularBookingService.createBooking({
bookingData: bookingRequest.body,
bookingMeta: {
Expand All @@ -220,6 +222,7 @@ export class BookingsController_2024_04_15 {
platformBookingUrl: bookingRequest.platformBookingUrl,
platformBookingLocation: bookingRequest.platformBookingLocation,
areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled,
reservedSlotUid,
},
});
if (booking.userId && booking.uid && booking.startTime) {
Expand Down Expand Up @@ -338,6 +341,7 @@ export class BookingsController_2024_04_15 {
}
}
const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed);
const reservedSlotUid = getReservedSlotUidFromRequest(req);
const createdBookings: BookingResponse[] = await this.recurringBookingService.createBooking({
bookingData: body.map((booking) => ({ ...booking, creationSource: CreationSource.API_V2 })),
bookingMeta: {
Expand All @@ -349,6 +353,7 @@ export class BookingsController_2024_04_15 {
platformBookingUrl: bookingRequest.platformBookingUrl,
platformBookingLocation: bookingRequest.platformBookingLocation,
noEmail: bookingRequest.body.noEmail,
reservedSlotUid,
},
});

Expand Down Expand Up @@ -675,6 +680,19 @@ export class BookingsController_2024_04_15 {
type === "no-show"
? `Error while marking no-show.`
: `Error while creating ${type ? type + " " : ""}booking.`;

if (
typeof err === "object" &&
err !== null &&
"message" in err &&
err.message === "reserved_slot_not_first_in_line"
) {
const secondsUntilRelease =
("data" in err && (err.data as { secondsUntilRelease?: number })?.secondsUntilRelease) ?? 300;
const message = `Someone else reserved this booking time slot before you. This time slot will be freed up in ${secondsUntilRelease} seconds.`;
throw new HttpException(message, 409);
}

if (err instanceof HttpError) {
const httpError = err as HttpError;
throw new HttpException(httpError?.message ?? errMsg, httpError?.statusCode ?? 500);
Expand Down
Loading
Loading