Skip to content

Commit 235c274

Browse files
committed
Merge branch 'main' into devin/1753300938-calendar-cache-sql-system
Signed-off-by: Omar López <zomars@me.com> # Conflicts: # apps/api/v2/package.json # yarn.lock
2 parents 6416ae2 + 618ef63 commit 235c274

34 files changed

Lines changed: 665 additions & 232 deletions

File tree

.changeset/rich-impalas-hug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@calcom/atoms": minor
3+
---
4+
5+
fix: Availability atom handleFormSubmit callbacks not triggering

apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/managed-user-bookings.e2e-spec.ts

Lines changed: 238 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import { NestExpressApplication } from "@nestjs/platform-express";
1515
import { Test } from "@nestjs/testing";
1616
import { PlatformOAuthClient, Team, User } from "@prisma/client";
1717
import * as request from "supertest";
18+
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
1819
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
1920
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
2021
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
2122
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
22-
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
2323
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
2424
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
2525
import { randomString } from "test/utils/randomString";
@@ -36,6 +36,7 @@ import {
3636
CreateBookingInput_2024_08_13,
3737
CreateEventTypeInput_2024_06_14,
3838
EventTypeOutput_2024_06_14,
39+
GetBookingOutput_2024_08_13,
3940
GetBookingsOutput_2024_08_13,
4041
} from "@calcom/platform-types";
4142

