Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6959f3c7c | ||
|
|
67af8b4973 | ||
|
|
7794f2596e | ||
|
|
167cf6e352 | ||
|
|
3d778ba481 | ||
|
|
b8f0000b59 | ||
|
|
618ef63017 | ||
|
|
ae8e633716 | ||
|
|
4a23832388 | ||
|
|
02e486d757 | ||
|
|
af6e24f433 | ||
|
|
f9e24f51a7 | ||
|
|
9e9f815809 | ||
|
|
a91561f3c2 | ||
|
|
ff79064ca7 | ||
|
|
1a573e1619 | ||
|
|
511be7386c |
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"@axiomhq/winston": "^1.2.0",
|
"@axiomhq/winston": "^1.2.0",
|
||||||
"@calcom/platform-constants": "*",
|
"@calcom/platform-constants": "*",
|
||||||
"@calcom/platform-enums": "*",
|
"@calcom/platform-enums": "*",
|
||||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.314",
|
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.318",
|
||||||
"@calcom/platform-types": "*",
|
"@calcom/platform-types": "*",
|
||||||
"@calcom/platform-utils": "*",
|
"@calcom/platform-utils": "*",
|
||||||
"@calcom/prisma": "*",
|
"@calcom/prisma": "*",
|
||||||
|
|||||||
+233
-3
@@ -15,11 +15,11 @@ import { NestExpressApplication } from "@nestjs/platform-express";
|
|||||||
import { Test } from "@nestjs/testing";
|
import { Test } from "@nestjs/testing";
|
||||||
import { PlatformOAuthClient, Team, User } from "@prisma/client";
|
import { PlatformOAuthClient, Team, User } from "@prisma/client";
|
||||||
import * as request from "supertest";
|
import * as request from "supertest";
|
||||||
|
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
import { randomString } from "test/utils/randomString";
|
import { randomString } from "test/utils/randomString";
|
||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
CreateBookingInput_2024_08_13,
|
CreateBookingInput_2024_08_13,
|
||||||
CreateEventTypeInput_2024_06_14,
|
CreateEventTypeInput_2024_06_14,
|
||||||
EventTypeOutput_2024_06_14,
|
EventTypeOutput_2024_06_14,
|
||||||
|
GetBookingOutput_2024_08_13,
|
||||||
GetBookingsOutput_2024_08_13,
|
GetBookingsOutput_2024_08_13,
|
||||||
} from "@calcom/platform-types";
|
} from "@calcom/platform-types";
|
||||||
|
|
||||||
@@ -50,9 +51,9 @@ describe("Managed user bookings 2024-08-13", () => {
|
|||||||
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||||
let schedulesRepositoryFixture: SchedulesRepositoryFixture;
|
|
||||||
let profilesRepositoryFixture: ProfileRepositoryFixture;
|
let profilesRepositoryFixture: ProfileRepositoryFixture;
|
||||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||||
|
let bookingsRepositoryFixture: BookingsRepositoryFixture;
|
||||||
|
|
||||||
const platformAdminEmail = `managed-users-bookings-2024-08-13-admin-${randomString()}@api.com`;
|
const platformAdminEmail = `managed-users-bookings-2024-08-13-admin-${randomString()}@api.com`;
|
||||||
let platformAdmin: User;
|
let platformAdmin: User;
|
||||||
@@ -67,6 +68,10 @@ describe("Managed user bookings 2024-08-13", () => {
|
|||||||
let thirdManagedUser: CreateManagedUserData;
|
let thirdManagedUser: CreateManagedUserData;
|
||||||
|
|
||||||
let firstManagedUserEventTypeId: number;
|
let firstManagedUserEventTypeId: number;
|
||||||
|
let eventTypeRequiresConfirmationId: number;
|
||||||
|
|
||||||
|
const orgAdminManagedUserEmail = `managed-user-bookings-2024-08-13-org-admin-${randomString()}@api.com`;
|
||||||
|
let orgAdminManagedUser: CreateManagedUserData;
|
||||||
|
|
||||||
let firstManagedUserBookingsCount = 0;
|
let firstManagedUserBookingsCount = 0;
|
||||||
let secondManagedUserBookingsCount = 0;
|
let secondManagedUserBookingsCount = 0;
|
||||||
@@ -85,9 +90,9 @@ describe("Managed user bookings 2024-08-13", () => {
|
|||||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||||
schedulesRepositoryFixture = new SchedulesRepositoryFixture(moduleRef);
|
|
||||||
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||||
|
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
|
||||||
|
|
||||||
platformAdmin = await userRepositoryFixture.create({ email: platformAdminEmail });
|
platformAdmin = await userRepositoryFixture.create({ email: platformAdminEmail });
|
||||||
|
|
||||||
@@ -95,6 +100,13 @@ describe("Managed user bookings 2024-08-13", () => {
|
|||||||
name: `oauth-client-users-organization-${randomString()}`,
|
name: `oauth-client-users-organization-${randomString()}`,
|
||||||
isPlatform: true,
|
isPlatform: true,
|
||||||
isOrganization: true,
|
isOrganization: true,
|
||||||
|
platformBilling: {
|
||||||
|
create: {
|
||||||
|
customerId: "cus_999",
|
||||||
|
plan: "SCALE",
|
||||||
|
subscriptionId: "sub_999",
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
oAuthClient = await createOAuthClient(organization.id);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
@@ -243,6 +255,59 @@ describe("Managed user bookings 2024-08-13", () => {
|
|||||||
thirdManagedUser = responseBody.data;
|
thirdManagedUser = responseBody.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`should create org admin managed user`, async () => {
|
||||||
|
const requestBody: CreateManagedUserInput = {
|
||||||
|
email: orgAdminManagedUserEmail,
|
||||||
|
timeZone: managedUsersTimeZone,
|
||||||
|
weekStart: "Monday",
|
||||||
|
timeFormat: 24,
|
||||||
|
locale: Locales.FR,
|
||||||
|
name: "Org Admin Smith",
|
||||||
|
avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png",
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post(`/api/v2/oauth-clients/${oAuthClient.id}/users`)
|
||||||
|
.set("x-cal-secret-key", oAuthClient.secret)
|
||||||
|
.send(requestBody)
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
const responseBody: CreateManagedUserOutput = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
expect(responseBody.data.user.email).toEqual(
|
||||||
|
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
|
||||||
|
);
|
||||||
|
expect(responseBody.data.accessToken).toBeDefined();
|
||||||
|
expect(responseBody.data.refreshToken).toBeDefined();
|
||||||
|
|
||||||
|
orgAdminManagedUser = responseBody.data;
|
||||||
|
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.post(`/v2/organizations/${organization.id}/memberships`)
|
||||||
|
.set("x-cal-client-id", oAuthClient.id)
|
||||||
|
.set("x-cal-secret-key", oAuthClient.secret)
|
||||||
|
.send({
|
||||||
|
userId: orgAdminManagedUser.user.id,
|
||||||
|
role: "ADMIN",
|
||||||
|
accepted: true,
|
||||||
|
})
|
||||||
|
.expect(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create an event type requiring confirmation for first managed user", async () => {
|
||||||
|
const eventTypeRequiresConfirmation = await eventTypesRepositoryFixture.create(
|
||||||
|
{
|
||||||
|
title: `managed-user-bookings-event-type-requires-confirmation-${randomString()}`,
|
||||||
|
slug: `managed-user-bookings-event-type-requires-confirmation-${randomString()}`,
|
||||||
|
length: 60,
|
||||||
|
requiresConfirmation: true,
|
||||||
|
},
|
||||||
|
firstManagedUser.user.id
|
||||||
|
);
|
||||||
|
eventTypeRequiresConfirmationId = eventTypeRequiresConfirmation.id;
|
||||||
|
});
|
||||||
|
|
||||||
describe("bookings using original emails", () => {
|
describe("bookings using original emails", () => {
|
||||||
it("managed user should be booked by managed user attendee and booking shows up in both users' bookings", async () => {
|
it("managed user should be booked by managed user attendee and booking shows up in both users' bookings", async () => {
|
||||||
const body: CreateBookingInput_2024_08_13 = {
|
const body: CreateBookingInput_2024_08_13 = {
|
||||||
@@ -543,10 +608,175 @@ describe("Managed user bookings 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("booking confirmation by org admin", () => {
|
||||||
|
it("should allow org admin managed user to confirm booking using access token", async () => {
|
||||||
|
const bookingRequiringConfirmation = await bookingsRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: firstManagedUser.user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startTime: new Date(Date.UTC(2030, 0, 9, 13, 0, 0)),
|
||||||
|
endTime: new Date(Date.UTC(2030, 0, 9, 14, 0, 0)),
|
||||||
|
title: "Booking requiring confirmation",
|
||||||
|
uid: `booking-requiring-confirmation-${randomString()}`,
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: eventTypeRequiresConfirmationId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location: "https://meet.google.com/abc-def-ghi",
|
||||||
|
customInputs: {},
|
||||||
|
metadata: {},
|
||||||
|
status: "PENDING",
|
||||||
|
responses: {
|
||||||
|
name: secondManagedUser.user.name,
|
||||||
|
email: secondManagedUserEmail,
|
||||||
|
},
|
||||||
|
attendees: {
|
||||||
|
create: {
|
||||||
|
email: secondManagedUserEmail,
|
||||||
|
name: secondManagedUser.user.name!,
|
||||||
|
locale: secondManagedUser.user.locale,
|
||||||
|
timeZone: secondManagedUser.user.timeZone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post(`/v2/bookings/${bookingRequiringConfirmation.uid}/confirm`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set("Authorization", `Bearer ${orgAdminManagedUser.accessToken}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const responseBody: GetBookingOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
|
||||||
|
const bookingData = responseBody.data as BookingOutput_2024_08_13;
|
||||||
|
expect(bookingData.status).toEqual("accepted");
|
||||||
|
expect(bookingData.uid).toEqual(bookingRequiringConfirmation.uid);
|
||||||
|
|
||||||
|
const confirmedBooking = await bookingsRepositoryFixture.getByUid(bookingRequiringConfirmation.uid);
|
||||||
|
expect(confirmedBooking?.status).toEqual("ACCEPTED");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow org admin managed user to reject booking using access token", async () => {
|
||||||
|
const bookingRequiringConfirmation = await bookingsRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: firstManagedUser.user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startTime: new Date(Date.UTC(2030, 0, 9, 15, 0, 0)),
|
||||||
|
endTime: new Date(Date.UTC(2030, 0, 9, 16, 0, 0)),
|
||||||
|
title: "Booking requiring rejection",
|
||||||
|
uid: `booking-requiring-rejection-${randomString()}`,
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: eventTypeRequiresConfirmationId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location: "https://meet.google.com/abc-def-ghi",
|
||||||
|
customInputs: {},
|
||||||
|
metadata: {},
|
||||||
|
status: "PENDING",
|
||||||
|
responses: {
|
||||||
|
name: secondManagedUser.user.name,
|
||||||
|
email: secondManagedUserEmail,
|
||||||
|
},
|
||||||
|
attendees: {
|
||||||
|
create: {
|
||||||
|
email: secondManagedUserEmail,
|
||||||
|
name: secondManagedUser.user.name!,
|
||||||
|
locale: secondManagedUser.user.locale,
|
||||||
|
timeZone: secondManagedUser.user.timeZone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post(`/v2/bookings/${bookingRequiringConfirmation.uid}/decline`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set("Authorization", `Bearer ${orgAdminManagedUser.accessToken}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const responseBody: GetBookingOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
|
||||||
|
const bookingData = responseBody.data as BookingOutput_2024_08_13;
|
||||||
|
expect(bookingData.status).toEqual("rejected");
|
||||||
|
expect(bookingData.uid).toEqual(bookingRequiringConfirmation.uid);
|
||||||
|
|
||||||
|
const rejectedBooking = await bookingsRepositoryFixture.getByUid(bookingRequiringConfirmation.uid);
|
||||||
|
expect(rejectedBooking?.status).toEqual("REJECTED");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return unauthorized when org admin tries to confirm regular user's booking", async () => {
|
||||||
|
const regularUser = await userRepositoryFixture.create({
|
||||||
|
email: `managed-user-bookings-2024-08-13-regular-user-${randomString()}@api.com`,
|
||||||
|
name: "Regular User",
|
||||||
|
});
|
||||||
|
|
||||||
|
const regularUserEventType = await eventTypesRepositoryFixture.create(
|
||||||
|
{
|
||||||
|
title: `regular-user-event-type-requires-confirmation-${randomString()}`,
|
||||||
|
slug: `regular-user-event-type-requires-confirmation-${randomString()}`,
|
||||||
|
length: 60,
|
||||||
|
requiresConfirmation: true,
|
||||||
|
},
|
||||||
|
regularUser.id
|
||||||
|
);
|
||||||
|
|
||||||
|
const regularUserBooking = await bookingsRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: regularUser.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startTime: new Date(Date.UTC(2030, 0, 9, 17, 0, 0)),
|
||||||
|
endTime: new Date(Date.UTC(2030, 0, 9, 18, 0, 0)),
|
||||||
|
title: "Regular user booking requiring confirmation",
|
||||||
|
uid: `regular-user-booking-${randomString()}`,
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: regularUserEventType.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location: "https://meet.google.com/abc-def-ghi",
|
||||||
|
customInputs: {},
|
||||||
|
metadata: {},
|
||||||
|
status: "PENDING",
|
||||||
|
responses: {
|
||||||
|
name: "External Attendee",
|
||||||
|
email: "external@example.com",
|
||||||
|
},
|
||||||
|
attendees: {
|
||||||
|
create: {
|
||||||
|
email: "external@example.com",
|
||||||
|
name: "External Attendee",
|
||||||
|
locale: "en",
|
||||||
|
timeZone: "Europe/Rome",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.post(`/v2/bookings/${regularUserBooking.uid}/confirm`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set("Authorization", `Bearer ${orgAdminManagedUser.accessToken}`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
await userRepositoryFixture.delete(regularUser.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await userRepositoryFixture.delete(firstManagedUser.user.id);
|
await userRepositoryFixture.delete(firstManagedUser.user.id);
|
||||||
await userRepositoryFixture.delete(secondManagedUser.user.id);
|
await userRepositoryFixture.delete(secondManagedUser.user.id);
|
||||||
await userRepositoryFixture.delete(thirdManagedUser.user.id);
|
await userRepositoryFixture.delete(thirdManagedUser.user.id);
|
||||||
|
await userRepositoryFixture.delete(orgAdminManagedUser.user.id);
|
||||||
await userRepositoryFixture.delete(platformAdmin.id);
|
await userRepositoryFixture.delete(platformAdmin.id);
|
||||||
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
||||||
await teamRepositoryFixture.delete(organization.id);
|
await teamRepositoryFixture.delete(organization.id);
|
||||||
|
|||||||
@@ -702,6 +702,7 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
beforeUpdatedDate: queryParams.beforeUpdatedAt,
|
beforeUpdatedDate: queryParams.beforeUpdatedAt,
|
||||||
afterCreatedDate: queryParams.afterCreatedAt,
|
afterCreatedDate: queryParams.afterCreatedAt,
|
||||||
beforeCreatedDate: queryParams.beforeCreatedAt,
|
beforeCreatedDate: queryParams.beforeCreatedAt,
|
||||||
|
bookingUid: queryParams.bookingUid,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+45
@@ -2147,6 +2147,51 @@ describe("Event types Endpoints", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("EventType Hidden Property", () => {
|
||||||
|
let createdEventTypeId: number;
|
||||||
|
|
||||||
|
it("should create an event type with hidden=true", async () => {
|
||||||
|
const createPayload = {
|
||||||
|
title: "Hidden Event",
|
||||||
|
slug: "hidden-event",
|
||||||
|
lengthInMinutes: 30,
|
||||||
|
hidden: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post("/api/v2/event-types")
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||||
|
.send(createPayload)
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||||
|
const createdEventType = responseBody.data;
|
||||||
|
expect(createdEventType).toHaveProperty("id");
|
||||||
|
expect(createdEventType.title).toEqual(createPayload.title);
|
||||||
|
expect(createdEventType.slug).toEqual(createPayload.slug);
|
||||||
|
expect(createdEventType.lengthInMinutes).toEqual(createPayload.lengthInMinutes);
|
||||||
|
expect(createdEventType.hidden).toBe(true);
|
||||||
|
|
||||||
|
createdEventTypeId = createdEventType.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update the hidden property to false", async () => {
|
||||||
|
const updatePayload = {
|
||||||
|
hidden: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.patch(`/api/v2/event-types/${createdEventTypeId}`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||||
|
.send(updatePayload)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||||
|
const updatedEventType = responseBody.data;
|
||||||
|
+expect(updatedEventType.hidden).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
||||||
await teamRepositoryFixture.delete(organization.id);
|
await teamRepositoryFixture.delete(organization.id);
|
||||||
|
|||||||
+3
@@ -89,6 +89,7 @@ type Input = Pick<
|
|||||||
| "hideCalendarEventDetails"
|
| "hideCalendarEventDetails"
|
||||||
| "hideOrganizerEmail"
|
| "hideOrganizerEmail"
|
||||||
| "calVideoSettings"
|
| "calVideoSettings"
|
||||||
|
| "hidden"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -127,6 +128,7 @@ export class OutputEventTypesService_2024_06_14 {
|
|||||||
hideCalendarEventDetails,
|
hideCalendarEventDetails,
|
||||||
hideOrganizerEmail,
|
hideOrganizerEmail,
|
||||||
calVideoSettings,
|
calVideoSettings,
|
||||||
|
hidden,
|
||||||
} = databaseEventType;
|
} = databaseEventType;
|
||||||
|
|
||||||
const locations = this.transformLocations(databaseEventType.locations);
|
const locations = this.transformLocations(databaseEventType.locations);
|
||||||
@@ -203,6 +205,7 @@ export class OutputEventTypesService_2024_06_14 {
|
|||||||
hideCalendarEventDetails,
|
hideCalendarEventDetails,
|
||||||
hideOrganizerEmail,
|
hideOrganizerEmail,
|
||||||
calVideoSettings,
|
calVideoSettings,
|
||||||
|
hidden,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -229,4 +229,19 @@ export class SchedulesRepository_2024_06_11 {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSchedulesByUserIds(userIds: number[], skip: number, take: number) {
|
||||||
|
return this.dbRead.prisma.schedule.findMany({
|
||||||
|
where: {
|
||||||
|
userId: {
|
||||||
|
in: userIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
availability: true,
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { OrganizationsTeamsBookingsModule } from "@/modules/organizations/teams/
|
|||||||
import { OrganizationsUsersBookingsModule } from "@/modules/organizations/users/bookings/organizations-users-bookings.module";
|
import { OrganizationsUsersBookingsModule } from "@/modules/organizations/users/bookings/organizations-users-bookings.module";
|
||||||
import { RouterModule } from "@/modules/router/router.module";
|
import { RouterModule } from "@/modules/router/router.module";
|
||||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||||
|
import { TeamsSchedulesModule } from "@/modules/teams/schedules/teams-schedules.module";
|
||||||
import { TimezoneModule } from "@/modules/timezones/timezones.module";
|
import { TimezoneModule } from "@/modules/timezones/timezones.module";
|
||||||
import { VerifiedResourcesModule } from "@/modules/verified-resources/verified-resources.module";
|
import { VerifiedResourcesModule } from "@/modules/verified-resources/verified-resources.module";
|
||||||
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
|
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
|
||||||
@@ -38,6 +39,7 @@ import { WebhooksModule } from "./webhooks/webhooks.module";
|
|||||||
OrganizationsRoutingFormsModule,
|
OrganizationsRoutingFormsModule,
|
||||||
VerifiedResourcesModule,
|
VerifiedResourcesModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
|
TeamsSchedulesModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class EndpointsModule implements NestModule {
|
export class EndpointsModule implements NestModule {
|
||||||
|
|||||||
+128
-1
@@ -66,6 +66,8 @@ describe("Organizations Bookings Endpoints 2024-08-13", () => {
|
|||||||
let orgEventTypeId2: number;
|
let orgEventTypeId2: number;
|
||||||
let nonOrgEventTypeId: number;
|
let nonOrgEventTypeId: number;
|
||||||
|
|
||||||
|
let collectiveOrgBookingUid: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
orgUserEmail2,
|
orgUserEmail2,
|
||||||
@@ -91,12 +93,17 @@ describe("Organizations Bookings Endpoints 2024-08-13", () => {
|
|||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||||
|
|
||||||
organization = await organizationsRepositoryFixture.create({ name: "organization bookings" });
|
organization = await organizationsRepositoryFixture.create({ name: "organization bookings" });
|
||||||
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
team1 = await teamRepositoryFixture.create({
|
team1 = await teamRepositoryFixture.create({
|
||||||
name: "team orgs booking 1",
|
name: "team orgs booking 1",
|
||||||
isOrganization: false,
|
isOrganization: false,
|
||||||
parent: { connect: { id: organization.id } },
|
parent: { connect: { id: organization.id } },
|
||||||
|
createdByOAuthClient: {
|
||||||
|
connect: {
|
||||||
|
id: oAuthClient.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
oAuthClient = await createOAuthClient(organization.id);
|
|
||||||
|
|
||||||
nonOrgUser1 = await userRepositoryFixture.create({
|
nonOrgUser1 = await userRepositoryFixture.create({
|
||||||
email: nonOrgUserEmail1,
|
email: nonOrgUserEmail1,
|
||||||
@@ -315,6 +322,7 @@ describe("Organizations Bookings Endpoints 2024-08-13", () => {
|
|||||||
|
|
||||||
if (responseDataIsBooking(responseBody.data)) {
|
if (responseDataIsBooking(responseBody.data)) {
|
||||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||||
|
collectiveOrgBookingUid = data.uid;
|
||||||
expect(data.id).toBeDefined();
|
expect(data.id).toBeDefined();
|
||||||
expect(data.uid).toBeDefined();
|
expect(data.uid).toBeDefined();
|
||||||
expect(data.hosts.length).toEqual(1);
|
expect(data.hosts.length).toEqual(1);
|
||||||
@@ -669,6 +677,125 @@ describe("Organizations Bookings Endpoints 2024-08-13", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("get by bookingUid param", () => {
|
||||||
|
it("should get a specific booking by bookingUid query param", async () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/organizations/${organization.id}/bookings?bookingUid=${collectiveOrgBookingUid}`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set(X_CAL_CLIENT_ID, oAuthClient.id)
|
||||||
|
.set(X_CAL_SECRET_KEY, oAuthClient.secret)
|
||||||
|
.expect(200)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
|
||||||
|
const data: (
|
||||||
|
| BookingOutput_2024_08_13
|
||||||
|
| RecurringBookingOutput_2024_08_13
|
||||||
|
| GetSeatedBookingOutput_2024_08_13
|
||||||
|
)[] = responseBody.data;
|
||||||
|
expect(data.length).toEqual(1);
|
||||||
|
expect(responseDataIsBooking(data[0])).toBe(true);
|
||||||
|
|
||||||
|
if (responseDataIsBooking(data[0])) {
|
||||||
|
const booking: BookingOutput_2024_08_13 = data[0];
|
||||||
|
expect(booking.uid).toEqual(collectiveOrgBookingUid);
|
||||||
|
expect(booking.eventTypeId).toEqual(orgEventTypeId);
|
||||||
|
expect(booking.status).toEqual("accepted");
|
||||||
|
expect(booking.attendees.length).toEqual(2);
|
||||||
|
expect(booking.attendees[0].name).toEqual("alice");
|
||||||
|
expect(booking.attendees[0].email).toEqual("alice@gmail.com");
|
||||||
|
} else {
|
||||||
|
throw new Error("Expected single booking but received different response type");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty array for non-existent booking UID query param", async () => {
|
||||||
|
const nonExistentUid = "non-existent-booking-uid";
|
||||||
|
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/organizations/${organization.id}/bookings?bookingUid=${nonExistentUid}`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set(X_CAL_CLIENT_ID, oAuthClient.id)
|
||||||
|
.set(X_CAL_SECRET_KEY, oAuthClient.secret)
|
||||||
|
.expect(200)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
expect(responseBody.data.length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty array for booking UID not belonging to the organization", async () => {
|
||||||
|
const regularUser = await userRepositoryFixture.create({
|
||||||
|
email: `org-bookings-regular-user-${Math.floor(Math.random() * 10000)}@api.com`,
|
||||||
|
name: "Regular User",
|
||||||
|
});
|
||||||
|
|
||||||
|
const regularUserEventType = await eventTypesRepositoryFixture.create(
|
||||||
|
{
|
||||||
|
title: `regular-user-event-type-${Math.floor(Math.random() * 10000)}`,
|
||||||
|
slug: `regular-user-event-type-${Math.floor(Math.random() * 10000)}`,
|
||||||
|
length: 60,
|
||||||
|
bookingFields: [],
|
||||||
|
locations: [],
|
||||||
|
},
|
||||||
|
regularUser.id
|
||||||
|
);
|
||||||
|
|
||||||
|
const regularUserBooking = await bookingsRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: regularUser.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startTime: new Date(Date.UTC(2030, 0, 15, 13, 0, 0)),
|
||||||
|
endTime: new Date(Date.UTC(2030, 0, 15, 14, 0, 0)),
|
||||||
|
title: "Regular user booking",
|
||||||
|
uid: `regular-user-booking-${Math.floor(Math.random() * 10000)}`,
|
||||||
|
eventType: {
|
||||||
|
connect: {
|
||||||
|
id: regularUserEventType.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location: "https://meet.google.com/regular-user",
|
||||||
|
customInputs: {},
|
||||||
|
metadata: {},
|
||||||
|
status: "ACCEPTED",
|
||||||
|
responses: {
|
||||||
|
name: "Regular Attendee",
|
||||||
|
email: "regular@example.com",
|
||||||
|
},
|
||||||
|
attendees: {
|
||||||
|
create: {
|
||||||
|
email: "regular@example.com",
|
||||||
|
name: "Regular Attendee",
|
||||||
|
locale: "en",
|
||||||
|
timeZone: "Europe/Rome",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/organizations/${organization.id}/bookings?bookingUid=${regularUserBooking.uid}`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set(X_CAL_CLIENT_ID, oAuthClient.id)
|
||||||
|
.set(X_CAL_SECRET_KEY, oAuthClient.secret)
|
||||||
|
.expect(200)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
expect(responseBody.data.length).toEqual(0);
|
||||||
|
|
||||||
|
await userRepositoryFixture.delete(regularUser.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createOAuthClient(organizationId: number) {
|
async function createOAuthClient(organizationId: number) {
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ type Input = Pick<
|
|||||||
| "hideOrganizerEmail"
|
| "hideOrganizerEmail"
|
||||||
| "team"
|
| "team"
|
||||||
| "calVideoSettings"
|
| "calVideoSettings"
|
||||||
|
| "hidden"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import { OrganizationsEventTypesPrivateLinksController } from "@/ee/event-types-private-links/controllers/organizations-event-types-private-links.controller";
|
||||||
|
import { EventTypesPrivateLinksModule } from "@/ee/event-types-private-links/event-types-private-links.module";
|
||||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||||
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
|
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
|
||||||
|
import { InputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/input-schedules.service";
|
||||||
|
import { SchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/schedules.service";
|
||||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||||
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.repository";
|
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.repository";
|
||||||
import { ConferencingService } from "@/modules/conferencing/services/conferencing.service";
|
import { ConferencingService } from "@/modules/conferencing/services/conferencing.service";
|
||||||
@@ -18,13 +22,10 @@ import { OrganizationAttributesService } from "@/modules/organizations/attribute
|
|||||||
import { OrganizationAttributeOptionRepository } from "@/modules/organizations/attributes/options/organization-attribute-options.repository";
|
import { OrganizationAttributeOptionRepository } from "@/modules/organizations/attributes/options/organization-attribute-options.repository";
|
||||||
import { OrganizationsAttributesOptionsController } from "@/modules/organizations/attributes/options/organizations-attributes-options.controller";
|
import { OrganizationsAttributesOptionsController } from "@/modules/organizations/attributes/options/organizations-attributes-options.controller";
|
||||||
import { OrganizationAttributeOptionService } from "@/modules/organizations/attributes/options/services/organization-attributes-option.service";
|
import { OrganizationAttributeOptionService } from "@/modules/organizations/attributes/options/services/organization-attributes-option.service";
|
||||||
import { OrganizationsConferencingController } from "@/modules/organizations/conferencing/organizations-conferencing.controller";
|
|
||||||
import { OrganizationsConferencingModule } from "@/modules/organizations/conferencing/organizations-conferencing.module";
|
import { OrganizationsConferencingModule } from "@/modules/organizations/conferencing/organizations-conferencing.module";
|
||||||
import { OrganizationsConferencingService } from "@/modules/organizations/conferencing/services/organizations-conferencing.service";
|
import { OrganizationsConferencingService } from "@/modules/organizations/conferencing/services/organizations-conferencing.service";
|
||||||
import { OrganizationsDelegationCredentialModule } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.module";
|
import { OrganizationsDelegationCredentialModule } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.module";
|
||||||
import { OrganizationsEventTypesController } from "@/modules/organizations/event-types/organizations-event-types.controller";
|
import { OrganizationsEventTypesController } from "@/modules/organizations/event-types/organizations-event-types.controller";
|
||||||
import { EventTypesPrivateLinksModule } from "@/ee/event-types-private-links/event-types-private-links.module";
|
|
||||||
import { OrganizationsEventTypesPrivateLinksController } from "@/ee/event-types-private-links/controllers/organizations-event-types-private-links.controller";
|
|
||||||
import { OrganizationsEventTypesRepository } from "@/modules/organizations/event-types/organizations-event-types.repository";
|
import { OrganizationsEventTypesRepository } from "@/modules/organizations/event-types/organizations-event-types.repository";
|
||||||
import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/event-types/pipes/team-event-types-response.transformer";
|
import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/event-types/pipes/team-event-types-response.transformer";
|
||||||
import { InputOrganizationsEventTypesService } from "@/modules/organizations/event-types/services/input.service";
|
import { InputOrganizationsEventTypesService } from "@/modules/organizations/event-types/services/input.service";
|
||||||
@@ -38,7 +39,6 @@ import { OrganizationsMembershipOutputService } from "@/modules/organizations/me
|
|||||||
import { OrganizationsMembershipService } from "@/modules/organizations/memberships/services/organizations-membership.service";
|
import { OrganizationsMembershipService } from "@/modules/organizations/memberships/services/organizations-membership.service";
|
||||||
import { OrganizationsOrganizationsModule } from "@/modules/organizations/organizations/organizations-organizations.module";
|
import { OrganizationsOrganizationsModule } from "@/modules/organizations/organizations/organizations-organizations.module";
|
||||||
import { OrganizationsSchedulesController } from "@/modules/organizations/schedules/organizations-schedules.controller";
|
import { OrganizationsSchedulesController } from "@/modules/organizations/schedules/organizations-schedules.controller";
|
||||||
import { OrganizationSchedulesRepository } from "@/modules/organizations/schedules/organizations-schedules.repository";
|
|
||||||
import { OrganizationsSchedulesService } from "@/modules/organizations/schedules/services/organizations-schedules.service";
|
import { OrganizationsSchedulesService } from "@/modules/organizations/schedules/services/organizations-schedules.service";
|
||||||
import { OrganizationsStripeModule } from "@/modules/organizations/stripe/organizations-stripe.module";
|
import { OrganizationsStripeModule } from "@/modules/organizations/stripe/organizations-stripe.module";
|
||||||
import { OrganizationsStripeService } from "@/modules/organizations/stripe/services/organizations-stripe.service";
|
import { OrganizationsStripeService } from "@/modules/organizations/stripe/services/organizations-stripe.service";
|
||||||
@@ -65,6 +65,7 @@ import { RedisModule } from "@/modules/redis/redis.module";
|
|||||||
import { RedisService } from "@/modules/redis/redis.service";
|
import { RedisService } from "@/modules/redis/redis.service";
|
||||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||||
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
|
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
|
||||||
|
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
|
||||||
import { TeamsModule } from "@/modules/teams/teams/teams.module";
|
import { TeamsModule } from "@/modules/teams/teams/teams.module";
|
||||||
import { TokensRepository } from "@/modules/tokens/tokens.repository";
|
import { TokensRepository } from "@/modules/tokens/tokens.repository";
|
||||||
import { UsersModule } from "@/modules/users/users.module";
|
import { UsersModule } from "@/modules/users/users.module";
|
||||||
@@ -102,7 +103,6 @@ import { Module } from "@nestjs/common";
|
|||||||
OrganizationsTeamsService,
|
OrganizationsTeamsService,
|
||||||
MembershipsRepository,
|
MembershipsRepository,
|
||||||
OrganizationsSchedulesService,
|
OrganizationsSchedulesService,
|
||||||
OrganizationSchedulesRepository,
|
|
||||||
OrganizationsUsersRepository,
|
OrganizationsUsersRepository,
|
||||||
OrganizationsUsersService,
|
OrganizationsUsersService,
|
||||||
EmailService,
|
EmailService,
|
||||||
@@ -144,6 +144,9 @@ import { Module } from "@nestjs/common";
|
|||||||
TeamWorkflowsService,
|
TeamWorkflowsService,
|
||||||
WorkflowsInputService,
|
WorkflowsInputService,
|
||||||
WorkflowsOutputService,
|
WorkflowsOutputService,
|
||||||
|
TeamsSchedulesService,
|
||||||
|
SchedulesService_2024_06_11,
|
||||||
|
InputSchedulesService_2024_06_11,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
OrganizationsService,
|
OrganizationsService,
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
|
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
|
||||||
import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/output-schedules.service";
|
import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/output-schedules.service";
|
||||||
import { OrganizationSchedulesRepository } from "@/modules/organizations/schedules/organizations-schedules.repository";
|
|
||||||
import { UsersRepository } from "@/modules/users/users.repository";
|
import { UsersRepository } from "@/modules/users/users.repository";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ import { ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrganizationsSchedulesService {
|
export class OrganizationsSchedulesService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly organizationSchedulesService: OrganizationSchedulesRepository,
|
|
||||||
private readonly outputSchedulesService: OutputSchedulesService_2024_06_11,
|
private readonly outputSchedulesService: OutputSchedulesService_2024_06_11,
|
||||||
|
private readonly schedulesRepository: SchedulesRepository_2024_06_11,
|
||||||
private readonly usersRepository: UsersRepository
|
private readonly usersRepository: UsersRepository
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ export class OrganizationsSchedulesService {
|
|||||||
const users = await this.usersRepository.getOrganizationUsers(organizationId);
|
const users = await this.usersRepository.getOrganizationUsers(organizationId);
|
||||||
const usersIds = users.map((user) => user.id);
|
const usersIds = users.map((user) => user.id);
|
||||||
|
|
||||||
const schedules = await this.organizationSchedulesService.getSchedulesByUserIds(usersIds, skip, take);
|
const schedules = await this.schedulesRepository.getSchedulesByUserIds(usersIds, skip, take);
|
||||||
|
|
||||||
const responseSchedules: ScheduleOutput_2024_06_11[] = [];
|
const responseSchedules: ScheduleOutput_2024_06_11[] = [];
|
||||||
|
|
||||||
|
|||||||
+28
-3
@@ -14,11 +14,12 @@ import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
|
|||||||
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||||
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
|
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
|
||||||
import { IsUserInOrgTeam } from "@/modules/auth/guards/users/is-user-in-org-team.guard";
|
import { IsUserInOrgTeam } from "@/modules/auth/guards/users/is-user-in-org-team.guard";
|
||||||
import { Controller, UseGuards, Get, Param, ParseIntPipe } from "@nestjs/common";
|
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
|
||||||
|
import { Controller, UseGuards, Get, Param, ParseIntPipe, Query } from "@nestjs/common";
|
||||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
import { GetSchedulesOutput_2024_06_11 } from "@calcom/platform-types";
|
import { GetSchedulesOutput_2024_06_11, SkipTakePagination } from "@calcom/platform-types";
|
||||||
|
|
||||||
@Controller({
|
@Controller({
|
||||||
path: "/v2/organizations/:orgId/teams/:teamId",
|
path: "/v2/organizations/:orgId/teams/:teamId",
|
||||||
@@ -29,7 +30,31 @@ import { GetSchedulesOutput_2024_06_11 } from "@calcom/platform-types";
|
|||||||
@ApiHeader(OPTIONAL_X_CAL_SECRET_KEY_HEADER)
|
@ApiHeader(OPTIONAL_X_CAL_SECRET_KEY_HEADER)
|
||||||
@ApiHeader(OPTIONAL_API_KEY_HEADER)
|
@ApiHeader(OPTIONAL_API_KEY_HEADER)
|
||||||
export class OrganizationsTeamsSchedulesController {
|
export class OrganizationsTeamsSchedulesController {
|
||||||
constructor(private schedulesService: SchedulesService_2024_06_11) {}
|
constructor(
|
||||||
|
private schedulesService: SchedulesService_2024_06_11,
|
||||||
|
|
||||||
|
private teamsSchedulesService: TeamsSchedulesService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@UseGuards(IsTeamInOrg)
|
||||||
|
@Roles("TEAM_ADMIN")
|
||||||
|
@PlatformPlan("ESSENTIALS")
|
||||||
|
@Get("/schedules")
|
||||||
|
@DocsTags("Orgs / Teams / Schedules")
|
||||||
|
@ApiOperation({ summary: "Get all team member schedules" })
|
||||||
|
async getTeamSchedules(
|
||||||
|
@Param("teamId", ParseIntPipe) teamId: number,
|
||||||
|
@Query() queryParams: SkipTakePagination
|
||||||
|
): Promise<GetSchedulesOutput_2024_06_11> {
|
||||||
|
const { skip, take } = queryParams;
|
||||||
|
|
||||||
|
const schedules = await this.teamsSchedulesService.getTeamSchedules(teamId, skip, take);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: SUCCESS_STATUS,
|
||||||
|
data: schedules,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Roles("TEAM_ADMIN")
|
@Roles("TEAM_ADMIN")
|
||||||
@PlatformPlan("ESSENTIALS")
|
@PlatformPlan("ESSENTIALS")
|
||||||
|
|||||||
+39
@@ -18,6 +18,7 @@ import { randomString } from "test/utils/randomString";
|
|||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
|
import { GetSchedulesOutput_2024_06_11 } from "@calcom/platform-types";
|
||||||
import { ApiSuccessResponse, ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
|
import { ApiSuccessResponse, ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
|
||||||
import { Team, Schedule } from "@calcom/prisma/client";
|
import { Team, Schedule } from "@calcom/prisma/client";
|
||||||
|
|
||||||
@@ -36,6 +37,8 @@ describe("Organizations Teams Schedules Endpoints", () => {
|
|||||||
let org: Team;
|
let org: Team;
|
||||||
let orgTeam: Team;
|
let orgTeam: Team;
|
||||||
let nonOrgTeam: Team;
|
let nonOrgTeam: Team;
|
||||||
|
|
||||||
|
let userSchedule: Schedule;
|
||||||
let user2Schedule: Schedule;
|
let user2Schedule: Schedule;
|
||||||
|
|
||||||
const userEmail = `organizations-teams-schedules-admin-${randomString()}@api.com`;
|
const userEmail = `organizations-teams-schedules-admin-${randomString()}@api.com`;
|
||||||
@@ -73,6 +76,16 @@ describe("Organizations Teams Schedules Endpoints", () => {
|
|||||||
username: userEmail2,
|
username: userEmail2,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
userSchedule = await scheduleRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: `organizations-teams-schedules-user-schedule-${randomString()}`,
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
});
|
||||||
|
|
||||||
user2Schedule = await scheduleRepositoryFixture.create({
|
user2Schedule = await scheduleRepositoryFixture.create({
|
||||||
user: {
|
user: {
|
||||||
connect: {
|
connect: {
|
||||||
@@ -214,6 +227,32 @@ describe("Organizations Teams Schedules Endpoints", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should get all the schedules of members in a team", async () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/organizations/${org.id}/teams/${orgTeam.id}/schedules`)
|
||||||
|
.expect(200)
|
||||||
|
.then((response) => {
|
||||||
|
const responseBody: GetSchedulesOutput_2024_06_11 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(Array.isArray(responseBody.data)).toBe(true);
|
||||||
|
expect(responseBody.data.length).toEqual(2);
|
||||||
|
|
||||||
|
const userOneSchedule = responseBody.data.find((schedule) => schedule.id === userSchedule.id);
|
||||||
|
const userTwoSchedule = responseBody.data.find((schedule) => schedule.id === user2Schedule.id);
|
||||||
|
|
||||||
|
expect(userOneSchedule).toBeDefined();
|
||||||
|
expect(userTwoSchedule).toBeDefined();
|
||||||
|
|
||||||
|
expect(userOneSchedule?.id).toEqual(userSchedule.id);
|
||||||
|
expect(userOneSchedule?.name).toEqual(userSchedule.name);
|
||||||
|
expect(userOneSchedule?.timeZone).toEqual(userSchedule.timeZone);
|
||||||
|
|
||||||
|
expect(userTwoSchedule?.id).toEqual(user2Schedule.id);
|
||||||
|
expect(userTwoSchedule?.name).toEqual(user2Schedule.name);
|
||||||
|
expect(userOneSchedule?.timeZone).toEqual(user2Schedule.timeZone);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await userRepositoryFixture.deleteByEmail(user.email);
|
await userRepositoryFixture.deleteByEmail(user.email);
|
||||||
await userRepositoryFixture.deleteByEmail(userToInviteViaApi.email);
|
await userRepositoryFixture.deleteByEmail(userToInviteViaApi.email);
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||||
|
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
|
||||||
|
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||||
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
|
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||||
|
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
|
||||||
|
import { Controller, UseGuards, Get, Param, ParseIntPipe, Query } from "@nestjs/common";
|
||||||
|
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
|
import { GetSchedulesOutput_2024_06_11, SkipTakePagination } from "@calcom/platform-types";
|
||||||
|
|
||||||
|
@Controller({
|
||||||
|
path: "/v2/teams/:teamId/schedules",
|
||||||
|
version: API_VERSIONS_VALUES,
|
||||||
|
})
|
||||||
|
@UseGuards(ApiAuthGuard, RolesGuard)
|
||||||
|
@DocsTags("Teams / Schedules")
|
||||||
|
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||||
|
export class TeamsSchedulesController {
|
||||||
|
constructor(private teamsSchedulesService: TeamsSchedulesService) {}
|
||||||
|
|
||||||
|
@Roles("TEAM_ADMIN")
|
||||||
|
@Get("/")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Get all team member schedules",
|
||||||
|
})
|
||||||
|
async getTeamSchedules(
|
||||||
|
@Param("teamId", ParseIntPipe) teamId: number,
|
||||||
|
@Query() queryParams: SkipTakePagination
|
||||||
|
): Promise<GetSchedulesOutput_2024_06_11> {
|
||||||
|
const { skip, take } = queryParams;
|
||||||
|
|
||||||
|
const schedules = await this.teamsSchedulesService.getTeamSchedules(teamId, skip, take);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: SUCCESS_STATUS,
|
||||||
|
data: schedules,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { bootstrap } from "@/app";
|
||||||
|
import { AppModule } from "@/app.module";
|
||||||
|
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||||
|
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||||
|
import { UsersModule } from "@/modules/users/users.module";
|
||||||
|
import { INestApplication } from "@nestjs/common";
|
||||||
|
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||||
|
import { Test } from "@nestjs/testing";
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
import * as request from "supertest";
|
||||||
|
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||||
|
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||||
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
|
import { GetSchedulesOutput_2024_06_11 } from "@calcom/platform-types";
|
||||||
|
import { Team, Schedule } from "@calcom/prisma/client";
|
||||||
|
|
||||||
|
describe("Teams Schedules Endpoints", () => {
|
||||||
|
describe("User Authentication - User is Team Admin", () => {
|
||||||
|
let app: INestApplication;
|
||||||
|
|
||||||
|
let userRepositoryFixture: UserRepositoryFixture;
|
||||||
|
|
||||||
|
let teamsRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
let scheduleRepositoryFixture: SchedulesRepositoryFixture;
|
||||||
|
|
||||||
|
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||||
|
|
||||||
|
let nonOrgTeam: Team;
|
||||||
|
|
||||||
|
let userSchedule: Schedule;
|
||||||
|
let user2Schedule: Schedule;
|
||||||
|
|
||||||
|
const userEmail = `teams-schedules-admin-${randomString()}@api.com`;
|
||||||
|
const userEmail2 = `teams-schedules-member-${randomString()}@api.com`;
|
||||||
|
|
||||||
|
let user: User;
|
||||||
|
let user2: User;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const moduleRef = await withApiAuth(
|
||||||
|
userEmail,
|
||||||
|
Test.createTestingModule({
|
||||||
|
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||||
|
})
|
||||||
|
).compile();
|
||||||
|
|
||||||
|
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||||
|
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
|
|
||||||
|
scheduleRepositoryFixture = new SchedulesRepositoryFixture(moduleRef);
|
||||||
|
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||||
|
|
||||||
|
user = await userRepositoryFixture.create({
|
||||||
|
email: userEmail,
|
||||||
|
username: userEmail,
|
||||||
|
});
|
||||||
|
user2 = await userRepositoryFixture.create({
|
||||||
|
email: userEmail2,
|
||||||
|
username: userEmail2,
|
||||||
|
});
|
||||||
|
|
||||||
|
userSchedule = await scheduleRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: `teams-schedules-user-schedule-${randomString()}`,
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
});
|
||||||
|
|
||||||
|
user2Schedule = await scheduleRepositoryFixture.create({
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: user2.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: `teams-schedules-user2-schedule-${randomString()}`,
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
});
|
||||||
|
|
||||||
|
nonOrgTeam = await teamsRepositoryFixture.create({
|
||||||
|
name: `teams-schedules-non-org-team-${randomString()}`,
|
||||||
|
isOrganization: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await membershipsRepositoryFixture.create({
|
||||||
|
role: "ADMIN",
|
||||||
|
user: { connect: { id: user.id } },
|
||||||
|
team: { connect: { id: nonOrgTeam.id } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await membershipsRepositoryFixture.create({
|
||||||
|
role: "MEMBER",
|
||||||
|
user: { connect: { id: user2.id } },
|
||||||
|
team: { connect: { id: nonOrgTeam.id } },
|
||||||
|
});
|
||||||
|
|
||||||
|
app = moduleRef.createNestApplication();
|
||||||
|
bootstrap(app as NestExpressApplication);
|
||||||
|
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get all the schedules of members in a team", async () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/teams/${nonOrgTeam.id}/schedules`)
|
||||||
|
.expect(200)
|
||||||
|
.then((response) => {
|
||||||
|
const responseBody: GetSchedulesOutput_2024_06_11 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(Array.isArray(responseBody.data)).toBe(true);
|
||||||
|
expect(responseBody.data.length).toEqual(2);
|
||||||
|
|
||||||
|
const userOneSchedule = responseBody.data.find((schedule) => schedule.id === userSchedule.id);
|
||||||
|
const userTwoSchedule = responseBody.data.find((schedule) => schedule.id === user2Schedule.id);
|
||||||
|
|
||||||
|
expect(userOneSchedule).toBeDefined();
|
||||||
|
expect(userTwoSchedule).toBeDefined();
|
||||||
|
|
||||||
|
expect(userOneSchedule?.id).toEqual(userSchedule.id);
|
||||||
|
expect(userOneSchedule?.name).toEqual(userSchedule.name);
|
||||||
|
expect(userOneSchedule?.timeZone).toEqual(userSchedule.timeZone);
|
||||||
|
|
||||||
|
expect(userTwoSchedule?.id).toEqual(user2Schedule.id);
|
||||||
|
expect(userTwoSchedule?.name).toEqual(user2Schedule.name);
|
||||||
|
expect(userOneSchedule?.timeZone).toEqual(user2Schedule.timeZone);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await userRepositoryFixture.deleteByEmail(user.email);
|
||||||
|
await userRepositoryFixture.deleteByEmail(user2.email);
|
||||||
|
await teamsRepositoryFixture.delete(nonOrgTeam.id);
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
|
||||||
|
import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/output-schedules.service";
|
||||||
|
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
|
import { ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TeamsSchedulesService {
|
||||||
|
constructor(
|
||||||
|
private readonly teamsRepository: TeamsRepository,
|
||||||
|
private readonly schedulesRepository: SchedulesRepository_2024_06_11,
|
||||||
|
private readonly outputSchedulesService: OutputSchedulesService_2024_06_11
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async getTeamSchedules(teamId: number, skip = 0, take = 250) {
|
||||||
|
const userIds = await this.teamsRepository.getTeamUsersIds(teamId);
|
||||||
|
const schedules = await this.schedulesRepository.getSchedulesByUserIds(userIds, skip, take);
|
||||||
|
|
||||||
|
const responseSchedules: ScheduleOutput_2024_06_11[] = [];
|
||||||
|
|
||||||
|
for (const schedule of schedules) {
|
||||||
|
responseSchedules.push(await this.outputSchedulesService.getResponseSchedule(schedule));
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseSchedules;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
|
||||||
|
import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/output-schedules.service";
|
||||||
|
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||||
|
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||||
|
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||||
|
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||||
|
import { OrganizationSchedulesRepository } from "@/modules/organizations/schedules/organizations-schedules.repository";
|
||||||
|
import { OrganizationsTeamsRepository } from "@/modules/organizations/teams/index/organizations-teams.repository";
|
||||||
|
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||||
|
import { RedisModule } from "@/modules/redis/redis.module";
|
||||||
|
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||||
|
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
|
||||||
|
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
|
||||||
|
import { TeamsSchedulesController } from "@/modules/teams/schedules/controllers/teams-schedules.controller";
|
||||||
|
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
|
||||||
|
import { TeamsController } from "@/modules/teams/teams/controllers/teams.controller";
|
||||||
|
import { TeamsService } from "@/modules/teams/teams/services/teams.service";
|
||||||
|
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
|
||||||
|
import { UsersRepository } from "@/modules/users/users.repository";
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule, MembershipsModule, RedisModule],
|
||||||
|
providers: [
|
||||||
|
TeamsRepository,
|
||||||
|
TeamsService,
|
||||||
|
TeamsMembershipsRepository,
|
||||||
|
TeamsMembershipsService,
|
||||||
|
OutputSchedulesService_2024_06_11,
|
||||||
|
OrganizationSchedulesRepository,
|
||||||
|
StripeService,
|
||||||
|
UsersRepository,
|
||||||
|
AppsRepository,
|
||||||
|
CredentialsRepository,
|
||||||
|
OrganizationsRepository,
|
||||||
|
TeamsSchedulesService,
|
||||||
|
OrganizationsTeamsRepository,
|
||||||
|
SchedulesRepository_2024_06_11,
|
||||||
|
],
|
||||||
|
controllers: [TeamsController, TeamsSchedulesController],
|
||||||
|
exports: [TeamsRepository],
|
||||||
|
})
|
||||||
|
export class TeamsSchedulesModule {}
|
||||||
+4
-1
@@ -5,6 +5,7 @@ import { unstable_cache } from "next/cache";
|
|||||||
import { RoleManagementFactory } from "@calcom/features/pbac/services/role-management.factory";
|
import { RoleManagementFactory } from "@calcom/features/pbac/services/role-management.factory";
|
||||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||||
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
|
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||||
|
|
||||||
import { TeamMembersView } from "~/teams/team-members-view";
|
import { TeamMembersView } from "~/teams/team-members-view";
|
||||||
@@ -36,8 +37,10 @@ const getCachedTeamRoles = unstable_cache(
|
|||||||
const getCachedTeamAttributes = unstable_cache(
|
const getCachedTeamAttributes = unstable_cache(
|
||||||
async (organizationId?: number) => {
|
async (organizationId?: number) => {
|
||||||
if (!organizationId) return [];
|
if (!organizationId) return [];
|
||||||
|
const attributeRepo = new PrismaAttributeRepository(prisma);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await PrismaAttributeRepository.findAllByOrgIdWithOptions({ orgId: organizationId });
|
return await attributeRepo.findAllByOrgIdWithOptions({ orgId: organizationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -4,6 +4,7 @@ import { unstable_cache } from "next/cache";
|
|||||||
|
|
||||||
import { RoleManagementFactory } from "@calcom/features/pbac/services/role-management.factory";
|
import { RoleManagementFactory } from "@calcom/features/pbac/services/role-management.factory";
|
||||||
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
|
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
import { viewerOrganizationsRouter } from "@calcom/trpc/server/routers/viewer/organizations/_router";
|
import { viewerOrganizationsRouter } from "@calcom/trpc/server/routers/viewer/organizations/_router";
|
||||||
|
|
||||||
import { MembersView } from "~/members/members-view";
|
import { MembersView } from "~/members/members-view";
|
||||||
@@ -19,7 +20,9 @@ export const generateMetadata = async () =>
|
|||||||
|
|
||||||
const getCachedAttributes = unstable_cache(
|
const getCachedAttributes = unstable_cache(
|
||||||
async (orgId: number) => {
|
async (orgId: number) => {
|
||||||
return await PrismaAttributeRepository.findAllByOrgIdWithOptions({ orgId });
|
const attributeRepo = new PrismaAttributeRepository(prisma);
|
||||||
|
|
||||||
|
return await attributeRepo.findAllByOrgIdWithOptions({ orgId });
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
{ revalidate: 3600, tags: ["viewer.attributes.list"] } // Cache for 1 hour
|
{ revalidate: 3600, tags: ["viewer.attributes.list"] } // Cache for 1 hour
|
||||||
|
|||||||
@@ -87,7 +87,10 @@ async function handler(req: NextRequest) {
|
|||||||
|
|
||||||
return new Response(img.body, {
|
return new Response(img.body, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { "Content-Type": "image/png", "cache-control": "max-age=0" },
|
headers: {
|
||||||
|
"Content-Type": "image/png",
|
||||||
|
"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ZodError) {
|
if (error instanceof ZodError) {
|
||||||
@@ -116,7 +119,13 @@ async function handler(req: NextRequest) {
|
|||||||
});
|
});
|
||||||
const img = new ImageResponse(<App name={name} description={description} slug={slug} />, ogConfig);
|
const img = new ImageResponse(<App name={name} description={description} slug={slug} />, ogConfig);
|
||||||
|
|
||||||
return new Response(img.body, { status: 200, headers: { "Content-Type": "image/png" } });
|
return new Response(img.body, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "image/png",
|
||||||
|
"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ZodError) {
|
if (error instanceof ZodError) {
|
||||||
return new Response(
|
return new Response(
|
||||||
@@ -144,7 +153,13 @@ async function handler(req: NextRequest) {
|
|||||||
|
|
||||||
const img = new ImageResponse(<Generic title={title} description={description} />, ogConfig);
|
const img = new ImageResponse(<Generic title={title} description={description} />, ogConfig);
|
||||||
|
|
||||||
return new Response(img.body, { status: 200, headers: { "Content-Type": "image/png" } });
|
return new Response(img.body, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "image/png",
|
||||||
|
"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ZodError) {
|
if (error instanceof ZodError) {
|
||||||
return new Response(
|
return new Response(
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { useMemo } from "react";
|
|||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import type { UseFormGetValues, UseFormSetValue, Control, FormState } from "react-hook-form";
|
import type { UseFormGetValues, UseFormSetValue, Control, FormState } from "react-hook-form";
|
||||||
|
|
||||||
import type { TLocationOptions } from "@calcom/features/eventtypes/components/Locations";
|
import type {
|
||||||
import type { TEventTypeLocation } from "@calcom/features/eventtypes/components/Locations";
|
TLocationOptions,
|
||||||
import Locations from "@calcom/features/eventtypes/components/Locations";
|
TEventTypeLocation,
|
||||||
|
} from "@calcom/features/eventtypes/components/locations/Locations";
|
||||||
|
import Locations from "@calcom/features/eventtypes/components/locations/Locations";
|
||||||
import type { LocationFormValues } from "@calcom/features/eventtypes/lib/types";
|
import type { LocationFormValues } from "@calcom/features/eventtypes/lib/types";
|
||||||
import type { SingleValueLocationOption } from "@calcom/features/form/components/LocationSelect";
|
import type { SingleValueLocationOption } from "@calcom/features/form/components/LocationSelect";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ function BookingsContent({ status }: BookingsProps) {
|
|||||||
columnHelper.accessor((row) => row.type === "data" && row.booking.user?.id, {
|
columnHelper.accessor((row) => row.type === "data" && row.booking.user?.id, {
|
||||||
id: "userId",
|
id: "userId",
|
||||||
header: t("member"),
|
header: t("member"),
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: user?.isTeamAdminOrOwner ?? false,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: () => null,
|
cell: () => null,
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "5.6.1",
|
"version": "5.6.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
|
|||||||
@@ -479,7 +479,6 @@ test.describe("Bookings", () => {
|
|||||||
const filterItemsConfig = [
|
const filterItemsConfig = [
|
||||||
{ key: "eventTypeId", name: "Event Type", testId: "add-filter-item-eventTypeId" },
|
{ key: "eventTypeId", name: "Event Type", testId: "add-filter-item-eventTypeId" },
|
||||||
{ key: "teamId", name: "Team", testId: "add-filter-item-teamId" },
|
{ key: "teamId", name: "Team", testId: "add-filter-item-teamId" },
|
||||||
{ key: "userId", name: "Member", testId: "add-filter-item-userId" },
|
|
||||||
{ key: "attendeeName", name: "Attendees Name", testId: "add-filter-item-attendeeName" },
|
{ key: "attendeeName", name: "Attendees Name", testId: "add-filter-item-attendeeName" },
|
||||||
{ key: "attendeeEmail", name: "Attendee Email", testId: "add-filter-item-attendeeEmail" },
|
{ key: "attendeeEmail", name: "Attendee Email", testId: "add-filter-item-attendeeEmail" },
|
||||||
{ key: "dateRange", name: "Date Range", testId: "add-filter-item-dateRange" },
|
{ key: "dateRange", name: "Date Range", testId: "add-filter-item-dateRange" },
|
||||||
@@ -488,8 +487,16 @@ test.describe("Bookings", () => {
|
|||||||
|
|
||||||
const getFilterItemLocator = (page: Page, testId: string) => page.locator(`[data-testid="${testId}"]`);
|
const getFilterItemLocator = (page: Page, testId: string) => page.locator(`[data-testid="${testId}"]`);
|
||||||
|
|
||||||
test.beforeEach(async ({ page, users }) => {
|
const setup = async ({
|
||||||
const user = await users.create();
|
isAdmin,
|
||||||
|
page,
|
||||||
|
users,
|
||||||
|
}: {
|
||||||
|
isAdmin: boolean;
|
||||||
|
page: Page;
|
||||||
|
users: Fixtures["users"];
|
||||||
|
}) => {
|
||||||
|
const user = isAdmin ? await users.create(undefined, { hasTeam: true }) : await users.create();
|
||||||
await user.apiLogin();
|
await user.apiLogin();
|
||||||
const bookingsGetResponse = page.waitForResponse((response) =>
|
const bookingsGetResponse = page.waitForResponse((response) =>
|
||||||
/\/api\/trpc\/bookings\/get.*/.test(response.url())
|
/\/api\/trpc\/bookings\/get.*/.test(response.url())
|
||||||
@@ -498,9 +505,10 @@ test.describe("Bookings", () => {
|
|||||||
await bookingsGetResponse;
|
await bookingsGetResponse;
|
||||||
await page.locator('[data-testid="add-filter-button"]').click();
|
await page.locator('[data-testid="add-filter-button"]').click();
|
||||||
await expect(page.locator(searchInputSelector)).toBeVisible();
|
await expect(page.locator(searchInputSelector)).toBeVisible();
|
||||||
});
|
};
|
||||||
|
|
||||||
test("should show all filter items initially and after clearing search", async ({ page }) => {
|
test("should show all filter items initially and after clearing search", async ({ page, users }) => {
|
||||||
|
await setup({ isAdmin: false, page, users });
|
||||||
const searchInput = page.locator(searchInputSelector);
|
const searchInput = page.locator(searchInputSelector);
|
||||||
|
|
||||||
// Initial check: all defined filter items should be visible
|
// Initial check: all defined filter items should be visible
|
||||||
@@ -524,7 +532,18 @@ test.describe("Bookings", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("search should be case-insensitive", async ({ page }) => {
|
test("should show admin-only filter", async ({ page, users }) => {
|
||||||
|
await setup({ isAdmin: true, page, users });
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
getFilterItemLocator(page, "add-filter-item-userId"),
|
||||||
|
`Item "Member" should be visible initially`
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("search should be case-insensitive", async ({ page, users }) => {
|
||||||
|
await setup({ isAdmin: true, page, users });
|
||||||
|
|
||||||
const searchInput = page.locator(searchInputSelector);
|
const searchInput = page.locator(searchInputSelector);
|
||||||
|
|
||||||
// Search for "member" (lowercase)
|
// Search for "member" (lowercase)
|
||||||
@@ -534,7 +553,8 @@ test.describe("Bookings", () => {
|
|||||||
await expect(getFilterItemLocator(page, "add-filter-item-teamId")).toBeHidden();
|
await expect(getFilterItemLocator(page, "add-filter-item-teamId")).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should individually find each filter item by its full name", async ({ page }) => {
|
test("should individually find each filter item by its full name", async ({ page, users }) => {
|
||||||
|
await setup({ isAdmin: false, page, users });
|
||||||
const searchInput = page.locator(searchInputSelector);
|
const searchInput = page.locator(searchInputSelector);
|
||||||
|
|
||||||
for (const targetItem of filterItemsConfig) {
|
for (const targetItem of filterItemsConfig) {
|
||||||
@@ -550,7 +570,8 @@ test.describe("Bookings", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should show no items for a non-matching search term", async ({ page }) => {
|
test("should show no items for a non-matching search term", async ({ page, users }) => {
|
||||||
|
await setup({ isAdmin: false, page, users });
|
||||||
const searchInput = page.locator(searchInputSelector);
|
const searchInput = page.locator(searchInputSelector);
|
||||||
const nonExistentTerm = "NonExistentFilterXYZ123";
|
const nonExistentTerm = "NonExistentFilterXYZ123";
|
||||||
await searchInput.fill(nonExistentTerm);
|
await searchInput.fill(nonExistentTerm);
|
||||||
|
|||||||
@@ -661,22 +661,30 @@ export default class Office365CalendarService implements Calendar {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleErrorJsonOffice365Calendar = <Type>(response: Response): Promise<Type | string> => {
|
private async handleErrorJsonOffice365Calendar<Type>(response: Response): Promise<Type | string> {
|
||||||
if (response.headers.get("content-encoding") === "gzip") {
|
if (response.headers.get("content-encoding") === "gzip") {
|
||||||
return response.text();
|
return response.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status === 204) {
|
if (response.status === 204) {
|
||||||
return new Promise((resolve) => resolve({} as Type));
|
return {} as Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok && response.status < 200 && response.status >= 300) {
|
if (!response.ok) {
|
||||||
response.json().then(console.log);
|
let errorBody: string | object;
|
||||||
throw Error(response.statusText);
|
try {
|
||||||
|
errorBody = await response.json();
|
||||||
|
} catch (e) {
|
||||||
|
errorBody = await response.text();
|
||||||
|
}
|
||||||
|
this.log.error(
|
||||||
|
`handleErrorJsonOffice365Calendar: Office365 API request failed with status ${response.status}`,
|
||||||
|
errorBody
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
};
|
}
|
||||||
|
|
||||||
async getMainTimeZone(): Promise<string> {
|
async getMainTimeZone(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -109,6 +109,15 @@ function WorkflowPage({ workflow: workflowId }: PageProps) {
|
|||||||
|
|
||||||
const isPending = isPendingWorkflow || isPendingEventTypes;
|
const isPending = isPendingWorkflow || isPendingEventTypes;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [isPending]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPending) {
|
if (!isPending) {
|
||||||
setFormData(workflow);
|
setFormData(workflow);
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { ErrorMessage } from "@hookform/error-message";
|
||||||
|
import { useFormContext, Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||||
|
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
|
||||||
|
import type { CalVideoSettings as CalVideoSettingsType } from "@calcom/features/eventtypes/lib/types";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import classNames from "@calcom/ui/classNames";
|
||||||
|
import { UpgradeTeamsBadge } from "@calcom/ui/components/badge";
|
||||||
|
import { TextField } from "@calcom/ui/components/form";
|
||||||
|
import { SettingsToggle } from "@calcom/ui/components/form";
|
||||||
|
|
||||||
|
import LocationSettingsContainer from "./LocationSettingsContainer";
|
||||||
|
|
||||||
|
const CalVideoSettings = ({ calVideoSettings }: { calVideoSettings?: CalVideoSettingsType }) => {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const formMethods = useFormContext<FormValues>();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
return (
|
||||||
|
<LocationSettingsContainer>
|
||||||
|
<Controller
|
||||||
|
name="calVideoSettings.disableRecordingForGuests"
|
||||||
|
defaultValue={!!calVideoSettings?.disableRecordingForGuests}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("disable_recording_for_guests")}
|
||||||
|
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
||||||
|
checked={value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="calVideoSettings.disableRecordingForOrganizer"
|
||||||
|
defaultValue={!!calVideoSettings?.disableRecordingForOrganizer}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("disable_recording_for_organizer")}
|
||||||
|
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
||||||
|
checked={value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!isPlatform && (
|
||||||
|
<Controller
|
||||||
|
name="calVideoSettings.enableAutomaticRecordingForOrganizer"
|
||||||
|
defaultValue={!!calVideoSettings?.enableAutomaticRecordingForOrganizer}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("enable_automatic_recording")}
|
||||||
|
labelClassName="text-sm"
|
||||||
|
checked={value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="calVideoSettings.enableAutomaticTranscription"
|
||||||
|
defaultValue={!!calVideoSettings?.enableAutomaticTranscription}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("enable_automatic_transcription")}
|
||||||
|
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
||||||
|
checked={value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!isPlatform && (
|
||||||
|
<Controller
|
||||||
|
name="calVideoSettings.disableTranscriptionForGuests"
|
||||||
|
defaultValue={!!calVideoSettings?.disableTranscriptionForGuests}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("disable_transcription_for_guests")}
|
||||||
|
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
||||||
|
checked={value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isPlatform && (
|
||||||
|
<Controller
|
||||||
|
name="calVideoSettings.disableTranscriptionForOrganizer"
|
||||||
|
defaultValue={!!calVideoSettings?.disableTranscriptionForOrganizer}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("disable_transcription_for_organizer")}
|
||||||
|
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
||||||
|
checked={value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label={t("enter_redirect_url_on_exit_description")}
|
||||||
|
defaultValue={calVideoSettings?.redirectUrlOnExit || ""}
|
||||||
|
data-testid="calVideoSettings.redirectUrlOnExit"
|
||||||
|
containerClassName="mt-4"
|
||||||
|
className="leading-6"
|
||||||
|
{...formMethods.register("calVideoSettings.redirectUrlOnExit", {
|
||||||
|
setValueAs: (v) => (!v || v.trim() === "" ? null : v),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<ErrorMessage
|
||||||
|
errors={formMethods.formState.errors?.calVideoSettings}
|
||||||
|
name="redirectUrlOnExit"
|
||||||
|
className={classNames("text-error text-sm")}
|
||||||
|
as="div"
|
||||||
|
id="calVideoSettings.redirectUrlOnExit-error"
|
||||||
|
/>
|
||||||
|
</LocationSettingsContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalVideoSettings;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const LocationSettingsContainer = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<div className="bg-muted mt-2 space-y-2 rounded-lg p-4">
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="flex flex-col gap-4">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LocationSettingsContainer;
|
||||||
+3
-129
@@ -25,14 +25,13 @@ import ServerTrans from "@calcom/lib/components/ServerTrans";
|
|||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import classNames from "@calcom/ui/classNames";
|
import classNames from "@calcom/ui/classNames";
|
||||||
import { UpgradeTeamsBadge } from "@calcom/ui/components/badge";
|
|
||||||
import { Button } from "@calcom/ui/components/button";
|
import { Button } from "@calcom/ui/components/button";
|
||||||
import { TextField } from "@calcom/ui/components/form";
|
|
||||||
import { SettingsToggle } from "@calcom/ui/components/form";
|
|
||||||
import { Input } from "@calcom/ui/components/form";
|
import { Input } from "@calcom/ui/components/form";
|
||||||
import { Icon } from "@calcom/ui/components/icon";
|
import { Icon } from "@calcom/ui/components/icon";
|
||||||
import { showToast } from "@calcom/ui/components/toast";
|
import { showToast } from "@calcom/ui/components/toast";
|
||||||
|
|
||||||
|
import CalVideoSettings from "./CalVideoSettings";
|
||||||
|
|
||||||
export type TEventTypeLocation = Pick<EventTypeSetupProps["eventType"], "locations" | "calVideoSettings">;
|
export type TEventTypeLocation = Pick<EventTypeSetupProps["eventType"], "locations" | "calVideoSettings">;
|
||||||
export type TLocationOptions = Pick<EventTypeSetupProps, "locationOptions">["locationOptions"];
|
export type TLocationOptions = Pick<EventTypeSetupProps, "locationOptions">["locationOptions"];
|
||||||
export type TDestinationCalendar = { integration: string } | null;
|
export type TDestinationCalendar = { integration: string } | null;
|
||||||
@@ -342,132 +341,7 @@ const Locations: React.FC<LocationsProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isCalVideo && !isPlatform && (
|
{isCalVideo && !isPlatform && <CalVideoSettings />}
|
||||||
<div className="bg-muted mt-2 space-y-2 rounded-lg p-4">
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<Controller
|
|
||||||
name="calVideoSettings.disableRecordingForGuests"
|
|
||||||
defaultValue={!!eventType.calVideoSettings?.disableRecordingForGuests}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
|
||||||
return (
|
|
||||||
<SettingsToggle
|
|
||||||
title={t("disable_recording_for_guests")}
|
|
||||||
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
|
||||||
checked={value}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="calVideoSettings.disableRecordingForOrganizer"
|
|
||||||
defaultValue={!!eventType.calVideoSettings?.disableRecordingForOrganizer}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
|
||||||
return (
|
|
||||||
<SettingsToggle
|
|
||||||
title={t("disable_recording_for_organizer")}
|
|
||||||
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
|
||||||
checked={value}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!isPlatform && (
|
|
||||||
<Controller
|
|
||||||
name="calVideoSettings.enableAutomaticRecordingForOrganizer"
|
|
||||||
defaultValue={!!eventType.calVideoSettings?.enableAutomaticRecordingForOrganizer}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
|
||||||
return (
|
|
||||||
<SettingsToggle
|
|
||||||
title={t("enable_automatic_recording")}
|
|
||||||
labelClassName="text-sm"
|
|
||||||
checked={value}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="calVideoSettings.enableAutomaticTranscription"
|
|
||||||
defaultValue={!!eventType.calVideoSettings?.enableAutomaticTranscription}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
|
||||||
return (
|
|
||||||
<SettingsToggle
|
|
||||||
title={t("enable_automatic_transcription")}
|
|
||||||
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
|
||||||
checked={value}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!isPlatform && (
|
|
||||||
<Controller
|
|
||||||
name="calVideoSettings.disableTranscriptionForGuests"
|
|
||||||
defaultValue={!!eventType.calVideoSettings?.disableTranscriptionForGuests}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
|
||||||
return (
|
|
||||||
<SettingsToggle
|
|
||||||
title={t("disable_transcription_for_guests")}
|
|
||||||
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
|
||||||
checked={value}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isPlatform && (
|
|
||||||
<Controller
|
|
||||||
name="calVideoSettings.disableTranscriptionForOrganizer"
|
|
||||||
defaultValue={!!eventType.calVideoSettings?.disableTranscriptionForOrganizer}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
|
||||||
return (
|
|
||||||
<SettingsToggle
|
|
||||||
title={t("disable_transcription_for_organizer")}
|
|
||||||
labelClassName="text-sm leading-6 whitespace-normal break-words"
|
|
||||||
checked={value}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label={t("enter_redirect_url_on_exit_description")}
|
|
||||||
defaultValue={eventType.calVideoSettings?.redirectUrlOnExit || ""}
|
|
||||||
data-testid="calVideoSettings.redirectUrlOnExit"
|
|
||||||
containerClassName="mt-4"
|
|
||||||
className="leading-6"
|
|
||||||
{...formMethods.register("calVideoSettings.redirectUrlOnExit", {
|
|
||||||
setValueAs: (v) => (!v || v.trim() === "" ? null : v),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<ErrorMessage
|
|
||||||
errors={formMethods.formState.errors?.calVideoSettings}
|
|
||||||
name="redirectUrlOnExit"
|
|
||||||
className={classNames("text-error text-sm")}
|
|
||||||
as="div"
|
|
||||||
id="calVideoSettings.redirectUrlOnExit-error"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{eventLocationType?.organizerInputType && (
|
{eventLocationType?.organizerInputType && (
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mt-2 space-y-2">
|
||||||
@@ -5,8 +5,6 @@ import type { MultiValue } from "react-select";
|
|||||||
|
|
||||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||||
import type { LocationCustomClassNames } from "@calcom/features/eventtypes/components/Locations";
|
|
||||||
import Locations from "@calcom/features/eventtypes/components/Locations";
|
|
||||||
import type {
|
import type {
|
||||||
EventTypeSetupProps,
|
EventTypeSetupProps,
|
||||||
InputClassNames,
|
InputClassNames,
|
||||||
@@ -28,6 +26,9 @@ import { Select } from "@calcom/ui/components/form";
|
|||||||
import { SettingsToggle } from "@calcom/ui/components/form";
|
import { SettingsToggle } from "@calcom/ui/components/form";
|
||||||
import { Skeleton } from "@calcom/ui/components/skeleton";
|
import { Skeleton } from "@calcom/ui/components/skeleton";
|
||||||
|
|
||||||
|
import type { LocationCustomClassNames } from "../../locations/Locations";
|
||||||
|
import Locations from "../../locations/Locations";
|
||||||
|
|
||||||
export type EventSetupTabCustomClassNames = {
|
export type EventSetupTabCustomClassNames = {
|
||||||
wrapper?: string;
|
wrapper?: string;
|
||||||
titleSection?: {
|
titleSection?: {
|
||||||
|
|||||||
@@ -153,6 +153,17 @@ export const getPublicEventSelect = (fetchAllUsers: boolean) => {
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
assignAllTeamMembers: true,
|
assignAllTeamMembers: true,
|
||||||
rescheduleWithSameRoundRobinHost: true,
|
rescheduleWithSameRoundRobinHost: true,
|
||||||
|
parent: {
|
||||||
|
select: {
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
theme: true,
|
||||||
|
brandColor: true,
|
||||||
|
darkBrandColor: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} satisfies Prisma.EventTypeSelect;
|
} satisfies Prisma.EventTypeSelect;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -866,10 +877,11 @@ type GetProfileFromEventInput = Omit<Event, "hosts"> & {
|
|||||||
|
|
||||||
export function getProfileFromEvent(event: GetProfileFromEventInput) {
|
export function getProfileFromEvent(event: GetProfileFromEventInput) {
|
||||||
const { team, subsetOfHosts: hosts, owner } = event;
|
const { team, subsetOfHosts: hosts, owner } = event;
|
||||||
const nonTeamprofile = hosts?.[0]?.user || owner;
|
const nonTeamProfile = hosts?.[0]?.user || owner;
|
||||||
const profile = team || nonTeamprofile;
|
const profile = team || nonTeamProfile;
|
||||||
if (!profile) throw new Error("Event has no owner");
|
if (!profile) throw new Error("Event has no owner");
|
||||||
|
|
||||||
|
const styleProfile = team || event.parent?.team || nonTeamProfile;
|
||||||
const username = "username" in profile ? profile.username : team?.slug;
|
const username = "username" in profile ? profile.username : team?.slug;
|
||||||
const weekStart = hosts?.[0]?.user?.weekStart || owner?.weekStart || "Monday";
|
const weekStart = hosts?.[0]?.user?.weekStart || owner?.weekStart || "Monday";
|
||||||
const eventMetaData = eventTypeMetaDataSchemaWithTypedApps.parse(event.metadata || {});
|
const eventMetaData = eventTypeMetaDataSchemaWithTypedApps.parse(event.metadata || {});
|
||||||
@@ -882,11 +894,11 @@ export function getProfileFromEvent(event: GetProfileFromEventInput) {
|
|||||||
image: team
|
image: team
|
||||||
? getOrgOrTeamAvatar(team)
|
? getOrgOrTeamAvatar(team)
|
||||||
: getUserAvatarUrl({
|
: getUserAvatarUrl({
|
||||||
avatarUrl: nonTeamprofile?.avatarUrl,
|
avatarUrl: nonTeamProfile?.avatarUrl,
|
||||||
}),
|
}),
|
||||||
brandColor: profile.brandColor,
|
brandColor: styleProfile.brandColor,
|
||||||
darkBrandColor: profile.darkBrandColor,
|
darkBrandColor: styleProfile.darkBrandColor,
|
||||||
theme: profile.theme,
|
theme: styleProfile.theme,
|
||||||
bookerLayouts: bookerLayoutsSchema.parse(
|
bookerLayouts: bookerLayoutsSchema.parse(
|
||||||
eventMetaData?.bookerLayouts ||
|
eventMetaData?.bookerLayouts ||
|
||||||
(userMetaData && "defaultBookerLayouts" in userMetaData ? userMetaData.defaultBookerLayouts : null)
|
(userMetaData && "defaultBookerLayouts" in userMetaData ? userMetaData.defaultBookerLayouts : null)
|
||||||
|
|||||||
@@ -168,15 +168,7 @@ export type FormValues = {
|
|||||||
restrictionScheduleId: number | null;
|
restrictionScheduleId: number | null;
|
||||||
useBookerTimezone: boolean;
|
useBookerTimezone: boolean;
|
||||||
restrictionScheduleName: string | null;
|
restrictionScheduleName: string | null;
|
||||||
calVideoSettings?: {
|
calVideoSettings?: CalVideoSettings;
|
||||||
disableRecordingForOrganizer?: boolean;
|
|
||||||
disableRecordingForGuests?: boolean;
|
|
||||||
enableAutomaticTranscription?: boolean;
|
|
||||||
enableAutomaticRecordingForOrganizer?: boolean;
|
|
||||||
disableTranscriptionForGuests?: boolean;
|
|
||||||
disableTranscriptionForOrganizer?: boolean;
|
|
||||||
redirectUrlOnExit?: string;
|
|
||||||
};
|
|
||||||
maxActiveBookingPerBookerOfferReschedule: boolean;
|
maxActiveBookingPerBookerOfferReschedule: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -243,3 +235,13 @@ export interface EventTypePlatformWrapperRef {
|
|||||||
validateForm: () => Promise<FormValidationResult>;
|
validateForm: () => Promise<FormValidationResult>;
|
||||||
handleFormSubmit: (callbacks?: { onSuccess?: () => void; onError?: (error: Error) => void }) => void;
|
handleFormSubmit: (callbacks?: { onSuccess?: () => void; onError?: (error: Error) => void }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CalVideoSettings {
|
||||||
|
disableRecordingForOrganizer?: boolean;
|
||||||
|
disableRecordingForGuests?: boolean;
|
||||||
|
enableAutomaticTranscription?: boolean;
|
||||||
|
enableAutomaticRecordingForOrganizer?: boolean;
|
||||||
|
disableTranscriptionForGuests?: boolean;
|
||||||
|
disableTranscriptionForOrganizer?: boolean;
|
||||||
|
redirectUrlOnExit?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useForm } from "react-hook-form";
|
|||||||
|
|
||||||
import type { Dayjs } from "@calcom/dayjs";
|
import type { Dayjs } from "@calcom/dayjs";
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { BookerStoreProvider } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||||
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
||||||
import { yyyymmdd } from "@calcom/lib/dayjs";
|
import { yyyymmdd } from "@calcom/lib/dayjs";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
@@ -136,19 +137,21 @@ const DateOverrideForm = ({
|
|||||||
className="p-6 sm:flex sm:p-0 xl:flex-row">
|
className="p-6 sm:flex sm:p-0 xl:flex-row">
|
||||||
<div className="sm:border-subtle w-full sm:border-r sm:p-4 sm:pr-6 md:p-8">
|
<div className="sm:border-subtle w-full sm:border-r sm:p-4 sm:pr-6 md:p-8">
|
||||||
<DialogHeader title={t("date_overrides_dialog_title")} />
|
<DialogHeader title={t("date_overrides_dialog_title")} />
|
||||||
<DatePicker
|
<BookerStoreProvider>
|
||||||
excludedDates={excludedDates}
|
<DatePicker
|
||||||
weekStart={weekStart}
|
excludedDates={excludedDates}
|
||||||
selected={selectedDates}
|
weekStart={weekStart}
|
||||||
onChange={(day) => {
|
selected={selectedDates}
|
||||||
if (day) onDateChange(day);
|
onChange={(day) => {
|
||||||
}}
|
if (day) onDateChange(day);
|
||||||
onMonthChange={(newMonth) => {
|
}}
|
||||||
setBrowsingDate(newMonth);
|
onMonthChange={(newMonth) => {
|
||||||
}}
|
setBrowsingDate(newMonth);
|
||||||
browsingDate={browsingDate}
|
}}
|
||||||
locale={isLocaleReady ? i18n.language : "en"}
|
browsingDate={browsingDate}
|
||||||
/>
|
locale={isLocaleReady ? i18n.language : "en"}
|
||||||
|
/>
|
||||||
|
</BookerStoreProvider>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative mt-8 flex w-full flex-col sm:mt-0 sm:p-4 md:p-8">
|
<div className="relative mt-8 flex w-full flex-col sm:mt-0 sm:p-4 md:p-8">
|
||||||
{selectedDates[0] ? (
|
{selectedDates[0] ? (
|
||||||
|
|||||||
@@ -213,21 +213,6 @@ describe("getAllDelegationCredentialsForUserIncludeServiceAccountKey", () => {
|
|||||||
buildDelegationCredentialGoogleMeetCredential(),
|
buildDelegationCredentialGoogleMeetCredential(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return empty array for non-Google platforms(as they are not supported yet)", async () => {
|
|
||||||
vi.mocked(
|
|
||||||
DelegationCredentialRepository.findUniqueByOrgMemberEmailIncludeSensitiveServiceAccountKey
|
|
||||||
).mockResolvedValue(
|
|
||||||
buildMockDelegationCredential({
|
|
||||||
workspacePlatform: {
|
|
||||||
name: "Microsoft",
|
|
||||||
slug: "microsoft",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const result = await getAllDelegationCredentialsForUserIncludeServiceAccountKey({ user: mockUser });
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildAllCredentials", () => {
|
describe("buildAllCredentials", () => {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import prisma from "@calcom/prisma";
|
import type { PrismaClient } from "@calcom/prisma";
|
||||||
|
|
||||||
export class PrismaAttributeRepository {
|
export class PrismaAttributeRepository {
|
||||||
static async findManyByNamesAndOrgIdIncludeOptions({
|
constructor(private prismaClient: PrismaClient) {}
|
||||||
|
|
||||||
|
async findManyByNamesAndOrgIdIncludeOptions({
|
||||||
attributeNames,
|
attributeNames,
|
||||||
orgId,
|
orgId,
|
||||||
}: {
|
}: {
|
||||||
attributeNames: string[];
|
attributeNames: string[];
|
||||||
orgId: number;
|
orgId: number;
|
||||||
}) {
|
}) {
|
||||||
return prisma.attribute.findMany({
|
return this.prismaClient.attribute.findMany({
|
||||||
where: {
|
where: {
|
||||||
name: { in: attributeNames, mode: "insensitive" },
|
name: { in: attributeNames, mode: "insensitive" },
|
||||||
teamId: orgId,
|
teamId: orgId,
|
||||||
@@ -25,9 +27,9 @@ export class PrismaAttributeRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findManyByOrgId({ orgId }: { orgId: number }) {
|
async findManyByOrgId({ orgId }: { orgId: number }) {
|
||||||
// It should be a faster query because of lesser number of attributes record and index on teamId
|
// It should be a faster query because of lesser number of attributes record and index on teamId
|
||||||
const result = await prisma.attribute.findMany({
|
const result = await this.prismaClient.attribute.findMany({
|
||||||
where: {
|
where: {
|
||||||
teamId: orgId,
|
teamId: orgId,
|
||||||
},
|
},
|
||||||
@@ -43,8 +45,8 @@ export class PrismaAttributeRepository {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findAllByOrgIdWithOptions({ orgId }: { orgId: number }) {
|
async findAllByOrgIdWithOptions({ orgId }: { orgId: number }) {
|
||||||
return await prisma.attribute.findMany({
|
return await this.prismaClient.attribute.findMany({
|
||||||
where: {
|
where: {
|
||||||
teamId: orgId,
|
teamId: orgId,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import prisma from "@calcom/prisma";
|
import type { PrismaClient } from "@calcom/prisma";
|
||||||
|
|
||||||
export class HostRepository {
|
export class HostRepository {
|
||||||
static async updateHostsSchedule(userId: number, oldScheduleId: number, newScheduleId: number) {
|
constructor(private prismaClient: PrismaClient) {}
|
||||||
return await prisma.host.updateMany({
|
|
||||||
|
async updateHostsSchedule(userId: number, oldScheduleId: number, newScheduleId: number) {
|
||||||
|
return await this.prismaClient.host.updateMany({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
scheduleId: oldScheduleId,
|
scheduleId: oldScheduleId,
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ const findAttributesByName = async ({
|
|||||||
orgId: number;
|
orgId: number;
|
||||||
attributeNames: AttributeName[];
|
attributeNames: AttributeName[];
|
||||||
}) => {
|
}) => {
|
||||||
const attributesFromDb = await PrismaAttributeRepository.findManyByNamesAndOrgIdIncludeOptions({
|
const attributeRepo = new PrismaAttributeRepository(prisma);
|
||||||
|
const attributesFromDb = await attributeRepo.findManyByNamesAndOrgIdIncludeOptions({
|
||||||
attributeNames,
|
attributeNames,
|
||||||
orgId,
|
orgId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -216,9 +216,11 @@ async function _getOrgMembershipToUserIdForTeam({ orgId, teamId }: { orgId: numb
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function _queryAllData({ orgId, teamId }: { orgId: number; teamId: number }) {
|
async function _queryAllData({ orgId, teamId }: { orgId: number; teamId: number }) {
|
||||||
|
const attributeRepo = new PrismaAttributeRepository(prisma);
|
||||||
|
|
||||||
const [orgMembershipToUserIdForTeamMembers, attributesOfTheOrg] = await Promise.all([
|
const [orgMembershipToUserIdForTeamMembers, attributesOfTheOrg] = await Promise.all([
|
||||||
_getOrgMembershipToUserIdForTeam({ orgId, teamId }),
|
_getOrgMembershipToUserIdForTeam({ orgId, teamId }),
|
||||||
PrismaAttributeRepository.findManyByOrgId({ orgId }),
|
attributeRepo.findManyByOrgId({ orgId }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const orgMembershipIds = Array.from(orgMembershipToUserIdForTeamMembers.keys());
|
const orgMembershipIds = Array.from(orgMembershipToUserIdForTeamMembers.keys());
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
## 1.1.2
|
## 1.1.2
|
||||||
|
|
||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- [#23174](https://github.com/calcom/cal.com/pull/23174) [`618ef63`](https://github.com/calcom/cal.com/commit/618ef630170eb1c05b4a131a01d0c2316b3d1880) Thanks [@SomayChauhan](https://github.com/SomayChauhan)! - fix: Availability atom handleFormSubmit callbacks not triggering
|
||||||
|
|
||||||
## 1.4.0
|
## 1.4.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ type AvailabilitySettingsProps = {
|
|||||||
isEventTypesFetching?: boolean;
|
isEventTypesFetching?: boolean;
|
||||||
handleBulkEditDialogToggle: () => void;
|
handleBulkEditDialogToggle: () => void;
|
||||||
};
|
};
|
||||||
|
callbacksRef?: React.MutableRefObject<{ onSuccess?: () => void; onError?: (error: Error) => void }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeleteDialogButton = ({
|
const DeleteDialogButton = ({
|
||||||
@@ -306,6 +307,7 @@ export const AvailabilitySettings = forwardRef<AvailabilitySettingsFormRef, Avai
|
|||||||
bulkUpdateModalProps,
|
bulkUpdateModalProps,
|
||||||
allowSetToDefault = true,
|
allowSetToDefault = true,
|
||||||
allowDelete = true,
|
allowDelete = true,
|
||||||
|
callbacksRef,
|
||||||
} = props;
|
} = props;
|
||||||
const [openSidebar, setOpenSidebar] = useState(false);
|
const [openSidebar, setOpenSidebar] = useState(false);
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
@@ -336,11 +338,9 @@ export const AvailabilitySettings = forwardRef<AvailabilitySettingsFormRef, Avai
|
|||||||
|
|
||||||
const saveButtonRef = useRef<HTMLButtonElement>(null);
|
const saveButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
const callbacksRef = useRef<{ onSuccess?: () => void; onError?: (error: Error) => void }>({});
|
|
||||||
|
|
||||||
const handleFormSubmit = useCallback(
|
const handleFormSubmit = useCallback(
|
||||||
(customCallbacks?: { onSuccess?: () => void; onError?: (error: Error) => void }) => {
|
(customCallbacks?: { onSuccess?: () => void; onError?: (error: Error) => void }) => {
|
||||||
if (customCallbacks) {
|
if (callbacksRef && customCallbacks) {
|
||||||
callbacksRef.current = customCallbacks;
|
callbacksRef.current = customCallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,14 +350,14 @@ export const AvailabilitySettings = forwardRef<AvailabilitySettingsFormRef, Avai
|
|||||||
form.handleSubmit(async (data) => {
|
form.handleSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
await handleSubmit(data);
|
await handleSubmit(data);
|
||||||
callbacksRef.current?.onSuccess?.();
|
callbacksRef?.current?.onSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
callbacksRef.current?.onError?.(error as Error);
|
callbacksRef?.current?.onError?.(error as Error);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[form, handleSubmit]
|
[form, handleSubmit, callbacksRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
const validateForm = useCallback(async () => {
|
const validateForm = useCallback(async () => {
|
||||||
|
|||||||
+2
-1
@@ -39,7 +39,7 @@ export type AvailabilitySettingsPlatformWrapperProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AvailabilitySettingsPlatformWrapper = forwardRef<
|
export const AvailabilitySettingsPlatformWrapper = forwardRef<
|
||||||
AvailabilitySettingsFormRef,
|
AvailabilitySettingsFormRef,
|
||||||
AvailabilitySettingsPlatformWrapperProps
|
AvailabilitySettingsPlatformWrapperProps
|
||||||
>(function AvailabilitySettingsPlatformWrapper(props, ref) {
|
>(function AvailabilitySettingsPlatformWrapper(props, ref) {
|
||||||
const {
|
const {
|
||||||
@@ -202,6 +202,7 @@ AvailabilitySettingsFormRef,
|
|||||||
allowDelete={allowDelete}
|
allowDelete={allowDelete}
|
||||||
allowSetToDefault={allowSetToDefault}
|
allowSetToDefault={allowSetToDefault}
|
||||||
onFormStateChange={onFormStateChange}
|
onFormStateChange={onFormStateChange}
|
||||||
|
callbacksRef={callbacksRef}
|
||||||
/>
|
/>
|
||||||
</AtomsWrapper>
|
</AtomsWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Customizable UI components to integrate scheduling into your product.",
|
"description": "Customizable UI components to integrate scheduling into your product.",
|
||||||
"authors": "Cal.com, Inc.",
|
"authors": "Cal.com, Inc.",
|
||||||
"version": "1.4.0",
|
"version": "1.5.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
|
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
|
||||||
"build": "NODE_OPTIONS='--max_old_space_size=12288' rm -rf dist && yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
|
"build": "NODE_OPTIONS='--max_old_space_size=12288' rm -rf dist && yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
|
||||||
|
|||||||
+5
@@ -446,6 +446,11 @@ class BaseCreateEventTypeInput {
|
|||||||
type: CalVideoSettings,
|
type: CalVideoSettings,
|
||||||
})
|
})
|
||||||
calVideoSettings?: CalVideoSettings;
|
calVideoSettings?: CalVideoSettings;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
@DocsPropertyOptional()
|
||||||
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
export class CreateEventTypeInput_2024_06_14 extends BaseCreateEventTypeInput {
|
export class CreateEventTypeInput_2024_06_14 extends BaseCreateEventTypeInput {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
+5
@@ -414,6 +414,11 @@ class BaseUpdateEventTypeInput {
|
|||||||
type: CalVideoSettings,
|
type: CalVideoSettings,
|
||||||
})
|
})
|
||||||
calVideoSettings?: CalVideoSettings;
|
calVideoSettings?: CalVideoSettings;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
@DocsPropertyOptional()
|
||||||
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
export class UpdateEventTypeInput_2024_06_14 extends BaseUpdateEventTypeInput {
|
export class UpdateEventTypeInput_2024_06_14 extends BaseUpdateEventTypeInput {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
+5
@@ -439,6 +439,11 @@ class BaseEventTypeOutput_2024_06_14 {
|
|||||||
type: CalVideoSettings,
|
type: CalVideoSettings,
|
||||||
})
|
})
|
||||||
calVideoSettings?: CalVideoSettings | null;
|
calVideoSettings?: CalVideoSettings | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
@DocsProperty()
|
||||||
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamEventTypeResponseHost extends TeamEventTypeHostInput {
|
export class TeamEventTypeResponseHost extends TeamEventTypeHostInput {
|
||||||
|
|||||||
Binary file not shown.
@@ -90,6 +90,9 @@ type OmitPrismaClient = Omit<
|
|||||||
// we cant pass tx to functions as types miss match since we have a custom prisma client https://github.com/prisma/prisma/discussions/20924#discussioncomment-10077649
|
// we cant pass tx to functions as types miss match since we have a custom prisma client https://github.com/prisma/prisma/discussions/20924#discussioncomment-10077649
|
||||||
export type PrismaTransaction = OmitPrismaClient;
|
export type PrismaTransaction = OmitPrismaClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use named export `prisma` instead
|
||||||
|
*/
|
||||||
export default prisma;
|
export default prisma;
|
||||||
|
|
||||||
export * from "./selects";
|
export * from "./selects";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
|
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
@@ -19,8 +20,9 @@ const listHandler = async (opts: GetOptions) => {
|
|||||||
message: "You need to be apart of an organization to use this feature",
|
message: "You need to be apart of an organization to use this feature",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const attributeRepo = new PrismaAttributeRepository(prisma);
|
||||||
|
|
||||||
return await PrismaAttributeRepository.findAllByOrgIdWithOptions({ orgId: org.id });
|
return await attributeRepo.findAllByOrgIdWithOptions({ orgId: org.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default listHandler;
|
export default listHandler;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import { HostRepository } from "@calcom/lib/server/repository/host";
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
import type { TrpcSessionUser } from "../../../../types";
|
import type { TrpcSessionUser } from "../../../../types";
|
||||||
import { updateHostsWithNewDefaultSchedule } from "../util";
|
|
||||||
import type { TDeleteInputSchema } from "./delete.schema";
|
import type { TDeleteInputSchema } from "./delete.schema";
|
||||||
|
|
||||||
type DeleteOptions = {
|
type DeleteOptions = {
|
||||||
@@ -15,6 +15,7 @@ type DeleteOptions = {
|
|||||||
|
|
||||||
export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
|
export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
|
||||||
const { user } = ctx;
|
const { user } = ctx;
|
||||||
|
const hostRepo = new HostRepository(prisma);
|
||||||
|
|
||||||
const scheduleToDelete = await prisma.schedule.findUnique({
|
const scheduleToDelete = await prisma.schedule.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@@ -46,7 +47,7 @@ export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
|
|||||||
// to throw the error if there arent any other schedules
|
// to throw the error if there arent any other schedules
|
||||||
if (!scheduleToSetAsDefault) throw new TRPCError({ code: "BAD_REQUEST" });
|
if (!scheduleToSetAsDefault) throw new TRPCError({ code: "BAD_REQUEST" });
|
||||||
|
|
||||||
await updateHostsWithNewDefaultSchedule(user.id, input.scheduleId, scheduleToSetAsDefault.id);
|
await hostRepo.updateHostsSchedule(user.id, input.scheduleId, scheduleToSetAsDefault.id);
|
||||||
|
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
@@ -57,7 +58,7 @@ export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (user.defaultScheduleId) {
|
} else if (user.defaultScheduleId) {
|
||||||
await updateHostsWithNewDefaultSchedule(user.id, input.scheduleId, user.defaultScheduleId);
|
await hostRepo.updateHostsSchedule(user.id, input.scheduleId, user.defaultScheduleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.schedule.delete({
|
await prisma.schedule.delete({
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||||
|
|
||||||
|
import { HostRepository } from "@calcom/lib/server/repository/host";
|
||||||
import { PrismaClient } from "@calcom/prisma";
|
import { PrismaClient } from "@calcom/prisma";
|
||||||
|
|
||||||
import {
|
import { getDefaultScheduleId, hasDefaultSchedule, setupDefaultSchedule } from "./util";
|
||||||
getDefaultScheduleId,
|
|
||||||
hasDefaultSchedule,
|
|
||||||
setupDefaultSchedule,
|
|
||||||
updateHostsWithNewDefaultSchedule,
|
|
||||||
} from "./util";
|
|
||||||
|
|
||||||
vi.mock("@calcom/prisma", () => {
|
vi.mock("@calcom/prisma", () => {
|
||||||
const mockPrisma = {
|
const mockPrisma = {
|
||||||
@@ -31,9 +27,11 @@ vi.mock("@calcom/prisma", () => {
|
|||||||
|
|
||||||
describe("Availability Utils", () => {
|
describe("Availability Utils", () => {
|
||||||
let prisma: PrismaClient;
|
let prisma: PrismaClient;
|
||||||
|
let hostRepo: HostRepository;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
prisma = new PrismaClient();
|
prisma = new PrismaClient();
|
||||||
|
hostRepo = new HostRepository(prisma);
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -158,7 +156,7 @@ describe("Availability Utils", () => {
|
|||||||
|
|
||||||
(prisma.host.updateMany as any).mockResolvedValue(updateResult);
|
(prisma.host.updateMany as any).mockResolvedValue(updateResult);
|
||||||
|
|
||||||
const result = await updateHostsWithNewDefaultSchedule(userId, oldScheduleId, newScheduleId, prisma);
|
const result = await hostRepo.updateHostsSchedule(userId, oldScheduleId, newScheduleId);
|
||||||
|
|
||||||
expect(prisma.host.updateMany).toHaveBeenCalledWith({
|
expect(prisma.host.updateMany).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
@@ -180,7 +178,7 @@ describe("Availability Utils", () => {
|
|||||||
|
|
||||||
(prisma.host.updateMany as any).mockResolvedValue(updateResult);
|
(prisma.host.updateMany as any).mockResolvedValue(updateResult);
|
||||||
|
|
||||||
const result = await updateHostsWithNewDefaultSchedule(userId, oldScheduleId, newScheduleId, prisma);
|
const result = await hostRepo.updateHostsSchedule(userId, oldScheduleId, newScheduleId);
|
||||||
|
|
||||||
expect(prisma.host.updateMany).toHaveBeenCalledWith({
|
expect(prisma.host.updateMany).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { User } from "@prisma/client";
|
import type { User } from "@prisma/client";
|
||||||
|
|
||||||
import { HostRepository } from "@calcom/lib/server/repository/host";
|
|
||||||
import type { PrismaClient } from "@calcom/prisma";
|
import type { PrismaClient } from "@calcom/prisma";
|
||||||
|
|
||||||
export const getDefaultScheduleId = async (userId: number, prisma: PrismaClient) => {
|
export const getDefaultScheduleId = async (userId: number, prisma: PrismaClient) => {
|
||||||
@@ -54,11 +53,3 @@ export const setupDefaultSchedule = async (userId: number, scheduleId: number, p
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateHostsWithNewDefaultSchedule = async (
|
|
||||||
userId: number,
|
|
||||||
defaultScheduleId: number,
|
|
||||||
scheduleId: number
|
|
||||||
) => {
|
|
||||||
return await HostRepository.updateHostsSchedule(userId, defaultScheduleId, scheduleId);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -440,5 +440,63 @@ const checkIfUserIsAuthorizedToConfirmBooking = async ({
|
|||||||
if (membership) return;
|
if (membership) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bookingUserId && (await isLoggedInUserOrgAdminOfBookingUser(loggedInUserId, bookingUserId))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "User is not authorized to confirm this booking" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "User is not authorized to confirm this booking" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function isLoggedInUserOrgAdminOfBookingUser(loggedInUserId: number, bookingUserId: number) {
|
||||||
|
const orgIdsWhereLoggedInUserAdmin = await getOrgIdsWhereAdmin(loggedInUserId);
|
||||||
|
|
||||||
|
if (orgIdsWhereLoggedInUserAdmin.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookingUserOrgMembership = await prisma.membership.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: bookingUserId,
|
||||||
|
teamId: {
|
||||||
|
in: orgIdsWhereLoggedInUserAdmin,
|
||||||
|
},
|
||||||
|
team: {
|
||||||
|
parentId: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bookingUserOrgMembership) return true;
|
||||||
|
|
||||||
|
const bookingUserOrgTeamMembership = await prisma.membership.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: bookingUserId,
|
||||||
|
team: {
|
||||||
|
parentId: {
|
||||||
|
in: orgIdsWhereLoggedInUserAdmin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!bookingUserOrgTeamMembership;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOrgIdsWhereAdmin(loggedInUserId: number) {
|
||||||
|
const loggedInUserOrgMemberships = await prisma.membership.findMany({
|
||||||
|
where: {
|
||||||
|
userId: loggedInUserId,
|
||||||
|
role: {
|
||||||
|
in: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||||
|
},
|
||||||
|
team: {
|
||||||
|
parentId: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
teamId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return loggedInUserOrgMemberships.map((m) => m.teamId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export async function toggleDelegationCredentialEnabled(
|
|||||||
if (slug === "google") {
|
if (slug === "google") {
|
||||||
calendarAppName = "Google Calendar";
|
calendarAppName = "Google Calendar";
|
||||||
conferencingAppName = "Google Meet";
|
conferencingAppName = "Google Meet";
|
||||||
} else if (slug === "microsoft") {
|
} else if (slug === "office365") {
|
||||||
calendarAppName = "Microsoft 365";
|
calendarAppName = "Microsoft 365";
|
||||||
conferencingAppName = "Microsoft Teams";
|
conferencingAppName = "Microsoft Teams";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2500,7 +2500,7 @@ __metadata:
|
|||||||
"@axiomhq/winston": ^1.2.0
|
"@axiomhq/winston": ^1.2.0
|
||||||
"@calcom/platform-constants": "*"
|
"@calcom/platform-constants": "*"
|
||||||
"@calcom/platform-enums": "*"
|
"@calcom/platform-enums": "*"
|
||||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.314"
|
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.318"
|
||||||
"@calcom/platform-types": "*"
|
"@calcom/platform-types": "*"
|
||||||
"@calcom/platform-utils": "*"
|
"@calcom/platform-utils": "*"
|
||||||
"@calcom/prisma": "*"
|
"@calcom/prisma": "*"
|
||||||
@@ -3559,13 +3559,13 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.314":
|
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.318":
|
||||||
version: 0.0.314
|
version: 0.0.318
|
||||||
resolution: "@calcom/platform-libraries@npm:0.0.314"
|
resolution: "@calcom/platform-libraries@npm:0.0.318"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@calcom/features": "*"
|
"@calcom/features": "*"
|
||||||
"@calcom/lib": "*"
|
"@calcom/lib": "*"
|
||||||
checksum: b59eb591273334ed7d7d07a8f0518eb8cb40255883dff49626f85d3bd804db4da357bf77cd4f7b14ae0743d0a699647232b39956af9123bdc5e7b2458cab30eb
|
checksum: a83dcb75ca855232a676a95bc57db0b4bff98b0b85aaa67df7af9b761278b21fcba47142c03ca03217aaa1fd71513f6440ee9ecae94ab39b29f80ff840dbe9d7
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user