fix: v2 event type locations (#17288)

* refactor: split internal locations in separate file

* refactor: rename TransformedLocationsSchema to InternalLocationsSchema

* feat: add missing locations in internal schema

* refactor: explicit input locations only for input

* refactor: explicit output locations only for output

* fix: v2

* fix: atoms

* test: internal to api locations transformation

* e2e tests

* upgrade platform libraries
This commit is contained in:
Lauris Skraucis
2024-10-24 17:21:17 +00:00
committed by GitHub
parent 50a41109d0
commit c629d08a9c
20 changed files with 1087 additions and 280 deletions
+1 -1
View File
@@ -28,7 +28,7 @@
"dependencies": {
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.51",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.52",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
@@ -1251,4 +1251,154 @@ describe("Event types Endpoints", () => {
await app.close();
});
});
describe("Handle event-types locations", () => {
let app: INestApplication;
let oAuthClient: PlatformOAuthClient;
let organization: Team;
let userRepositoryFixture: UserRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let teamRepositoryFixture: TeamRepositoryFixture;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
const userEmail = "locations-event-types-test-e2e@api.com";
const name = "bob-the-locations-builder";
const username = name;
let user: User;
let legacyEventTypeId1: number;
let legacyEventTypeId2: number;
beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
providers: [PrismaExceptionFilter, HttpExceptionFilter],
imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule],
})
)
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
organization = await teamRepositoryFixture.create({ name: "organization" });
oAuthClient = await createOAuthClient(organization.id);
user = await userRepositoryFixture.create({
email: userEmail,
name,
username,
});
await app.init();
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
name: "name",
redirectUris: ["redirect-uri"],
permissions: 32,
};
const secret = "secret";
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
return client;
}
it("should return integration location with link and credentialId", async () => {
const eventTypeInput = {
title: "event type discord",
description: "event type description",
length: 40,
hidden: false,
slug: "discord-event-type",
locations: [
{
type: "integrations:discord_video",
link: "https://discord.com/users/100",
credentialId: 100,
},
],
schedulingType: SchedulingType.ROUND_ROBIN,
bookingFields: [],
};
const legacyEventType = await eventTypesRepositoryFixture.create(eventTypeInput, user.id);
legacyEventTypeId1 = legacyEventType.id;
return request(app.getHttpServer())
.get(`/api/v2/event-types/${legacyEventTypeId1}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
.expect(200)
.then(async (response) => {
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
const fetchedEventType = responseBody.data;
expect(fetchedEventType.locations).toEqual([
{
type: "integration",
integration: "discord-video",
link: eventTypeInput.locations[0].link,
credentialId: eventTypeInput.locations[0].credentialId,
},
]);
});
});
it("should return unsupported location", async () => {
const eventTypeInput = {
title: "event type not existing",
description: "event type description",
length: 40,
hidden: false,
slug: "not-existing-event-type",
locations: [
{
type: "this-type-does-not-exist",
},
],
schedulingType: SchedulingType.ROUND_ROBIN,
bookingFields: [],
};
const legacyEventType = await eventTypesRepositoryFixture.create(eventTypeInput, user.id);
legacyEventTypeId1 = legacyEventType.id;
return request(app.getHttpServer())
.get(`/api/v2/event-types/${legacyEventTypeId1}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
.expect(200)
.then(async (response) => {
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
const fetchedEventType = responseBody.data;
expect(fetchedEventType.locations).toEqual([
{ type: "unknown", location: JSON.stringify(eventTypeInput.locations[0]) },
]);
});
});
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
try {
await eventTypesRepositoryFixture.delete(legacyEventTypeId1);
await eventTypesRepositoryFixture.delete(legacyEventTypeId2);
} catch (e) {
// Event type might have been deleted by the test
}
try {
await userRepositoryFixture.delete(user.id);
} catch (e) {
// User might have been deleted by the test
}
await app.close();
});
});
});
@@ -7,7 +7,7 @@ import {
transformLocationsInternalToApi,
transformBookingFieldsInternalToApi,
parseRecurringEvent,
TransformedLocationsSchema,
InternalLocationSchema,
BookingFieldsSchema,
SystemField,
CustomField,
@@ -20,12 +20,14 @@ import {
transformEventTypeColorsInternalToApi,
parseEventTypeColor,
transformSeatsInternalToApi,
InternalLocation,
} from "@calcom/platform-libraries";
import {
TransformFutureBookingsLimitSchema_2024_06_14,
BookerLayoutsTransformedSchema,
NoticeThresholdTransformedSchema,
EventTypeOutput_2024_06_14,
OutputUnknownLocation_2024_06_14,
} from "@calcom/platform-types";
type EventTypeRelations = {
@@ -190,7 +192,20 @@ export class OutputEventTypesService_2024_06_14 {
transformLocations(locations: any) {
if (!locations) return [];
return transformLocationsInternalToApi(TransformedLocationsSchema.parse(locations));
const knownLocations: InternalLocation[] = [];
const unknownLocations: OutputUnknownLocation_2024_06_14[] = [];
const parsedLocations = locations.map((location: any) => {
const result = InternalLocationSchema.safeParse(location);
if (result.success) {
return knownLocations.push(result.data);
} else {
return unknownLocations.push({ type: "unknown", location: JSON.stringify(location) });
}
});
return [...transformLocationsInternalToApi(knownLocations), ...unknownLocations];
}
transformDestinationCalendar(destinationCalendar?: DestinationCalendar | null) {
+275 -45
View File
@@ -5485,6 +5485,9 @@
},
"data": {
"$ref": "#/components/schemas/CreateManagedUserData"
},
"error": {
"type": "object"
}
},
"required": [
@@ -5849,7 +5852,7 @@
"refreshToken"
]
},
"AddressLocation_2024_06_14": {
"InputAddressLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
@@ -5871,7 +5874,7 @@
"public"
]
},
"LinkLocation_2024_06_14": {
"InputLinkLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
@@ -5893,7 +5896,7 @@
"public"
]
},
"IntegrationLocation_2024_06_14": {
"InputIntegrationLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
@@ -5914,7 +5917,7 @@
"integration"
]
},
"PhoneLocation_2024_06_14": {
"InputPhoneLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
@@ -6606,7 +6609,7 @@
"showAvailabilityCount"
]
},
"AttendeeAddressLocation_2024_06_14": {
"InputAttendeeAddressLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
@@ -6619,7 +6622,7 @@
"type"
]
},
"AttendeePhoneLocation_2024_06_14": {
"InputAttendeePhoneLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
@@ -6632,7 +6635,7 @@
"type"
]
},
"AttendeeDefinedLocation_2024_06_14": {
"InputAttendeeDefinedLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
@@ -6734,25 +6737,25 @@
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/AddressLocation_2024_06_14"
"$ref": "#/components/schemas/InputAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/LinkLocation_2024_06_14"
"$ref": "#/components/schemas/InputLinkLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/IntegrationLocation_2024_06_14"
"$ref": "#/components/schemas/InputIntegrationLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/PhoneLocation_2024_06_14"
"$ref": "#/components/schemas/InputPhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeeAddressLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeeAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeePhoneLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeePhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeeDefinedLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeeDefinedLocation_2024_06_14"
}
]
}
@@ -6943,6 +6946,219 @@
"slug"
]
},
"OutputAddressLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"address",
"link",
"integration",
"phone",
"attendeeAddress",
"attendeePhone",
"attendeeDefined"
],
"example": "address",
"description": "only allowed value for type is `address`"
},
"address": {
"type": "string",
"example": "123 Example St, City, Country"
},
"public": {
"type": "boolean"
}
},
"required": [
"type",
"address",
"public"
]
},
"OutputLinkLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"address",
"link",
"integration",
"phone",
"attendeeAddress",
"attendeePhone",
"attendeeDefined"
],
"example": "link",
"description": "only allowed value for type is `link`"
},
"link": {
"type": "string",
"example": "https://customvideo.com/join/123456"
},
"public": {
"type": "boolean"
}
},
"required": [
"type",
"link",
"public"
]
},
"OutputIntegrationLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"address",
"link",
"integration",
"phone",
"attendeeAddress",
"attendeePhone",
"attendeeDefined",
"conferencing",
"unknown"
],
"example": "integration",
"description": "only allowed value for type is `integration`"
},
"integration": {
"type": "string",
"enum": [
"cal-video",
"google-meet",
"zoom",
"whereby-video",
"whatsapp-video",
"webex-video",
"telegram-video",
"tandem",
"sylaps-video",
"skype-video",
"sirius-video",
"signal-video",
"shimmer-video",
"salesroom-video",
"roam-video",
"riverside-video",
"ping-video",
"office365-video",
"mirotalk-video",
"jitsi",
"jelly-video",
"jelly-conferencing",
"huddle",
"facetime-video",
"element-call-video",
"eightxeight-video",
"discord-video",
"demodesk-video",
"campsite-conferencing",
"campfire-video",
"around-video"
],
"example": "cal-video"
},
"link": {
"type": "string"
},
"credentialdId": {
"type": "number"
}
},
"required": [
"type",
"integration"
]
},
"OutputPhoneLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"address",
"link",
"integration",
"phone",
"attendeeAddress",
"attendeePhone",
"attendeeDefined"
],
"example": "phone",
"description": "only allowed value for type is `phone`"
},
"phone": {
"type": "string",
"example": "+37120993151"
},
"public": {
"type": "boolean"
}
},
"required": [
"type",
"phone",
"public"
]
},
"OutputConferencingLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"address",
"link",
"integration",
"phone",
"attendeeAddress",
"attendeePhone",
"attendeeDefined",
"conferencing",
"unknown"
],
"example": "conferencing",
"description": "only allowed value for type is `conferencing`"
}
},
"required": [
"type"
]
},
"OutputUnknownLocation_2024_06_14": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"address",
"link",
"integration",
"phone",
"attendeeAddress",
"attendeePhone",
"attendeeDefined",
"conferencing",
"unknown"
],
"example": "unknown",
"description": "only allowed value for type is `unknown`"
},
"location": {
"type": "string"
}
},
"required": [
"type",
"location"
]
},
"EmailDefaultFieldOutput_2024_06_14": {
"type": "object",
"properties": {
@@ -7885,16 +8101,22 @@
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/AddressLocation_2024_06_14"
"$ref": "#/components/schemas/OutputAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/LinkLocation_2024_06_14"
"$ref": "#/components/schemas/OutputLinkLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/IntegrationLocation_2024_06_14"
"$ref": "#/components/schemas/OutputIntegrationLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/PhoneLocation_2024_06_14"
"$ref": "#/components/schemas/OutputPhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/OutputConferencingLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/OutputUnknownLocation_2024_06_14"
}
]
}
@@ -8218,25 +8440,25 @@
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/AddressLocation_2024_06_14"
"$ref": "#/components/schemas/InputAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/LinkLocation_2024_06_14"
"$ref": "#/components/schemas/InputLinkLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/IntegrationLocation_2024_06_14"
"$ref": "#/components/schemas/InputIntegrationLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/PhoneLocation_2024_06_14"
"$ref": "#/components/schemas/InputPhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeeAddressLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeeAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeePhoneLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeePhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeeDefinedLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeeDefinedLocation_2024_06_14"
}
]
}
@@ -9697,25 +9919,25 @@
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/AddressLocation_2024_06_14"
"$ref": "#/components/schemas/InputAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/LinkLocation_2024_06_14"
"$ref": "#/components/schemas/InputLinkLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/IntegrationLocation_2024_06_14"
"$ref": "#/components/schemas/InputIntegrationLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/PhoneLocation_2024_06_14"
"$ref": "#/components/schemas/InputPhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeeAddressLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeeAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeePhoneLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeePhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeeDefinedLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeeDefinedLocation_2024_06_14"
}
]
}
@@ -10013,16 +10235,22 @@
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/AddressLocation_2024_06_14"
"$ref": "#/components/schemas/OutputAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/LinkLocation_2024_06_14"
"$ref": "#/components/schemas/OutputLinkLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/IntegrationLocation_2024_06_14"
"$ref": "#/components/schemas/OutputIntegrationLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/PhoneLocation_2024_06_14"
"$ref": "#/components/schemas/OutputPhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/OutputConferencingLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/OutputUnknownLocation_2024_06_14"
}
]
}
@@ -10433,25 +10661,25 @@
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/AddressLocation_2024_06_14"
"$ref": "#/components/schemas/InputAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/LinkLocation_2024_06_14"
"$ref": "#/components/schemas/InputLinkLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/IntegrationLocation_2024_06_14"
"$ref": "#/components/schemas/InputIntegrationLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/PhoneLocation_2024_06_14"
"$ref": "#/components/schemas/InputPhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeeAddressLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeeAddressLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeePhoneLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeePhoneLocation_2024_06_14"
},
{
"$ref": "#/components/schemas/AttendeeDefinedLocation_2024_06_14"
"$ref": "#/components/schemas/InputAttendeeDefinedLocation_2024_06_14"
}
]
}
@@ -11425,7 +11653,8 @@
"RECORDING_TRANSCRIPTION_GENERATED",
"OOO_CREATED",
"AFTER_HOSTS_CAL_VIDEO_NO_SHOW",
"AFTER_GUESTS_CAL_VIDEO_NO_SHOW"
"AFTER_GUESTS_CAL_VIDEO_NO_SHOW",
"FORM_SUBMITTED_NO_EVENT"
]
},
"active": {
@@ -11501,7 +11730,8 @@
"RECORDING_TRANSCRIPTION_GENERATED",
"OOO_CREATED",
"AFTER_HOSTS_CAL_VIDEO_NO_SHOW",
"AFTER_GUESTS_CAL_VIDEO_NO_SHOW"
"AFTER_GUESTS_CAL_VIDEO_NO_SHOW",
"FORM_SUBMITTED_NO_EVENT"
]
},
"active": {
@@ -9,7 +9,7 @@ import {
} from "@calcom/platform-enums/monorepo";
import type {
InputBookingField_2024_06_14,
Location_2024_06_14,
InputLocation_2024_06_14,
BookingLimitsCount_2024_06_14,
BookingWindow_2024_06_14,
BookerLayouts_2024_06_14,
@@ -19,9 +19,9 @@ import type {
CreateEventTypeInput_2024_06_14,
SeatOptionsTransformedSchema,
SeatOptionsDisabledSchema,
AttendeeAddressLocation_2024_06_14,
AttendeePhoneLocation_2024_06_14,
AttendeeDefinedLocation_2024_06_14,
InputAttendeeAddressLocation_2024_06_14,
InputAttendeePhoneLocation_2024_06_14,
InputAttendeeDefinedLocation_2024_06_14,
} from "@calcom/platform-types";
import {
@@ -45,7 +45,7 @@ import {
describe("transformLocationsApiToInternal", () => {
it("should transform address", () => {
const input: Location_2024_06_14[] = [
const input: InputLocation_2024_06_14[] = [
{
type: "address",
address: "London road 10-1",
@@ -61,7 +61,7 @@ describe("transformLocationsApiToInternal", () => {
});
it("should transform link", () => {
const input: Location_2024_06_14[] = [
const input: InputLocation_2024_06_14[] = [
{
type: "link",
link: "https://customvideo.com/join/123456",
@@ -79,7 +79,7 @@ describe("transformLocationsApiToInternal", () => {
});
it("should transform integration", () => {
const input: Location_2024_06_14[] = [
const input: InputLocation_2024_06_14[] = [
{
type: "integration",
integration: "cal-video",
@@ -94,7 +94,7 @@ describe("transformLocationsApiToInternal", () => {
});
it("should transform phone", () => {
const input: Location_2024_06_14[] = [
const input: InputLocation_2024_06_14[] = [
{
type: "phone",
phone: "+37120993151",
@@ -112,7 +112,7 @@ describe("transformLocationsApiToInternal", () => {
});
it("should transform attendee address", () => {
const input: AttendeeAddressLocation_2024_06_14[] = [
const input: InputAttendeeAddressLocation_2024_06_14[] = [
{
type: "attendeeAddress",
},
@@ -125,7 +125,7 @@ describe("transformLocationsApiToInternal", () => {
});
it("should transform attendee phone", () => {
const input: AttendeePhoneLocation_2024_06_14[] = [
const input: InputAttendeePhoneLocation_2024_06_14[] = [
{
type: "attendeePhone",
},
@@ -138,7 +138,7 @@ describe("transformLocationsApiToInternal", () => {
});
it("should transform attendee defined", () => {
const input: AttendeeDefinedLocation_2024_06_14[] = [
const input: InputAttendeeDefinedLocation_2024_06_14[] = [
{
type: "attendeeDefined",
},
@@ -1,14 +1,21 @@
import { z } from "zod";
import type { InputLocation_2024_06_14 } from "@calcom/platform-types";
import { type Integration_2024_06_14 } from "@calcom/platform-types";
import { type CreateEventTypeInput_2024_06_14, type Integration_2024_06_14 } from "@calcom/platform-types";
import type {
AttendeeAddressLocation,
AttendeeDefinedLocation,
AttendeePhoneLocation,
OrganizerAddressLocation,
OrganizerIntegrationLocation,
OrganizerLinkLocation,
OrganizerPhoneLocation,
} from "../internal/locations";
const integrationsMapping: Record<Integration_2024_06_14, "integrations:daily"> = {
const apiToInternalintegrationsMapping: Record<Integration_2024_06_14, "integrations:daily"> = {
"cal-video": "integrations:daily",
};
export function transformLocationsApiToInternal(
inputLocations: CreateEventTypeInput_2024_06_14["locations"]
) {
export function transformLocationsApiToInternal(inputLocations: InputLocation_2024_06_14[] | undefined) {
if (!inputLocations) {
return [];
}
@@ -31,7 +38,7 @@ export function transformLocationsApiToInternal(
displayLocationPublicly: location.public,
} satisfies OrganizerLinkLocation;
case "integration":
const integrationLabel = integrationsMapping[location.integration];
const integrationLabel = apiToInternalintegrationsMapping[location.integration];
return { type: integrationLabel } satisfies OrganizerIntegrationLocation;
case "phone":
return {
@@ -44,69 +51,7 @@ export function transformLocationsApiToInternal(
case "attendeeDefined":
return { type: "somewhereElse" } satisfies AttendeeDefinedLocation;
default:
throw new Error(`Unsupported location type '${type}'`);
throw new Error(`Unsupported input location type '${type}'`);
}
});
}
const integrationsMappingSchema = {
"cal-video": z.literal("integrations:daily"),
};
const OrganizerAddressSchema = z.object({
type: z.literal("inPerson"),
address: z.string(),
displayLocationPublicly: z.boolean().default(false),
});
const OrganizerLinkSchema = z.object({
type: z.literal("link"),
link: z.string().url(),
displayLocationPublicly: z.boolean().default(false),
});
const OrganizerIntegrationSchema = z.object({
type: z.union([integrationsMappingSchema["cal-video"], integrationsMappingSchema["cal-video"]]),
});
const OrganizerPhoneSchema = z.object({
type: z.literal("userPhone"),
hostPhoneNumber: z.string(),
displayLocationPublicly: z.boolean().default(false),
});
const OrganizerConferencingSchema = z.object({
type: z.literal("conferencing"),
});
const AttendeeAddressSchema = z.object({
type: z.literal("attendeeInPerson"),
});
const AttendeePhoneSchema = z.object({
type: z.literal("phone"),
});
const AttendeeDefinedSchema = z.object({
type: z.literal("somewhereElse"),
});
type OrganizerAddressLocation = z.infer<typeof OrganizerAddressSchema>;
type OrganizerLinkLocation = z.infer<typeof OrganizerLinkSchema>;
export type OrganizerIntegrationLocation = z.infer<typeof OrganizerIntegrationSchema>;
type OrganizerPhoneLocation = z.infer<typeof OrganizerPhoneSchema>;
type AttendeeAddressLocation = z.infer<typeof AttendeeAddressSchema>;
type AttendeePhoneLocation = z.infer<typeof AttendeePhoneSchema>;
type AttendeeDefinedLocation = z.infer<typeof AttendeeDefinedSchema>;
const TransformedLocationSchema = z.union([
OrganizerAddressSchema,
OrganizerLinkSchema,
OrganizerIntegrationSchema,
OrganizerPhoneSchema,
OrganizerConferencingSchema,
AttendeeAddressSchema,
AttendeePhoneSchema,
AttendeeDefinedSchema,
]);
export const TransformedLocationsSchema = z.array(TransformedLocationSchema);
@@ -1,2 +1,3 @@
export * from "./api-to-internal";
export * from "./internal-to-api";
export * from "./internal/locations";
@@ -7,10 +7,6 @@ import {
NoticeThresholdUnitEnum,
} from "@calcom/platform-enums/monorepo";
import type {
AddressLocation_2024_06_14,
LinkLocation_2024_06_14,
PhoneLocation_2024_06_14,
IntegrationLocation_2024_06_14,
TransformBookingLimitsSchema_2024_06_14,
TransformFutureBookingsLimitSchema_2024_06_14,
BookerLayoutsTransformedSchema,
@@ -18,9 +14,14 @@ import type {
TransformRecurringEventSchema_2024_06_14,
SeatOptionsTransformedSchema,
SeatOptionsDisabledSchema,
AttendeeAddressLocation_2024_06_14,
AttendeePhoneLocation_2024_06_14,
AttendeeDefinedLocation_2024_06_14,
OutputAddressLocation_2024_06_14,
OutputAttendeeAddressLocation_2024_06_14,
OutputAttendeeDefinedLocation_2024_06_14,
OutputAttendeePhoneLocation_2024_06_14,
OutputIntegrationLocation_2024_06_14,
OutputLinkLocation_2024_06_14,
OutputPhoneLocation_2024_06_14,
OutputUnknownLocation_2024_06_14,
} from "@calcom/platform-types";
import {
@@ -51,7 +52,7 @@ describe("transformLocationsInternalToApi", () => {
},
];
const expectedOutput: AddressLocation_2024_06_14[] = [
const expectedOutput: OutputAddressLocation_2024_06_14[] = [
{
type: "address",
address: "1234 Main St",
@@ -73,7 +74,7 @@ describe("transformLocationsInternalToApi", () => {
},
];
const expectedOutput: LinkLocation_2024_06_14[] = [
const expectedOutput: OutputLinkLocation_2024_06_14[] = [
{
type: "link",
link: "https://example.com",
@@ -95,7 +96,7 @@ describe("transformLocationsInternalToApi", () => {
},
];
const expectedOutput: PhoneLocation_2024_06_14[] = [
const expectedOutput: OutputPhoneLocation_2024_06_14[] = [
{
type: "phone",
phone: "123456789",
@@ -115,7 +116,7 @@ describe("transformLocationsInternalToApi", () => {
},
];
const expectedOutput: IntegrationLocation_2024_06_14[] = [
const expectedOutput: OutputIntegrationLocation_2024_06_14[] = [
{
type: "integration",
integration: "cal-video",
@@ -127,6 +128,91 @@ describe("transformLocationsInternalToApi", () => {
expect(result).toEqual(expectedOutput);
});
it("should transform integration location", () => {
const transformedLocation = [
{
type: "integrations:discord_video" as const,
},
];
const expectedOutput: OutputIntegrationLocation_2024_06_14[] = [
{
type: "integration",
integration: "discord-video",
},
];
const result = transformLocationsInternalToApi(transformedLocation);
expect(result).toEqual(expectedOutput);
});
it("should transform integration location with link and credentialId", () => {
const transformedLocation = [
{
type: "integrations:discord_video" as const,
link: "https://discord.com/users/100",
credentialId: 100,
},
];
const expectedOutput: OutputIntegrationLocation_2024_06_14[] = [
{
type: "integration",
integration: "discord-video",
link: "https://discord.com/users/100",
credentialId: 100,
},
];
const result = transformLocationsInternalToApi(transformedLocation);
expect(result).toEqual(expectedOutput);
});
it("should transform unknown location", () => {
const transformedLocation = [
{
type: "unknown" as const,
location: "unknown location",
},
];
const expectedOutput: OutputUnknownLocation_2024_06_14[] = [
{
type: "unknown",
location: JSON.stringify(transformedLocation[0]),
},
];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const result = transformLocationsInternalToApi(transformedLocation);
expect(result).toEqual(expectedOutput);
});
it("should transform unknown integration location", () => {
const transformedLocation = [
{
type: "integrations:unknown_video" as const,
},
];
const expectedOutput: OutputUnknownLocation_2024_06_14[] = [
{
type: "unknown",
location: JSON.stringify(transformedLocation[0]),
},
];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const result = transformLocationsInternalToApi(transformedLocation);
expect(result).toEqual(expectedOutput);
});
it("should reverse transform attendee address location", () => {
const transformedLocation = [
{
@@ -134,7 +220,7 @@ describe("transformLocationsInternalToApi", () => {
},
];
const expectedOutput: AttendeeAddressLocation_2024_06_14[] = [
const expectedOutput: OutputAttendeeAddressLocation_2024_06_14[] = [
{
type: "attendeeAddress",
},
@@ -151,7 +237,7 @@ describe("transformLocationsInternalToApi", () => {
},
];
const expectedOutput: AttendeePhoneLocation_2024_06_14[] = [
const expectedOutput: OutputAttendeePhoneLocation_2024_06_14[] = [
{
type: "attendeePhone",
},
@@ -168,7 +254,7 @@ describe("transformLocationsInternalToApi", () => {
},
];
const expectedOutput: AttendeeDefinedLocation_2024_06_14[] = [
const expectedOutput: OutputAttendeeDefinedLocation_2024_06_14[] = [
{
type: "attendeeDefined",
},
@@ -208,11 +294,11 @@ describe("transformBookingFieldsInternalToApi", () => {
fullName: {
fields: [
{
name: "fullName",
name: "fullName" as const,
label: "custom label",
placeholder: "custom placeholder",
type: "text",
required: true,
type: "text" as const,
required: true as const,
},
],
},
@@ -1,34 +1,64 @@
import type {
AddressLocation_2024_06_14,
IntegrationLocation_2024_06_14,
LinkLocation_2024_06_14,
PhoneLocation_2024_06_14,
Integration_2024_06_14,
AttendeePhoneLocation_2024_06_14,
AttendeeAddressLocation_2024_06_14,
AttendeeDefinedLocation_2024_06_14,
OutputConferencingLocation_2024_06_14,
OutputIntegration_2024_06_14,
OutputIntegrationLocation_2024_06_14,
OutputAddressLocation_2024_06_14,
OutputAttendeeAddressLocation_2024_06_14,
OutputAttendeeDefinedLocation_2024_06_14,
OutputAttendeePhoneLocation_2024_06_14,
OutputLinkLocation_2024_06_14,
OutputPhoneLocation_2024_06_14,
OutputUnknownLocation_2024_06_14,
} from "@calcom/platform-types";
import type { transformLocationsApiToInternal } from "../api-to-internal";
import type { InternalLocation } from "../internal/locations";
const reverseIntegrationsMapping: Record<string, Integration_2024_06_14> = {
const internalToApiIntegrationsMapping: Record<string, OutputIntegration_2024_06_14> = {
"integrations:daily": "cal-video",
"integrations:google:meet": "google-meet",
"integrations:zoom": "zoom",
"integrations:whereby_video": "whereby-video",
"integrations:whatsapp_video": "whatsapp-video",
"integrations:webex_video": "webex-video",
"integrations:telegram_video": "telegram-video",
"integrations:tandem": "tandem",
"integrations:sylaps_video": "sylaps-video",
"integrations:skype_video": "skype-video",
"integrations:sirius_video_video": "sirius-video",
"integrations:signal_video": "signal-video",
"integrations:shimmer_video": "shimmer-video",
"integrations:salesroom_video": "salesroom-video",
"integrations:roam_video": "roam-video",
"integrations:riverside_video": "riverside-video",
"integrations:ping_video": "ping-video",
"integrations:office365_video": "office365-video",
"integrations:mirotalk_video": "mirotalk-video",
"integrations:jitsi": "jitsi",
"integrations:jelly_video": "jelly-video",
"integrations:jelly_conferencing": "jelly-conferencing",
"integrations:huddle01": "huddle",
"integrations:facetime_video": "facetime-video",
"integrations:element-call_video": "element-call-video",
"integrations:eightxeight_video": "eightxeight-video",
"integrations:discord_video": "discord-video",
"integrations:demodesk_video": "demodesk-video",
"integrations:campsite_conferencing": "campsite-conferencing",
"integrations:campfire_video": "campfire-video",
"integrations:around_video": "around-video",
};
export function transformLocationsInternalToApi(
transformedLocations: ReturnType<typeof transformLocationsApiToInternal>
) {
if (!transformedLocations) {
export function transformLocationsInternalToApi(internalLocations: InternalLocation[] | undefined) {
if (!internalLocations) {
return [];
}
return transformedLocations.map((location) => {
return internalLocations.map((location) => {
switch (location.type) {
case "inPerson": {
if (!location.address) {
throw new Error("Address location must have an address");
}
const addressLocation: AddressLocation_2024_06_14 = {
const addressLocation: OutputAddressLocation_2024_06_14 = {
type: "address",
address: location.address,
public: location.displayLocationPublicly,
@@ -36,7 +66,7 @@ export function transformLocationsInternalToApi(
return addressLocation;
}
case "attendeeInPerson": {
const attendeeAddressLocation: AttendeeAddressLocation_2024_06_14 = {
const attendeeAddressLocation: OutputAttendeeAddressLocation_2024_06_14 = {
type: "attendeeAddress",
};
return attendeeAddressLocation;
@@ -45,7 +75,7 @@ export function transformLocationsInternalToApi(
if (!location.link) {
throw new Error("Link location must have a link");
}
const linkLocation: LinkLocation_2024_06_14 = {
const linkLocation: OutputLinkLocation_2024_06_14 = {
type: "link",
link: location.link,
public: location.displayLocationPublicly,
@@ -56,7 +86,7 @@ export function transformLocationsInternalToApi(
if (!location.hostPhoneNumber) {
throw new Error("Phone location must have a phone number");
}
const phoneLocation: PhoneLocation_2024_06_14 = {
const phoneLocation: OutputPhoneLocation_2024_06_14 = {
type: "phone",
phone: location.hostPhoneNumber,
public: location.displayLocationPublicly,
@@ -64,25 +94,37 @@ export function transformLocationsInternalToApi(
return phoneLocation;
}
case "phone": {
const attendeePhoneLocation: AttendeePhoneLocation_2024_06_14 = {
const attendeePhoneLocation: OutputAttendeePhoneLocation_2024_06_14 = {
type: "attendeePhone",
};
return attendeePhoneLocation;
}
case "somewhereElse": {
const attendeeDefinedLocation: AttendeeDefinedLocation_2024_06_14 = {
const attendeeDefinedLocation: OutputAttendeeDefinedLocation_2024_06_14 = {
type: "attendeeDefined",
};
return attendeeDefinedLocation;
}
case "conferencing": {
const conferencingLocation: OutputConferencingLocation_2024_06_14 = {
type: "conferencing",
};
return conferencingLocation;
}
default: {
const integrationType = reverseIntegrationsMapping[location.type];
const integrationType = internalToApiIntegrationsMapping[location.type];
if (!integrationType) {
throw new Error(`Unsupported integration type '${location.type}'.`);
const unknown: OutputUnknownLocation_2024_06_14 = {
type: "unknown",
location: JSON.stringify(location),
};
return unknown;
}
const integration: IntegrationLocation_2024_06_14 = {
const integration: OutputIntegrationLocation_2024_06_14 = {
type: "integration",
integration: integrationType,
link: location.link,
credentialId: location.credentialId,
};
return integration;
}
@@ -0,0 +1,139 @@
import { z } from "zod";
const integrationsApiAvailable = {
"cal-video": z.literal("integrations:daily"),
};
// note(Lauris): these are read only aka they exist in database and are to be returned by a READ operation
const integrationsApiUnavailable = {
"google-meet": z.literal("integrations:google:meet"),
zoom: z.literal("integrations:zoom"),
"whereby-video": z.literal("integrations:whereby_video"),
"whatsapp-video": z.literal("integrations:whatsapp_video"),
"webex-video": z.literal("integrations:webex_video"),
"telegram-video": z.literal("integrations:telegram_video"),
tandem: z.literal("integrations:tandem"),
"sylaps-video": z.literal("integrations:sylaps_video"),
"skype-video": z.literal("integrations:skype_video"),
"sirius-video": z.literal("integrations:sirius_video_video"),
"signal-video": z.literal("integrations:signal_video"),
"shimmer-video": z.literal("integrations:shimmer_video"),
"salesroom-video": z.literal("integrations:salesroom_video"),
"roam-video": z.literal("integrations:roam_video"),
"riverside-video": z.literal("integrations:riverside_video"),
"ping-video": z.literal("integrations:ping_video"),
"office365-video": z.literal("integrations:office365_video"),
"mirotalk-video": z.literal("integrations:mirotalk_video"),
jitsi: z.literal("integrations:jitsi"),
"jelly-video": z.literal("integrations:jelly_video"),
"jelly-conferencing": z.literal("integrations:jelly_conferencing"),
huddle: z.literal("integrations:huddle01"),
"facetime-video": z.literal("integrations:facetime_video"),
"element-call-video": z.literal("integrations:element-call_video"),
"eightxeight-video": z.literal("integrations:eightxeight_video"),
"discord-video": z.literal("integrations:discord_video"),
"demodesk-video": z.literal("integrations:demodesk_video"),
"campsite-conferencing": z.literal("integrations:campsite_conferencing"),
"campfire-video": z.literal("integrations:campfire_video"),
"around-video": z.literal("integrations:around_video"),
};
export const integrationsApiToInternalMappingSchema = {
...integrationsApiAvailable,
...integrationsApiUnavailable,
};
const OrganizerIntegrationSchema = z.object({
type: z.union([
integrationsApiToInternalMappingSchema["cal-video"],
integrationsApiToInternalMappingSchema["google-meet"],
integrationsApiToInternalMappingSchema["zoom"],
integrationsApiToInternalMappingSchema["whereby-video"],
integrationsApiToInternalMappingSchema["whatsapp-video"],
integrationsApiToInternalMappingSchema["webex-video"],
integrationsApiToInternalMappingSchema["telegram-video"],
integrationsApiToInternalMappingSchema["tandem"],
integrationsApiToInternalMappingSchema["sylaps-video"],
integrationsApiToInternalMappingSchema["skype-video"],
integrationsApiToInternalMappingSchema["sirius-video"],
integrationsApiToInternalMappingSchema["signal-video"],
integrationsApiToInternalMappingSchema["shimmer-video"],
integrationsApiToInternalMappingSchema["salesroom-video"],
integrationsApiToInternalMappingSchema["roam-video"],
integrationsApiToInternalMappingSchema["riverside-video"],
integrationsApiToInternalMappingSchema["ping-video"],
integrationsApiToInternalMappingSchema["office365-video"],
integrationsApiToInternalMappingSchema["mirotalk-video"],
integrationsApiToInternalMappingSchema["jitsi"],
integrationsApiToInternalMappingSchema["jelly-video"],
integrationsApiToInternalMappingSchema["jelly-conferencing"],
integrationsApiToInternalMappingSchema["huddle"],
integrationsApiToInternalMappingSchema["facetime-video"],
integrationsApiToInternalMappingSchema["element-call-video"],
integrationsApiToInternalMappingSchema["eightxeight-video"],
integrationsApiToInternalMappingSchema["discord-video"],
integrationsApiToInternalMappingSchema["demodesk-video"],
integrationsApiToInternalMappingSchema["campsite-conferencing"],
integrationsApiToInternalMappingSchema["campfire-video"],
integrationsApiToInternalMappingSchema["around-video"],
]),
link: z.string().url().optional(),
credentialId: z.number().optional(),
});
const OrganizerAddressSchema = z.object({
type: z.literal("inPerson"),
address: z.string(),
displayLocationPublicly: z.boolean().default(false),
});
const OrganizerLinkSchema = z.object({
type: z.literal("link"),
link: z.string().url(),
displayLocationPublicly: z.boolean().default(false),
});
const OrganizerPhoneSchema = z.object({
type: z.literal("userPhone"),
hostPhoneNumber: z.string(),
displayLocationPublicly: z.boolean().default(false),
});
const OrganizerConferencingSchema = z.object({
type: z.literal("conferencing"),
});
const AttendeeAddressSchema = z.object({
type: z.literal("attendeeInPerson"),
});
const AttendeePhoneSchema = z.object({
type: z.literal("phone"),
});
const AttendeeDefinedSchema = z.object({
type: z.literal("somewhereElse"),
});
export type OrganizerAddressLocation = z.infer<typeof OrganizerAddressSchema>;
export type OrganizerLinkLocation = z.infer<typeof OrganizerLinkSchema>;
export type OrganizerIntegrationLocation = z.infer<typeof OrganizerIntegrationSchema>;
export type OrganizerPhoneLocation = z.infer<typeof OrganizerPhoneSchema>;
export type OrganizerConferencingSchema = z.infer<typeof OrganizerConferencingSchema>;
export type AttendeeAddressLocation = z.infer<typeof AttendeeAddressSchema>;
export type AttendeePhoneLocation = z.infer<typeof AttendeePhoneSchema>;
export type AttendeeDefinedLocation = z.infer<typeof AttendeeDefinedSchema>;
export const InternalLocationSchema = z.union([
OrganizerAddressSchema,
OrganizerLinkSchema,
OrganizerIntegrationSchema,
OrganizerPhoneSchema,
OrganizerConferencingSchema,
AttendeeAddressSchema,
AttendeePhoneSchema,
AttendeeDefinedSchema,
]);
export type InternalLocation = z.infer<typeof InternalLocationSchema>;
export const InternalLocationsSchema = z.array(InternalLocationSchema);
@@ -14,6 +14,7 @@ import type {
CustomFieldOutput_2024_06_14,
EmailDefaultFieldOutput_2024_06_14,
EventTypeOutput_2024_06_14,
InputLocation_2024_06_14,
NameDefaultFieldOutput_2024_06_14,
TeamEventTypeOutput_2024_06_14,
} from "@calcom/platform-types";
@@ -199,7 +200,9 @@ function isDefaultEvent(eventSlug: string) {
}
function getLocations(locations: EventTypeOutput_2024_06_14["locations"]) {
const transformed = transformLocationsApiToInternal(locations);
const transformed = transformLocationsApiToInternal(
locations.filter((location) => isAtomSupportedLocation(location))
);
const withPrivateHidden = transformed.map((location) => {
const { displayLocationPublicly, type } = location;
@@ -224,6 +227,20 @@ function getLocations(locations: EventTypeOutput_2024_06_14["locations"]) {
return withPrivateHidden;
}
function isAtomSupportedLocation(
location: EventTypeOutput_2024_06_14["locations"][number]
): location is InputLocation_2024_06_14 {
return (
location.type === "address" ||
location.type === "attendeeAddress" ||
location.type === "link" ||
location.type === "phone" ||
location.type === "attendeePhone" ||
location.type === "attendeeDefined" ||
(location.type === "integration" && location.integration === "cal-video")
);
}
function getBookingFields(
bookingFields: EventTypeOutput_2024_06_14["bookingFields"],
defaultFormValues: BookerPlatformWrapperAtomProps["defaultFormValues"] | undefined
+3 -1
View File
@@ -126,7 +126,8 @@ export {
transformEventTypeColorsInternalToApi,
transformSeatsInternalToApi,
// note(Lauris): schemas
TransformedLocationsSchema,
InternalLocationsSchema,
InternalLocationSchema,
BookingFieldsSchema,
// note(Lauris): constants
systemBeforeFieldName,
@@ -140,6 +141,7 @@ export type {
CustomField,
NameSystemField,
EmailSystemField,
InternalLocation,
} from "@calcom/lib/event-types/transformers";
export { parseBookingLimit, parseEventTypeColor } from "@calcom/lib";
@@ -54,16 +54,16 @@ import { DestinationCalendar_2024_06_14 } from "./destination-calendar.input";
import { Disabled_2024_06_14 } from "./disabled.input";
import { EventTypeColor_2024_06_14 } from "./event-type-color.input";
import {
AddressLocation_2024_06_14,
AttendeeAddressLocation_2024_06_14,
AttendeeDefinedLocation_2024_06_14,
AttendeePhoneLocation_2024_06_14,
IntegrationLocation_2024_06_14,
LinkLocation_2024_06_14,
PhoneLocation_2024_06_14,
InputAddressLocation_2024_06_14,
InputAttendeeAddressLocation_2024_06_14,
InputAttendeeDefinedLocation_2024_06_14,
InputAttendeePhoneLocation_2024_06_14,
InputIntegrationLocation_2024_06_14,
InputLinkLocation_2024_06_14,
InputPhoneLocation_2024_06_14,
ValidateLocations_2024_06_14,
} from "./locations.input";
import type { Location_2024_06_14 } from "./locations.input";
import type { InputLocation_2024_06_14 } from "./locations.input";
import { Recurrence_2024_06_14 } from "./recurrence.input";
import { Seats_2024_06_14 } from "./seats.input";
@@ -74,10 +74,10 @@ export const CREATE_EVENT_DESCRIPTION_EXAMPLE =
export const CREATE_EVENT_SLUG_EXAMPLE = "learn-the-secrets-of-masterchief";
@ApiExtraModels(
AddressLocation_2024_06_14,
LinkLocation_2024_06_14,
IntegrationLocation_2024_06_14,
PhoneLocation_2024_06_14,
InputAddressLocation_2024_06_14,
InputLinkLocation_2024_06_14,
InputIntegrationLocation_2024_06_14,
InputPhoneLocation_2024_06_14,
PhoneFieldInput_2024_06_14,
AddressFieldInput_2024_06_14,
TextFieldInput_2024_06_14,
@@ -98,9 +98,9 @@ export const CREATE_EVENT_SLUG_EXAMPLE = "learn-the-secrets-of-masterchief";
Recurrence_2024_06_14,
BaseConfirmationPolicy_2024_06_14,
Seats_2024_06_14,
AttendeeAddressLocation_2024_06_14,
AttendeePhoneLocation_2024_06_14,
AttendeeDefinedLocation_2024_06_14
InputAttendeeAddressLocation_2024_06_14,
InputAttendeePhoneLocation_2024_06_14,
InputAttendeeDefinedLocation_2024_06_14
)
export class CreateEventTypeInput_2024_06_14 {
@IsInt()
@@ -127,18 +127,18 @@ export class CreateEventTypeInput_2024_06_14 {
description:
"Locations where the event will take place. If not provided, cal video link will be used as the location.",
oneOf: [
{ $ref: getSchemaPath(AddressLocation_2024_06_14) },
{ $ref: getSchemaPath(LinkLocation_2024_06_14) },
{ $ref: getSchemaPath(IntegrationLocation_2024_06_14) },
{ $ref: getSchemaPath(PhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(AttendeeAddressLocation_2024_06_14) },
{ $ref: getSchemaPath(AttendeePhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(AttendeeDefinedLocation_2024_06_14) },
{ $ref: getSchemaPath(InputAddressLocation_2024_06_14) },
{ $ref: getSchemaPath(InputLinkLocation_2024_06_14) },
{ $ref: getSchemaPath(InputIntegrationLocation_2024_06_14) },
{ $ref: getSchemaPath(InputPhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(InputAttendeeAddressLocation_2024_06_14) },
{ $ref: getSchemaPath(InputAttendeePhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(InputAttendeeDefinedLocation_2024_06_14) },
],
type: "array",
})
@Type(() => Object)
locations?: Location_2024_06_14[];
locations?: InputLocation_2024_06_14[];
@IsOptional()
@ValidateInputBookingFields_2024_06_14()
@@ -5,7 +5,7 @@ import { IsString, IsUrl, IsIn, IsPhoneNumber, IsBoolean } from "class-validator
import type { ValidationOptions, ValidatorConstraintInterface } from "class-validator";
import { registerDecorator, validate, ValidatorConstraint } from "class-validator";
const locations = [
export const inputLocations = [
"address",
"link",
"integration",
@@ -15,8 +15,8 @@ const locations = [
"attendeeDefined",
] as const;
export class AddressLocation_2024_06_14 {
@IsIn(locations)
export class InputAddressLocation_2024_06_14 {
@IsIn(inputLocations)
@DocsProperty({ example: "address", description: "only allowed value for type is `address`" })
type!: "address";
@@ -29,8 +29,8 @@ export class AddressLocation_2024_06_14 {
public!: boolean;
}
export class LinkLocation_2024_06_14 {
@IsIn(locations)
export class InputLinkLocation_2024_06_14 {
@IsIn(inputLocations)
@DocsProperty({ example: "link", description: "only allowed value for type is `link`" })
type!: "link";
@@ -46,8 +46,8 @@ export class LinkLocation_2024_06_14 {
const integrations = ["cal-video"] as const;
export type Integration_2024_06_14 = (typeof integrations)[number];
export class IntegrationLocation_2024_06_14 {
@IsIn(locations)
export class InputIntegrationLocation_2024_06_14 {
@IsIn(inputLocations)
@DocsProperty({ example: "integration", description: "only allowed value for type is `integration`" })
type!: "integration";
@@ -56,8 +56,8 @@ export class IntegrationLocation_2024_06_14 {
integration!: Integration_2024_06_14;
}
export class PhoneLocation_2024_06_14 {
@IsIn(locations)
export class InputPhoneLocation_2024_06_14 {
@IsIn(inputLocations)
@DocsProperty({ example: "phone", description: "only allowed value for type is `phone`" })
type!: "phone";
@@ -70,22 +70,22 @@ export class PhoneLocation_2024_06_14 {
public!: boolean;
}
export class AttendeeAddressLocation_2024_06_14 {
@IsIn(locations)
export class InputAttendeeAddressLocation_2024_06_14 {
@IsIn(inputLocations)
@DocsProperty({
example: "attendeeAddress",
description: "only allowed value for type is `attendeeAddress`",
})
type!: "attendeeAddress";
}
export class AttendeePhoneLocation_2024_06_14 {
@IsIn(locations)
export class InputAttendeePhoneLocation_2024_06_14 {
@IsIn(inputLocations)
@DocsProperty({ example: "attendeePhone", description: "only allowed value for type is `attendeePhone`" })
type!: "attendeePhone";
}
export class AttendeeDefinedLocation_2024_06_14 {
@IsIn(locations)
export class InputAttendeeDefinedLocation_2024_06_14 {
@IsIn(inputLocations)
@DocsProperty({
example: "attendeeDefined",
description: "only allowed value for type is `attendeeDefined`",
@@ -93,25 +93,25 @@ export class AttendeeDefinedLocation_2024_06_14 {
type!: "attendeeDefined";
}
export type Location_2024_06_14 =
| AddressLocation_2024_06_14
| LinkLocation_2024_06_14
| IntegrationLocation_2024_06_14
| PhoneLocation_2024_06_14
| AttendeeAddressLocation_2024_06_14
| AttendeePhoneLocation_2024_06_14
| AttendeeDefinedLocation_2024_06_14;
export type InputLocation_2024_06_14 =
| InputAddressLocation_2024_06_14
| InputLinkLocation_2024_06_14
| InputIntegrationLocation_2024_06_14
| InputPhoneLocation_2024_06_14
| InputAttendeeAddressLocation_2024_06_14
| InputAttendeePhoneLocation_2024_06_14
| InputAttendeeDefinedLocation_2024_06_14;
@ValidatorConstraint({ async: true })
class LocationValidator_2024_06_14 implements ValidatorConstraintInterface {
private classTypeMap: { [key: string]: new () => Location_2024_06_14 } = {
address: AddressLocation_2024_06_14,
link: LinkLocation_2024_06_14,
integration: IntegrationLocation_2024_06_14,
phone: PhoneLocation_2024_06_14,
attendeePhone: AttendeePhoneLocation_2024_06_14,
attendeeAddress: AttendeeAddressLocation_2024_06_14,
attendeeDefined: AttendeeDefinedLocation_2024_06_14,
class InputLocationValidator_2024_06_14 implements ValidatorConstraintInterface {
private classTypeMap: { [key: string]: new () => InputLocation_2024_06_14 } = {
address: InputAddressLocation_2024_06_14,
link: InputLinkLocation_2024_06_14,
integration: InputIntegrationLocation_2024_06_14,
phone: InputPhoneLocation_2024_06_14,
attendeePhone: InputAttendeePhoneLocation_2024_06_14,
attendeeAddress: InputAttendeeAddressLocation_2024_06_14,
attendeeDefined: InputAttendeeDefinedLocation_2024_06_14,
};
async validate(locations: { type: string }[]) {
@@ -160,7 +160,7 @@ export function ValidateLocations_2024_06_14(validationOptions?: ValidationOptio
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: new LocationValidator_2024_06_14(),
validator: new InputLocationValidator_2024_06_14(),
});
};
}
@@ -50,24 +50,24 @@ import { DestinationCalendar_2024_06_14 } from "./destination-calendar.input";
import { Disabled_2024_06_14 } from "./disabled.input";
import { EventTypeColor_2024_06_14 } from "./event-type-color.input";
import {
AddressLocation_2024_06_14,
AttendeeAddressLocation_2024_06_14,
AttendeeDefinedLocation_2024_06_14,
AttendeePhoneLocation_2024_06_14,
IntegrationLocation_2024_06_14,
LinkLocation_2024_06_14,
PhoneLocation_2024_06_14,
InputAddressLocation_2024_06_14,
InputAttendeeAddressLocation_2024_06_14,
InputAttendeeDefinedLocation_2024_06_14,
InputAttendeePhoneLocation_2024_06_14,
InputIntegrationLocation_2024_06_14,
InputLinkLocation_2024_06_14,
InputPhoneLocation_2024_06_14,
ValidateLocations_2024_06_14,
} from "./locations.input";
import type { Location_2024_06_14 } from "./locations.input";
import type { InputLocation_2024_06_14 } from "./locations.input";
import { Recurrence_2024_06_14 } from "./recurrence.input";
import { Seats_2024_06_14 } from "./seats.input";
@ApiExtraModels(
AddressLocation_2024_06_14,
LinkLocation_2024_06_14,
IntegrationLocation_2024_06_14,
PhoneLocation_2024_06_14,
InputAddressLocation_2024_06_14,
InputLinkLocation_2024_06_14,
InputIntegrationLocation_2024_06_14,
InputPhoneLocation_2024_06_14,
PhoneFieldInput_2024_06_14,
AddressFieldInput_2024_06_14,
TextFieldInput_2024_06_14,
@@ -88,9 +88,9 @@ import { Seats_2024_06_14 } from "./seats.input";
Recurrence_2024_06_14,
BaseConfirmationPolicy_2024_06_14,
Seats_2024_06_14,
AttendeeAddressLocation_2024_06_14,
AttendeePhoneLocation_2024_06_14,
AttendeeDefinedLocation_2024_06_14
InputAttendeeAddressLocation_2024_06_14,
InputAttendeePhoneLocation_2024_06_14,
InputAttendeeDefinedLocation_2024_06_14
)
export class UpdateEventTypeInput_2024_06_14 {
@IsOptional()
@@ -120,18 +120,18 @@ export class UpdateEventTypeInput_2024_06_14 {
description:
"Locations where the event will take place. If not provided, cal video link will be used as the location.",
oneOf: [
{ $ref: getSchemaPath(AddressLocation_2024_06_14) },
{ $ref: getSchemaPath(LinkLocation_2024_06_14) },
{ $ref: getSchemaPath(IntegrationLocation_2024_06_14) },
{ $ref: getSchemaPath(PhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(AttendeeAddressLocation_2024_06_14) },
{ $ref: getSchemaPath(AttendeePhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(AttendeeDefinedLocation_2024_06_14) },
{ $ref: getSchemaPath(InputAddressLocation_2024_06_14) },
{ $ref: getSchemaPath(InputLinkLocation_2024_06_14) },
{ $ref: getSchemaPath(InputIntegrationLocation_2024_06_14) },
{ $ref: getSchemaPath(InputPhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(InputAttendeeAddressLocation_2024_06_14) },
{ $ref: getSchemaPath(InputAttendeePhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(InputAttendeeDefinedLocation_2024_06_14) },
],
type: "array",
})
@Type(() => Object)
locations?: Location_2024_06_14[];
locations?: InputLocation_2024_06_14[];
@IsOptional()
@ValidateInputBookingFields_2024_06_14()
@@ -11,11 +11,7 @@ import {
ValidateNested,
} from "class-validator";
import type {
Location_2024_06_14,
BookingWindow_2024_06_14,
BookingLimitsDuration_2024_06_14,
} from "../inputs";
import type { BookingWindow_2024_06_14, BookingLimitsDuration_2024_06_14 } from "../inputs";
import {
EventTypeColor_2024_06_14,
Seats_2024_06_14,
@@ -30,13 +26,6 @@ import { BookerLayouts_2024_06_14 } from "../inputs/booker-layouts.input";
import type { BookingLimitsCount_2024_06_14 } from "../inputs/booking-limits-count.input";
import type { ConfirmationPolicy_2024_06_14 } from "../inputs/confirmation-policy.input";
import { DestinationCalendar_2024_06_14 } from "../inputs/destination-calendar.input";
import {
AddressLocation_2024_06_14,
IntegrationLocation_2024_06_14,
LinkLocation_2024_06_14,
PhoneLocation_2024_06_14,
ValidateLocations_2024_06_14,
} from "../inputs/locations.input";
import {
EmailDefaultFieldOutput_2024_06_14,
NameDefaultFieldOutput_2024_06_14,
@@ -59,6 +48,16 @@ import {
} from "../outputs/booking-fields.output";
import type { OutputBookingField_2024_06_14 } from "./booking-fields.output";
import { ValidateOutputBookingFields_2024_06_14 } from "./booking-fields.output";
import type { OutputLocation_2024_06_14 } from "./locations.output";
import {
OutputAddressLocation_2024_06_14,
OutputConferencingLocation_2024_06_14,
OutputIntegrationLocation_2024_06_14,
OutputLinkLocation_2024_06_14,
OutputPhoneLocation_2024_06_14,
OutputUnknownLocation_2024_06_14,
ValidateOutputLocations_2024_06_14,
} from "./locations.output";
enum SchedulingTypeEnum {
ROUND_ROBIN = "ROUND_ROBIN",
@@ -95,10 +94,12 @@ class User_2024_06_14 {
}
@ApiExtraModels(
AddressLocation_2024_06_14,
LinkLocation_2024_06_14,
IntegrationLocation_2024_06_14,
PhoneLocation_2024_06_14,
OutputAddressLocation_2024_06_14,
OutputLinkLocation_2024_06_14,
OutputIntegrationLocation_2024_06_14,
OutputPhoneLocation_2024_06_14,
OutputConferencingLocation_2024_06_14,
OutputUnknownLocation_2024_06_14,
EmailDefaultFieldOutput_2024_06_14,
NameDefaultFieldOutput_2024_06_14,
LocationDefaultFieldOutput_2024_06_14,
@@ -146,18 +147,20 @@ class BaseEventTypeOutput_2024_06_14 {
})
description!: string;
@ValidateLocations_2024_06_14()
@ValidateOutputLocations_2024_06_14()
@DocsProperty({
oneOf: [
{ $ref: getSchemaPath(AddressLocation_2024_06_14) },
{ $ref: getSchemaPath(LinkLocation_2024_06_14) },
{ $ref: getSchemaPath(IntegrationLocation_2024_06_14) },
{ $ref: getSchemaPath(PhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(OutputAddressLocation_2024_06_14) },
{ $ref: getSchemaPath(OutputLinkLocation_2024_06_14) },
{ $ref: getSchemaPath(OutputIntegrationLocation_2024_06_14) },
{ $ref: getSchemaPath(OutputPhoneLocation_2024_06_14) },
{ $ref: getSchemaPath(OutputConferencingLocation_2024_06_14) },
{ $ref: getSchemaPath(OutputUnknownLocation_2024_06_14) },
],
type: "array",
})
@Type(() => Object)
locations!: Location_2024_06_14[];
locations!: OutputLocation_2024_06_14[];
@ValidateOutputBookingFields_2024_06_14()
@DocsProperty()
@@ -1,2 +1,3 @@
export * from "./event-type.output";
export * from "./booking-fields.output";
export * from "./locations.output";
@@ -0,0 +1,167 @@
import { BadRequestException } from "@nestjs/common";
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
import { plainToInstance } from "class-transformer";
import { IsUrl, IsIn, IsOptional, IsNumber, IsString } from "class-validator";
import type { ValidationOptions, ValidatorConstraintInterface } from "class-validator";
import { registerDecorator, validate, ValidatorConstraint } from "class-validator";
import {
InputAddressLocation_2024_06_14,
InputAttendeeAddressLocation_2024_06_14,
InputAttendeeDefinedLocation_2024_06_14,
InputAttendeePhoneLocation_2024_06_14,
InputLinkLocation_2024_06_14,
inputLocations,
InputPhoneLocation_2024_06_14,
} from "../inputs";
const outputLocations = [...inputLocations, "conferencing", "unknown"] as const;
export class OutputAddressLocation_2024_06_14 extends InputAddressLocation_2024_06_14 {}
export class OutputLinkLocation_2024_06_14 extends InputLinkLocation_2024_06_14 {}
export class OutputPhoneLocation_2024_06_14 extends InputPhoneLocation_2024_06_14 {}
export class OutputAttendeeAddressLocation_2024_06_14 extends InputAttendeeAddressLocation_2024_06_14 {}
export class OutputAttendeePhoneLocation_2024_06_14 extends InputAttendeePhoneLocation_2024_06_14 {}
export class OutputAttendeeDefinedLocation_2024_06_14 extends InputAttendeeDefinedLocation_2024_06_14 {}
const integrationsValues = [
"cal-video",
"google-meet",
"zoom",
"whereby-video",
"whatsapp-video",
"webex-video",
"telegram-video",
"tandem",
"sylaps-video",
"skype-video",
"sirius-video",
"signal-video",
"shimmer-video",
"salesroom-video",
"roam-video",
"riverside-video",
"ping-video",
"office365-video",
"mirotalk-video",
"jitsi",
"jelly-video",
"jelly-conferencing",
"huddle",
"facetime-video",
"element-call-video",
"eightxeight-video",
"discord-video",
"demodesk-video",
"campsite-conferencing",
"campfire-video",
"around-video",
] as const;
export type OutputIntegration_2024_06_14 = (typeof integrationsValues)[number];
export class OutputIntegrationLocation_2024_06_14 {
@IsIn(outputLocations)
@DocsProperty({ example: "integration", description: "only allowed value for type is `integration`" })
type!: "integration";
@IsIn(integrationsValues)
@DocsProperty({ example: integrationsValues[0], enum: integrationsValues })
integration!: OutputIntegration_2024_06_14;
@IsUrl()
@IsOptional()
link?: string;
@IsNumber()
@IsOptional()
credentialId?: number;
}
export class OutputConferencingLocation_2024_06_14 {
@IsIn(outputLocations)
@DocsProperty({ example: "conferencing", description: "only allowed value for type is `conferencing`" })
type!: "conferencing";
}
export class OutputUnknownLocation_2024_06_14 {
@IsIn(outputLocations)
@DocsProperty({ example: "unknown", description: "only allowed value for type is `unknown`" })
type!: "unknown";
@IsString()
location!: string;
}
export type OutputLocation_2024_06_14 =
| OutputAddressLocation_2024_06_14
| OutputLinkLocation_2024_06_14
| OutputIntegrationLocation_2024_06_14
| OutputPhoneLocation_2024_06_14
| OutputAttendeeAddressLocation_2024_06_14
| OutputAttendeePhoneLocation_2024_06_14
| OutputAttendeeDefinedLocation_2024_06_14
| OutputConferencingLocation_2024_06_14
| OutputUnknownLocation_2024_06_14;
@ValidatorConstraint({ async: true })
class OutputLocationValidator_2024_06_14 implements ValidatorConstraintInterface {
private classTypeMap: { [key: string]: new () => OutputLocation_2024_06_14 } = {
address: OutputAddressLocation_2024_06_14,
link: OutputLinkLocation_2024_06_14,
integration: OutputIntegrationLocation_2024_06_14,
phone: OutputPhoneLocation_2024_06_14,
attendeePhone: OutputAttendeePhoneLocation_2024_06_14,
attendeeAddress: OutputAttendeeAddressLocation_2024_06_14,
attendeeDefined: OutputAttendeeDefinedLocation_2024_06_14,
conferencing: OutputConferencingLocation_2024_06_14,
unknown: OutputUnknownLocation_2024_06_14,
};
async validate(locations: { type: string }[]) {
if (!Array.isArray(locations)) {
throw new BadRequestException(`'locations' must be an array.`);
}
if (!locations.length) {
throw new BadRequestException(`'locations' must contain at least 1 location.`);
}
for (const location of locations) {
const { type } = location;
if (!type) {
throw new BadRequestException(`Each object in 'locations' must have a 'type' property.`);
}
const ClassType = this.classTypeMap[type];
if (!ClassType) {
throw new BadRequestException(`Unsupported output type '${type}'.`);
}
const instance = plainToInstance(ClassType, location);
const errors = await validate(instance);
if (errors.length > 0) {
const message = errors.flatMap((error) => Object.values(error.constraints || {})).join(", ");
throw new BadRequestException(`Validation failed for ${type} location: ${message}`);
}
}
return true;
}
defaultMessage() {
return `Validation failed for one or more location entries.`;
}
}
export function ValidateOutputLocations_2024_06_14(validationOptions?: ValidationOptions) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function (object: any, propertyName: string) {
registerDecorator({
name: "ValidateLocation",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: new OutputLocationValidator_2024_06_14(),
});
};
}
@@ -7,7 +7,7 @@ import type {
transformRecurrenceApiToInternal,
transformSeatsApiToInternal,
transformBookingFieldsApiToInternal,
TransformedLocationsSchema,
InternalLocationsSchema,
} from "@calcom/lib/event-types/transformers";
import type { CreateEventTypeInput_2024_06_14, ConfirmationPolicyTransformedSchema } from "../inputs";
@@ -32,7 +32,7 @@ export type InputEventTransformed_2024_06_14 = Omit<
slug: string;
eventName?: string;
bookingLimits?: ReturnType<typeof transformIntervalLimitsApiToInternal>;
locations?: z.infer<typeof TransformedLocationsSchema>;
locations?: z.infer<typeof InternalLocationsSchema>;
bookingFields?: ReturnType<typeof transformBookingFieldsApiToInternal>;
durationLimits?: ReturnType<typeof transformIntervalLimitsApiToInternal>;
recurringEvent?: ReturnType<typeof transformRecurrenceApiToInternal>;
+14 -5
View File
@@ -4125,7 +4125,7 @@ __metadata:
dependencies:
"@calcom/platform-constants": "*"
"@calcom/platform-enums": "*"
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.51"
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.52"
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2"
"@calcom/platform-types": "*"
"@calcom/platform-utils": "*"
@@ -4559,6 +4559,15 @@ __metadata:
languageName: unknown
linkType: soft
"@calcom/dialpad@workspace:packages/app-store/dialpad":
version: 0.0.0-use.local
resolution: "@calcom/dialpad@workspace:packages/app-store/dialpad"
dependencies:
"@calcom/lib": "*"
"@calcom/types": "*"
languageName: unknown
linkType: soft
"@calcom/discord@workspace:packages/app-store/discord":
version: 0.0.0-use.local
resolution: "@calcom/discord@workspace:packages/app-store/discord"
@@ -5132,14 +5141,14 @@ __metadata:
languageName: node
linkType: hard
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.51":
version: 0.0.51
resolution: "@calcom/platform-libraries@npm:0.0.51"
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.52":
version: 0.0.52
resolution: "@calcom/platform-libraries@npm:0.0.52"
dependencies:
"@calcom/core": "*"
"@calcom/features": "*"
"@calcom/lib": "*"
checksum: f7b97302730da01067e7417da94aa3e1a3e0e967698516edd15d72be1f2d57f40c382f5b70401aa3758a46f3a9a5a4f29e0fc4b7c8565a9cb4a4ff10ed4b5dc2
checksum: 35d86bd7f99885f5960a1d1b4c45e1f8e8c0d945dcfded1a3be53b1e931cba04aaad252e433decb6f650b376fbb788e2f6bab2b88e5455b7a39065f84fdec1ff
languageName: node
linkType: hard