@@ -50,9 +51,9 @@ describe("Managed user bookings 2024-08-13", () => {
5051
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
5152
let teamRepositoryFixture: TeamRepositoryFixture;
5253
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
53-
let schedulesRepositoryFixture: SchedulesRepositoryFixture;
5454
let profilesRepositoryFixture: ProfileRepositoryFixture;
5555
let membershipsRepositoryFixture: MembershipRepositoryFixture;
56+
let bookingsRepositoryFixture: BookingsRepositoryFixture;
5657

5758
const platformAdminEmail = `managed-users-bookings-2024-08-13-admin-${randomString()}@api.com`;
5859
let platformAdmin: User;
@@ -67,6 +68,10 @@ describe("Managed user bookings 2024-08-13", () => {
6768
let thirdManagedUser: CreateManagedUserData;
6869

6970
let firstManagedUserEventTypeId: number;
71+
let eventTypeRequiresConfirmationId: number;
72+
73+
const orgAdminManagedUserEmail = `managed-user-bookings-2024-08-13-org-admin-${randomString()}@api.com`;
74+
let orgAdminManagedUser: CreateManagedUserData;
7075

7176
let firstManagedUserBookingsCount = 0;
7277
let secondManagedUserBookingsCount = 0;
@@ -85,16 +90,23 @@ describe("Managed user bookings 2024-08-13", () => {
8590
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
8691
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
8792
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
88-
schedulesRepositoryFixture = new SchedulesRepositoryFixture(moduleRef);
8993
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
9094
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
95+
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
9196

9297
platformAdmin = await userRepositoryFixture.create({ email: platformAdminEmail });
9398

9499
organization = await teamRepositoryFixture.create({
95100
name: `oauth-client-users-organization-${randomString()}`,
96101
isPlatform: true,
97102
isOrganization: true,
103+
platformBilling: {
104+
create: {
105+
customerId: "cus_999",
106+
plan: "SCALE",
107+
subscriptionId: "sub_999",
108+
},
109+
},
98110
});
99111
oAuthClient = await createOAuthClient(organization.id);
100112

@@ -243,6 +255,59 @@ describe("Managed user bookings 2024-08-13", () => {
243255
thirdManagedUser = responseBody.data;
244256
});
245257

258+
it(`should create org admin managed user`, async () => {
259+
const requestBody: CreateManagedUserInput = {
260+
email: orgAdminManagedUserEmail,
261+
timeZone: managedUsersTimeZone,
262+
weekStart: "Monday",
263+
timeFormat: 24,
264+
locale: Locales.FR,
265+
name: "Org Admin Smith",
266+
avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png",
267+
};
268+
269+
const response = await request(app.getHttpServer())
270+
.post(`/api/v2/oauth-clients/${oAuthClient.id}/users`)
271+
.set("x-cal-secret-key", oAuthClient.secret)
272+
.send(requestBody)
273+
.expect(201);
274+
275+
const responseBody: CreateManagedUserOutput = response.body;
276+
expect(responseBody.status).toEqual(SUCCESS_STATUS);
277+
expect(responseBody.data).toBeDefined();
278+
expect(responseBody.data.user.email).toEqual(
279+
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
280+
);
281+
expect(responseBody.data.accessToken).toBeDefined();
282+
expect(responseBody.data.refreshToken).toBeDefined();
283+
284+
orgAdminManagedUser = responseBody.data;
285+
286+
await request(app.getHttpServer())
287+
.post(`/v2/organizations/${organization.id}/memberships`)
288+
.set("x-cal-client-id", oAuthClient.id)
289+
.set("x-cal-secret-key", oAuthClient.secret)
290+
.send({
291+
userId: orgAdminManagedUser.user.id,
292+
role: "ADMIN",
293+
accepted: true,
294+
})
295+
.expect(201);
296+
});
297+
298+
it("should create an event type requiring confirmation for first managed user", async () => {
299+
const eventTypeRequiresConfirmation = await eventTypesRepositoryFixture.create(
300+
{
301+
title: `managed-user-bookings-event-type-requires-confirmation-${randomString()}`,
302+
slug: `managed-user-bookings-event-type-requires-confirmation-${randomString()}`,
303+
length: 60,
304+
requiresConfirmation: true,
305+
},
306+
firstManagedUser.user.id
307+
);
308+
eventTypeRequiresConfirmationId = eventTypeRequiresConfirmation.id;
309+
});
310+
246311
describe("bookings using original emails", () => {
247312
it("managed user should be booked by managed user attendee and booking shows up in both users' bookings", async () => {
248313
const body: CreateBookingInput_2024_08_13 = {
@@ -543,10 +608,180 @@ describe("Managed user bookings 2024-08-13", () => {
543608
});
544609
});
545610

611+
describe("booking confirmation by org admin", () => {
612+
it("should allow org admin managed user to confirm booking using access token", async () => {
613+
const bookingRequiringConfirmation = await bookingsRepositoryFixture.create({
614+
user: {
615+
connect: {
616+
id: firstManagedUser.user.id,
617+
},
618+
},
619+
startTime: new Date(Date.UTC(2030, 0, 9, 13, 0, 0)),
620+
endTime: new Date(Date.UTC(2030, 0, 9, 14, 0, 0)),
621+
title: "Booking requiring confirmation",
622+
uid: `booking-requiring-confirmation-${randomString()}`,
623+
eventType: {
624+
connect: {
625+
id: eventTypeRequiresConfirmationId,
626+
},
627+
},
628+
location: "https://meet.google.com/abc-def-ghi",
629+
customInputs: {},
630+
metadata: {},
631+
status: "PENDING",
632+
responses: {
633+
name: secondManagedUser.user.name,
634+
email: secondManagedUserEmail,
635+
},
636+
attendees: {
637+
create: {
638+
email: secondManagedUserEmail,
639+
name: secondManagedUser.user.name!,
640+
locale: secondManagedUser.user.locale,
641+
timeZone: secondManagedUser.user.timeZone,
642+
},
643+
},
644+
});
645+
646+
const response = await request(app.getHttpServer())
647+
.post(`/v2/bookings/${bookingRequiringConfirmation.uid}/confirm`)
648+
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
649+
.set("Authorization", `Bearer ${orgAdminManagedUser.accessToken}`)
650+
.expect(200);
651+
652+
const responseBody: GetBookingOutput_2024_08_13 = response.body;
653+
expect(responseBody.status).toEqual(SUCCESS_STATUS);
654+
expect(responseBody.data).toBeDefined();
655+
656+
const bookingData = responseBody.data as BookingOutput_2024_08_13;
657+
expect(bookingData.status).toEqual("accepted");
658+
expect(bookingData.uid).toEqual(bookingRequiringConfirmation.uid);
659+
660+
const confirmedBooking = await bookingsRepositoryFixture.getByUid(bookingRequiringConfirmation.uid);
661+
expect(confirmedBooking?.status).toEqual("ACCEPTED");
662+
});
663+
664+
it("should allow org admin managed user to reject booking using access token", async () => {
665+
const bookingRequiringConfirmation = await bookingsRepositoryFixture.create({
666+
user: {
667+
connect: {
668+
id: firstManagedUser.user.id,
669+
},
670+
},
671+
startTime: new Date(Date.UTC(2030, 0, 9, 15, 0, 0)),
672+
endTime: new Date(Date.UTC(2030, 0, 9, 16, 0, 0)),
673+
title: "Booking requiring rejection",
674+
uid: `booking-requiring-rejection-${randomString()}`,
675+
eventType: {
676+
connect: {
677+
id: eventTypeRequiresConfirmationId,
678+
},
679+
},
680+
location: "https://meet.google.com/abc-def-ghi",
681+
customInputs: {},
682+
metadata: {},
683+
status: "PENDING",
684+
responses: {
685+
name: secondManagedUser.user.name,
686+
email: secondManagedUserEmail,
687+
},
688+
attendees: {
689+
create: {
690+
email: secondManagedUserEmail,
691+
name: secondManagedUser.user.name!,
692+
locale: secondManagedUser.user.locale,
693+
timeZone: secondManagedUser.user.timeZone,
694+
},
695+
},
696+
});
697+
698+
const response = await request(app.getHttpServer())
699+
.post(`/v2/bookings/${bookingRequiringConfirmation.uid}/decline`)
700+
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
701+
.set("Authorization", `Bearer ${orgAdminManagedUser.accessToken}`)
702+
.expect(200);
703+
704+
const responseBody: GetBookingOutput_2024_08_13 = response.body;
705+
expect(responseBody.status).toEqual(SUCCESS_STATUS);
706+
expect(responseBody.data).toBeDefined();
707+
708+
const bookingData = responseBody.data as BookingOutput_2024_08_13;
709+
expect(bookingData.status).toEqual("rejected");
710+
expect(bookingData.uid).toEqual(bookingRequiringConfirmation.uid);
711+
712+
const rejectedBooking = await bookingsRepositoryFixture.getByUid(bookingRequiringConfirmation.uid);
713+
expect(rejectedBooking?.status).toEqual("REJECTED");
714+
});
715+
716+
it("should return unauthorized when org admin tries to confirm regular user's booking", async () => {
717+
// Create regular user (not managed)
718+
const regularUser = await userRepositoryFixture.create({
719+
email: `managed-user-bookings-2024-08-13-regular-user-${randomString()}@api.com`,
720+
name: "Regular User",
721+
});
722+
723+
// Create event type requiring confirmation for regular user
724+
const regularUserEventType = await eventTypesRepositoryFixture.create(
725+
{
726+
title: `regular-user-event-type-requires-confirmation-${randomString()}`,
727+
slug: `regular-user-event-type-requires-confirmation-${randomString()}`,
728+
length: 60,
729+
requiresConfirmation: true,
730+
},
731+
regularUser.id
732+
);
733+
734+
// Create booking for regular user
735+
const regularUserBooking = await bookingsRepositoryFixture.create({
736+
user: {
737+
connect: {
738+
id: regularUser.id,
739+
},
740+
},
741+
startTime: new Date(Date.UTC(2030, 0, 9, 17, 0, 0)),
742+
endTime: new Date(Date.UTC(2030, 0, 9, 18, 0, 0)),
743+
title: "Regular user booking requiring confirmation",
744+
uid: `regular-user-booking-${randomString()}`,
745+
eventType: {
746+
connect: {
747+
id: regularUserEventType.id,
748+
},
749+
},
750+
location: "https://meet.google.com/abc-def-ghi",
751+
customInputs: {},
752+
metadata: {},
753+
status: "PENDING",
754+
responses: {
755+
name: "External Attendee",
756+
email: "external@example.com",
757+
},
758+
attendees: {
759+
create: {
760+
email: "external@example.com",
761+
name: "External Attendee",
762+
locale: "en",
763+
timeZone: "Europe/Rome",
764+
},
765+
},
766+
});
767+
768+
// Org admin should not be able to confirm regular user's booking
769+
await request(app.getHttpServer())
770+
.post(`/v2/bookings/${regularUserBooking.uid}/confirm`)
771+
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
772+
.set("Authorization", `Bearer ${orgAdminManagedUser.accessToken}`)
773+
.expect(401);
774+
775+
// Clean up
776+
await userRepositoryFixture.delete(regularUser.id);
777+
});
778+
});
779+
546780
afterAll(async () => {
547781
await userRepositoryFixture.delete(firstManagedUser.user.id);
548782
await userRepositoryFixture.delete(secondManagedUser.user.id);
549783
await userRepositoryFixture.delete(thirdManagedUser.user.id);
784+
await userRepositoryFixture.delete(orgAdminManagedUser.user.id);
550785
await userRepositoryFixture.delete(platformAdmin.id);
551786
await oauthClientRepositoryFixture.delete(oAuthClient.id);
552787
await teamRepositoryFixture.delete(organization.id);

apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,6 +2147,51 @@ describe("Event types Endpoints", () => {
21472147
});
21482148
});
21492149

2150+
describe("EventType Hidden Property", () => {
2151+
let createdEventTypeId: number;
2152+
2153+
it("should create an event type with hidden=true", async () => {
2154+
const createPayload = {
2155+
title: "Hidden Event",
2156+
slug: "hidden-event",
2157+
lengthInMinutes: 30,
2158+
hidden: true,
2159+
};
2160+
2161+
const response = await request(app.getHttpServer())
2162+
.post("/api/v2/event-types")
2163+
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
2164+
.send(createPayload)
2165+
.expect(201);
2166+
2167+
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
2168+
const createdEventType = responseBody.data;
2169+
expect(createdEventType).toHaveProperty("id");
2170+
expect(createdEventType.title).toEqual(createPayload.title);
2171+
expect(createdEventType.slug).toEqual(createPayload.slug);
2172+
expect(createdEventType.lengthInMinutes).toEqual(createPayload.lengthInMinutes);
2173+
expect(createdEventType.hidden).toBe(true);
2174+
2175+
createdEventTypeId = createdEventType.id;
2176+
});
2177+
2178+
it("should update the hidden property to false", async () => {
2179+
const updatePayload = {
2180+
hidden: false,
2181+
};
2182+
2183+
const response = await request(app.getHttpServer())
2184+
.patch(`/api/v2/event-types/${createdEventTypeId}`)
2185+
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
2186+
.send(updatePayload)
2187+
.expect(200);
2188+
2189+
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
2190+
const updatedEventType = responseBody.data;
2191+
+expect(updatedEventType.hidden).toBe(false);
2192+
});
2193+
});
2194+
21502195
afterAll(async () => {
21512196
await oauthClientRepositoryFixture.delete(oAuthClient.id);
21522197
await teamRepositoryFixture.delete(organization.id);

apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ type Input = Pick<
8989
| "hideCalendarEventDetails"
9090
| "hideOrganizerEmail"
9191
| "calVideoSettings"
92+
| "hidden"
9293
>;
9394

9495
@Injectable()
@@ -127,6 +128,7 @@ export class OutputEventTypesService_2024_06_14 {
127128
hideCalendarEventDetails,
128129
hideOrganizerEmail,
129130
calVideoSettings,
131+
hidden,
130132
} = databaseEventType;
131133

132134
const locations = this.transformLocations(databaseEventType.locations);
@@ -203,6 +205,7 @@ export class OutputEventTypesService_2024_06_14 {
203205
hideCalendarEventDetails,
204206
hideOrganizerEmail,
205207
calVideoSettings,
208+
hidden,
206209
};
207210
}
208211

apps/api/v2/src/modules/organizations/event-types/services/output.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ type Input = Pick<
7474
| "hideOrganizerEmail"
7575
| "team"
7676
| "calVideoSettings"
77+
| "hidden"
7778
>;
7879

7980
@Injectable()

0 commit comments

Comments
 (0)