Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9ee907245 | ||
|
|
eac6b12e91 | ||
|
|
1dc63e0df7 | ||
|
|
96cd3f7b2f | ||
|
|
7943a83e7d | ||
|
|
01c16ad150 | ||
|
|
42807481c1 | ||
|
|
032c41fefd | ||
|
|
41041226b4 | ||
|
|
56ab4e8415 | ||
|
|
8ee6d2befa | ||
|
|
9cd133ff10 | ||
|
|
6df473c5fb | ||
|
|
4606fc6ace | ||
|
|
78ddf71823 | ||
|
|
15cb687307 | ||
|
|
c0ecfcd910 | ||
|
|
2e86f8404f | ||
|
|
b94b16316f | ||
|
|
f80b780aae | ||
|
|
00dc467f25 | ||
|
|
d644c09bac | ||
|
|
5d9c7bfde9 | ||
|
|
cfd1c5dfe3 | ||
|
|
d7e5a8e588 | ||
|
|
3b992fa9bd | ||
|
|
8ad442f2be | ||
|
|
695a4d7a59 | ||
|
|
9bcea0fa01 | ||
|
|
9a4c8c147b | ||
|
|
a48ad2219f | ||
|
|
55a97b71c8 | ||
|
|
b1b8b0135e | ||
|
|
08d4c800e9 | ||
|
|
efeb0a8f41 | ||
|
|
207147f5ff | ||
|
|
6bb8a7c6a9 | ||
|
|
aa7f9719c9 | ||
|
|
e045dcff11 | ||
|
|
80eb6daf1f | ||
|
|
b0bf274896 | ||
|
|
4762d475cb | ||
|
|
c7d9c63390 | ||
|
|
853f9bc436 | ||
|
|
ca19aa00c1 | ||
|
|
467c979aa7 | ||
|
|
9f9bf10040 | ||
|
|
8102192987 | ||
|
|
232f9d288c | ||
|
|
7dab100382 | ||
|
|
91d4f1c219 | ||
|
|
a64e7f9c2a | ||
|
|
3c1297aa72 | ||
|
|
96f7a01c5f | ||
|
|
a4a05878ef | ||
|
|
352d68ae1c | ||
|
|
ffd034679c | ||
|
|
d64ae3c901 | ||
|
|
7250e22257 | ||
|
|
f459e5d9c4 | ||
|
|
30bb0f1383 | ||
|
|
24c993a3d2 | ||
|
|
aa4276be5b | ||
|
|
413aba6e52 | ||
|
|
c2547acb3d | ||
|
|
cc1b6bac4a | ||
|
|
d5d90a8b87 | ||
|
|
40333c81c5 | ||
|
|
ff92979311 | ||
|
|
f63edef48d | ||
|
|
37e56ebbf9 | ||
|
|
07c38f1f33 | ||
|
|
192238f4bf | ||
|
|
94a26031a8 | ||
|
|
e9f20b1a0b | ||
|
|
7cb9e2b99b | ||
|
|
dd45d8cdbc | ||
|
|
3a181f1dac | ||
|
|
c3abfa51ba | ||
|
|
6498af167a | ||
|
|
30dce7bacd | ||
|
|
3be9e1bd38 | ||
|
|
db52f44293 | ||
|
|
20d6eb5200 | ||
|
|
424d94f16d |
@@ -357,13 +357,6 @@ E2E_TEST_OIDC_USER_PASSWORD=
|
||||
|
||||
# ***********************************************************************************************************
|
||||
|
||||
# provide a value between 0 and 100 to ensure the percentage of traffic
|
||||
# redirected from the legacy to the future pages
|
||||
AB_TEST_BUCKET_PROBABILITY=50
|
||||
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
|
||||
APP_ROUTER_APPS_ENABLED=0
|
||||
APP_ROUTER_TEAM_ENABLED=0
|
||||
|
||||
# disable setry server source maps
|
||||
SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1
|
||||
|
||||
|
||||
@@ -14,4 +14,4 @@ jobs:
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- name: Run Lint
|
||||
run: yarn lint
|
||||
continue-on-error: true
|
||||
continue-on-error: false
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
undecided:
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- "@calcom/prisma"
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- "@calcom/prisma"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/prisma"
|
||||
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.151",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.152",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { BookingsController_2024_04_15 } from "@/ee/bookings/2024-04-15/controllers/bookings.controller";
|
||||
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
|
||||
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { BillingModule } from "@/modules/billing/billing.module";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { TokensRepository } from "@/modules/tokens/tokens.repository";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
@@ -33,6 +38,11 @@ import { Module } from "@nestjs/common";
|
||||
ApiKeysRepository,
|
||||
OAuthClientUsersService,
|
||||
PlatformBookingsService,
|
||||
CalendarsService,
|
||||
CredentialsRepository,
|
||||
AppsRepository,
|
||||
CalendarsRepository,
|
||||
SelectedCalendarsRepository,
|
||||
],
|
||||
controllers: [BookingsController_2024_04_15],
|
||||
})
|
||||
|
||||
@@ -351,6 +351,64 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a booking with split name", async () => {
|
||||
const bookingStart = "2040-05-21T11:30:00.000Z";
|
||||
const bookingEnd = "2040-05-21T12:30:00.000Z";
|
||||
const bookingEventTypeId = eventTypeId;
|
||||
const bookingTimeZone = "Europe/London";
|
||||
const bookingLanguage = "en";
|
||||
const bookingHashedLink = "";
|
||||
const bookingMetadata = {
|
||||
timeFormat: "12",
|
||||
meetingType: "organizer-phone",
|
||||
};
|
||||
const bookingResponses = {
|
||||
name: { firstName: "John", lastName: "Doe" },
|
||||
email: "tester@example.com",
|
||||
location: {
|
||||
value: "link",
|
||||
optionValue: "",
|
||||
},
|
||||
notes: "test",
|
||||
};
|
||||
|
||||
const body: CreateBookingInput_2024_04_15 = {
|
||||
start: bookingStart,
|
||||
end: bookingEnd,
|
||||
eventTypeId: bookingEventTypeId,
|
||||
timeZone: bookingTimeZone,
|
||||
language: bookingLanguage,
|
||||
metadata: bookingMetadata,
|
||||
hashedLink: bookingHashedLink,
|
||||
responses: bookingResponses,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<Awaited<ReturnType<typeof handleNewBooking>>> =
|
||||
response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.userPrimaryEmail).toBeDefined();
|
||||
expect(responseBody.data.userPrimaryEmail).toEqual(userEmail);
|
||||
expect(responseBody.data.id).toBeDefined();
|
||||
expect(responseBody.data.uid).toBeDefined();
|
||||
expect(responseBody.data.startTime).toEqual(bookingStart);
|
||||
expect(responseBody.data.eventTypeId).toEqual(bookingEventTypeId);
|
||||
expect(responseBody.data.user.timeZone).toEqual(bookingTimeZone);
|
||||
expect(responseBody.data.metadata).toEqual(bookingMetadata);
|
||||
expect(responseBody.data.responses).toEqual({
|
||||
...bookingResponses,
|
||||
name: `${bookingResponses.name.firstName} ${bookingResponses.name.lastName}`,
|
||||
});
|
||||
|
||||
createdBooking = responseBody.data;
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
|
||||
|
||||
@@ -12,9 +12,42 @@ import {
|
||||
isEmail,
|
||||
Validate,
|
||||
} from "class-validator";
|
||||
import { ValidationOptions, registerDecorator } from "class-validator";
|
||||
|
||||
import { RESCHEDULED_BY_DOCS } from "@calcom/platform-types";
|
||||
|
||||
type BookingName = { firstName: string; lastName: string };
|
||||
|
||||
function ValidateBookingName(validationOptions?: ValidationOptions) {
|
||||
return function (target: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: "validateBookingName",
|
||||
target: target.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any) {
|
||||
if (typeof value === "string") {
|
||||
return value.trim().length > 0;
|
||||
}
|
||||
if (typeof value === "object" && value !== null) {
|
||||
return (
|
||||
typeof value.firstName === "string" &&
|
||||
typeof value.lastName === "string" &&
|
||||
value.firstName.trim().length > 0 &&
|
||||
value.lastName.trim().length > 0
|
||||
);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
defaultMessage(): string {
|
||||
return "Name must be either a non-empty string or an object with non-empty firstName and lastName";
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
class Location {
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
@@ -26,9 +59,14 @@ class Location {
|
||||
}
|
||||
|
||||
class Response {
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
name!: string;
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ type: "string" },
|
||||
{ type: "object", properties: { firstName: { type: "string" }, lastName: { type: "string" } } },
|
||||
],
|
||||
})
|
||||
@ValidateBookingName()
|
||||
name!: string | BookingName;
|
||||
|
||||
@Validate((value: string) => !value || isEmail(value), {
|
||||
message: "Invalid response email",
|
||||
|
||||
@@ -4,19 +4,24 @@ import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bo
|
||||
import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/input.service";
|
||||
import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service";
|
||||
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
|
||||
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { BillingModule } from "@/modules/billing/billing.module";
|
||||
import { BookingSeatModule } from "@/modules/booking-seat/booking-seat.module";
|
||||
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { TokensRepository } from "@/modules/tokens/tokens.repository";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
@@ -47,6 +52,11 @@ import { Module } from "@nestjs/common";
|
||||
BookingSeatRepository,
|
||||
ApiKeysRepository,
|
||||
PlatformBookingsService,
|
||||
CalendarsService,
|
||||
CredentialsRepository,
|
||||
AppsRepository,
|
||||
CalendarsRepository,
|
||||
SelectedCalendarsRepository,
|
||||
],
|
||||
controllers: [BookingsController_2024_08_13],
|
||||
exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13],
|
||||
|
||||
@@ -434,7 +434,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
}
|
||||
|
||||
describe("get individual booking", () => {
|
||||
it("should should get a seated booking with split name responses", async () => {
|
||||
it("should get a seated booking with split name responses", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${bookingWithSplitName.uid}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -457,7 +457,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get a seated booking with split name responses", async () => {
|
||||
it("should get a seated booking with split name responses", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${seatedBookingWithSplitName.uid}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import { PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
|
||||
@@ -66,6 +68,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const team2EventTypeSlug = `team-bookings-event-type-${randomString()}`;
|
||||
const phoneOnlyEventTypeSlug = `team-bookings-event-type-${randomString()}`;
|
||||
|
||||
let phoneBasedBooking: BookingOutput_2024_08_13;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
teamUserEmail,
|
||||
@@ -408,12 +412,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
it("should create a phone based booking", async () => {
|
||||
const phoneNumber = "+919876543210";
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 15, 0, 0)).toISOString(),
|
||||
eventTypeId: phoneOnlyEventTypeId,
|
||||
attendee: {
|
||||
name: "alice",
|
||||
phoneNumber: "+919876543210",
|
||||
phoneNumber,
|
||||
timeZone: "Europe/Madrid",
|
||||
language: "es",
|
||||
},
|
||||
@@ -453,6 +458,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
expect(data.meetingUrl).toEqual(body.meetingUrl);
|
||||
expect(data.absentHost).toEqual(false);
|
||||
expect(data.bookingFieldsResponses.attendeePhoneNumber).toEqual(phoneNumber);
|
||||
phoneBasedBooking = data;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibly recurring bookings"
|
||||
@@ -523,7 +530,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
describe("get team bookings", () => {
|
||||
it("should should get bookings by teamId", async () => {
|
||||
it("should get bookings by teamId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?teamId=${team1.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -542,7 +549,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by teamId", async () => {
|
||||
it("should get bookings by teamId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?teamId=${team2.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -598,7 +605,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by teamIds", async () => {
|
||||
it("should get bookings by teamIds", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?teamIds=${team1.id},${team2.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -619,6 +626,59 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("reschedule", () => {
|
||||
it("should reschedule phone based booking", async () => {
|
||||
const body: RescheduleBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2035, 0, 8, 14, 0, 0)).toISOString(),
|
||||
reschedulingReason: "Flying to mars that day",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${phoneBasedBooking.uid}/reschedule`)
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseDataIsBooking(responseBody.data)).toBe(true);
|
||||
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts.length).toEqual(1);
|
||||
expect(data.hosts[0].id).toEqual(teamUser.id);
|
||||
expect(data.status).toEqual("accepted");
|
||||
expect(data.start).toEqual(body.start);
|
||||
expect(data.end).toEqual(new Date(Date.UTC(2035, 0, 8, 14, 15, 0)).toISOString());
|
||||
expect(data.duration).toEqual(15);
|
||||
expect(data.eventTypeId).toEqual(phoneOnlyEventTypeId);
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: phoneBasedBooking.attendees[0].name,
|
||||
email: phoneBasedBooking.attendees[0].email,
|
||||
phoneNumber: phoneBasedBooking.attendees[0].phoneNumber,
|
||||
timeZone: phoneBasedBooking.attendees[0].timeZone,
|
||||
language: phoneBasedBooking.attendees[0].language,
|
||||
absent: false,
|
||||
});
|
||||
expect(data.meetingUrl).toEqual(phoneBasedBooking.meetingUrl);
|
||||
expect(data.absentHost).toEqual(false);
|
||||
expect(data.bookingFieldsResponses.attendeePhoneNumber).toEqual(
|
||||
phoneBasedBooking.bookingFieldsResponses.attendeePhoneNumber
|
||||
);
|
||||
phoneBasedBooking = data;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibly recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function createOAuthClient(organizationId: number) {
|
||||
const data = {
|
||||
logo: "logo-url",
|
||||
|
||||
@@ -527,7 +527,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
describe("get individual booking", () => {
|
||||
it("should should get a booking", async () => {
|
||||
it("should get a booking", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${createdBooking.uid}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -562,7 +562,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get a booking with rating", async () => {
|
||||
it("should get a booking with rating", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${bookingInThePast.uid}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -586,7 +586,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get 1 recurrence of a recurring booking", async () => {
|
||||
it("should get 1 recurrence of a recurring booking", async () => {
|
||||
const recurrenceUid = createdRecurringBooking[0].uid;
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${recurrenceUid}`)
|
||||
@@ -622,7 +622,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get all recurrences of the recurring bookings", async () => {
|
||||
it("should get all recurrences of the recurring bookings", async () => {
|
||||
const recurringBookingUid = createdRecurringBooking[0].recurringBookingUid;
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${recurringBookingUid}`)
|
||||
@@ -691,7 +691,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
describe("get bookings", () => {
|
||||
it("should should get all bookings", async () => {
|
||||
it("should get all bookings", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -709,7 +709,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should take bookings", async () => {
|
||||
it("should take bookings", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?take=3`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -727,7 +727,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should skip bookings", async () => {
|
||||
it("should skip bookings", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?skip=2`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -745,7 +745,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get upcoming bookings", async () => {
|
||||
it("should get upcoming bookings", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?status=upcoming`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -763,7 +763,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get past bookings", async () => {
|
||||
it("should get past bookings", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?status=past`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -781,7 +781,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get upcoming and past bookings", async () => {
|
||||
it("should get upcoming and past bookings", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?status=upcoming,past`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -799,7 +799,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get recurring booking recurrences", async () => {
|
||||
it("should get recurring booking recurrences", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?status=recurring`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -817,7 +817,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by attendee email", async () => {
|
||||
it("should get bookings by attendee email", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?attendeeEmail=mr_proper@gmail.com`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -835,7 +835,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by attendee name", async () => {
|
||||
it("should get bookings by attendee name", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?attendeeName=Mr Proper Recurring`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -853,7 +853,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by eventTypeId", async () => {
|
||||
it("should get bookings by eventTypeId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -871,7 +871,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by eventTypeIds", async () => {
|
||||
it("should get bookings by eventTypeIds", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeIds=${eventTypeId},${recurringEventTypeId}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -889,7 +889,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by after specified start time", async () => {
|
||||
it("should get bookings by after specified start time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?afterStart=${createdRecurringBooking[1].start}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -907,7 +907,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by before specified end time", async () => {
|
||||
it("should get bookings by before specified end time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?beforeEnd=${createdRecurringBooking[0].end}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -925,7 +925,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings after specified update time", async () => {
|
||||
it("should get bookings after specified update time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?afterUpdatedAt=${createdRecurringBooking[1].updatedAt}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -943,7 +943,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings before specified update time", async () => {
|
||||
it("should get bookings before specified update time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?beforeUpdatedAt=${createdRecurringBooking[0].updatedAt}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -961,7 +961,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings in update range", async () => {
|
||||
it("should get bookings in update range", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(
|
||||
`/v2/bookings?afterUpdatedAt=${createdRecurringBooking[0].updatedAt}&beforeUpdatedAt=${createdRecurringBooking[2].updatedAt}`
|
||||
@@ -982,7 +982,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
describe("createdAt filter", () => {
|
||||
it("should should get bookings after specified createdAt time", async () => {
|
||||
it("should get bookings after specified createdAt time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?afterCreatedAt=${createdRecurringBooking[1].createdAt}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1000,7 +1000,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings before specified createdAt time", async () => {
|
||||
it("should get bookings before specified createdAt time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?beforeCreatedAt=${createdRecurringBooking[0].createdAt}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1018,7 +1018,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings in createdAt range", async () => {
|
||||
it("should get bookings in createdAt range", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(
|
||||
`/v2/bookings?afterCreatedAt=${createdRecurringBooking[0].createdAt}&beforeCreatedAt=${createdRecurringBooking[1].createdAt}`
|
||||
@@ -1039,7 +1039,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by start in descending order", async () => {
|
||||
it("should sort bookings by start in descending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortStart=desc`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1059,7 +1059,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by start in ascending order", async () => {
|
||||
it("should sort bookings by start in ascending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortStart=asc`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1079,7 +1079,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by end in descending order", async () => {
|
||||
it("should sort bookings by end in descending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortEnd=desc`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1099,7 +1099,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by end in ascending order", async () => {
|
||||
it("should sort bookings by end in ascending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortEnd=asc`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1119,7 +1119,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by created in descending order", async () => {
|
||||
it("should sort bookings by created in descending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortCreated=desc`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1139,7 +1139,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by created in ascending order", async () => {
|
||||
it("should sort bookings by created in ascending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortCreated=asc`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1159,7 +1159,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by updated in descending order", async () => {
|
||||
it("should sort bookings by updated in descending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortUpdatedAt=desc`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1179,7 +1179,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by updated in ascending order", async () => {
|
||||
it("should sort bookings by updated in ascending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortUpdatedAt=asc`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
@@ -1201,7 +1201,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
describe("reschedule bookings", () => {
|
||||
it("should should reschedule normal booking", async () => {
|
||||
it("should reschedule normal booking", async () => {
|
||||
const body: RescheduleBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2035, 0, 8, 14, 0, 0)).toISOString(),
|
||||
reschedulingReason: "Flying to mars that day",
|
||||
@@ -1949,6 +1949,95 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("rescheduling booking with invalid booking responses", () => {
|
||||
it("should reschedule", async () => {
|
||||
const email = `user-bookings-attendee-${randomString(10)}@gmail.com`;
|
||||
const username = `user-bookings-attendee-${randomString(10)}`;
|
||||
const locale = "it";
|
||||
const timeZone = "Europe/Rome";
|
||||
const attendee = await userRepositoryFixture.create({
|
||||
email,
|
||||
username,
|
||||
locale,
|
||||
timeZone,
|
||||
});
|
||||
|
||||
const booking = await bookingsRepositoryFixture.create({
|
||||
uid: `booking-uid-${randomString(10)}`,
|
||||
title: "booking title",
|
||||
startTime: "2060-09-05T11:00:00.000Z",
|
||||
endTime: "2060-09-05T12:00:00.000Z",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
},
|
||||
status: "ACCEPTED",
|
||||
metadata: {},
|
||||
responses: "null",
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
attendees: {
|
||||
create: {
|
||||
email: email,
|
||||
name: username,
|
||||
timeZone,
|
||||
locale,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const body: RescheduleBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2060, 0, 9, 14, 0, 0)).toISOString(),
|
||||
reschedulingReason: "Flying to mars that day",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${booking.uid}/reschedule`)
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.reschedulingReason).toEqual(body.reschedulingReason);
|
||||
expect(data.start).toEqual(body.start);
|
||||
expect(data.end).toEqual(new Date(Date.UTC(2060, 0, 9, 15, 0, 0)).toISOString());
|
||||
expect(data.rescheduledFromUid).toEqual(booking.uid);
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual("accepted");
|
||||
expect(data.eventTypeId).toEqual(booking.eventTypeId);
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: attendee.username,
|
||||
email: attendee.email,
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: false,
|
||||
});
|
||||
expect(data.bookingFieldsResponses).toEqual({
|
||||
name: attendee.username,
|
||||
email: attendee.email,
|
||||
rescheduledReason: body.reschedulingReason,
|
||||
guests: [],
|
||||
});
|
||||
await bookingsRepositoryFixture.deleteById(data.id);
|
||||
await userRepositoryFixture.delete(attendee.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function responseDataIsRecurranceBooking(data: any): data is RecurringBookingOutput_2024_08_13 {
|
||||
return (
|
||||
!Array.isArray(data) &&
|
||||
|
||||
@@ -7,6 +7,8 @@ import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/output-event-types.service";
|
||||
import { hashAPIKey, isApiKey, stripApiKey } from "@/lib/api-key";
|
||||
import { defaultBookingResponses } from "@/lib/safe-parse/default-responses-booking";
|
||||
import { safeParse } from "@/lib/safe-parse/safe-parse";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
@@ -41,7 +43,7 @@ import {
|
||||
RescheduleSeatedBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import { BookingInputLocation_2024_08_13 } from "@calcom/platform-types/bookings/2024-08-13/inputs/location.input";
|
||||
import { EventType, PlatformOAuthClient } from "@calcom/prisma/client";
|
||||
import { EventType } from "@calcom/prisma/client";
|
||||
|
||||
type BookingRequest = NextApiRequest & { userId: number | undefined } & OAuthRequestParams;
|
||||
|
||||
@@ -429,6 +431,7 @@ export class InputBookingsService_2024_08_13 {
|
||||
...(inputBooking.bookingFieldsResponses || {}),
|
||||
name: inputBooking.attendee.name,
|
||||
email: attendeeEmail,
|
||||
attendeePhoneNumber: inputBooking.attendee.phoneNumber,
|
||||
guests,
|
||||
location,
|
||||
},
|
||||
@@ -567,8 +570,19 @@ export class InputBookingsService_2024_08_13 {
|
||||
throw new NotFoundException(`Event type with id=${booking.eventTypeId} not found`);
|
||||
}
|
||||
|
||||
const bookingResponses = bookingResponsesSchema.parse(booking.responses);
|
||||
const attendee = booking.attendees.find((attendee) => attendee.email === bookingResponses.email);
|
||||
const bookingResponses = safeParse(
|
||||
bookingResponsesSchema,
|
||||
booking.responses,
|
||||
defaultBookingResponses,
|
||||
false
|
||||
);
|
||||
const bookingResponsesMissing =
|
||||
bookingResponses.name === defaultBookingResponses.name &&
|
||||
bookingResponses.email === defaultBookingResponses.email;
|
||||
|
||||
const attendee = bookingResponsesMissing
|
||||
? booking.attendees[0]
|
||||
: booking.attendees.find((attendee) => attendee.email === bookingResponses.email);
|
||||
|
||||
if (!attendee) {
|
||||
throw new NotFoundException(
|
||||
@@ -576,6 +590,12 @@ export class InputBookingsService_2024_08_13 {
|
||||
);
|
||||
}
|
||||
|
||||
if (bookingResponsesMissing) {
|
||||
bookingResponses.name = attendee.name;
|
||||
bookingResponses.email = attendee.email;
|
||||
bookingResponses.attendeePhoneNumber = attendee.phoneNumber || undefined;
|
||||
}
|
||||
|
||||
const startTime = DateTime.fromISO(inputBooking.start, { zone: "utc" }).setZone(attendee.timeZone);
|
||||
const endTime = startTime.plus({ minutes: eventType.length });
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { Booking, BookingSeat } from "@calcom/prisma/client";
|
||||
export const bookingResponsesSchema = z
|
||||
.object({
|
||||
email: z.string(),
|
||||
attendeePhoneNumber: z.string().optional(),
|
||||
name: z.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
@@ -45,6 +46,7 @@ export const seatedBookingDataSchema = z
|
||||
responses: z
|
||||
.object({
|
||||
email: z.string(),
|
||||
attendeePhoneNumber: z.string().optional(),
|
||||
name: z.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
|
||||
@@ -42,6 +42,10 @@ export class PlatformBookingsService {
|
||||
} else if (eventType?.teamId) {
|
||||
oAuthClient = await this.oAuthClientRepository.getByTeamId(eventType.teamId);
|
||||
}
|
||||
// Last resort check the hosts of the event-type
|
||||
if (!oAuthClient && eventType?.teamId) {
|
||||
oAuthClient = await this.oAuthClientRepository.getByEventTypeHosts(eventTypeId);
|
||||
}
|
||||
|
||||
if (oAuthClient) {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { Process, Processor } from "@nestjs/bull";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { Job } from "bull";
|
||||
|
||||
export const DEFAULT_CALENDARS_JOB = "default_calendars_job";
|
||||
export const CALENDARS_QUEUE = "calendars";
|
||||
export type DefaultCalendarsJobDataType = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
@Processor(CALENDARS_QUEUE)
|
||||
export class CalendarsProcessor {
|
||||
private readonly logger = new Logger(CalendarsProcessor.name);
|
||||
|
||||
constructor(public readonly calendarsService: CalendarsService) {}
|
||||
|
||||
@Process(DEFAULT_CALENDARS_JOB)
|
||||
async handleEnsureDefaultCalendars(job: Job<DefaultCalendarsJobDataType>) {
|
||||
const { userId } = job.data;
|
||||
try {
|
||||
// getCalendars calls getConnectedDestinationCalendarsAndEnsureDefaultsInDb from platform libraries
|
||||
// which gets the calendars from third party providers and ensure default calendars are set in DB
|
||||
await this.calendarsService.getCalendars(userId);
|
||||
} catch (err) {
|
||||
this.logger.error(`Failed to load default calendars of user with id: ${userId}`, {
|
||||
userId,
|
||||
err,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
+50
-1
@@ -116,6 +116,7 @@ describe("Event types Endpoints", () => {
|
||||
slug: "email",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldLocation = {
|
||||
@@ -336,6 +337,46 @@ describe("Event types Endpoints", () => {
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should not be able to create phone-only event type", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Phone coding consultation",
|
||||
slug: "phone-coding-consultation",
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
type: "email",
|
||||
required: false,
|
||||
label: "Email",
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: true,
|
||||
label: "Phone number",
|
||||
hidden: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.error.message).toBe(
|
||||
"checkIsEmailUserAccessible - Email booking field must be required and visible"
|
||||
);
|
||||
});
|
||||
|
||||
it("should create an event type", async () => {
|
||||
const nameBookingField: NameDefaultFieldInput_2024_06_14 = {
|
||||
type: "name",
|
||||
@@ -1191,7 +1232,14 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
const expectedReturnSystemFields = [
|
||||
{ isDefault: true, required: true, slug: "name", type: "name", disableOnPrefill: false },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
|
||||
{
|
||||
isDefault: true,
|
||||
required: true,
|
||||
slug: "email",
|
||||
type: "email",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
@@ -1644,6 +1692,7 @@ describe("Event types Endpoints", () => {
|
||||
slug: "email",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
updateEventType,
|
||||
getEventTypesPublic,
|
||||
EventTypesPublic,
|
||||
SystemField,
|
||||
CustomField,
|
||||
} from "@calcom/platform-libraries/event-types";
|
||||
import { GetEventTypesQuery_2024_06_14, InputEventTransformed_2024_06_14 } from "@calcom/platform-types";
|
||||
import { EventType } from "@calcom/prisma/client";
|
||||
@@ -31,6 +33,9 @@ export class EventTypesService_2024_06_14 {
|
||||
) {}
|
||||
|
||||
async createUserEventType(user: UserWithProfile, body: InputEventTransformed_2024_06_14) {
|
||||
if (body.bookingFields) {
|
||||
this.checkHasUserAccessibleEmailBookingField(body.bookingFields);
|
||||
}
|
||||
await this.checkCanCreateEventType(user.id, body);
|
||||
const eventTypeUser = await this.getUserToCreateEvent(user);
|
||||
const { destinationCalendar: _destinationCalendar, ...rest } = body;
|
||||
@@ -78,6 +83,16 @@ export class EventTypesService_2024_06_14 {
|
||||
await this.checkUserOwnsSchedule(userId, body.scheduleId);
|
||||
}
|
||||
|
||||
checkHasUserAccessibleEmailBookingField(bookingFields: (SystemField | CustomField)[]) {
|
||||
const emailField = bookingFields.find((field) => field.type === "email" && field.name === "email");
|
||||
const isEmailFieldRequiredAndVisible = emailField?.required && !emailField?.hidden;
|
||||
if (!isEmailFieldRequiredAndVisible) {
|
||||
throw new BadRequestException(
|
||||
"checkIsEmailUserAccessible - Email booking field must be required and visible"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getEventTypeByUsernameAndSlug(
|
||||
username: string,
|
||||
eventTypeSlug: string,
|
||||
@@ -219,6 +234,9 @@ export class EventTypesService_2024_06_14 {
|
||||
body: Partial<InputEventTransformed_2024_06_14>,
|
||||
user: UserWithProfile
|
||||
) {
|
||||
if (body.bookingFields) {
|
||||
this.checkHasUserAccessibleEmailBookingField(body.bookingFields);
|
||||
}
|
||||
await this.checkCanUpdateEventType(user.id, eventTypeId, body.scheduleId);
|
||||
const eventTypeUser = await this.getUserToUpdateEvent(user);
|
||||
|
||||
|
||||
+25
-14
@@ -37,8 +37,6 @@ import {
|
||||
} from "@calcom/platform-types";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { OutputEventTypesService_2024_06_14 } from "./output-event-types.service";
|
||||
|
||||
interface ValidationContext {
|
||||
eventTypeId?: number;
|
||||
seatsPerTimeSlot?: number | null;
|
||||
@@ -51,7 +49,6 @@ interface ValidationContext {
|
||||
export class InputEventTypesService_2024_06_14 {
|
||||
constructor(
|
||||
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
|
||||
private readonly outputEventTypesService: OutputEventTypesService_2024_06_14,
|
||||
private readonly calendarsService: CalendarsService
|
||||
) {}
|
||||
|
||||
@@ -128,12 +125,11 @@ export class InputEventTypesService_2024_06_14 {
|
||||
} = inputEventType;
|
||||
const confirmationPolicyTransformed = this.transformInputConfirmationPolicy(confirmationPolicy);
|
||||
|
||||
const hasMultipleLocations = (locations || defaultLocations).length > 1;
|
||||
const eventType = {
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: this.transformInputLocations(locations || defaultLocations),
|
||||
bookingFields: this.transformInputBookingFields(bookingFields, hasMultipleLocations),
|
||||
bookingFields: this.transformInputBookingFields(bookingFields),
|
||||
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
|
||||
durationLimits: bookingLimitsDuration
|
||||
? this.transformInputIntervalLimits(bookingLimitsDuration)
|
||||
@@ -182,15 +178,12 @@ export class InputEventTypesService_2024_06_14 {
|
||||
: {};
|
||||
|
||||
const confirmationPolicyTransformed = this.transformInputConfirmationPolicy(confirmationPolicy);
|
||||
const hasMultipleLocations = !!(locations && locations?.length > 1);
|
||||
|
||||
const eventType = {
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: locations ? this.transformInputLocations(locations) : undefined,
|
||||
bookingFields: bookingFields
|
||||
? this.transformInputBookingFields(bookingFields, hasMultipleLocations)
|
||||
: undefined,
|
||||
bookingFields: bookingFields ? this.transformInputBookingFields(bookingFields) : undefined,
|
||||
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
|
||||
durationLimits: bookingLimitsDuration
|
||||
? this.transformInputIntervalLimits(bookingLimitsDuration)
|
||||
@@ -220,10 +213,7 @@ export class InputEventTypesService_2024_06_14 {
|
||||
return transformLocationsApiToInternal(inputLocations);
|
||||
}
|
||||
|
||||
transformInputBookingFields(
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"],
|
||||
hasMultipleLocations: boolean
|
||||
) {
|
||||
transformInputBookingFields(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]) {
|
||||
const internalFields: (SystemField | CustomField)[] = inputBookingFields
|
||||
? transformBookingFieldsApiToInternal(inputBookingFields)
|
||||
: [];
|
||||
@@ -252,7 +242,28 @@ export class InputEventTypesService_2024_06_14 {
|
||||
systemCustomRescheduleReasonField || systemAfterFieldRescheduleReason,
|
||||
];
|
||||
|
||||
return [...defaultFieldsBefore, ...userCustomFields, ...defaultFieldsAfter];
|
||||
const bookingFields = [...defaultFieldsBefore, ...userCustomFields, ...defaultFieldsAfter];
|
||||
|
||||
if (!this.hasEmailOrPhoneOnlySetup(bookingFields)) {
|
||||
throw new BadRequestException(
|
||||
"Booking fields validation failed: visible and required email or visible and required attendee phone field is needed."
|
||||
);
|
||||
}
|
||||
|
||||
return bookingFields;
|
||||
}
|
||||
|
||||
hasEmailOrPhoneOnlySetup(bookingFields: (SystemField | CustomField)[]) {
|
||||
const emailField = bookingFields.find((field) => field.type === "email" && field.name === "email");
|
||||
const attendeePhoneNumberField = bookingFields.find(
|
||||
(field) => field.type === "phone" && field.name === "attendeePhoneNumber"
|
||||
);
|
||||
|
||||
const isEmailFieldRequiredAndVisible = emailField?.required && !emailField?.hidden;
|
||||
const isAttendeePhoneNumberFieldRequiredAndVisible =
|
||||
attendeePhoneNumberField?.required && !attendeePhoneNumberField?.hidden;
|
||||
|
||||
return isEmailFieldRequiredAndVisible || isAttendeePhoneNumberFieldRequiredAndVisible;
|
||||
}
|
||||
|
||||
isUserCustomField(field: SystemField | CustomField): field is CustomField {
|
||||
|
||||
@@ -16,29 +16,74 @@ export class TRPCExceptionFilter implements ExceptionFilter {
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
let statusCode = 500;
|
||||
let errorMessage = exception.message;
|
||||
|
||||
switch (exception.code) {
|
||||
case "UNAUTHORIZED":
|
||||
statusCode = 401;
|
||||
errorMessage = "You are not authorized to access this resource";
|
||||
break;
|
||||
case "FORBIDDEN":
|
||||
statusCode = 403;
|
||||
errorMessage = "You don't have necessary permissions to access this resource";
|
||||
break;
|
||||
case "NOT_FOUND":
|
||||
statusCode = 404;
|
||||
errorMessage = "The requested resource was not found";
|
||||
break;
|
||||
case "INTERNAL_SERVER_ERROR":
|
||||
statusCode = 500;
|
||||
errorMessage = "An unexpected error occurred on the server. Please try again later";
|
||||
break;
|
||||
case "BAD_REQUEST":
|
||||
statusCode = 400;
|
||||
errorMessage = "Bad request: Please check your request data and try again";
|
||||
break;
|
||||
case "CONFLICT":
|
||||
statusCode = 409;
|
||||
errorMessage = "Could not process the request due to a resource conflict with the current state";
|
||||
break;
|
||||
case "TOO_MANY_REQUESTS":
|
||||
statusCode = 429;
|
||||
errorMessage = "You have exceeded the allowed number of requests";
|
||||
break;
|
||||
case "PAYLOAD_TOO_LARGE":
|
||||
statusCode = 413;
|
||||
errorMessage = "The request payload is too large";
|
||||
break;
|
||||
case "CLIENT_CLOSED_REQUEST":
|
||||
statusCode = 499;
|
||||
errorMessage =
|
||||
"The client closed the connection before the server could finish processing the request";
|
||||
break;
|
||||
case "METHOD_NOT_SUPPORTED":
|
||||
statusCode = 405;
|
||||
errorMessage = "The requested method is not supported for this resource";
|
||||
case "NOT_IMPLEMENTED":
|
||||
statusCode = 501;
|
||||
errorMessage = "The requested method is not implemented on the server";
|
||||
break;
|
||||
case "PRECONDITION_FAILED":
|
||||
statusCode = 412;
|
||||
errorMessage =
|
||||
"The server does not meet one of the preconditions that the requester put on the request";
|
||||
break;
|
||||
case "TIMEOUT":
|
||||
statusCode = 408;
|
||||
errorMessage = "The request took too long to complete. Please try again later";
|
||||
break;
|
||||
case "UNPROCESSABLE_CONTENT":
|
||||
statusCode = 422;
|
||||
errorMessage =
|
||||
"The server was unable to process the request because the request payload is semantically incorrect or because the request is syntactically incorrect";
|
||||
break;
|
||||
case "PARSE_ERROR":
|
||||
statusCode = 400;
|
||||
errorMessage = "The request could not be parsed due to invalid syntax.";
|
||||
break;
|
||||
default:
|
||||
statusCode = 500;
|
||||
errorMessage = exception.message;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -57,7 +102,7 @@ export class TRPCExceptionFilter implements ExceptionFilter {
|
||||
status: ERROR_STATUS,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
error: { code: exception.name, message: exception.message },
|
||||
error: { code: exception.name, message: errorMessage },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ export const defaultBookingResponses = {
|
||||
name: "unknown",
|
||||
email: "unknown",
|
||||
guests: [],
|
||||
rescheduleReason: "unknown",
|
||||
rescheduledReason: "unknown",
|
||||
};
|
||||
|
||||
export const defaultBookingMetadata = { videoCallUrl: "unknown" };
|
||||
|
||||
@@ -21,13 +21,23 @@ export class ResponseInterceptor implements NestInterceptor {
|
||||
const { statusCode } = response;
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
let jsonBodyString = "{}";
|
||||
|
||||
try {
|
||||
if (data && typeof data === "object") {
|
||||
jsonBodyString = JSON.stringify(data);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error("Could not parse request body");
|
||||
}
|
||||
|
||||
this.logger.log("Outgoing Response", {
|
||||
requestId,
|
||||
method,
|
||||
url,
|
||||
statusCode,
|
||||
responseTime,
|
||||
responseBody: data,
|
||||
responseBody: jsonBodyString,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
})
|
||||
|
||||
@@ -11,13 +11,22 @@ export class RequestIdMiddleware implements NestMiddleware {
|
||||
const requestId = uuid();
|
||||
req.headers["X-Request-Id"] = requestId;
|
||||
const { method, headers, body: requestBody, baseUrl } = req;
|
||||
let jsonBodyString = "{}";
|
||||
|
||||
try {
|
||||
if (requestBody && typeof requestBody === "object") {
|
||||
jsonBodyString = JSON.stringify(requestBody);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error("Could not parse request body");
|
||||
}
|
||||
|
||||
this.logger.log("Incoming Request", {
|
||||
requestId,
|
||||
method,
|
||||
url: baseUrl,
|
||||
headers: filterReqHeaders(headers),
|
||||
requestBody,
|
||||
requestBody: jsonBodyString,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
|
||||
@@ -37,11 +37,11 @@ import {
|
||||
Req,
|
||||
HttpException,
|
||||
} from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { ApiOperation, ApiParam, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToInstance } from "class-transformer";
|
||||
import { Request } from "express";
|
||||
|
||||
import { GOOGLE_MEET, ZOOM, SUCCESS_STATUS, OFFICE_365_VIDEO } from "@calcom/platform-constants";
|
||||
import { GOOGLE_MEET, ZOOM, SUCCESS_STATUS, OFFICE_365_VIDEO, CAL_VIDEO } from "@calcom/platform-constants";
|
||||
|
||||
export type OAuthCallbackState = {
|
||||
accessToken: string;
|
||||
@@ -71,6 +71,12 @@ export class ConferencingController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@ApiOperation({ summary: "Connect your conferencing application" })
|
||||
@ApiParam({
|
||||
name: "app",
|
||||
description: "Conferencing application type",
|
||||
enum: [GOOGLE_MEET],
|
||||
required: true,
|
||||
})
|
||||
async connect(
|
||||
@GetUser("id") userId: number,
|
||||
@Param("app") app: string
|
||||
@@ -93,6 +99,12 @@ export class ConferencingController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@ApiOperation({ summary: "Get OAuth conferencing app auth url" })
|
||||
@ApiParam({
|
||||
name: "app",
|
||||
description: "Conferencing application type",
|
||||
enum: [ZOOM, OFFICE_365_VIDEO],
|
||||
required: true,
|
||||
})
|
||||
async redirect(
|
||||
@Req() req: Request,
|
||||
@Headers("Authorization") authorization: string,
|
||||
@@ -138,6 +150,12 @@ export class ConferencingController {
|
||||
@UseGuards()
|
||||
@Redirect(undefined, 301)
|
||||
@ApiOperation({ summary: "conferencing apps oauths callback" })
|
||||
@ApiParam({
|
||||
name: "app",
|
||||
description: "Conferencing application type",
|
||||
enum: [ZOOM, OFFICE_365_VIDEO],
|
||||
required: true,
|
||||
})
|
||||
async save(
|
||||
@Query("state") state: string,
|
||||
@Param("app") app: string,
|
||||
@@ -203,6 +221,12 @@ export class ConferencingController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@ApiOperation({ summary: "Set your default conferencing application" })
|
||||
@ApiParam({
|
||||
name: "app",
|
||||
description: "Conferencing application type",
|
||||
enum: [GOOGLE_MEET, ZOOM, OFFICE_365_VIDEO, CAL_VIDEO],
|
||||
required: true,
|
||||
})
|
||||
async default(
|
||||
@GetUser() user: UserWithProfile,
|
||||
@Param("app") app: string
|
||||
@@ -224,6 +248,12 @@ export class ConferencingController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@ApiOperation({ summary: "Disconnect your conferencing application" })
|
||||
@ApiParam({
|
||||
name: "app",
|
||||
description: "Conferencing application type",
|
||||
enum: [GOOGLE_MEET, ZOOM, OFFICE_365_VIDEO],
|
||||
required: true,
|
||||
})
|
||||
async disconnect(
|
||||
@GetUser() user: UserWithProfile,
|
||||
@Param("app") app: string
|
||||
|
||||
+1
-14
@@ -29,19 +29,12 @@ import {
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ApiOperation as DocsOperation,
|
||||
ApiCreatedResponse as DocsCreatedResponse,
|
||||
ApiTags,
|
||||
} from "@nestjs/swagger";
|
||||
import { ApiCreatedResponse as DocsCreatedResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { User, MembershipRole } from "@prisma/client";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { CreateOAuthClientInput, UpdateOAuthClientInput, Pagination } from "@calcom/platform-types";
|
||||
|
||||
const AUTH_DOCUMENTATION = `⚠️ First, this endpoint requires \`Cookie: next-auth.session-token=eyJhbGciOiJ\` header. Log into Cal web app using owner of organization that was created after visiting \`/settings/organizations/new\`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
|
||||
Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.`;
|
||||
|
||||
@Controller({
|
||||
path: "/v2/oauth-clients",
|
||||
version: API_VERSIONS_VALUES,
|
||||
@@ -61,7 +54,6 @@ export class OAuthClientsController {
|
||||
@Post("/")
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
@DocsCreatedResponse({
|
||||
description: "Create an OAuth client",
|
||||
type: CreateOAuthClientResponseDto,
|
||||
@@ -90,7 +82,6 @@ export class OAuthClientsController {
|
||||
@Get("/")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
async getOAuthClients(@GetOrgId() organizationId: number): Promise<GetOAuthClientsResponseDto> {
|
||||
const clients = await this.oAuthClientsService.getOAuthClients(organizationId);
|
||||
return { status: SUCCESS_STATUS, data: clients };
|
||||
@@ -99,7 +90,6 @@ export class OAuthClientsController {
|
||||
@Get("/:clientId")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
@UseGuards(OAuthClientGuard)
|
||||
async getOAuthClientById(@Param("clientId") clientId: string): Promise<GetOAuthClientResponseDto> {
|
||||
const client = await this.oAuthClientsService.getOAuthClientById(clientId);
|
||||
@@ -109,7 +99,6 @@ export class OAuthClientsController {
|
||||
@Get("/:clientId/managed-users")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
@UseGuards(OAuthClientGuard)
|
||||
async getOAuthClientManagedUsersById(
|
||||
@Param("clientId") clientId: string,
|
||||
@@ -128,7 +117,6 @@ export class OAuthClientsController {
|
||||
@Patch("/:clientId")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
@UseGuards(OAuthClientGuard)
|
||||
async updateOAuthClient(
|
||||
@Param("clientId") clientId: string,
|
||||
@@ -142,7 +130,6 @@ export class OAuthClientsController {
|
||||
@Delete("/:clientId")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
@UseGuards(OAuthClientGuard)
|
||||
async deleteOAuthClient(@Param("clientId") clientId: string): Promise<GetOAuthClientResponseDto> {
|
||||
this.logger.log(`Deleting OAuth Client with ID: ${clientId}`);
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { AuthModule } from "@/modules/auth/auth.module";
|
||||
import { BillingModule } from "@/modules/billing/billing.module";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { OAuthClientUsersController } from "@/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller";
|
||||
import { OAuthClientsController } from "@/modules/oauth-clients/controllers/oauth-clients/oauth-clients.controller";
|
||||
@@ -16,6 +20,7 @@ import { OrganizationsModule } from "@/modules/organizations/organizations.modul
|
||||
import { OrganizationsTeamsService } from "@/modules/organizations/teams/index/services/organizations-teams.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { TokensRepository } from "@/modules/tokens/tokens.repository";
|
||||
@@ -42,6 +47,11 @@ import { JwtService } from "@nestjs/jwt";
|
||||
OAuthClientRepository,
|
||||
TokensRepository,
|
||||
OAuthFlowService,
|
||||
CalendarsService,
|
||||
CredentialsRepository,
|
||||
AppsRepository,
|
||||
CalendarsRepository,
|
||||
SelectedCalendarsRepository,
|
||||
OAuthClientUsersService,
|
||||
OrganizationsTeamsService,
|
||||
OAuthClientsService,
|
||||
|
||||
@@ -118,4 +118,19 @@ export class OAuthClientRepository {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getByEventTypeHosts(eventTypeId: number) {
|
||||
const hostWithUserPlatformClient = await this.dbRead.prisma.host.findFirst({
|
||||
select: {
|
||||
user: { select: { platformOAuthClients: true } },
|
||||
},
|
||||
where: {
|
||||
eventTypeId: eventTypeId,
|
||||
user: {
|
||||
isPlatformManaged: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
return hostWithUserPlatformClient?.user?.platformOAuthClients?.[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { EventTypesService_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/services/event-types.service";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { GetManagedUsersInput } from "@/modules/oauth-clients/controllers/oauth-client-users/inputs/get-managed-users.input";
|
||||
@@ -5,18 +6,21 @@ import { TokensRepository } from "@/modules/tokens/tokens.repository";
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, ConflictException, Injectable } from "@nestjs/common";
|
||||
import { BadRequestException, ConflictException, Injectable, Logger } from "@nestjs/common";
|
||||
import { User, CreationSource, PlatformOAuthClient } from "@prisma/client";
|
||||
|
||||
import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries";
|
||||
|
||||
@Injectable()
|
||||
export class OAuthClientUsersService {
|
||||
private readonly logger = new Logger("OAuthClientUsersService");
|
||||
|
||||
constructor(
|
||||
private readonly userRepository: UsersRepository,
|
||||
private readonly tokensRepository: TokensRepository,
|
||||
private readonly eventTypesService: EventTypesService_2024_04_15,
|
||||
private readonly schedulesService: SchedulesService_2024_04_15
|
||||
private readonly schedulesService: SchedulesService_2024_04_15,
|
||||
private readonly calendarsService: CalendarsService
|
||||
) {}
|
||||
|
||||
async createOAuthClientUser(oAuthClient: PlatformOAuthClient, body: CreateManagedUserInput) {
|
||||
@@ -87,6 +91,13 @@ export class OAuthClientUsersService {
|
||||
user.defaultScheduleId = defaultSchedule.id;
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.log(`Setting default calendars in db for user with id ${user.id}`);
|
||||
await this.calendarsService.getCalendars(user.id);
|
||||
} catch (err) {
|
||||
this.logger.error(`Could not get calendars of new managed user with id ${user.id}`);
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
tokens: {
|
||||
|
||||
+18
-1
@@ -1,3 +1,5 @@
|
||||
import { CalendarsModule } from "@/ee/calendars/calendars.module";
|
||||
import { CALENDARS_QUEUE, CalendarsProcessor } from "@/ee/calendars/processors/calendars.processor";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { OrganizationsDelegationCredentialController } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.controller";
|
||||
import { OrganizationsDelegationCredentialRepository } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.repository";
|
||||
@@ -5,16 +7,31 @@ import { OrganizationsRepository } from "@/modules/organizations/index/organizat
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { BullModule } from "@nestjs/bull";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { OrganizationsDelegationCredentialService } from "./services/organizations-delegation-credential.service";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, StripeModule, RedisModule, MembershipsModule],
|
||||
imports: [
|
||||
PrismaModule,
|
||||
StripeModule,
|
||||
RedisModule,
|
||||
CalendarsModule,
|
||||
MembershipsModule,
|
||||
BullModule.registerQueue({
|
||||
name: CALENDARS_QUEUE,
|
||||
limiter: {
|
||||
max: 1,
|
||||
duration: 1000,
|
||||
},
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
OrganizationsDelegationCredentialService,
|
||||
OrganizationsDelegationCredentialRepository,
|
||||
OrganizationsRepository,
|
||||
CalendarsProcessor,
|
||||
],
|
||||
controllers: [OrganizationsDelegationCredentialController],
|
||||
})
|
||||
|
||||
+12
@@ -28,4 +28,16 @@ export class OrganizationsDelegationCredentialRepository {
|
||||
include: { workspacePlatform: true },
|
||||
});
|
||||
}
|
||||
|
||||
async findDelegatedUserProfiles(orgId: number, domain: string) {
|
||||
return this.dbRead.prisma.profile.findMany({
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
where: {
|
||||
organizationId: orgId,
|
||||
user: { email: { endsWith: domain } },
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+47
-3
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
CALENDARS_QUEUE,
|
||||
DEFAULT_CALENDARS_JOB,
|
||||
DefaultCalendarsJobDataType,
|
||||
} from "@/ee/calendars/processors/calendars.processor";
|
||||
import { CreateDelegationCredentialInput } from "@/modules/organizations/delegation-credentials/inputs/create-delegation-credential.input";
|
||||
import {
|
||||
GoogleServiceAccountKeyInput,
|
||||
@@ -5,9 +10,10 @@ import {
|
||||
} from "@/modules/organizations/delegation-credentials/inputs/service-account-key.input";
|
||||
import { UpdateDelegationCredentialInput } from "@/modules/organizations/delegation-credentials/inputs/update-delegation-credential.input";
|
||||
import { OrganizationsDelegationCredentialRepository } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.repository";
|
||||
import { DelegationCredentialOutput } from "@/modules/organizations/delegation-credentials/outputs/delegation-credential.output";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { InjectQueue } from "@nestjs/bull";
|
||||
import { Injectable, NotFoundException, Logger } from "@nestjs/common";
|
||||
import { User } from "@prisma/client";
|
||||
import { Queue } from "bull";
|
||||
|
||||
import { encryptServiceAccountKey } from "@calcom/platform-libraries";
|
||||
import {
|
||||
@@ -17,8 +23,11 @@ import {
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsDelegationCredentialService {
|
||||
private logger = new Logger("OrganizationsDelegationCredentialService");
|
||||
|
||||
constructor(
|
||||
private readonly organizationsDelegationCredentialRepository: OrganizationsDelegationCredentialRepository
|
||||
private readonly organizationsDelegationCredentialRepository: OrganizationsDelegationCredentialRepository,
|
||||
@InjectQueue(CALENDARS_QUEUE) private readonly calendarsQueue: Queue
|
||||
) {}
|
||||
async createDelegationCredential(
|
||||
orgId: number,
|
||||
@@ -59,9 +68,44 @@ export class OrganizationsDelegationCredentialService {
|
||||
await this.updateDelegationCredentialServiceAccountKey(delegationCredentialId, body.serviceAccountKey);
|
||||
}
|
||||
|
||||
// once delegation credentials are enabled, slowly set all the destination calendars of delegated users
|
||||
if (body.enabled === true && delegationCredential.enabled === false) {
|
||||
await this.ensureDefaultCalendars(orgId, delegationCredential.domain);
|
||||
}
|
||||
|
||||
return { ...delegationCredential, enabled: body?.enabled ?? delegationCredential?.enabled };
|
||||
}
|
||||
|
||||
async ensureDefaultCalendars(orgId: number, domain: string) {
|
||||
try {
|
||||
const delegatedUserProfiles =
|
||||
await this.organizationsDelegationCredentialRepository.findDelegatedUserProfiles(orgId, domain);
|
||||
|
||||
delegatedUserProfiles.forEach(async (profile) => {
|
||||
if (profile.userId) {
|
||||
const job = await this.calendarsQueue.getJob(`${DEFAULT_CALENDARS_JOB}_${profile.userId}`);
|
||||
if (job) {
|
||||
await job.remove();
|
||||
this.logger.log(`Removed default calendar job for user with id: ${profile.userId}`);
|
||||
}
|
||||
this.logger.log(`Adding default calendar job for user with id: ${profile.userId}`);
|
||||
await this.calendarsQueue.add(
|
||||
DEFAULT_CALENDARS_JOB,
|
||||
{
|
||||
userId: profile.userId,
|
||||
} satisfies DefaultCalendarsJobDataType,
|
||||
{ jobId: `${DEFAULT_CALENDARS_JOB}_${profile.userId}`, removeOnComplete: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
err,
|
||||
`Could not ensure default calendars for delegated users in org with id:${orgId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateDelegationCredentialEnabled(
|
||||
orgId: number,
|
||||
delegationCredentialId: string,
|
||||
|
||||
+2
-2
@@ -81,7 +81,7 @@ export class OrganizationsEventTypesController {
|
||||
bodyEventType
|
||||
);
|
||||
|
||||
const eventType = await this.organizationsEventTypesService.createTeamEventType(
|
||||
const eventType = await this.organizationsEventTypesService.createOrganizationTeamEventType(
|
||||
user,
|
||||
teamId,
|
||||
orgId,
|
||||
@@ -212,7 +212,7 @@ export class OrganizationsEventTypesController {
|
||||
bodyEventType
|
||||
);
|
||||
|
||||
const eventType = await this.organizationsEventTypesService.updateTeamEventType(
|
||||
const eventType = await this.organizationsEventTypesService.updateOrganizationTeamEventType(
|
||||
eventTypeId,
|
||||
teamId,
|
||||
transformedBody,
|
||||
|
||||
+245
-8
@@ -324,7 +324,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
const data = responseBody.data;
|
||||
expect(data.title).toEqual(body.title);
|
||||
expect(data.hosts.length).toEqual(2);
|
||||
expect(data.schedulingType).toEqual("COLLECTIVE");
|
||||
expect(data.schedulingType).toEqual("collective");
|
||||
evaluateHost(body.hosts[0], data.hosts[0]);
|
||||
evaluateHost(body.hosts[1], data.hosts[1]);
|
||||
expect(data.bookingLimitsCount).toEqual(body.bookingLimitsCount);
|
||||
@@ -457,8 +457,8 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
const data = responseBody.data;
|
||||
expect(data.length).toEqual(2);
|
||||
|
||||
const eventTypeCollective = data.find((eventType) => eventType.schedulingType === "COLLECTIVE");
|
||||
const eventTypeManaged = data.find((eventType) => eventType.schedulingType === "MANAGED");
|
||||
const eventTypeCollective = data.find((eventType) => eventType.schedulingType === "collective");
|
||||
const eventTypeManaged = data.find((eventType) => eventType.schedulingType === "managed");
|
||||
|
||||
expect(eventTypeCollective?.title).toEqual(collectiveEventType.title);
|
||||
expect(eventTypeCollective?.hosts.length).toEqual(2);
|
||||
@@ -564,7 +564,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
).toEqual(newTitle);
|
||||
|
||||
const responseTeamEvent = responseBody.data.find(
|
||||
(eventType) => eventType.schedulingType === "MANAGED"
|
||||
(eventType) => eventType.schedulingType === "managed"
|
||||
);
|
||||
expect(responseTeamEvent).toBeDefined();
|
||||
expect(responseTeamEvent?.title).toEqual(newTitle);
|
||||
@@ -581,6 +581,123 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to create phone-only event type", async () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
title: "Phone coding consultation",
|
||||
slug: "phone-coding-consultation",
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
{
|
||||
type: "organizersDefaultApp",
|
||||
},
|
||||
],
|
||||
schedulingType: "COLLECTIVE",
|
||||
hosts: [
|
||||
{
|
||||
userId: teammate1.id,
|
||||
mandatory: true,
|
||||
priority: "high",
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
type: "email",
|
||||
required: false,
|
||||
label: "Email",
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: true,
|
||||
label: "Phone number",
|
||||
hidden: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams/${team.id}/event-types`)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const data = responseBody.data;
|
||||
expect(data.bookingFields).toEqual([
|
||||
{
|
||||
isDefault: true,
|
||||
type: "name",
|
||||
slug: "name",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "email",
|
||||
slug: "email",
|
||||
required: false,
|
||||
label: "Email",
|
||||
disableOnPrefill: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: true,
|
||||
hidden: false,
|
||||
label: "Phone number",
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "text",
|
||||
slug: "title",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "multiemail",
|
||||
slug: "guests",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "rescheduleReason",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to configure phone-only event type", async () => {
|
||||
const body: UpdateTeamEventTypeInput_2024_06_14 = {
|
||||
bookingFields: [
|
||||
@@ -588,13 +705,14 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
type: "email",
|
||||
required: false,
|
||||
label: "Email",
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: true,
|
||||
label: "Phone number",
|
||||
hidden: true,
|
||||
hidden: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -622,6 +740,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
required: false,
|
||||
label: "Email",
|
||||
disableOnPrefill: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
@@ -635,7 +754,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: true,
|
||||
hidden: true,
|
||||
hidden: false,
|
||||
label: "Phone number",
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
@@ -704,7 +823,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
expect(managedTeamEventTypes[0].assignAllTeamMembers).toEqual(true);
|
||||
|
||||
const responseTeamEvent = responseBody.data.find(
|
||||
(eventType) => eventType.schedulingType === "MANAGED"
|
||||
(eventType) => eventType.schedulingType === "managed"
|
||||
);
|
||||
expect(responseTeamEvent).toBeDefined();
|
||||
expect(responseTeamEvent?.teamId).toEqual(team.id);
|
||||
@@ -776,7 +895,14 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
|
||||
expect(fetchedEventType.bookingFields).toEqual([
|
||||
{ isDefault: true, required: true, slug: "name", type: "name", disableOnPrefill: false },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
|
||||
{
|
||||
isDefault: true,
|
||||
required: true,
|
||||
slug: "email",
|
||||
type: "email",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
disableOnPrefill: false,
|
||||
isDefault: true,
|
||||
@@ -828,6 +954,117 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a round robin team event-type", async () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
successRedirectUrl: "https://masterchief.com/argentina/flan/video/1234",
|
||||
title: "Coding consultation round robin",
|
||||
slug: `organizations-event-types-round-robin-${randomString()}`,
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
type: "select",
|
||||
label: "select which language is your codebase in",
|
||||
slug: "select-language",
|
||||
required: true,
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
},
|
||||
],
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
schedulingType: "roundRobin",
|
||||
hosts: [
|
||||
{
|
||||
userId: teammate1.id,
|
||||
mandatory: true,
|
||||
priority: "high",
|
||||
},
|
||||
{
|
||||
userId: teammate2.id,
|
||||
mandatory: false,
|
||||
priority: "medium",
|
||||
},
|
||||
],
|
||||
bookingLimitsCount: {
|
||||
day: 2,
|
||||
week: 5,
|
||||
},
|
||||
onlyShowFirstAvailableSlot: true,
|
||||
bookingLimitsDuration: {
|
||||
day: 60,
|
||||
week: 100,
|
||||
},
|
||||
offsetStart: 30,
|
||||
bookingWindow: {
|
||||
type: BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays,
|
||||
value: 30,
|
||||
rolling: true,
|
||||
},
|
||||
bookerLayouts: {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsInputEnum_2024_06_14.column,
|
||||
BookerLayoutsInputEnum_2024_06_14.month,
|
||||
BookerLayoutsInputEnum_2024_06_14.week,
|
||||
],
|
||||
defaultLayout: BookerLayoutsInputEnum_2024_06_14.month,
|
||||
},
|
||||
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.TIME,
|
||||
noticeThreshold: {
|
||||
count: 60,
|
||||
unit: NoticeThresholdUnitEnum.MINUTES,
|
||||
},
|
||||
blockUnconfirmedBookingsInBooker: true,
|
||||
},
|
||||
requiresBookerEmailVerification: true,
|
||||
hideCalendarNotes: true,
|
||||
hideCalendarEventDetails: true,
|
||||
lockTimeZoneToggleOnBookingPage: true,
|
||||
color: {
|
||||
darkThemeHex: "#292929",
|
||||
lightThemeHex: "#fafafa",
|
||||
},
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams/${team.id}/event-types`)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then((response) => {
|
||||
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
|
||||
const data = responseBody.data;
|
||||
expect(data.title).toEqual(body.title);
|
||||
expect(data.hosts.length).toEqual(2);
|
||||
expect(data.schedulingType).toEqual("roundRobin");
|
||||
evaluateHost(body.hosts[0], data.hosts[0]);
|
||||
evaluateHost(body.hosts[1], data.hosts[1]);
|
||||
expect(data.bookingLimitsCount).toEqual(body.bookingLimitsCount);
|
||||
expect(data.onlyShowFirstAvailableSlot).toEqual(body.onlyShowFirstAvailableSlot);
|
||||
expect(data.bookingLimitsDuration).toEqual(body.bookingLimitsDuration);
|
||||
expect(data.offsetStart).toEqual(body.offsetStart);
|
||||
expect(data.bookingWindow).toEqual(body.bookingWindow);
|
||||
expect(data.bookerLayouts).toEqual(body.bookerLayouts);
|
||||
expect(data.confirmationPolicy).toEqual(body.confirmationPolicy);
|
||||
expect(data.requiresBookerEmailVerification).toEqual(body.requiresBookerEmailVerification);
|
||||
expect(data.hideCalendarNotes).toEqual(body.hideCalendarNotes);
|
||||
expect(data.hideCalendarEventDetails).toEqual(body.hideCalendarEventDetails);
|
||||
expect(data.lockTimeZoneToggleOnBookingPage).toEqual(body.lockTimeZoneToggleOnBookingPage);
|
||||
expect(data.color).toEqual(body.color);
|
||||
expect(data.successRedirectUrl).toEqual("https://masterchief.com/argentina/flan/video/1234");
|
||||
collectiveEventType = responseBody.data;
|
||||
});
|
||||
});
|
||||
|
||||
function evaluateHost(expected: Host, received: Host | undefined) {
|
||||
expect(expected.userId).toEqual(received?.userId);
|
||||
expect(expected.mandatory).toEqual(received?.mandatory);
|
||||
|
||||
+4
-4
@@ -26,7 +26,7 @@ export class OrganizationsEventTypesService {
|
||||
private readonly usersService: UsersService
|
||||
) {}
|
||||
|
||||
async createTeamEventType(
|
||||
async createOrganizationTeamEventType(
|
||||
user: UserWithProfile,
|
||||
teamId: number,
|
||||
orgId: number,
|
||||
@@ -48,7 +48,7 @@ export class OrganizationsEventTypesService {
|
||||
},
|
||||
});
|
||||
|
||||
return this.teamsEventTypesService.updateTeamEventType(eventTypeCreated.id, teamId, body, user);
|
||||
return this.teamsEventTypesService.updateTeamEventType(eventTypeCreated.id, teamId, body, user, true);
|
||||
}
|
||||
|
||||
async getUserToCreateTeamEvent(user: UserWithProfile, organizationId: number) {
|
||||
@@ -91,13 +91,13 @@ export class OrganizationsEventTypesService {
|
||||
return await this.organizationEventTypesRepository.getOrganizationTeamsEventTypes(orgId, skip, take);
|
||||
}
|
||||
|
||||
async updateTeamEventType(
|
||||
async updateOrganizationTeamEventType(
|
||||
eventTypeId: number,
|
||||
teamId: number,
|
||||
body: TransformedUpdateTeamEventTypeInput,
|
||||
user: UserWithProfile
|
||||
): Promise<DatabaseTeamEventType | DatabaseTeamEventType[]> {
|
||||
return this.teamsEventTypesService.updateTeamEventType(eventTypeId, teamId, body, user);
|
||||
return this.teamsEventTypesService.updateTeamEventType(eventTypeId, teamId, body, user, true);
|
||||
}
|
||||
|
||||
async deleteTeamEventType(teamId: number, eventTypeId: number) {
|
||||
|
||||
@@ -100,7 +100,9 @@ export class OutputOrganizationsEventTypesService {
|
||||
teamId,
|
||||
ownerId: userId,
|
||||
parentEventTypeId: parentId,
|
||||
schedulingType: databaseEventType.schedulingType,
|
||||
schedulingType: databaseEventType.schedulingType
|
||||
? this.getResponseSchedulingType(databaseEventType.schedulingType)
|
||||
: databaseEventType.schedulingType,
|
||||
assignAllTeamMembers: teamId ? assignAllTeamMembers : undefined,
|
||||
team: {
|
||||
id: teamId,
|
||||
@@ -116,6 +118,19 @@ export class OutputOrganizationsEventTypesService {
|
||||
};
|
||||
}
|
||||
|
||||
getResponseSchedulingType(schedulingType: SchedulingType) {
|
||||
if (schedulingType === SchedulingType.COLLECTIVE) {
|
||||
return "collective";
|
||||
}
|
||||
if (schedulingType === SchedulingType.ROUND_ROBIN) {
|
||||
return "roundRobin";
|
||||
}
|
||||
if (schedulingType === SchedulingType.MANAGED) {
|
||||
return "managed";
|
||||
}
|
||||
return schedulingType;
|
||||
}
|
||||
|
||||
async getManagedEventTypeHosts(eventTypeId: number) {
|
||||
const children = await this.teamsEventTypesRepository.getEventTypeChildren(eventTypeId);
|
||||
const transformedHosts: TeamEventTypeResponseHost[] = [];
|
||||
@@ -144,7 +159,7 @@ export class OutputOrganizationsEventTypesService {
|
||||
transformedHosts.push({
|
||||
userId: databaseHost.userId,
|
||||
name: databaseUser?.name || "",
|
||||
mandatory: databaseHost.isFixed,
|
||||
mandatory: !!databaseHost.isFixed,
|
||||
priority: getPriorityLabel(databaseHost.priority || 2),
|
||||
avatarUrl: databaseUser?.avatarUrl,
|
||||
});
|
||||
|
||||
+4
-3
@@ -37,11 +37,12 @@ export class OrganizationsMembershipRepository {
|
||||
}
|
||||
|
||||
async createOrgMembership(organizationId: number, data: CreateOrgMembershipDto) {
|
||||
return this.dbWrite.prisma.membership.create({
|
||||
data: { ...data, teamId: organizationId },
|
||||
return this.dbWrite.prisma.membership.upsert({
|
||||
create: { ...data, teamId: organizationId },
|
||||
update: { role: data.role, accepted: data.accepted, disableImpersonation: data.disableImpersonation },
|
||||
where: { userId_teamId: { userId: data.userId, teamId: organizationId } },
|
||||
});
|
||||
}
|
||||
|
||||
async updateOrgMembership(organizationId: number, membershipId: number, data: UpdateOrgMembershipDto) {
|
||||
return this.dbWrite.prisma.membership.update({
|
||||
data: { ...data },
|
||||
|
||||
+1
-1
@@ -265,7 +265,7 @@ describe("Organizations TeamsBookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
describe("get team bookings", () => {
|
||||
it("should should get bookings by teamId", async () => {
|
||||
it("should get bookings by teamId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${organization.id}/teams/${team1.id}/bookings`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
|
||||
+98
@@ -75,6 +75,9 @@ describe("Slots 2024-09-04 Endpoints", () => {
|
||||
const seatedEventTypeSlug = `slots-2024-09-04-seated-event-type-${randomString()}`;
|
||||
let seatedEventType: EventType;
|
||||
|
||||
const variableLengthEventTypeSlug = `slots-2024-09-04-variable-length-event-type-${randomString()}`;
|
||||
let variableLengthEventType: EventType;
|
||||
|
||||
let reservedSlot: ReserveSlotOutputData_2024_09_04;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -168,6 +171,17 @@ describe("Slots 2024-09-04 Endpoints", () => {
|
||||
);
|
||||
seatedEventType = seatedEvent;
|
||||
|
||||
const variableLengthEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "frisbee match",
|
||||
slug: `slots-2024-09-04-variable-length-event-type-${randomString()}`,
|
||||
length: 15,
|
||||
metadata: { multipleDuration: [15, 30, 45, 60] },
|
||||
},
|
||||
user.id
|
||||
);
|
||||
variableLengthEventType = variableLengthEvent;
|
||||
|
||||
team = await teamRepositoryFixture.create({
|
||||
name: `slots-2024-09-04-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
@@ -1246,6 +1260,90 @@ describe("Slots 2024-09-04 Endpoints", () => {
|
||||
await bookingsRepositoryFixture.deleteById(booking.id);
|
||||
});
|
||||
|
||||
describe("variable length", () => {
|
||||
it("should not be able to reserve a slot for variable length event type with invalid duration", async () => {
|
||||
const slotStartTime = "2050-09-05T10:00:00.000Z";
|
||||
const reserveResponse = await request(app.getHttpServer())
|
||||
.post(`/v2/slots/reservations`)
|
||||
.send({
|
||||
eventTypeId: variableLengthEventType.id,
|
||||
slotStart: slotStartTime,
|
||||
slotDuration: 1000,
|
||||
})
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_09_04)
|
||||
.expect(400);
|
||||
|
||||
expect(reserveResponse.body.error.message).toEqual(
|
||||
"Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: 15, 30, 45, 60"
|
||||
);
|
||||
});
|
||||
|
||||
it("should reserve a slot with slot duration for variable event type length", async () => {
|
||||
// note(Lauris): mock current date to test slots release time
|
||||
const now = "2049-09-05T12:00:00.000Z";
|
||||
const newDate = DateTime.fromISO(now, { zone: "UTC" }).toJSDate();
|
||||
advanceTo(newDate);
|
||||
|
||||
const slotDuration = 60;
|
||||
const slotStartTime = "2050-09-05T10:00:00.000Z";
|
||||
const reserveResponse = await request(app.getHttpServer())
|
||||
.post(`/v2/slots/reservations`)
|
||||
.send({
|
||||
eventTypeId: variableLengthEventType.id,
|
||||
slotStart: slotStartTime,
|
||||
slotDuration,
|
||||
})
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_09_04)
|
||||
.expect(201);
|
||||
|
||||
const reserveResponseBody: ReserveSlotOutputResponse_2024_09_04 = reserveResponse.body;
|
||||
expect(reserveResponseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const responseReservedSlot: ReserveSlotOutputData_2024_09_04 = reserveResponseBody.data;
|
||||
expect(responseReservedSlot.reservationUid).toBeDefined();
|
||||
expect(responseReservedSlot.eventTypeId).toEqual(variableLengthEventType.id);
|
||||
expect(responseReservedSlot.slotStart).toEqual(slotStartTime);
|
||||
expect(responseReservedSlot.slotDuration).toEqual(slotDuration);
|
||||
expect(responseReservedSlot.slotEnd).toEqual(
|
||||
DateTime.fromISO(slotStartTime, { zone: "UTC" }).plus({ minutes: slotDuration }).toISO()
|
||||
);
|
||||
expect(responseReservedSlot.reservationDuration).toEqual(5);
|
||||
|
||||
if (!responseReservedSlot.reservationUid) {
|
||||
throw new Error("Reserved slot uid is undefined");
|
||||
}
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(
|
||||
`/v2/slots?eventTypeId=${variableLengthEventType.id}&start=2050-09-05&end=2050-09-09&duration=60`
|
||||
)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_09_04)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: GetSlotsOutput_2024_09_04 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const slots = responseBody.data;
|
||||
|
||||
expect(slots).toBeDefined();
|
||||
const days = Object.keys(slots);
|
||||
expect(days.length).toEqual(5);
|
||||
|
||||
const expectedSlotsUTC2050_09_05 = expectedSlotsUTC["2050-09-05"].filter(
|
||||
(slot) => slot.start !== slotStartTime
|
||||
);
|
||||
expect(slots).toEqual({ ...expectedSlotsUTC, "2050-09-05": expectedSlotsUTC2050_09_05 });
|
||||
|
||||
const dbSlot = await selectedSlotsRepositoryFixture.getByUid(reservedSlot.reservationUid);
|
||||
expect(dbSlot).toBeDefined();
|
||||
if (dbSlot) {
|
||||
const dbReleaseAt = DateTime.fromJSDate(dbSlot.releaseAt, { zone: "UTC" }).toISO();
|
||||
const expectedReleaseAt = DateTime.fromISO(now, { zone: "UTC" }).plus({ minutes: 5 }).toISO();
|
||||
expect(dbReleaseAt).toEqual(expectedReleaseAt);
|
||||
expect(responseReservedSlot.reservationUntil).toEqual(expectedReleaseAt);
|
||||
}
|
||||
clear();
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
await userRepositoryFixture.deleteByEmail(unrelatedUser.email);
|
||||
|
||||
@@ -21,9 +21,11 @@ import { getAvailableSlots } from "@calcom/platform-libraries/slots";
|
||||
import { GetSlotsInput_2024_09_04, ReserveSlotInput_2024_09_04 } from "@calcom/platform-types";
|
||||
import { EventType } from "@calcom/prisma/client";
|
||||
|
||||
const eventTypeMetadataSchema = z.object({
|
||||
multipleDuration: z.number().array().optional(),
|
||||
});
|
||||
const eventTypeMetadataSchema = z
|
||||
.object({
|
||||
multipleDuration: z.number().array().optional(),
|
||||
})
|
||||
.nullable();
|
||||
|
||||
const DEFAULT_RESERVATION_DURATION = 5;
|
||||
|
||||
@@ -89,17 +91,8 @@ export class SlotsService_2024_09_04 {
|
||||
throw new BadRequestException("Invalid start date");
|
||||
}
|
||||
|
||||
const metadata = eventTypeMetadataSchema.parse(eventType);
|
||||
if (
|
||||
input.slotDuration &&
|
||||
metadata.multipleDuration &&
|
||||
!metadata.multipleDuration.includes(input.slotDuration)
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
`Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: ${metadata.multipleDuration.join(
|
||||
", "
|
||||
)}`
|
||||
);
|
||||
if (input.slotDuration) {
|
||||
this.validateSlotDuration(eventType, input.slotDuration);
|
||||
}
|
||||
|
||||
const endDate = startDate.plus({ minutes: input.slotDuration ?? eventType.length });
|
||||
@@ -160,6 +153,23 @@ export class SlotsService_2024_09_04 {
|
||||
return this.slotsOutputService.getReservationSlotCreated(slot, reservationDuration);
|
||||
}
|
||||
|
||||
validateSlotDuration(eventType: EventType, inputSlotDuration: number) {
|
||||
const eventTypeMetadata = eventTypeMetadataSchema.parse(eventType.metadata);
|
||||
if (!eventTypeMetadata?.multipleDuration) {
|
||||
throw new BadRequestException(
|
||||
"You passed 'slotDuration' but this event type is not a variable length event type."
|
||||
);
|
||||
}
|
||||
|
||||
if (!eventTypeMetadata.multipleDuration.includes(inputSlotDuration)) {
|
||||
throw new BadRequestException(
|
||||
`Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: ${eventTypeMetadata.multipleDuration.join(
|
||||
", "
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async canSpecifyCustomReservationDuration(authUserId: number, eventType: EventType) {
|
||||
if (eventType.userId) {
|
||||
return await this.canSpecifyCustomReservationDurationIndividualEvent(authUserId, eventType.userId);
|
||||
@@ -214,17 +224,8 @@ export class SlotsService_2024_09_04 {
|
||||
throw new BadRequestException("Invalid start date");
|
||||
}
|
||||
|
||||
const metadata = eventTypeMetadataSchema.parse(eventType);
|
||||
if (
|
||||
input.slotDuration &&
|
||||
metadata.multipleDuration &&
|
||||
!metadata.multipleDuration.includes(input.slotDuration)
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
`Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: ${metadata.multipleDuration.join(
|
||||
", "
|
||||
)}`
|
||||
);
|
||||
if (input.slotDuration) {
|
||||
this.validateSlotDuration(eventType, input.slotDuration);
|
||||
}
|
||||
|
||||
const endDate = startDate.plus({ minutes: input.slotDuration ?? eventType.length });
|
||||
|
||||
+55
-5
@@ -167,6 +167,56 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
return request(app.getHttpServer()).post(`/v2/teams/${team.id}/event-types`).send(body).expect(404);
|
||||
});
|
||||
|
||||
it("should not be able to create phone-only event type", async () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
title: "Phone coding consultation",
|
||||
slug: "phone-coding-consultation",
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
{
|
||||
type: "organizersDefaultApp",
|
||||
},
|
||||
],
|
||||
schedulingType: "COLLECTIVE",
|
||||
hosts: [
|
||||
{
|
||||
userId: teamMember1.id,
|
||||
},
|
||||
{
|
||||
userId: teamMember2.id,
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
type: "email",
|
||||
required: false,
|
||||
label: "Email",
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: true,
|
||||
label: "Phone number",
|
||||
hidden: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post(`/v2/teams/${team.id}/event-types`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
expect(response.body.error.message).toBe(
|
||||
"checkIsEmailUserAccessible - Email booking field must be required and visible"
|
||||
);
|
||||
});
|
||||
|
||||
it("should create a collective team event-type", async () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
title: `teams-event-types-collective-${randomString()}`,
|
||||
@@ -253,7 +303,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
const data = responseBody.data;
|
||||
expect(data.title).toEqual(body.title);
|
||||
expect(data.hosts.length).toEqual(2);
|
||||
expect(data.schedulingType).toEqual("COLLECTIVE");
|
||||
expect(data.schedulingType).toEqual("collective");
|
||||
evaluateHost(body.hosts[0], data.hosts[0]);
|
||||
evaluateHost(body.hosts[1], data.hosts[1]);
|
||||
expect(data.bookingLimitsCount).toEqual(body.bookingLimitsCount);
|
||||
@@ -373,8 +423,8 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
const data = responseBody.data;
|
||||
expect(data.length).toEqual(2);
|
||||
|
||||
const eventTypeCollective = data.find((eventType) => eventType.schedulingType === "COLLECTIVE");
|
||||
const eventTypeManaged = data.find((eventType) => eventType.schedulingType === "MANAGED");
|
||||
const eventTypeCollective = data.find((eventType) => eventType.schedulingType === "collective");
|
||||
const eventTypeManaged = data.find((eventType) => eventType.schedulingType === "managed");
|
||||
|
||||
expect(eventTypeCollective?.title).toEqual(collectiveEventType.title);
|
||||
expect(eventTypeCollective?.hosts.length).toEqual(2);
|
||||
@@ -466,7 +516,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
).toEqual(newTitle);
|
||||
|
||||
const responseTeamEvent = responseBody.data.find(
|
||||
(eventType) => eventType.schedulingType === "MANAGED"
|
||||
(eventType) => eventType.schedulingType === "managed"
|
||||
);
|
||||
expect(responseTeamEvent).toBeDefined();
|
||||
expect(responseTeamEvent?.title).toEqual(newTitle);
|
||||
@@ -512,7 +562,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
expect(managedTeamEventTypes[0].assignAllTeamMembers).toEqual(true);
|
||||
|
||||
const responseTeamEvent = responseBody.data.find(
|
||||
(eventType) => eventType.schedulingType === "MANAGED"
|
||||
(eventType) => eventType.schedulingType === "managed"
|
||||
);
|
||||
expect(responseTeamEvent).toBeDefined();
|
||||
expect(responseTeamEvent?.teamId).toEqual(team.id);
|
||||
|
||||
+2
-1
@@ -181,7 +181,8 @@ export class TeamsEventTypesController {
|
||||
eventTypeId,
|
||||
teamId,
|
||||
transformedBody,
|
||||
user
|
||||
user,
|
||||
false
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -30,6 +30,10 @@ export class TeamsEventTypesService {
|
||||
teamId: number,
|
||||
body: TransformedCreateTeamEventTypeInput
|
||||
): Promise<DatabaseTeamEventType | DatabaseTeamEventType[]> {
|
||||
// note(Lauris): once phone only event types / bookings are enabled for simple users remove checkHasUserAccessibleEmailBookingField check
|
||||
if (body.bookingFields) {
|
||||
this.eventTypesService.checkHasUserAccessibleEmailBookingField(body.bookingFields);
|
||||
}
|
||||
const eventTypeUser = await this.getUserToCreateTeamEvent(user);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { hosts, children, destinationCalendar, ...rest } = body;
|
||||
@@ -44,7 +48,7 @@ export class TeamsEventTypesService {
|
||||
},
|
||||
});
|
||||
|
||||
return this.updateTeamEventType(eventTypeCreated.id, teamId, body, user);
|
||||
return this.updateTeamEventType(eventTypeCreated.id, teamId, body, user, false);
|
||||
}
|
||||
|
||||
async validateEventTypeExists(teamId: number, eventTypeId: number) {
|
||||
@@ -105,8 +109,14 @@ export class TeamsEventTypesService {
|
||||
eventTypeId: number,
|
||||
teamId: number,
|
||||
body: TransformedUpdateTeamEventTypeInput,
|
||||
user: UserWithProfile
|
||||
user: UserWithProfile,
|
||||
// note(Lauris): once phone only event types / bookings are enabled for simple users remove isOrg parameter (right now only organization team event types support hidden / non-required email field)
|
||||
isOrg: boolean
|
||||
): Promise<DatabaseTeamEventType | DatabaseTeamEventType[]> {
|
||||
if (!isOrg && body.bookingFields) {
|
||||
// note(Lauris): once phone only event types / bookings are enabled for simple users remove checkHasUserAccessibleEmailBookingField check
|
||||
this.eventTypesService.checkHasUserAccessibleEmailBookingField(body.bookingFields);
|
||||
}
|
||||
await this.validateEventTypeExists(teamId, eventTypeId);
|
||||
const eventTypeUser = await this.eventTypesService.getUserToUpdateEvent(user);
|
||||
await updateEventType({
|
||||
|
||||
@@ -3756,6 +3756,26 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter bookings that have been created after this date string.",
|
||||
"example": "?afterCreatedAt=2025-03-07T10:00:00.000Z",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter bookings that have been created before this date string.",
|
||||
"example": "?beforeCreatedAt=2025-03-14T11:00:00.000Z",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
@@ -4722,7 +4742,11 @@
|
||||
"name": "app",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Conferencing application type",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"google-meet"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -4761,7 +4785,12 @@
|
||||
"name": "app",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Conferencing application type",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"zoom",
|
||||
"msteams"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -4816,7 +4845,12 @@
|
||||
"name": "app",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Conferencing application type",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"zoom",
|
||||
"msteams"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -4870,7 +4904,14 @@
|
||||
"name": "app",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Conferencing application type",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"google-meet",
|
||||
"zoom",
|
||||
"msteams",
|
||||
"daily-video"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -4923,7 +4964,13 @@
|
||||
"name": "app",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Conferencing application type",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"google-meet",
|
||||
"zoom",
|
||||
"msteams"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -5516,8 +5563,6 @@
|
||||
"/v2/oauth-clients": {
|
||||
"post": {
|
||||
"operationId": "OAuthClientsController_createOAuthClient",
|
||||
"summary": "",
|
||||
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
@@ -5547,8 +5592,6 @@
|
||||
},
|
||||
"get": {
|
||||
"operationId": "OAuthClientsController_getOAuthClients",
|
||||
"summary": "",
|
||||
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
@@ -5570,8 +5613,6 @@
|
||||
"/v2/oauth-clients/{clientId}": {
|
||||
"get": {
|
||||
"operationId": "OAuthClientsController_getOAuthClientById",
|
||||
"summary": "",
|
||||
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "clientId",
|
||||
@@ -5600,8 +5641,6 @@
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "OAuthClientsController_updateOAuthClient",
|
||||
"summary": "",
|
||||
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "clientId",
|
||||
@@ -5640,8 +5679,6 @@
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "OAuthClientsController_deleteOAuthClient",
|
||||
"summary": "",
|
||||
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "clientId",
|
||||
@@ -5672,8 +5709,6 @@
|
||||
"/v2/oauth-clients/{clientId}/managed-users": {
|
||||
"get": {
|
||||
"operationId": "OAuthClientsController_getOAuthClientManagedUsersById",
|
||||
"summary": "",
|
||||
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "clientId",
|
||||
@@ -8263,7 +8298,7 @@
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for the field in format `some-slug`. It is used to access response to this booking field during the booking",
|
||||
"description": "Unique identifier for the field in format `some-slug`. It is used to access response to this booking field during the booking. Special slug is `attendeePhoneNumber` - if you create\n a phone input field with this slug for organization team event type you can create an organization team event type that can be booked using phone without requiring an email by setting {\"type\": \"email\", \"required\": false, \"hidden\": true} to the email booking field input in the request body.",
|
||||
"example": "some-slug"
|
||||
},
|
||||
"label": {
|
||||
@@ -9109,7 +9144,12 @@
|
||||
"type": "string"
|
||||
},
|
||||
"required": {
|
||||
"type": "object"
|
||||
"type": "object",
|
||||
"description": "Can be set to false only for organization team event types and if you also pass booking field {type: \"phone\", slug: \"attendeePhoneNumber\", required: true, hidden: false, label: \"whatever label\"} of booking field type PhoneFieldInput_2024_06_14 - this is done\n to enable phone only bookings where during the booking attendee can provide only their phone number and not provide email, so you must pass to the email booking field {hidden: true, required: false}.\n If true show under event type settings but don't show this booking field in the Booker. If false show in both."
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"description": "Can be set to true only for organization team event types and if you also pass booking field {type: \"phone\", slug: \"attendeePhoneNumber\", required: true, hidden: false, label: \"whatever label\"} of booking field type PhoneFieldInput_2024_06_14 - this is done\n to enable phone only bookings where during the booking attendee can provide only their phone number and not provide email, so you must pass to the email booking field {hidden: true, required: false}.\n If true show under event type settings but don't show this booking field in the Booker. If false show in both."
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
@@ -9122,7 +9162,6 @@
|
||||
"required": [
|
||||
"type",
|
||||
"label",
|
||||
"required",
|
||||
"placeholder"
|
||||
]
|
||||
},
|
||||
@@ -9845,7 +9884,12 @@
|
||||
},
|
||||
"required": {
|
||||
"type": "object",
|
||||
"default": true
|
||||
"default": true,
|
||||
"description": "Can be set to false only for organization team event types and if you also pass booking field {type: \"phone\", slug: \"attendeePhoneNumber\", required: true, hidden: false, label: \"whatever label\"} of booking field type PhoneFieldInput_2024_06_14 - this is done\n to enable phone only bookings where during the booking attendee can provide only their phone number and not provide email, so you must pass to the email booking field {hidden: true, required: false}.\n If true show under event type settings but don't show this booking field in the Booker. If false show in both."
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"description": "If true show under event type settings but don't show this booking field in the Booker. If false show in both. Can only be hidden\n for organization team event types when also providing attendee phone number booking field."
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
@@ -9867,7 +9911,6 @@
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"required",
|
||||
"isDefault",
|
||||
"slug"
|
||||
]
|
||||
@@ -10645,7 +10688,7 @@
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for the field in format `some-slug`. It is used to access response to this booking field during the booking",
|
||||
"description": "Unique identifier for the field in format `some-slug`. It is used to access response to this booking field during the booking. Special slug is `attendeePhoneNumber` - if you create\n a phone input field with this slug for organization team event type you can create an organization team event type that can be booked using phone without requiring an email by setting {\"type\": \"email\", \"required\": false, \"hidden\": true} to the email booking field input in the request body.",
|
||||
"example": "some-slug"
|
||||
},
|
||||
"label": {
|
||||
@@ -13139,7 +13182,14 @@
|
||||
"example": "https://masterchief.com/argentina/flan/video/9129412"
|
||||
},
|
||||
"schedulingType": {
|
||||
"type": "object"
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"collective",
|
||||
"roundRobin",
|
||||
"managed"
|
||||
],
|
||||
"example": "collective",
|
||||
"description": "The scheduling type for the team event - collective, roundRobin or managed."
|
||||
},
|
||||
"hosts": {
|
||||
"type": "array",
|
||||
@@ -13570,11 +13620,10 @@
|
||||
},
|
||||
"schedulingType": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"enum": [
|
||||
"ROUND_ROBIN",
|
||||
"COLLECTIVE",
|
||||
"MANAGED"
|
||||
"roundRobin",
|
||||
"collective",
|
||||
"managed"
|
||||
]
|
||||
},
|
||||
"team": {
|
||||
@@ -16480,6 +16529,19 @@
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"BookingInputOrganizersDefaultAppLocation_2024_08_13": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "organizersDefaultApp",
|
||||
"description": "only available for team event types and the only allowed value for type is `organizersDefaultApp` - it refers to the default app defined by the organizer."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"ValidateBookingLocation_2024_08_13": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
@@ -16629,6 +16691,9 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingInputPhoneLocation_2024_08_13"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingInputOrganizersDefaultAppLocation_2024_08_13"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -16719,6 +16784,9 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingInputPhoneLocation_2024_08_13"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingInputOrganizersDefaultAppLocation_2024_08_13"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -16815,6 +16883,9 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingInputPhoneLocation_2024_08_13"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/BookingInputOrganizersDefaultAppLocation_2024_08_13"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import TypePage, { type PageProps as ClientPageProps } from "~/users/views/users
|
||||
const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
return <TypePage {...props} />;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { PageProps as LegacyPageProps } from "~/users/views/users-type-publ
|
||||
import LegacyPage from "~/users/views/users-type-public-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
|
||||
const { booking, isSEOIndexable = true, eventData, isBrandingHidden } = props;
|
||||
@@ -31,7 +31,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
})),
|
||||
],
|
||||
};
|
||||
const decodedParams = decodeParams(params);
|
||||
const decodedParams = decodeParams(await params);
|
||||
const metadata = await generateMeetingMetadata(
|
||||
meeting,
|
||||
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
@@ -52,7 +52,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
|
||||
return <LegacyPage {...props} />;
|
||||
|
||||
@@ -11,7 +11,7 @@ import User, { type PageProps as ClientPageProps } from "~/users/views/users-pub
|
||||
const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
return <User {...props} />;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,9 @@ import type { PageProps as LegacyPageProps } from "~/users/views/users-public-vi
|
||||
import LegacyPage from "~/users/views/users-public-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
|
||||
const { profile, markdownStrippedBio, isOrgSEOIndexable, entity } = props;
|
||||
const isOrg = !!profile?.organization;
|
||||
@@ -31,7 +33,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
() => markdownStrippedBio,
|
||||
false,
|
||||
getOrgFullOrigin(entity.orgSlug ?? null),
|
||||
`/${decodeParams(params).user}`
|
||||
`/${decodeParams(await params).user}`
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -45,7 +47,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
|
||||
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
|
||||
return <LegacyPage {...props} />;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { headers } from "next/headers";
|
||||
import PageWrapper from "@components/PageWrapperAppDir";
|
||||
|
||||
export default async function BookingPageWrapperLayout({ children }: { children: React.ReactNode }) {
|
||||
const h = headers();
|
||||
const h = await headers();
|
||||
const nonce = h.get("x-nonce") ?? undefined;
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,7 +15,7 @@ const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
|
||||
export type ClientPageProps = UserTypePageProps | TeamTypePageProps;
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
if ((props as TeamTypePageProps)?.teamId) return <TeamTypePage {...(props as TeamTypePageProps)} />;
|
||||
return <UserTypePage {...(props as UserTypePageProps)} />;
|
||||
|
||||
@@ -17,7 +17,7 @@ export type OrgTypePageProps = UserTypePageProps | TeamTypePageProps;
|
||||
const getData = withAppDirSsr<OrgTypePageProps>(getServerSideProps);
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
|
||||
const { booking, isSEOIndexable = true, eventData, isBrandingHidden } = props;
|
||||
@@ -36,7 +36,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
})),
|
||||
],
|
||||
};
|
||||
const decodedParams = decodeParams(params);
|
||||
const decodedParams = decodeParams(await params);
|
||||
const metadata = await generateMeetingMetadata(
|
||||
meeting,
|
||||
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
@@ -56,7 +56,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
};
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
if ((props as TeamTypePageProps)?.teamId) {
|
||||
return <TeamTypePage {...(props as TeamTypePageProps)} />;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
|
||||
export type ClientPageProps = UserPageProps | TeamPageProps;
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
if ((props as TeamPageProps)?.team) return <TeamPage {...(props as TeamPageProps)} />;
|
||||
return <UserPage {...(props as UserPageProps)} />;
|
||||
|
||||
@@ -18,7 +18,7 @@ export type OrgPageProps = UserPageProps | TeamPageProps;
|
||||
const getData = withAppDirSsr<OrgPageProps>(getServerSideProps);
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
|
||||
if ((props as TeamPageProps)?.team) {
|
||||
@@ -30,7 +30,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
image: getOrgOrTeamAvatar(team),
|
||||
},
|
||||
};
|
||||
const decodedParams = decodeParams(params);
|
||||
const decodedParams = decodeParams(await params);
|
||||
return {
|
||||
...(await generateMeetingMetadata(
|
||||
meeting,
|
||||
@@ -78,7 +78,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
};
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
if ((props as TeamPageProps)?.team) {
|
||||
return <TeamPage {...(props as TeamPageProps)} />;
|
||||
}
|
||||
|
||||
+5
-3
@@ -12,7 +12,7 @@ import type { Props } from "~/org/[orgSlug]/instant-meeting/team/[slug]/[type]/i
|
||||
import Page from "~/org/[orgSlug]/instant-meeting/team/[slug]/[type]/instant-meeting-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const { isBrandingHidden, eventData } = await getData(context);
|
||||
|
||||
const profileName = eventData?.profile.name ?? "";
|
||||
@@ -29,7 +29,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
})),
|
||||
],
|
||||
};
|
||||
const decodedParams = decodeParams(params);
|
||||
const decodedParams = decodeParams(await params);
|
||||
const metadata = await generateMeetingMetadata(
|
||||
meeting,
|
||||
() => `${title} | ${profileName}`,
|
||||
@@ -51,7 +51,9 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
const getData = withAppDirSsr<Props>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: _PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
return <Page {...props} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import TypePage, { type PageProps as ClientPageProps } from "~/team/type-view";
|
||||
const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
return <TypePage {...props} />;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import LegacyPage from "~/team/type-view";
|
||||
import type { PageProps as LegacyPageProps } from "~/team/type-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
const { booking, isSEOIndexable, eventData, isBrandingHidden } = props;
|
||||
|
||||
@@ -29,7 +29,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
})),
|
||||
],
|
||||
};
|
||||
const decodedParams = decodeParams(params);
|
||||
const decodedParams = decodeParams(await params);
|
||||
const metadata = await generateMeetingMetadata(
|
||||
meeting,
|
||||
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
@@ -51,7 +51,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
return <LegacyPage {...props} />;
|
||||
};
|
||||
export default ServerPage;
|
||||
|
||||
@@ -10,7 +10,7 @@ import TeamPage, { type PageProps as ClientPageProps } from "~/team/team-view";
|
||||
const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
return <TeamPage {...props} />;
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import LegacyPage from "~/team/team-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const { team, markdownStrippedBio, isSEOIndexable, currentOrgDomain } = await getData(
|
||||
buildLegacyCtx(headers(), cookies(), params, searchParams)
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
|
||||
const meeting = {
|
||||
@@ -24,7 +24,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
image: getOrgOrTeamAvatar(team),
|
||||
},
|
||||
};
|
||||
const decodedParams = decodeParams(params);
|
||||
const decodedParams = decodeParams(await params);
|
||||
const metadata = await generateMeetingMetadata(
|
||||
meeting,
|
||||
(t) => team.name || t("nameless_team"),
|
||||
@@ -45,7 +45,9 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: _PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
return <LegacyPage {...props} />;
|
||||
};
|
||||
export default ServerPage;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import type { LayoutProps } from "@calcom/features/shell/Shell";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
export const ShellMainAppDirBackButton = ({ backPath }: { backPath: LayoutProps["backPath"] }) => {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -17,7 +17,7 @@ export const generateMetadata = async () => {
|
||||
};
|
||||
|
||||
const Page = async () => {
|
||||
// const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
|
||||
// const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
// const userId = session?.user?.id;
|
||||
// const orgId = session?.user?.org?.id;
|
||||
// if (!userId || !orgId) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async ({ params }: PageProps) => {
|
||||
const parsed = querySchema.safeParse(params);
|
||||
const parsed = querySchema.safeParse(await params);
|
||||
if (!parsed.success) {
|
||||
redirect("/bookings/upcoming");
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const session = await getServerSession({ req: context.req });
|
||||
|
||||
if (!session?.user?.id) {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
|
||||
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<Shell withoutSeo={true} withoutMain={true}>
|
||||
{children}
|
||||
</Shell>
|
||||
);
|
||||
return <Shell withoutMain={true}>{children}</Shell>;
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
||||
@@ -16,8 +16,9 @@ export const generateMetadata = async () =>
|
||||
(t) => t("create_manage_teams_collaborative")
|
||||
);
|
||||
|
||||
const ServerPage = async ({ searchParams }: ServerPageProps) => {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
|
||||
const ServerPage = async ({ searchParams: _searchParams }: ServerPageProps) => {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const searchParams = await _searchParams;
|
||||
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
|
||||
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}` : null;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
export const generateMetadata = () =>
|
||||
_generateMetadata(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, showToast } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
export default function CopyButton({ error }: { error: string }) {
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
import CopyButton from "./copy-button";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const paramsSchema = z.object({
|
||||
});
|
||||
|
||||
export const generateMetadata = async ({ params }: _PageProps) => {
|
||||
const p = paramsSchema.safeParse(params);
|
||||
const p = paramsSchema.safeParse(await params);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
@@ -33,7 +33,7 @@ export const generateMetadata = async ({ params }: _PageProps) => {
|
||||
};
|
||||
|
||||
async function Page({ params }: _PageProps) {
|
||||
const p = paramsSchema.safeParse(params);
|
||||
const p = paramsSchema.safeParse(await params);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
|
||||
@@ -9,7 +9,8 @@ import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import SetupView, { type PageProps as ClientPageProps } from "~/apps/[slug]/setup/setup-view";
|
||||
|
||||
export const generateMetadata = async ({ params }: ServerPageProps) => {
|
||||
export const generateMetadata = async ({ params: _params }: ServerPageProps) => {
|
||||
const params = await _params;
|
||||
const metadata = await _generateMetadata(
|
||||
() => `${params.slug}`,
|
||||
() => ""
|
||||
@@ -26,7 +27,7 @@ export const generateMetadata = async ({ params }: ServerPageProps) => {
|
||||
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
|
||||
const { dehydratedState, ...props } = await getData(context);
|
||||
return <SetupView {...props} />;
|
||||
|
||||
@@ -13,7 +13,7 @@ const querySchema = z.object({
|
||||
});
|
||||
|
||||
async function Page({ params, searchParams }: PageProps) {
|
||||
const parsed = querySchema.safeParse({ ...params, ...searchParams });
|
||||
const parsed = querySchema.safeParse({ ...(await params), ...(await searchParams) });
|
||||
if (!parsed.success) {
|
||||
redirect("/apps/categories/calendar");
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import Page from "~/apps/categories/categories-view";
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
|
||||
async function ServerPage({ params, searchParams }: PageProps) {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
|
||||
return <Page {...props} />;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { OnboardingPageProps } from "~/apps/installation/[[...step]]/step-v
|
||||
import Page from "~/apps/installation/[[...step]]/step-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
|
||||
const { appMetadata } = await getData(legacyCtx);
|
||||
return await _generateMetadata(
|
||||
@@ -22,7 +22,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const getData = withAppDirSsr<OnboardingPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
return <Page {...props} />;
|
||||
};
|
||||
export default ServerPage;
|
||||
|
||||
@@ -19,7 +19,7 @@ export const generateMetadata = async () => {
|
||||
};
|
||||
|
||||
const InstalledAppsWrapper = async ({ params }: PageProps) => {
|
||||
const parsedParams = querySchema.safeParse(params);
|
||||
const parsedParams = querySchema.safeParse(await params);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
redirect("/apps/installed/calendar");
|
||||
|
||||
@@ -18,7 +18,7 @@ export const generateMetadata = async () => {
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
|
||||
const props = await getData(context);
|
||||
return <AppsPage {...props} />;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { FormProvider as ReactHookFormProvider, useForm } from "react-hook-form";
|
||||
|
||||
export default function FormProvider({ children }: { children: React.ReactNode }) {
|
||||
const methods = useForm();
|
||||
|
||||
return <ReactHookFormProvider {...methods}>{children}</ReactHookFormProvider>;
|
||||
}
|
||||
@@ -3,23 +3,25 @@ import type { PageProps as ServerPageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import type { routingServerSidePropsConfig } from "@calcom/app-store/routing-forms/pages/app-routing.config";
|
||||
import LayoutHandler from "@calcom/app-store/routing-forms/pages/layout-handler/[...appPages]";
|
||||
import { routingFormsComponents } from "@calcom/app-store/routing-forms/pages/app-routing.client-config";
|
||||
import type { routingServerSidePropsConfig } from "@calcom/app-store/routing-forms/pages/app-routing.server-config";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
|
||||
import { getServerSideProps } from "@lib/apps/routing-forms/[...pages]/getServerSideProps";
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import FormProvider from "./FormProvider";
|
||||
|
||||
const normalizePages = (pages: string[] | string | undefined) => {
|
||||
const normalizedPages = Array.isArray(pages) ? pages : pages?.split("/") ?? [];
|
||||
return {
|
||||
mainPage: normalizedPages[0],
|
||||
mainPage: normalizedPages[0] ?? "forms",
|
||||
subPages: normalizedPages.slice(1),
|
||||
};
|
||||
};
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: { pages: string[] } }) => {
|
||||
const { mainPage } = normalizePages(params.pages);
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ pages: string[] }> }) => {
|
||||
const { mainPage } = normalizePages((await params).pages);
|
||||
return await _generateMetadata(
|
||||
// TODO: Need to show the actual form name instead of "Form"
|
||||
(t) => (mainPage === "routing-link" ? `Form | Cal.com Forms` : `${t("routing_forms")} | Cal.com Forms`),
|
||||
@@ -32,22 +34,24 @@ type GetServerSidePropsResult =
|
||||
const getData = withAppDirSsr<GetServerSidePropsResult>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
const { mainPage, subPages } = normalizePages(params.pages);
|
||||
const { mainPage } = normalizePages((await params).pages);
|
||||
|
||||
const componentProps = {
|
||||
...props,
|
||||
pages: subPages,
|
||||
};
|
||||
const Component = await routingFormsComponents[mainPage as keyof typeof routingFormsComponents]();
|
||||
const FinalComponent = () => (
|
||||
<FormProvider>
|
||||
<Component {...(props as any)} />
|
||||
</FormProvider>
|
||||
);
|
||||
|
||||
if (mainPage === "routing-link") {
|
||||
return <LayoutHandler {...componentProps} />;
|
||||
return <FinalComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Shell withoutMain withoutSeo>
|
||||
<LayoutHandler {...componentProps} />
|
||||
<Shell withoutMain>
|
||||
<FinalComponent />
|
||||
</Shell>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import Link from "next/link";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, Icon } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
|
||||
import AuthContainer from "@components/ui/AuthContainer";
|
||||
|
||||
@@ -20,7 +21,7 @@ const querySchema = z.object({
|
||||
|
||||
const ServerPage = async ({ searchParams }: PageProps) => {
|
||||
const t = await getTranslate();
|
||||
const { error } = querySchema.parse({ error: searchParams?.error || undefined });
|
||||
const { error } = querySchema.parse({ error: (await searchParams)?.error || undefined });
|
||||
const errorMsg = t("error_during_login") + (error ? ` Error code: ${error}` : "");
|
||||
return (
|
||||
<AuthContainer>
|
||||
|
||||
@@ -19,7 +19,7 @@ export const generateMetadata = async () => {
|
||||
|
||||
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
|
||||
return <SetNewUserPassword {...props} />;
|
||||
|
||||
@@ -18,7 +18,7 @@ export const generateMetadata = async () => {
|
||||
};
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const session = await getServerSession({ req: context.req });
|
||||
|
||||
if (session) {
|
||||
|
||||
@@ -20,7 +20,9 @@ export const generateMetadata = async () => {
|
||||
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
return <Login {...props} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ export const generateMetadata = async () => {
|
||||
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
// cookie will be cleared in `/apps/web/middleware.ts`
|
||||
const h = headers();
|
||||
const context = buildLegacyCtx(h, cookies(), params, searchParams);
|
||||
const h = await headers();
|
||||
const context = buildLegacyCtx(h, await cookies(), await params, await searchParams);
|
||||
await ssrInit(context);
|
||||
|
||||
return <Logout query={context.query} />;
|
||||
|
||||
@@ -20,7 +20,9 @@ export const generateMetadata = async () => {
|
||||
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
return <Setup {...props} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { PageProps as ClientPageProps } from "~/auth/signin-view";
|
||||
|
||||
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const session = await getServerSession({ req: context.req });
|
||||
if (session) {
|
||||
redirect("/");
|
||||
|
||||
@@ -11,7 +11,7 @@ import SSOProviderView from "~/auth/sso/provider-view";
|
||||
|
||||
const getData = withAppDirSsr<SSOProviderPageProps>(getServerSideProps);
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
|
||||
return <SSOProviderView {...props} />;
|
||||
|
||||
@@ -11,7 +11,7 @@ import SSODirectView from "~/auth/sso/direct-view";
|
||||
|
||||
const getData = withAppDirSsr<SSODirectPageProps>(getServerSideProps);
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
|
||||
return <SSODirectView {...props} />;
|
||||
|
||||
@@ -19,7 +19,9 @@ export const generateMetadata = async () => {
|
||||
|
||||
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
return <VerifyEmailChange {...props} />;
|
||||
};
|
||||
export default ServerPage;
|
||||
|
||||
@@ -13,7 +13,7 @@ const querySchema = z.object({
|
||||
});
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const p = { ...params, ...searchParams };
|
||||
const p = { ...(await params), ...(await searchParams) };
|
||||
const { sessionId, stripeCustomerId } = querySchema.parse(p);
|
||||
|
||||
const data = await StripeService.getCheckoutSession({
|
||||
|
||||
@@ -25,7 +25,7 @@ const querySchema = z.object({
|
||||
const getSchedule = cache((id: number) => ScheduleRepository.findScheduleById({ id }));
|
||||
|
||||
export const generateMetadata = async ({ params }: PageProps) => {
|
||||
const parsed = querySchema.safeParse(params);
|
||||
const parsed = querySchema.safeParse(await params);
|
||||
if (!parsed.success) {
|
||||
notFound();
|
||||
}
|
||||
@@ -43,13 +43,13 @@ export const generateMetadata = async ({ params }: PageProps) => {
|
||||
};
|
||||
|
||||
const Page = async ({ params }: PageProps) => {
|
||||
const parsed = querySchema.safeParse(params);
|
||||
const parsed = querySchema.safeParse(await params);
|
||||
if (!parsed.success) {
|
||||
notFound();
|
||||
}
|
||||
// const scheduleId = Number(params.schedule);
|
||||
// const scheduleId = Number(await params.schedule);
|
||||
|
||||
// const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
|
||||
// const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
// const userId = session?.user?.id;
|
||||
// if (!userId) {
|
||||
// notFound();
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
import { ErrorBoundary, Icon } from "@calcom/ui";
|
||||
import { ErrorBoundary } from "@calcom/ui/components/errorBoundary";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
|
||||
export default function TroubleshooterLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
const getEmbedData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getEmbedData(context);
|
||||
return <OldPage {...props} />;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const { bookingInfo, eventType, recurringBookings, orgSlug } = await getData(
|
||||
buildLegacyCtx(headers(), cookies(), params, searchParams)
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation;
|
||||
|
||||
@@ -33,7 +33,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: _PageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(context);
|
||||
return <OldPage {...props} />;
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import { type PageProps } from "@lib/d/[link]/[slug]/getServerSideProps";
|
||||
import Type from "~/d/[link]/d-type-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const pageProps = await getData(legacyCtx);
|
||||
|
||||
const { booking, eventData, isBrandingHidden } = pageProps;
|
||||
@@ -27,7 +27,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
const ServerPage = async ({ params, searchParams }: _PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const pageProps = await getData(legacyCtx);
|
||||
|
||||
return <Type {...pageProps} />;
|
||||
|
||||
@@ -22,7 +22,7 @@ const querySchema = z.object({
|
||||
});
|
||||
|
||||
export const generateMetadata = async ({ params }: _PageProps) => {
|
||||
const parsed = querySchema.safeParse(params);
|
||||
const parsed = querySchema.safeParse(await params);
|
||||
if (!parsed.success) {
|
||||
return await _generateMetadata(
|
||||
(t) => `${t("event_type")}`,
|
||||
@@ -43,7 +43,7 @@ export const generateMetadata = async ({ params }: _PageProps) => {
|
||||
const getData = withAppDirSsr<EventTypePageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: _PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
|
||||
return <EventTypePageWrapper {...props} />;
|
||||
|
||||
@@ -22,7 +22,7 @@ export const generateMetadata = async () => {
|
||||
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
|
||||
|
||||
const props = await getData(context);
|
||||
return <Page {...props} />;
|
||||
|
||||
@@ -5,7 +5,9 @@ import { useSession } from "next-auth/react";
|
||||
import { UpgradeTip } from "@calcom/features/tips";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon, Button, ButtonGroup } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { ButtonGroup } from "@calcom/ui/components/buttonGroup";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
|
||||
export default function UpgradeTipWrapper({ children }: { children: React.ReactNode }) {
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -8,7 +8,7 @@ export default async function InsightsLayout({ children }: { children: React.Rea
|
||||
const t = await getTranslate();
|
||||
return (
|
||||
<div>
|
||||
<Shell withoutMain={false} withoutSeo={true} heading={t("insights")} subtitle={t("insights_subtitle")}>
|
||||
<Shell withoutMain={false} heading={t("insights")} subtitle={t("insights_subtitle")}>
|
||||
<UpgradeTipWrapper>{children}</UpgradeTipWrapper>
|
||||
</Shell>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { headers } from "next/headers";
|
||||
import PageWrapper from "@components/PageWrapperAppDir";
|
||||
|
||||
export default async function PageWrapperLayout({ children }: { children: React.ReactNode }) {
|
||||
const h = headers();
|
||||
const h = await headers();
|
||||
const nonce = h.get("x-nonce") ?? undefined;
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,7 +10,9 @@ import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
const eventName = props.booking.title;
|
||||
return await _generateMetadata(
|
||||
(t) => `${t("payment")} | ${eventName} | ${APP_NAME}`,
|
||||
@@ -21,7 +23,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const getData = withAppDirSsr<PaymentPageProps>(getServerSideProps);
|
||||
|
||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
const props = await getData(
|
||||
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
|
||||
);
|
||||
|
||||
return <PaymentPage {...props} />;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user