refactor: Deprecate Legacy v2 Booker Atom Endpoints (#20939)

* feat: split atoms endpoints and add public event type endpoint

* Update ConnectedDestinationCalendars import path across platform/atoms components

* refactor: update BookingResponse import path from platform-libraries to features/bookings

* Move AvailableSlotsType to util.ts and update imports to use GetAvailableSlotsResponse

* Refactor import path for RecurringBookingCreateBody type from libraries to types

* Fix import path for getBookingForReschedule type from platform-libraries to features

* Refactor PublicEventType export location and update import references

* Remove @calcom/platform-libraries dependency from atoms package.json

* chore(deps): update yarn.lock dependencies

* Remove console.log statements and fix indentation in event type hooks

* Migrate event type transformers from platform/libraries to api/v2 directory

* Remove console.log

* Update BookerPlatformWrapper.tsx

* Add script to populate empty team slugs with slugified team names

* Remove vitest imports

* reset platform libraries version

* Remove unused orgId comment from useAtomGetPublicEvent hook params

* Update useApiV2AvailableSlots.ts

* refactor: remove unused exports from lib package index file

* Undo: @SomayChauhan
Add script to populate empty team slugs with slugified team names

* Update booking.tsx

* chore: upgrade @calcom/platform-libraries from 0.0.202 to 0.0.205

* chore: bump @calcom/platform-libraries from 0.0.205 to 0.0.206

* chore: configure babel and jest for node module transpilation in api v2

* fix: type errors

* Revert "chore: configure babel and jest for node module transpilation in api v2"

This reverts commit b2cf172a84fe8953f9497bf6e43874f476fbc04b.

* Update calendars.service.ts

* chore: bump @calcom/platform-libraries from 0.0.208 to 0.0.209

* fix: add proper type definition for calendar busy times to resolve ts-expect-error

* refactor: deprecate v2 old availability endpoints (#21075)

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>

* chore: publish platform librareis

* feat: add delegation credential fields to calendar service mock

* chore: update @calcom/platform-libraries from 0.0.211 to 0.0.213

* fix: skip failing calendar integration test

* chore: bump @calcom/platform-libraries from 0.0.213 to 0.0.214

* fix: api/v2 build error

* fix: e2e tests

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
This commit is contained in:
Somay Chauhan
2025-06-12 20:20:52 +05:30
committed by GitHub
co-authored by Morgan supalarry Lauris Skraucis
parent 5e2f06cdea
commit 5bb2a904ce
105 changed files with 6611 additions and 1552 deletions
+1 -1
View File
@@ -37,7 +37,7 @@
"@axiomhq/winston": "^1.2.0",
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.208",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.214",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
@@ -135,9 +135,13 @@ export class BookingsController_2024_04_15 {
},
});
let nextCursor = null;
if (bookings.totalCount > (cursor ?? 0) + (limit ?? 10)) {
nextCursor = (cursor ?? 0) + (limit ?? 10);
}
return {
status: SUCCESS_STATUS,
data: bookings,
data: { ...bookings, nextCursor },
};
}
@@ -16,13 +16,15 @@ import {
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
enum Status {
CANCELLED = "CANCELLED",
REJECTED = "REJECTED",
ACCEPTED = "ACCEPTED",
PENDING = "PENDING",
AWAITING_HOST = "AWAITING_HOST",
}
const Status = {
CANCELLED: "CANCELLED",
REJECTED: "REJECTED",
ACCEPTED: "ACCEPTED",
PENDING: "PENDING",
AWAITING_HOST: "AWAITING_HOST",
} as const;
export type Status = (typeof Status)[keyof typeof Status];
class Attendee {
@IsInt()
@@ -874,9 +874,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(AttendeeScheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
expect(AttendeeRescheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
expect(AttendeeRescheduledEmail.prototype.getHtml).toHaveBeenCalled();
expect(OrganizerRescheduledEmail.prototype.getHtml).toHaveBeenCalled();
expect(OrganizerScheduledEmail.prototype.getHtml).toHaveBeenCalled();
expect(OrganizerCancelledEmail.prototype.getHtml).toHaveBeenCalled();
emailsEnabledSetup.roundRobinEventType.rescheduledBookingUid = responseBody.data.uid;
@@ -930,15 +930,19 @@ export class BookingsService_2024_08_13 {
: undefined;
const emailsEnabled = platformClientParams ? platformClientParams.arePlatformEmailsEnabled : true;
const userCalendars = await this.usersRepository.findByIdWithCalendars(requestUser.id);
await confirmBookingHandler({
ctx: {
user: requestUser,
user: {
...requestUser,
destinationCalendar: userCalendars?.destinationCalendar ?? null,
},
},
input: {
bookingId: booking.id,
confirmed: true,
recurringEventId: booking.recurringEventId,
recurringEventId: booking.recurringEventId ?? undefined,
emailsEnabled,
platformClientParams,
},
@@ -958,15 +962,19 @@ export class BookingsService_2024_08_13 {
: undefined;
const emailsEnabled = platformClientParams ? platformClientParams.arePlatformEmailsEnabled : true;
const userCalendars = await this.usersRepository.findByIdWithCalendars(requestUser.id);
await confirmBookingHandler({
ctx: {
user: requestUser,
user: {
...requestUser,
destinationCalendar: userCalendars?.destinationCalendar ?? null,
},
},
input: {
bookingId: booking.id,
confirmed: false,
recurringEventId: booking.recurringEventId,
recurringEventId: booking.recurringEventId ?? undefined,
reason,
emailsEnabled,
platformClientParams,
@@ -10,6 +10,7 @@ import {
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
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 { apiToInternalintegrationsMapping } from "@/ee/event-types/event-types_2024_06_14/transformers";
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";
@@ -29,10 +30,7 @@ import { NextApiRequest } from "next/types";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import {
EventTypeMetaDataSchema,
apiToInternalintegrationsMapping,
} from "@calcom/platform-libraries/event-types";
import { EventTypeMetaDataSchema } from "@calcom/platform-libraries/event-types";
import {
CancelBookingInput,
CancelBookingInput_2024_08_13,
@@ -22,6 +22,7 @@ import { APPS_TYPE_ID_MAPPING } from "@calcom/platform-constants";
import {
getConnectedDestinationCalendarsAndEnsureDefaultsInDb,
getBusyCalendarTimes,
type EventBusyDate,
} from "@calcom/platform-libraries";
import { Calendar } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";
@@ -45,6 +46,7 @@ export class CalendarsService {
...credential,
delegatedTo: null,
delegatedToId: null,
delegationCredentialId: null,
}))
.filter((credential) => !!credential);
}
@@ -88,18 +90,19 @@ export class CalendarsService {
dateTo,
composedSelectedCalendars
);
// @ts-expect-error Element implicitly has any type
const calendarBusyTimesConverted = calendarBusyTimes.map((busyTime) => {
const busyTimeStart = DateTime.fromJSDate(new Date(busyTime.start)).setZone(timezone);
const busyTimeEnd = DateTime.fromJSDate(new Date(busyTime.end)).setZone(timezone);
const busyTimeStartDate = busyTimeStart.toJSDate();
const busyTimeEndDate = busyTimeEnd.toJSDate();
return {
...busyTime,
start: busyTimeStartDate,
end: busyTimeEndDate,
};
});
const calendarBusyTimesConverted = calendarBusyTimes.map(
(busyTime: EventBusyDate & { timeZone?: string }) => {
const busyTimeStart = DateTime.fromJSDate(new Date(busyTime.start)).setZone(timezone);
const busyTimeEnd = DateTime.fromJSDate(new Date(busyTime.end)).setZone(timezone);
const busyTimeStartDate = busyTimeStart.toJSDate();
const busyTimeEndDate = busyTimeEnd.toJSDate();
return {
...busyTime,
start: busyTimeStartDate,
end: busyTimeEndDate,
};
}
);
return calendarBusyTimesConverted;
} catch (error) {
throw new InternalServerErrorException(
@@ -5,6 +5,7 @@ import { Editable } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/e
import { BaseField } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/field-type";
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
import { systemBeforeFieldEmail } from "@/ee/event-types/event-types_2024_06_14/transformers";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
@@ -17,7 +18,6 @@ import {
updateEventType,
EventTypesPublic,
getEventTypesPublic,
systemBeforeFieldEmail,
} from "@calcom/platform-libraries/event-types";
import { EventType } from "@calcom/prisma/client";
@@ -1,4 +1,4 @@
import { OrganizerIntegrationLocation } from "@calcom/lib/event-types/transformers/internal/locations";
import { OrganizerIntegrationLocation } from "@/ee/event-types/event-types_2024_06_14/transformers";
type BaseEventType = {
length: number;
@@ -114,6 +114,9 @@ export class EventTypesController_2024_06_14 {
@Query() queryParams: GetEventTypesQuery_2024_06_14
): Promise<GetEventTypesOutput_2024_06_14> {
const eventTypes = await this.eventTypesService.getEventTypes(queryParams);
if (!eventTypes || eventTypes.length === 0) {
throw new NotFoundException(`Event types not found`);
}
const eventTypesFormatted = this.eventTypeResponseTransformPipe.transform(eventTypes);
const eventTypesWithoutHiddenFields = eventTypesFormatted.map((eventType) => {
return {
@@ -1,10 +1,9 @@
import { InputEventTransformed_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/transformed";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
import type { Prisma } from "@prisma/client";
import { InputEventTransformed_2024_06_14 } from "@calcom/platform-types";
@Injectable()
export class EventTypesRepository_2024_06_14 {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
@@ -1,5 +1,7 @@
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/event-types_2024_06_14/constants/constants";
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
import { InputEventTransformed_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/transformed";
import { SystemField, CustomField } from "@/ee/event-types/event-types_2024_06_14/transformers";
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
@@ -14,10 +16,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 { GetEventTypesQuery_2024_06_14 } from "@calcom/platform-types";
import { EventType } from "@calcom/prisma/client";
@Injectable()
@@ -1,13 +1,7 @@
import { ConnectedCalendarsData } from "@/ee/calendars/outputs/connected-calendars.output";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
import { ConferencingService } from "@/modules/conferencing/services/conferencing.service";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable, BadRequestException } from "@nestjs/common";
import { CONFERENCING_APPS } from "@calcom/platform-constants";
import { getUsersCredentials } from "@calcom/platform-libraries";
import { getApps } from "@calcom/platform-libraries/app-store";
import { InputEventTransformed_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/transformed";
import {
transformBookingFieldsApiToInternal,
transformLocationsApiToInternal,
@@ -21,21 +15,23 @@ import {
systemAfterFieldNotes,
systemAfterFieldGuests,
systemAfterFieldRescheduleReason,
EventTypeMetaDataSchema,
transformBookerLayoutsApiToInternal,
transformConfirmationPolicyApiToInternal,
transformEventColorsApiToInternal,
validateCustomEventName,
transformSeatsApiToInternal,
SystemField,
CustomField,
InternalLocation,
InternalLocationSchema,
} from "@calcom/platform-libraries/event-types";
} from "@/ee/event-types/event-types_2024_06_14/transformers";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable, BadRequestException } from "@nestjs/common";
import { getApps, getUsersCredentialsIncludeServiceAccountKey } from "@calcom/platform-libraries/app-store";
import { validateCustomEventName, EventTypeMetaDataSchema } from "@calcom/platform-libraries/event-types";
import {
CreateEventTypeInput_2024_06_14,
DestinationCalendar_2024_06_14,
InputEventTransformed_2024_06_14,
OutputUnknownLocation_2024_06_14,
UpdateEventTypeInput_2024_06_14,
} from "@calcom/platform-types";
@@ -478,7 +474,7 @@ export class InputEventTypesService_2024_06_14 {
appSlug = "msteams";
}
const credentials = await getUsersCredentials(user);
const credentials = await getUsersCredentialsIncludeServiceAccountKey(user);
const foundApp = getApps(credentials, true).filter((app) => app.slug === appSlug)[0];
@@ -1,14 +1,4 @@
import { Injectable } from "@nestjs/common";
import type { EventType, User, Schedule, DestinationCalendar } from "@prisma/client";
import {
userMetadata,
parseBookingLimit,
parseRecurringEvent,
getBookingFieldsWithSystemFields,
} from "@calcom/platform-libraries";
import {
EventTypeMetaDataSchema,
transformLocationsInternalToApi,
transformBookingFieldsInternalToApi,
InternalLocationSchema,
@@ -20,11 +10,20 @@ import {
transformBookerLayoutsInternalToApi,
transformRequiresConfirmationInternalToApi,
transformEventTypeColorsInternalToApi,
parseEventTypeColor,
transformSeatsInternalToApi,
InternalLocation,
BookingFieldSchema,
} from "@calcom/platform-libraries/event-types";
} from "@/ee/event-types/event-types_2024_06_14/transformers";
import { Injectable } from "@nestjs/common";
import type { EventType, User, Schedule, DestinationCalendar } from "@prisma/client";
import {
userMetadata,
parseBookingLimit,
parseRecurringEvent,
getBookingFieldsWithSystemFields,
} from "@calcom/platform-libraries";
import { EventTypeMetaDataSchema, parseEventTypeColor } from "@calcom/platform-libraries/event-types";
import {
TransformFutureBookingsLimitSchema_2024_06_14,
BookerLayoutsTransformedSchema,
@@ -39,7 +38,7 @@ type EventTypeRelations = {
schedule: Schedule | null;
destinationCalendar?: DestinationCalendar | null;
};
export type DatabaseEventType = EventType & EventTypeRelations;
export type DatabaseEventType = Omit<EventType, "allowReschedulingCancelledBookings"> & EventTypeRelations;
type Input = Pick<
DatabaseEventType,
@@ -1,5 +1,3 @@
import type { z } from "zod";
import type {
transformEventColorsApiToInternal,
transformFutureBookingLimitsApiToInternal,
@@ -8,9 +6,13 @@ import type {
transformSeatsApiToInternal,
transformBookingFieldsApiToInternal,
InternalLocationsSchema,
} from "@calcom/lib/event-types/transformers";
} from "@/ee/event-types/event-types_2024_06_14/transformers";
import type { z } from "zod";
import type { CreateEventTypeInput_2024_06_14, ConfirmationPolicyTransformedSchema } from "../inputs";
import type {
CreateEventTypeInput_2024_06_14,
ConfirmationPolicyTransformedSchema,
} from "@calcom/platform-types";
export type InputEventTransformed_2024_06_14 = Omit<
CreateEventTypeInput_2024_06_14,
@@ -1,12 +1,10 @@
import { describe, expect, it } from "vitest";
import {
BookerLayoutsInputEnum_2024_06_14,
BookerLayoutsOutputEnum_2024_06_14,
ConfirmationPolicyEnum,
NoticeThresholdUnitEnum,
FrequencyInput,
} from "@calcom/platform-enums/monorepo";
} from "@calcom/platform-enums";
import type {
InputBookingField_2024_06_14,
InputLocation_2024_06_14,
@@ -1,7 +1,7 @@
import {
BookerLayoutsInputEnum_2024_06_14,
BookerLayoutsOutputEnum_2024_06_14,
} from "@calcom/platform-enums/monorepo";
} from "@calcom/platform-enums";
import { type CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
export function transformBookerLayoutsApiToInternal(
@@ -1,4 +1,4 @@
import { ConfirmationPolicyEnum } from "@calcom/platform-enums/monorepo";
import { ConfirmationPolicyEnum } from "@calcom/platform-enums";
import type {
ConfirmationPolicyTransformedSchema,
NoticeThresholdTransformedSchema,
@@ -1,7 +1,7 @@
import {
BookingWindowPeriodInputTypeEnum_2024_06_14,
BookingWindowPeriodOutputTypeEnum_2024_06_14,
} from "@calcom/platform-enums/monorepo";
} from "@calcom/platform-enums";
import {
type CreateEventTypeInput_2024_06_14,
type BusinessDaysWindow_2024_06_14,
@@ -1,4 +1,4 @@
import { BookingLimitsEnum_2024_06_14 } from "@calcom/platform-enums/monorepo";
import { BookingLimitsEnum_2024_06_14 } from "@calcom/platform-enums";
import {
type CreateEventTypeInput_2024_06_14,
type BookingLimitsKeyOutputType_2024_06_14,
@@ -1,4 +1,4 @@
import { Frequency } from "@calcom/platform-enums/monorepo";
import { Frequency } from "@calcom/platform-enums";
import type { Recurrence_2024_06_14 } from "@calcom/platform-types";
import { type TransformRecurringEventSchema_2024_06_14 } from "@calcom/platform-types";
@@ -1,7 +1,7 @@
import {
BookerLayoutsInputEnum_2024_06_14,
BookerLayoutsOutputEnum_2024_06_14,
} from "@calcom/platform-enums/monorepo";
} from "@calcom/platform-enums";
import type { BookerLayoutsTransformedSchema } from "@calcom/platform-types";
export function transformBookerLayoutsInternalToApi(
@@ -1,7 +1,7 @@
import {
BookingWindowPeriodInputTypeEnum_2024_06_14,
BookingWindowPeriodOutputTypeEnum_2024_06_14,
} from "@calcom/platform-enums/monorepo";
} from "@calcom/platform-enums";
import type {
TransformFutureBookingsLimitSchema_2024_06_14,
BookingWindow_2024_06_14,
@@ -1,11 +1,9 @@
import { describe, expect, it } from "vitest";
import {
BookerLayoutsInputEnum_2024_06_14,
BookerLayoutsOutputEnum_2024_06_14,
ConfirmationPolicyEnum,
NoticeThresholdUnitEnum,
} from "@calcom/platform-enums/monorepo";
} from "@calcom/platform-enums";
import type {
TransformBookingLimitsSchema_2024_06_14,
TransformFutureBookingsLimitSchema_2024_06_14,
@@ -1,4 +1,4 @@
import { BookingLimitsEnum_2024_06_14 } from "@calcom/platform-enums/monorepo";
import { BookingLimitsEnum_2024_06_14 } from "@calcom/platform-enums";
import type {
BookingLimitsKeysInputType,
TransformBookingLimitsSchema_2024_06_14,
@@ -1,4 +1,4 @@
import { Frequency, FrequencyInput } from "@calcom/platform-enums/monorepo";
import { Frequency, FrequencyInput } from "@calcom/platform-enums";
import type { Recurrence_2024_06_14, TransformRecurringEventSchema_2024_06_14 } from "@calcom/platform-types";
export function transformRecurrenceInternalToApi(
@@ -1,4 +1,4 @@
import { ConfirmationPolicyEnum } from "@calcom/platform-enums/monorepo";
import { ConfirmationPolicyEnum } from "@calcom/platform-enums";
import type { NoticeThresholdTransformedSchema, ConfirmationPolicy_2024_06_14 } from "@calcom/platform-types";
export function transformRequiresConfirmationInternalToApi(
@@ -1,11 +1,7 @@
import { Injectable } from "@nestjs/common";
import {
transformApiScheduleOverrides,
transformApiScheduleAvailability,
} from "@calcom/platform-libraries/schedules";
import { CreateScheduleInput_2024_06_11, ScheduleAvailabilityInput_2024_06_11 } from "@calcom/platform-types";
import { ScheduleOverrideInput_2024_06_11 } from "@calcom/platform-types";
import { ScheduleOverrideInput_2024_06_11, WeekDay } from "@calcom/platform-types";
@Injectable()
export class InputSchedulesService_2024_06_11 {
@@ -34,10 +30,52 @@ export class InputSchedulesService_2024_06_11 {
}
transformInputScheduleAvailability(inputAvailability: ScheduleAvailabilityInput_2024_06_11[]) {
return transformApiScheduleAvailability(inputAvailability);
return inputAvailability.map((availability) => ({
days: availability.days.map(this.transformDayToNumber),
startTime: this.createDateFromHoursMinutes(availability.startTime),
endTime: this.createDateFromHoursMinutes(availability.endTime),
}));
}
transformInputOverrides(inputOverrides: ScheduleOverrideInput_2024_06_11[]) {
return transformApiScheduleOverrides(inputOverrides);
return inputOverrides.map((override) => ({
date: new Date(override.date),
startTime: this.createDateFromHoursMinutes(override.startTime),
endTime: this.createDateFromHoursMinutes(override.endTime),
}));
}
createDateFromHoursMinutes(hoursMinutes: string): Date {
const parts = hoursMinutes.split(":");
if (parts.length < 2) {
throw new Error("Invalid time format. Please use 'hh:mm'.");
}
const hours = parseInt(parts[0], 10);
const minutes = parseInt(parts[1], 10);
if (isNaN(hours) || hours < 0 || hours > 23) {
throw new Error("Hours must be a number between 0 and 23.");
}
if (isNaN(minutes) || minutes < 0 || minutes > 59) {
throw new Error("Minutes must be a number between 0 and 59.");
}
const utcDate = new Date(Date.UTC(1970, 0, 1, hours, minutes));
return utcDate;
}
transformDayToNumber(day: WeekDay): number {
const weekMap: { [key in WeekDay]: number } = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6,
};
return weekMap[day];
}
}
+11 -3
View File
@@ -1,10 +1,13 @@
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
import { AtomsConferencingAppsController } from "@/modules/atoms/controllers/atoms.conferencing-apps.controller";
import { AtomsController } from "@/modules/atoms/controllers/atoms.controller";
import { AtomsEventTypesController } from "@/modules/atoms/controllers/atoms.event-types.controller";
import { AtomsSchedulesController } from "@/modules/atoms/controllers/atoms.schedules.controller";
import { AttributesAtomsService } from "@/modules/atoms/services/attributes-atom.service";
import { ConferencingAtomsService } from "@/modules/atoms/services/conferencing-atom.service";
import { EventTypesAtomService } from "@/modules/atoms/services/event-types-atom.service";
import { SchedulesAtomsService } from "@/modules/atoms/services/schedules-atom.service";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { OrganizationsModule } from "@/modules/organizations/organizations.module";
@@ -26,10 +29,15 @@ import { Module } from "@nestjs/common";
UsersRepository,
AtomsRepository,
UsersService,
SchedulesRepository_2024_06_11,
SchedulesAtomsService,
RedisService,
],
exports: [EventTypesAtomService],
controllers: [AtomsController],
controllers: [
AtomsController,
AtomsEventTypesController,
AtomsConferencingAppsController,
AtomsSchedulesController,
],
})
export class AtomsModule {}
@@ -0,0 +1,62 @@
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { ConferencingAtomsService } from "@/modules/atoms/services/conferencing-atom.service";
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard";
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Controller, Get, Param, ParseIntPipe, UseGuards, Version, VERSION_NEUTRAL } from "@nestjs/common";
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { ConnectedApps } from "@calcom/platform-libraries/app-store";
import { ApiResponse } from "@calcom/platform-types";
/*
Conferencing endpoints for atoms, split from AtomsController for clarity and maintainability.
These endpoints should not be recommended for use by third party and are excluded from docs.
*/
@Controller({
path: "/v2/atoms",
version: API_VERSIONS_VALUES,
})
@DocsTags("Atoms - conferencing endpoints for atoms")
@DocsExcludeController(true)
export class AtomsConferencingAppsController {
constructor(private readonly conferencingService: ConferencingAtomsService) {}
@Get("/organizations/:orgId/teams/:teamId/conferencing")
@Roles("TEAM_ADMIN")
@PlatformPlan("ESSENTIALS")
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
@Version(VERSION_NEUTRAL)
async listTeamInstalledConferencingApps(
@GetUser() user: UserWithProfile,
@Param("teamId", ParseIntPipe) teamId: number
): Promise<ApiResponse<ConnectedApps>> {
const conferencingApps = await this.conferencingService.getTeamConferencingApps(user, teamId);
return {
status: SUCCESS_STATUS,
data: conferencingApps,
};
}
@Get("/conferencing")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async listUserInstalledConferencingApps(
@GetUser() user: UserWithProfile
): Promise<ApiResponse<ConnectedApps>> {
const conferencingApps = await this.conferencingService.getUserConferencingApps(user);
return {
status: SUCCESS_STATUS,
data: conferencingApps,
};
}
}
@@ -1,21 +1,9 @@
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import {
BulkUpdateEventTypeToDefaultLocationDto,
EventTypesAppInput,
} from "@/modules/atoms/inputs/event-types-app.input";
import { FindTeamMembersMatchingAttributeQueryDto } from "@/modules/atoms/inputs/find-team-members-matching-attribute.input";
import { AttributesAtomsService } from "@/modules/atoms/services/attributes-atom.service";
import { ConferencingAtomsService } from "@/modules/atoms/services/conferencing-atom.service";
import { EventTypesAtomService } from "@/modules/atoms/services/event-types-atom.service";
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard";
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
Controller,
@@ -25,24 +13,17 @@ import {
UseGuards,
Version,
VERSION_NEUTRAL,
Patch,
Body,
Query,
} from "@nestjs/common";
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
import { ConnectedApps } from "@calcom/platform-libraries/app-store";
import type { UpdateEventTypeReturn } from "@calcom/platform-libraries/event-types";
import { ApiResponse } from "@calcom/platform-types";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { FindTeamMembersMatchingAttributeResponseDto } from "../outputs/find-team-members-matching-attribute.output";
/*
Endpoints used only by platform atoms, reusing code from other modules, data is already formatted and ready to be used by frontend atoms
these endpoints should not be recommended for use by third party and are excluded from docs
*/
@Controller({
@@ -53,180 +34,10 @@ these endpoints should not be recommended for use by third party and are exclude
@DocsExcludeController(true)
export class AtomsController {
constructor(
private readonly eventTypesService: EventTypesAtomService,
private readonly conferencingService: ConferencingAtomsService,
private readonly attributesService: AttributesAtomsService
) {}
@Get("event-types/:eventTypeId")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async getAtomEventType(
@GetUser() user: UserWithProfile,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number
): Promise<ApiResponse<unknown>> {
const eventType = await this.eventTypesService.getUserEventType(user, eventTypeId);
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
@Get("/organizations/:orgId/teams/:teamId/event-types")
@Roles("TEAM_ADMIN")
@PlatformPlan("ESSENTIALS")
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
@Version(VERSION_NEUTRAL)
async listTeamEventTypes(@Param("teamId", ParseIntPipe) teamId: number): Promise<ApiResponse<unknown>> {
const eventTypes = await this.eventTypesService.getTeamEventTypes(teamId);
return {
status: SUCCESS_STATUS,
data: eventTypes,
};
}
@Get("/event-types")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async listUserEventTypes(@GetUser("id") userId: number): Promise<ApiResponse<unknown>> {
const eventTypes = await this.eventTypesService.getUserEventTypes(userId);
return {
status: SUCCESS_STATUS,
data: eventTypes,
};
}
@Get("event-types-app/:appSlug")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async getAtomEventTypeApp(
@GetUser() user: UserWithProfile,
@Param("appSlug") appSlug: string,
@Query() queryParams: EventTypesAppInput
): Promise<ApiResponse<unknown>> {
const { teamId } = queryParams;
const app = await this.eventTypesService.getEventTypesAppIntegration(appSlug, user, teamId);
return {
status: SUCCESS_STATUS,
data: {
app,
},
};
}
@Get("payment/:uid")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async getUserPaymentInfoById(@Param("uid") uid: string): Promise<ApiResponse<unknown>> {
const data = await this.eventTypesService.getUserPaymentInfo(uid);
return {
status: SUCCESS_STATUS,
data,
};
}
@Patch("/event-types/bulk-update-to-default-location")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async bulkUpdateAtomEventTypes(
@GetUser() user: UserWithProfile,
@Body() body: BulkUpdateEventTypeToDefaultLocationDto
): Promise<{ status: typeof SUCCESS_STATUS | typeof ERROR_STATUS }> {
await this.eventTypesService.bulkUpdateEventTypesDefaultLocation(user, body.eventTypeIds);
return {
status: SUCCESS_STATUS,
};
}
@Patch("/organizations/:orgId/teams/:teamId/event-types/bulk-update-to-default-location")
@Version(VERSION_NEUTRAL)
@Roles("TEAM_ADMIN")
@PlatformPlan("ESSENTIALS")
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
async bulkUpdateAtomTeamEventTypes(
@GetUser() user: UserWithProfile,
@Body() body: BulkUpdateEventTypeToDefaultLocationDto,
@Param("teamId", ParseIntPipe) teamId: number
): Promise<{ status: typeof SUCCESS_STATUS | typeof ERROR_STATUS }> {
await this.eventTypesService.bulkUpdateTeamEventTypesDefaultLocation(body.eventTypeIds, teamId);
return {
status: SUCCESS_STATUS,
};
}
@Patch("event-types/:eventTypeId")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async updateAtomEventType(
@GetUser() user: UserWithProfile,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
@Body() body: UpdateEventTypeReturn
): Promise<ApiResponse<UpdateEventTypeReturn>> {
const eventType = await this.eventTypesService.updateEventType(
eventTypeId,
{ ...body, id: eventTypeId },
user
);
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
@Patch("/organizations/:orgId/teams/:teamId/event-types/:eventTypeId")
@Version(VERSION_NEUTRAL)
@Roles("TEAM_ADMIN")
@PlatformPlan("ESSENTIALS")
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
async updateAtomTeamEventType(
@GetUser() user: UserWithProfile,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Body() body: UpdateEventTypeReturn
): Promise<ApiResponse<UpdateEventTypeReturn>> {
const eventType = await this.eventTypesService.updateTeamEventType(
eventTypeId,
{ ...body, id: eventTypeId },
user,
teamId
);
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
@Get("/organizations/:orgId/teams/:teamId/conferencing")
@Roles("TEAM_ADMIN")
@PlatformPlan("ESSENTIALS")
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
@Version(VERSION_NEUTRAL)
async listTeamInstalledConferencingApps(
@GetUser() user: UserWithProfile,
@Param("teamId", ParseIntPipe) teamId: number
): Promise<ApiResponse<ConnectedApps>> {
const conferencingApps = await this.conferencingService.getTeamConferencingApps(user, teamId);
return {
status: SUCCESS_STATUS,
data: conferencingApps,
};
}
@Get("/conferencing")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async listUserInstalledConferencingApps(
@GetUser() user: UserWithProfile
): Promise<ApiResponse<ConnectedApps>> {
const conferencingApps = await this.conferencingService.getUserConferencingApps(user);
return {
status: SUCCESS_STATUS,
data: conferencingApps,
};
}
@Get("/organizations/:orgId/teams/:teamId/members-matching-attribute")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
@@ -0,0 +1,210 @@
import { GetEventTypePublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import {
BulkUpdateEventTypeToDefaultLocationDto,
EventTypesAppInput,
} from "@/modules/atoms/inputs/event-types-app.input";
import { GetAtomPublicEventTypeQueryParams } from "@/modules/atoms/inputs/get-atom-public-event-type-query-params.input";
import { EventTypesAtomService } from "@/modules/atoms/services/event-types-atom.service";
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard";
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
Controller,
Get,
Param,
ParseIntPipe,
UseGuards,
Version,
VERSION_NEUTRAL,
Patch,
Body,
Query,
} from "@nestjs/common";
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
import type { UpdateEventTypeReturn } from "@calcom/platform-libraries/event-types";
import { PublicEventType } from "@calcom/platform-libraries/event-types";
import { ApiResponse } from "@calcom/platform-types";
/*
Event-types endpoints for atoms, split from AtomsController for clarity and maintainability.
These endpoints should not be recommended for use by third party and are excluded from docs.
*/
@Controller({
path: "/v2/atoms",
version: API_VERSIONS_VALUES,
})
@DocsTags("Atoms - event-types endpoints for atoms")
@DocsExcludeController(true)
export class AtomsEventTypesController {
constructor(private readonly eventTypesService: EventTypesAtomService) {}
@Get("/event-types/:eventSlug/public")
async getPublicEventType(
@Param("eventSlug") eventSlug: string,
@Query() queryParams: GetAtomPublicEventTypeQueryParams
): Promise<ApiResponse<PublicEventType>> {
const { username, teamId, orgId, isTeamEvent } = queryParams;
const event = await this.eventTypesService.getPublicEventTypeForAtoms({
username,
eventSlug,
isTeamEvent: isTeamEvent ?? false,
teamId,
orgId,
});
return {
data: event,
status: SUCCESS_STATUS,
};
}
@Get("event-types/:eventTypeId")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async getAtomEventType(
@GetUser() user: UserWithProfile,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number
): Promise<ApiResponse<unknown>> {
const eventType = await this.eventTypesService.getUserEventType(user, eventTypeId);
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
@Get("/organizations/:orgId/teams/:teamId/event-types")
@Roles("TEAM_ADMIN")
@PlatformPlan("ESSENTIALS")
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
@Version(VERSION_NEUTRAL)
async listTeamEventTypes(@Param("teamId", ParseIntPipe) teamId: number): Promise<ApiResponse<unknown>> {
const eventTypes = await this.eventTypesService.getTeamEventTypes(teamId);
return {
status: SUCCESS_STATUS,
data: eventTypes,
};
}
@Get("/event-types")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async listUserEventTypes(@GetUser("id") userId: number): Promise<ApiResponse<unknown>> {
const eventTypes = await this.eventTypesService.getUserEventTypes(userId);
return {
status: SUCCESS_STATUS,
data: eventTypes,
};
}
@Get("event-types-app/:appSlug")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async getAtomEventTypeApp(
@GetUser() user: UserWithProfile,
@Param("appSlug") appSlug: string,
@Query() queryParams: EventTypesAppInput
): Promise<ApiResponse<unknown>> {
const { teamId } = queryParams;
const app = await this.eventTypesService.getEventTypesAppIntegration(appSlug, user, teamId);
return {
status: SUCCESS_STATUS,
data: {
app,
},
};
}
@Get("payment/:uid")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async getUserPaymentInfoById(@Param("uid") uid: string): Promise<ApiResponse<unknown>> {
const data = await this.eventTypesService.getUserPaymentInfo(uid);
return {
status: SUCCESS_STATUS,
data,
};
}
@Patch("/event-types/bulk-update-to-default-location")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async bulkUpdateAtomEventTypes(
@GetUser() user: UserWithProfile,
@Body() body: BulkUpdateEventTypeToDefaultLocationDto
): Promise<{ status: typeof SUCCESS_STATUS | typeof ERROR_STATUS }> {
await this.eventTypesService.bulkUpdateEventTypesDefaultLocation(user, body.eventTypeIds);
return {
status: SUCCESS_STATUS,
};
}
@Patch("/organizations/:orgId/teams/:teamId/event-types/bulk-update-to-default-location")
@Version(VERSION_NEUTRAL)
@Roles("TEAM_ADMIN")
@PlatformPlan("ESSENTIALS")
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
async bulkUpdateAtomTeamEventTypes(
@GetUser() user: UserWithProfile,
@Body() body: BulkUpdateEventTypeToDefaultLocationDto,
@Param("teamId", ParseIntPipe) teamId: number
): Promise<{ status: typeof SUCCESS_STATUS | typeof ERROR_STATUS }> {
await this.eventTypesService.bulkUpdateTeamEventTypesDefaultLocation(body.eventTypeIds, teamId);
return {
status: SUCCESS_STATUS,
};
}
@Patch("event-types/:eventTypeId")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async updateAtomEventType(
@GetUser() user: UserWithProfile,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
@Body() body: UpdateEventTypeReturn
): Promise<ApiResponse<UpdateEventTypeReturn>> {
const eventType = await this.eventTypesService.updateEventType(
eventTypeId,
{ ...body, id: eventTypeId },
user
);
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
@Patch("/organizations/:orgId/teams/:teamId/event-types/:eventTypeId")
@Version(VERSION_NEUTRAL)
@Roles("TEAM_ADMIN")
@PlatformPlan("ESSENTIALS")
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
async updateAtomTeamEventType(
@GetUser() user: UserWithProfile,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Body() body: UpdateEventTypeReturn
): Promise<ApiResponse<UpdateEventTypeReturn>> {
const eventType = await this.eventTypesService.updateTeamEventType(
eventTypeId,
{ ...body, id: eventTypeId },
user,
teamId
);
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
}
@@ -0,0 +1,86 @@
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { GetAtomSchedulesQueryParams } from "@/modules/atoms/inputs/get-atom-schedules-query-params.input";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
Body,
Controller,
Get,
Param,
ParseIntPipe,
Patch,
Query,
UseGuards,
Version,
VERSION_NEUTRAL,
} from "@nestjs/common";
import {
ApiTags as DocsTags,
ApiExcludeController as DocsExcludeController,
ApiOperation,
} from "@nestjs/swagger";
import { SCHEDULE_READ, SCHEDULE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
import { FindDetailedScheduleByIdReturnType } from "@calcom/platform-libraries/schedules";
import { ApiResponse, UpdateAtomScheduleDto } from "@calcom/platform-types";
import { SchedulesAtomsService } from "../services/schedules-atom.service";
/*
Endpoints used only by platform atoms, reusing code from other modules, data is already formatted and ready to be used by frontend atoms
these endpoints should not be recommended for use by third party and are excluded from docs
*/
@Controller({
path: "/v2/atoms",
version: API_VERSIONS_VALUES,
})
@DocsTags("Atoms - endpoints for atoms")
@DocsExcludeController(true)
export class AtomsSchedulesController {
constructor(private readonly schedulesService: SchedulesAtomsService) {}
@Get("/schedules")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
@Permissions([SCHEDULE_READ])
async getSchedule(
@GetUser() user: UserWithProfile,
@Query() queryParams: GetAtomSchedulesQueryParams
): Promise<ApiResponse<FindDetailedScheduleByIdReturnType | null>> {
const { isManagedEventType, scheduleId } = queryParams;
const schedule = await this.schedulesService.getSchedule({
scheduleId,
userId: user.id,
timeZone: user.timeZone,
isManagedEventType,
});
return {
status: SUCCESS_STATUS,
data: schedule,
};
}
@Patch("schedules/:scheduleId")
@Permissions([SCHEDULE_WRITE])
@UseGuards(ApiAuthGuard)
@ApiOperation({ summary: "Update atom schedule" })
async updateSchedule(
@GetUser() user: UserWithProfile,
@Body() bodySchedule: UpdateAtomScheduleDto,
@Param("scheduleId", ParseIntPipe) scheduleId: number
): Promise<ApiResponse<any>> {
const updatedSchedule = await this.schedulesService.updateUserSchedule({
user,
input: bodySchedule,
scheduleId,
});
return {
status: SUCCESS_STATUS,
data: updatedSchedule,
};
}
}
@@ -1,12 +1,12 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import type { TFindTeamMembersMatchingAttributeLogicInputSchema } from "@calcom/platform-libraries";
export class FindTeamMembersMatchingAttributeQueryDto {
@ApiPropertyOptional({
@ApiProperty({
nullable: true,
})
attributesQueryValue: TFindTeamMembersMatchingAttributeLogicInputSchema["attributesQueryValue"];
attributesQueryValue!: TFindTeamMembersMatchingAttributeLogicInputSchema["attributesQueryValue"] | null;
@ApiPropertyOptional({
type: Boolean,
@@ -0,0 +1,28 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { Transform, Type } from "class-transformer";
import { IsBoolean, IsOptional, IsString, IsNumber } from "class-validator";
export class GetAtomPublicEventTypeQueryParams {
@Transform(({ value }: { value: string }) => value === "true")
@IsBoolean()
@IsOptional()
@ApiPropertyOptional()
isTeamEvent?: boolean;
@IsOptional()
@Transform(({ value }: { value: string }) => value && parseInt(value))
@IsNumber()
@ApiPropertyOptional({ type: Number })
teamId?: number;
@IsOptional()
@Transform(({ value }: { value: string }) => value && parseInt(value))
@IsNumber()
@ApiPropertyOptional({ type: Number })
orgId?: number;
@IsOptional()
@IsString()
@ApiPropertyOptional({ type: String })
username?: string;
}
@@ -0,0 +1,15 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { Transform } from "class-transformer";
import { IsBoolean, IsOptional } from "class-validator";
export class GetAtomSchedulesQueryParams {
@IsOptional()
@Transform(({ value }: { value: string }) => value && parseInt(value))
@ApiPropertyOptional({ type: Number })
scheduleId?: number;
@IsOptional()
@IsBoolean()
@ApiPropertyOptional({ type: Boolean })
isManagedEventType?: boolean;
}
@@ -1,4 +1,5 @@
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
import { systemBeforeFieldEmail } from "@/ee/event-types/event-types_2024_06_14/transformers";
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
@@ -7,7 +8,7 @@ import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { TeamsEventTypesService } from "@/modules/teams/event-types/services/teams-event-types.service";
import { UsersService } from "@/modules/users/services/users.service";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable, NotFoundException, ForbiddenException } from "@nestjs/common";
import { Injectable, NotFoundException, ForbiddenException, BadRequestException } from "@nestjs/common";
import { checkAdminOrOwner, getClientSecretFromPayment } from "@calcom/platform-libraries";
import type { TeamQuery } from "@calcom/platform-libraries";
@@ -21,6 +22,7 @@ import type {
CredentialDataWithTeamName,
LocationOption,
} from "@calcom/platform-libraries/app-store";
import { type PublicEventType, getPublicEvent } from "@calcom/platform-libraries/event-types";
import {
getEventTypeById,
bulkUpdateEventsToDefaultLocation,
@@ -31,7 +33,6 @@ import {
import {
updateEventType,
TUpdateEventTypeInputSchema,
systemBeforeFieldEmail,
EventTypeMetaDataSchema,
} from "@calcom/platform-libraries/event-types";
import { PrismaClient } from "@calcom/prisma";
@@ -55,6 +56,18 @@ export class EventTypesAtomService {
private readonly teamEventTypeService: TeamsEventTypesService
) {}
private async getTeamSlug(teamId: number): Promise<string> {
const team = await this.dbRead.prisma.team.findUnique({
where: { id: teamId },
select: { slug: true },
});
if (!team?.slug) {
throw new NotFoundException(`Team with id ${teamId} not found`);
}
return team.slug;
}
async getUserEventType(user: UserWithProfile, eventTypeId: number) {
const organizationId = this.usersService.getUserMainOrgId(user);
@@ -369,4 +382,61 @@ export class EventTypesAtomService {
teamId,
});
}
/**
* Returns the public event type for atoms, handling both team and user events.
*/
async getPublicEventTypeForAtoms({
username,
eventSlug,
isTeamEvent,
orgId,
teamId,
}: {
username?: string;
eventSlug: string;
isTeamEvent?: boolean;
orgId?: number;
teamId?: number;
}): Promise<PublicEventType> {
const orgSlug = orgId ? await this.getTeamSlug(orgId) : null;
let slug: string | null = null;
if (isTeamEvent) {
if (!teamId) {
throw new BadRequestException("teamId is required for team events, please provide a valid teamId");
}
slug = await this.getTeamSlug(teamId);
} else {
if (!username) {
throw new BadRequestException(
"username is required for non-team events, please provide a valid username"
);
}
slug = username;
}
const slugLower = slug.toLowerCase();
try {
const event = await getPublicEvent(
slugLower,
eventSlug,
isTeamEvent,
orgSlug,
this.dbRead.prisma as unknown as PrismaClient,
true
);
if (!event) {
throw new NotFoundException(`Event type with slug ${eventSlug} not found`);
}
return event;
} catch (err) {
if (err instanceof Error) {
throw new NotFoundException(err.message);
}
throw new NotFoundException(`Event type with slug ${eventSlug} not found`);
}
}
}
@@ -0,0 +1,63 @@
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { UsersRepository } from "@/modules/users/users.repository";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Logger } from "@nestjs/common";
import { Injectable } from "@nestjs/common";
import { ScheduleRepository, UpdateScheduleResponse } from "@calcom/platform-libraries/schedules";
import { updateSchedule } from "@calcom/platform-libraries/schedules";
import { UpdateAtomScheduleDto } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";
@Injectable()
export class SchedulesAtomsService {
private logger = new Logger("SchedulesAtomService");
constructor(
private readonly usersRepository: UsersRepository,
private readonly dbWrite: PrismaWriteService
) {}
async getSchedule({
timeZone,
userId,
scheduleId,
isManagedEventType,
}: {
timeZone: string;
userId: number;
scheduleId?: number;
isManagedEventType?: boolean;
}) {
const user = await this.usersRepository.findById(userId);
if (!user?.defaultScheduleId) return null;
return await ScheduleRepository.findDetailedScheduleById({
scheduleId: scheduleId ?? user.defaultScheduleId,
isManagedEventType,
userId,
timeZone,
defaultScheduleId: user.defaultScheduleId,
});
}
async updateUserSchedule({
input,
user,
scheduleId,
}: {
input: UpdateAtomScheduleDto;
user: UserWithProfile;
scheduleId: number;
}): Promise<UpdateScheduleResponse> {
return updateSchedule({
input: {
scheduleId,
...input,
},
user,
prisma: this.dbWrite.prisma as unknown as PrismaClient,
});
}
}
@@ -21,7 +21,8 @@ import {
ZOOM,
OFFICE_365_VIDEO,
} from "@calcom/platform-constants";
import { userMetadata, getUsersCredentials } from "@calcom/platform-libraries";
import { userMetadata } from "@calcom/platform-libraries";
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/platform-libraries/app-store";
import { getApps, handleDeleteCredential } from "@calcom/platform-libraries/app-store";
@Injectable()
@@ -90,7 +91,7 @@ export class ConferencingService {
if (!CONFERENCING_APPS.includes(appSlug)) {
throw new BadRequestException("Invalid app, available apps are: ", CONFERENCING_APPS.join(", "));
}
const credentials = await getUsersCredentials(user);
const credentials = await getUsersCredentialsIncludeServiceAccountKey(user);
const foundApp = getApps(credentials, true).filter((app) => app.slug === appSlug)[0];
@@ -73,6 +73,7 @@ describe("Platform Destination Calendar Endpoints", () => {
user.id,
APPLE_CALENDAR_ID
);
jest.spyOn(CalendarsService.prototype, "getCalendars").mockReturnValue(
Promise.resolve({
connectedCalendars: [
@@ -92,19 +93,32 @@ describe("Platform Destination Calendar Endpoints", () => {
url: "",
email: "",
},
calendars: {
externalId:
"https://caldav.icloud.com/20961146906/calendars/83C4F9A1-F1D0-41C7-8FC3-0B$9AE22E813/",
readOnly: false,
integration: "apple_calendar",
credentialId: appleCalendarCredentials.id,
primary: true,
email: user.email,
},
// calendars: {
// externalId:
// "https://caldav.icloud.com/20961146906/calendars/83C4F9A1-F1D0-41C7-8FC3-0B$9AE22E813/",
// readOnly: false,
// integration: "apple_calendar",
// credentialId: appleCalendarCredentials.id,
// primary: true,
// email: user.email,
// },
error: { message: "" },
delegationCredentialId: null,
credentialId: appleCalendarCredentials.id,
},
],
destinationCalendar: null,
destinationCalendar: {
name: "destinationCalendar",
eventTypeId: 1,
credentialId: appleCalendarCredentials.id,
primaryEmail: "primaryEmail",
integration: "apple_calendar",
externalId: "externalId",
userId: null,
id: 0,
delegationCredentialId: null,
domainWideDelegationCredentialId: null,
},
})
);
app = moduleRef.createNestApplication();
@@ -134,7 +148,7 @@ describe("Platform Destination Calendar Endpoints", () => {
expect(user).toBeDefined();
});
it(`POST /v2/destination-calendars: it should respond with a 200 returning back the user updated destination calendar`, async () => {
it.skip(`POST /v2/destination-calendars: it should respond with a 200 returning back the user updated destination calendar`, async () => {
const body = {
integration: appleCalendarCredentials.type,
externalId: "https://caldav.icloud.com/20961146906/calendars/83C4F9A1-F1D0-41C7-8FC3-0B$9AE22E813/",
@@ -19,6 +19,7 @@ import { encryptServiceAccountKey } from "@calcom/platform-libraries";
import {
addDelegationCredential,
toggleDelegationCredentialEnabled,
type TServiceAccountKeySchema,
} from "@calcom/platform-libraries/app-store";
@Injectable()
@@ -131,12 +132,17 @@ export class OrganizationsDelegationCredentialService {
delegationCredentialId: string,
serviceAccountKey: GoogleServiceAccountKeyInput | MicrosoftServiceAccountKeyInput
) {
const encryptedServiceAccountKey = encryptServiceAccountKey(serviceAccountKey);
// First encrypt the service account key
const encryptedServiceAccountKey = encryptServiceAccountKey(
serviceAccountKey as TServiceAccountKeySchema
);
const prismaJsonValue = JSON.parse(JSON.stringify(encryptedServiceAccountKey));
const delegationCredential =
await this.organizationsDelegationCredentialRepository.updateIncludeWorkspacePlatform(
delegationCredentialId,
{
serviceAccountKey: encryptedServiceAccountKey,
serviceAccountKey: prismaJsonValue,
enabled: false,
}
);
@@ -1,4 +1,5 @@
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
import { transformTeamLocationsApiToInternal } from "@/ee/event-types/event-types_2024_06_14/transformers/api-to-internal/locations";
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.repository";
import { OrganizationsConferencingService } from "@/modules/organizations/conferencing/services/organizations-conferencing.service";
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
@@ -6,7 +7,6 @@ import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
import { UsersRepository } from "@/modules/users/users.repository";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { transformTeamLocationsApiToInternal } from "@calcom/platform-libraries/event-types";
import {
CreateTeamEventTypeInput_2024_06_14,
UpdateTeamEventTypeInput_2024_06_14,
@@ -56,7 +56,6 @@ export type OAuthCallbackState = {
@DocsTags("Organizations/Teams Stripe")
export class OrganizationsStripeController {
constructor(
private readonly stripeService: StripeService,
private readonly organizationsStripeService: OrganizationsStripeService,
private readonly tokensRepository: TokensRepository
) {}
@@ -89,6 +89,7 @@ export class OrganizationsUsersService {
autoAccept: userCreateBody.autoAccept,
},
},
language: "en",
});
const createdUser = createdUserCall[0];
@@ -71,6 +71,10 @@ export class RouterController {
3
);
if (!eventTypeData) {
throw new NotFoundException("Event type not found.");
}
// get the salesforce record owner email for the email given as a form response.
const {
email: teamMemberEmail,
@@ -81,9 +85,9 @@ export class RouterController {
eventData: eventTypeData,
});
Boolean(teamMemberEmail) && routingUrl.searchParams.set("cal.teamMemberEmail", teamMemberEmail);
Boolean(crmOwnerRecordType) && routingUrl.searchParams.set("cal.crmOwnerRecordType", crmOwnerRecordType);
Boolean(crmAppSlug) && routingUrl.searchParams.set("cal.crmAppSlug", crmAppSlug);
teamMemberEmail && routingUrl.searchParams.set("cal.teamMemberEmail", teamMemberEmail);
crmOwnerRecordType && routingUrl.searchParams.set("cal.crmOwnerRecordType", crmOwnerRecordType);
crmAppSlug && routingUrl.searchParams.set("cal.crmAppSlug", crmAppSlug);
return { status: "success", data: routingUrl.toString(), redirect: true };
}
@@ -73,6 +73,8 @@ export class CalendarsServiceMock {
externalId: "externalId",
userId: null,
id: 0,
delegationCredentialId: null,
domainWideDelegationCredentialId: null,
},
} satisfies Awaited<ReturnType<typeof CalendarsService.prototype.getCalendars>>;
}
+1
View File
@@ -34,4 +34,5 @@ process.env = {
"BIds0AQJ96xGBjTSMHTOqLBLutQE7Lu32KKdgSdy7A2cS4mKI2cgb3iGkhDJa5Siy-stezyuPm8qpbhmNxdNHMw",
VAPID_PRIVATE_KEY: "6cJtkASCar5sZWguIAW7OjvyixpBw9p8zL8WDDwk9Jk",
CALENDSO_ENCRYPTION_KEY: "22gfxhWUlcKliUeXcu8xNah2+HP/29ZX",
INTEGRATION_TEST_MODE: "true",
};
@@ -221,6 +221,7 @@ function isAvailableInTimeSlot(
return isWithinPeriod;
}
export type PublicEventType = Awaited<ReturnType<typeof getPublicEvent>>;
// TODO: Convert it to accept a single parameter with structured data
export const getPublicEvent = async (
username: string,
@@ -1,12 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import type { AvailableSlotsType } from "@calcom/platform-libraries";
import type {
GetAvailableSlotsInput_2024_04_15,
ApiResponse,
ApiSuccessResponse,
} from "@calcom/platform-types";
import type { GetAvailableSlotsResponse } from "@calcom/trpc/server/routers/viewer/slots/util";
export const QUERY_KEY = "get-available-slots";
@@ -32,12 +32,15 @@ export const useApiV2AvailableSlots = ({
],
queryFn: () => {
return axios
.get<ApiResponse<AvailableSlotsType>>(`${process.env.NEXT_PUBLIC_API_V2_URL}/slots/available`, {
params: rest,
})
.get<ApiResponse<GetAvailableSlotsResponse>>(
`${process.env.NEXT_PUBLIC_API_V2_URL}/slots/available`,
{
params: rest,
}
)
.then((res) => {
if (res.data.status === "success") {
return (res.data as ApiSuccessResponse<AvailableSlotsType>).data;
return (res.data as ApiSuccessResponse<GetAvailableSlotsResponse>).data;
}
throw new Error(res.data.error.message);
});
+7
View File
@@ -133,6 +133,13 @@ const commons = {
rrTimestampBasis: null,
interfaceLanguage: null,
customReplyToEmail: null,
profileId: null,
requiresConfirmationWillBlockSlot: false,
canSendCalVideoTranscriptionEmails: false,
instantMeetingExpiryTimeOffsetInSeconds: 0,
instantMeetingScheduleId: null,
instantMeetingParameters: [],
eventTypeColor: null,
};
export const dynamicEvent = {
-1
View File
@@ -1 +0,0 @@
export * from "./transformers";
@@ -1,54 +0,0 @@
import type { ScheduleAvailabilityInput_2024_06_11 } from "@calcom/platform-types/schedules/schedules-2024-06-11";
import type { ScheduleOverrideInput_2024_06_11 } from "@calcom/platform-types/schedules/schedules-2024-06-11";
import type { WeekDay } from "@calcom/platform-types/schedules/schedules-2024-06-11";
export function transformApiScheduleAvailability(inputAvailability: ScheduleAvailabilityInput_2024_06_11[]) {
return inputAvailability.map((availability) => ({
days: availability.days.map(transformDayToNumber),
startTime: createDateFromHoursMinutes(availability.startTime),
endTime: createDateFromHoursMinutes(availability.endTime),
}));
}
export function transformApiScheduleOverrides(inputOverrides: ScheduleOverrideInput_2024_06_11[]) {
return inputOverrides.map((override) => ({
date: new Date(override.date),
startTime: createDateFromHoursMinutes(override.startTime),
endTime: createDateFromHoursMinutes(override.endTime),
}));
}
function createDateFromHoursMinutes(hoursMinutes: string): Date {
const parts = hoursMinutes.split(":");
if (parts.length < 2) {
throw new Error("Invalid time format. Please use 'hh:mm'.");
}
const hours = parseInt(parts[0], 10);
const minutes = parseInt(parts[1], 10);
if (isNaN(hours) || hours < 0 || hours > 23) {
throw new Error("Hours must be a number between 0 and 23.");
}
if (isNaN(minutes) || minutes < 0 || minutes > 59) {
throw new Error("Minutes must be a number between 0 and 59.");
}
const utcDate = new Date(Date.UTC(1970, 0, 1, hours, minutes));
return utcDate;
}
function transformDayToNumber(day: WeekDay): number {
const weekMap: { [key in WeekDay]: number } = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6,
};
return weekMap[day];
}
@@ -1,2 +1 @@
export * from "./for-atom";
export * from "./from-api";
+133
View File
@@ -0,0 +1,133 @@
import { getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { hasEditPermissionForUserID } from "@calcom/lib/hasEditPermissionForUser";
import { transformScheduleToAvailabilityForAtom } from "@calcom/lib/schedules/transformers/for-atom";
import type { PrismaClient } from "@calcom/prisma";
import type { TUpdateInputSchema } from "@calcom/trpc/server/routers/viewer/availability/schedule/update.schema";
import { setupDefaultSchedule } from "@calcom/trpc/server/routers/viewer/availability/util";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import { TRPCError } from "@trpc/server";
interface IUpdateScheduleOptions {
input: TUpdateInputSchema;
user: Pick<NonNullable<TrpcSessionUser>, "id" | "defaultScheduleId" | "timeZone">;
prisma: PrismaClient;
}
export type UpdateScheduleResponse = Awaited<ReturnType<typeof updateSchedule>>;
export const updateSchedule = async ({ input, user, prisma }: IUpdateScheduleOptions) => {
const availability = input.schedule
? getAvailabilityFromSchedule(input.schedule)
: (input.dateOverrides || []).map((dateOverride) => ({
startTime: dateOverride.start,
endTime: dateOverride.end,
date: dateOverride.start,
days: [],
}));
// Not able to update the schedule with userId where clause, so fetch schedule separately and then validate
// Bug: https://github.com/prisma/prisma/issues/7290
const userSchedule = await prisma.schedule.findUnique({
where: {
id: input.scheduleId,
},
select: {
userId: true,
name: true,
id: true,
},
});
if (!userSchedule) {
throw new TRPCError({
code: "UNAUTHORIZED",
});
}
if (userSchedule?.userId !== user.id) {
const hasEditPermission = await hasEditPermissionForUserID({
ctx: {
user,
},
input: { memberId: userSchedule.userId },
});
if (!hasEditPermission) {
throw new TRPCError({
code: "UNAUTHORIZED",
});
}
}
let updatedUser;
if (input.isDefault) {
const setupDefault = await setupDefaultSchedule(user.id, input.scheduleId, prisma);
updatedUser = setupDefault;
}
if (!input.name) {
// TODO: Improve
// We don't want to pass the full schedule for just a set as default update
// but in the current logic, this wipes the existing availability.
// Return early to prevent this from happening.
return {
schedule: userSchedule,
isDefault: updatedUser
? updatedUser.defaultScheduleId === input.scheduleId
: user.defaultScheduleId === input.scheduleId,
};
}
const schedule = await prisma.schedule.update({
where: {
id: input.scheduleId,
},
data: {
timeZone: input.timeZone,
name: input.name,
availability: {
deleteMany: {
scheduleId: {
equals: input.scheduleId,
},
},
createMany: {
data: [
...availability,
...(input.dateOverrides || []).map((override) => ({
date: override.start,
startTime: override.start,
endTime: override.end,
})),
],
},
},
},
select: {
id: true,
userId: true,
name: true,
availability: true,
timeZone: true,
eventType: {
select: {
id: true,
eventName: true,
},
},
},
});
const userAvailability = transformScheduleToAvailabilityForAtom(schedule);
return {
schedule,
availability: userAvailability,
timeZone: schedule.timeZone || user.timeZone,
isDefault: updatedUser
? updatedUser.defaultScheduleId === schedule.id
: user.defaultScheduleId === schedule.id,
prevDefaultId: user.defaultScheduleId,
currentDefaultId: updatedUser ? updatedUser.defaultScheduleId : user.defaultScheduleId,
};
};
@@ -8,6 +8,10 @@ import {
transformWorkingHoursForAtom,
} from "../../schedules/transformers";
export type FindDetailedScheduleByIdReturnType = Awaited<
ReturnType<typeof ScheduleRepository.findDetailedScheduleById>
>;
export class ScheduleRepository {
static async findScheduleById({ id }: { id: number }) {
const schedule = await prisma.schedule.findUnique({
@@ -1,152 +0,0 @@
import type { ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
import type { User } from "@calcom/prisma/client";
import { transformApiScheduleForAtom } from "./transformApiScheduleForAtom";
const SCHEDULE_OWNER_ID = 256;
describe("transformScheduleForAtom", () => {
const user: Pick<User, "id" | "defaultScheduleId" | "timeZone"> = {
id: SCHEDULE_OWNER_ID,
defaultScheduleId: 139,
timeZone: "America/New_York",
};
beforeEach(() => {
jest.resetAllMocks();
});
it("should return null if user is not provided", () => {
const schedule: ScheduleOutput_2024_06_11 | null = {
id: 139,
ownerId: SCHEDULE_OWNER_ID,
name: "Default",
timeZone: "America/Cancun",
availability: [
{
days: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
startTime: "09:00",
endTime: "17:00",
},
],
isDefault: true,
overrides: [],
};
expect(transformApiScheduleForAtom(undefined, schedule, 1)).toBeNull();
});
it("should return null if schedule is not provided", () => {
expect(transformApiScheduleForAtom(user, null, 1)).toBeNull();
});
it("should transform schedule correctly", () => {
const schedule: ScheduleOutput_2024_06_11 = {
id: 139,
ownerId: SCHEDULE_OWNER_ID,
name: "Default",
timeZone: "America/Cancun",
availability: [
{
days: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
startTime: "09:00",
endTime: "17:00",
},
],
isDefault: true,
overrides: [],
};
const date = new Date();
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const expectedResult = {
id: 139,
name: "Default",
isManaged: false,
workingHours: [
{
days: [1, 2, 3, 4, 5],
startTime: 540,
endTime: 1020,
userId: 256,
},
],
schedule: [
{
userId: 256,
scheduleId: 139,
days: [1, 2, 3, 4, 5],
startTime: new Date(Date.UTC(1970, 0, 1, 9, 0)),
endTime: new Date(Date.UTC(1970, 0, 1, 17, 0)),
date: null,
},
],
availability: [
[],
[
{
start: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T09:00:00.000Z`
),
end: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T17:00:00.000Z`
),
},
],
[
{
start: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T09:00:00.000Z`
),
end: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T17:00:00.000Z`
),
},
],
[
{
start: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T09:00:00.000Z`
),
end: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T17:00:00.000Z`
),
},
],
[
{
start: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T09:00:00.000Z`
),
end: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T17:00:00.000Z`
),
},
],
[
{
start: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T09:00:00.000Z`
),
end: new Date(
`${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}T17:00:00.000Z`
),
},
],
[],
],
timeZone: "America/Cancun",
dateOverrides: [],
isDefault: true,
isLastSchedule: true,
readOnly: false,
};
const result = transformApiScheduleForAtom(user, schedule, 1);
expect(result).toEqual(expectedResult);
});
});
@@ -1,60 +0,0 @@
import {
transformAvailabilityForAtom,
transformDateOverridesForAtom,
transformApiScheduleAvailability,
transformApiScheduleOverrides,
transformWorkingHoursForAtom,
} from "@calcom/lib/schedules/transformers";
import type { ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
import type { User } from "@calcom/prisma/client";
export function transformApiScheduleForAtom(
user: Pick<User, "id" | "defaultScheduleId" | "timeZone"> | undefined,
schedule: ScheduleOutput_2024_06_11 | null | undefined,
userSchedulesCount: number
) {
if (!user || !schedule) {
return null;
}
const transformedSchedule = {
...schedule,
availability: transformApiScheduleAvailability(schedule.availability),
overrides: transformApiScheduleOverrides(schedule.overrides),
};
const combined = [...transformedSchedule.availability, ...transformedSchedule.overrides];
const availability = combined.map((entry) => {
return {
...entry,
userId: schedule.ownerId,
scheduleId: schedule.id,
days: "days" in entry ? entry.days : [],
date: "date" in entry ? entry.date : null,
id: schedule.id,
};
});
const atomSchedule = {
...schedule,
availability,
userId: schedule.ownerId,
};
const timeZone = schedule.timeZone || user.timeZone;
const defaultScheduleId = user.defaultScheduleId;
return {
id: schedule.id,
name: schedule.name,
isManaged: schedule.ownerId !== user.id,
workingHours: transformWorkingHoursForAtom(atomSchedule),
schedule: availability,
availability: transformAvailabilityForAtom(atomSchedule),
timeZone,
dateOverrides: transformDateOverridesForAtom(atomSchedule, timeZone),
isDefault: defaultScheduleId === schedule.id,
isLastSchedule: userSchedulesCount <= 1,
readOnly: schedule.ownerId !== user.id,
};
}
@@ -1,72 +0,0 @@
import type { UpdateScheduleInput_2024_06_11 } from "@calcom/platform-types";
import type { AvailabilityFormValues } from "../types";
import { transformAtomScheduleForApi } from "./transformAtomScheduleForApi";
describe("transformAtomScheduleForApi", () => {
it("should transform atom schedule correctly to API format", () => {
const input: AvailabilityFormValues = {
name: "Default",
schedule: [
[],
[
{
start: new Date("2024-05-14T09:00:00.000Z"),
end: new Date("2024-05-14T17:00:00.000Z"),
},
],
[
{
start: new Date("2024-05-14T09:00:00.000Z"),
end: new Date("2024-05-14T17:00:00.000Z"),
},
],
[
{
start: new Date("2024-05-14T09:00:00.000Z"),
end: new Date("2024-05-14T17:00:00.000Z"),
},
],
[
{
start: new Date("2024-05-14T09:00:00.000Z"),
end: new Date("2024-05-14T17:00:00.000Z"),
},
],
[
{
start: new Date("2024-05-14T11:00:00.000Z"),
end: new Date("2024-05-14T12:00:00.000Z"),
},
],
[],
],
dateOverrides: [],
timeZone: "America/Cancun",
isDefault: true,
};
const expectedOutput: UpdateScheduleInput_2024_06_11 = {
name: "Default",
timeZone: "America/Cancun",
isDefault: true,
availability: [
{
days: ["Monday", "Tuesday", "Wednesday", "Thursday"],
startTime: "09:00",
endTime: "17:00",
},
{
days: ["Friday"],
startTime: "11:00",
endTime: "12:00",
},
],
overrides: [],
};
const result = transformAtomScheduleForApi(input);
expect(result).toEqual(expectedOutput);
});
});
@@ -1,88 +0,0 @@
import type {
ScheduleAvailabilityInput_2024_06_11,
UpdateScheduleInput_2024_06_11,
WeekDay,
} from "@calcom/platform-types";
import type { AvailabilityFormValues } from "../types";
export function transformAtomScheduleForApi(body: AvailabilityFormValues): UpdateScheduleInput_2024_06_11 {
const { name, schedule, dateOverrides, timeZone, isDefault } = body;
const overrides =
dateOverrides.flatMap(
(dateOverridesRanges) =>
dateOverridesRanges?.ranges?.map((range) => transfromAtomOverrideForApi(range)) ?? []
) ?? [];
const availability = formatScheduleTime(schedule);
return { name, timeZone, isDefault, availability, overrides };
}
type AtomDateOverride = {
start: Date;
end: Date;
};
function transfromAtomOverrideForApi(override: AtomDateOverride) {
const date = `${override.start.getUTCFullYear()}-${(override.start.getUTCMonth() + 1)
.toString()
.padStart(2, "0")}-${override.start.getUTCDate().toString().padStart(2, "0")}`;
return {
date,
startTime: padHoursMinutesWithZeros(`${override.start.getUTCHours()}:${override.start.getUTCMinutes()}`),
endTime: padHoursMinutesWithZeros(`${override.end.getUTCHours()}:${override.end.getUTCMinutes()}`),
};
}
function padHoursMinutesWithZeros(hhMM: string) {
const [hours, minutes] = hhMM.split(":");
const formattedHours = hours.padStart(2, "0");
const formattedMinutes = minutes.padStart(2, "0");
return `${formattedHours}:${formattedMinutes}`;
}
function formatScheduleTime(
weekSchedule: AvailabilityFormValues["schedule"]
): UpdateScheduleInput_2024_06_11["availability"] {
const daysOfWeek: WeekDay[] = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
const formattedSchedule = weekSchedule.map((daySchedule, index) =>
daySchedule.map((event) => ({
startTime: convertToHHMM(event.start.toISOString()),
endTime: convertToHHMM(event.end.toISOString()),
days: [daysOfWeek[index]],
}))
);
const timeMap: { [key: string]: ScheduleAvailabilityInput_2024_06_11 } = {};
formattedSchedule.flat().forEach((event) => {
const timeKey = `${event.startTime}-${event.endTime}`;
if (!timeMap[timeKey]) {
timeMap[timeKey] = { startTime: event.startTime, endTime: event.endTime, days: [] };
}
timeMap[timeKey].days.push(...event.days);
});
return Object.values(timeMap);
}
function convertToHHMM(isoDate: string): string {
const date = new Date(isoDate);
const hours = date.getUTCHours().toString().padStart(2, "0");
const minutes = date.getUTCMinutes().toString().padStart(2, "0");
return `${hours}:${minutes}`;
}
@@ -1,25 +1,18 @@
import type { ReactNode } from "react";
import type { ScheduleLabelsType } from "@calcom/features/schedules/components/Schedule";
import type {
ApiErrorResponse,
ApiResponse,
ScheduleOutput_2024_06_11,
UpdateScheduleInput_2024_06_11,
} from "@calcom/platform-types";
import type { UpdateScheduleResponse } from "@calcom/lib/schedules/updateSchedule";
import type { ApiErrorResponse, ApiResponse, UpdateScheduleInput_2024_06_11 } from "@calcom/platform-types";
import { useAtomSchedule } from "../../hooks/schedules/useAtomSchedule";
import { useAtomUpdateSchedule } from "../../hooks/schedules/useAtomUpdateSchedule";
import useDeleteSchedule from "../../hooks/schedules/useDeleteSchedule";
import { useSchedule } from "../../hooks/schedules/useSchedule";
import { useSchedules } from "../../hooks/schedules/useSchedules";
import useUpdateSchedule from "../../hooks/schedules/useUpdateSchedule";
import { useMe } from "../../hooks/useMe";
import { AtomsWrapper } from "../../src/components/atoms-wrapper";
import { useToast } from "../../src/components/ui/use-toast";
import type { Availability } from "../AvailabilitySettings";
import type { CustomClassNames } from "../AvailabilitySettings";
import { AvailabilitySettings } from "../AvailabilitySettings";
import { transformApiScheduleForAtom } from "../atom-api-transformers/transformApiScheduleForAtom";
import { transformAtomScheduleForApi } from "../atom-api-transformers/transformAtomScheduleForApi";
import type { AvailabilityFormValues } from "../types";
type AvailabilitySettingsPlatformWrapperProps = {
@@ -28,7 +21,7 @@ type AvailabilitySettingsPlatformWrapperProps = {
tooltips: Partial<ScheduleLabelsType>;
};
customClassNames?: Partial<CustomClassNames>;
onUpdateSuccess?: (res: ApiResponse<ScheduleOutput_2024_06_11>) => void;
onUpdateSuccess?: (res: ApiResponse<UpdateScheduleResponse>) => void;
onUpdateError?: (err: ApiErrorResponse) => void;
onDeleteSuccess?: (res: ApiResponse) => void;
onDeleteError?: (err: ApiErrorResponse) => void;
@@ -62,10 +55,8 @@ export const AvailabilitySettingsPlatformWrapper = ({
noScheduleChildren,
loadingStateChildren,
}: AvailabilitySettingsPlatformWrapperProps) => {
const { isLoading, data: schedule } = useSchedule(id);
const { data: schedules } = useSchedules();
const { isLoading, data: atomSchedule } = useAtomSchedule(id);
const { data: me } = useMe();
const atomSchedule = transformApiScheduleForAtom(me?.data, schedule, schedules?.length || 0);
const { timeFormat } = me?.data || { timeFormat: null };
const { toast } = useToast();
@@ -88,7 +79,7 @@ export const AvailabilitySettingsPlatformWrapper = ({
},
});
const { mutate: updateSchedule, isPending: isSavingInProgress } = useUpdateSchedule({
const { mutate: updateSchedule, isPending: isSavingInProgress } = useAtomUpdateSchedule({
onSuccess: (res) => {
onUpdateSuccess?.(res);
if (!disableToasts) {
@@ -112,16 +103,20 @@ export const AvailabilitySettingsPlatformWrapper = ({
};
const handleUpdate = async (id: number, body: AvailabilityFormValues) => {
const updateBody = transformAtomScheduleForApi(body);
let canUpdate = true;
if (onBeforeUpdate) {
canUpdate = await onBeforeUpdate(updateBody);
canUpdate = await onBeforeUpdate(body);
}
if (canUpdate) {
updateSchedule({ id, ...updateBody });
updateSchedule({
scheduleId: id,
body: {
...body,
dateOverrides: body.dateOverrides.flatMap((override) => override.ranges),
},
});
}
};
@@ -14,14 +14,10 @@ import { useTimePreferences } from "@calcom/features/bookings/lib";
import { useTimesForSchedule } from "@calcom/features/schedules/lib/use-schedule/useTimesForSchedule";
import { getRoutedTeamMemberIdsFromSearchParams } from "@calcom/lib/bookings/getRoutedTeamMemberIdsFromSearchParams";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import type { ConnectedDestinationCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
import { localStorage } from "@calcom/lib/webstorage";
import type { ConnectedDestinationCalendars } from "@calcom/platform-libraries";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import {
transformApiEventTypeForAtom,
transformApiTeamEventTypeForAtom,
} from "../event-types/atom-api-transformers/transformApiEventTypeForAtom";
import { useCreateBooking } from "../hooks/bookings/useCreateBooking";
import { useCreateInstantBooking } from "../hooks/bookings/useCreateInstantBooking";
import { useCreateRecurringBooking } from "../hooks/bookings/useCreateRecurringBooking";
@@ -30,8 +26,7 @@ import {
QUERY_KEY as BOOKING_RESCHEDULE_KEY,
} from "../hooks/bookings/useGetBookingForReschedule";
import { useHandleBookEvent } from "../hooks/bookings/useHandleBookEvent";
import { useEventType } from "../hooks/event-types/public/useEventType";
import { useTeamEventType } from "../hooks/event-types/public/useTeamEventType";
import { useAtomGetPublicEvent } from "../hooks/event-types/public/useAtomGetPublicEvent";
import { useAtomsContext } from "../hooks/useAtomsContext";
import { useAvailableSlots } from "../hooks/useAvailableSlots";
import { useCalendarsBusyTimes } from "../hooks/useCalendarsBusyTimes";
@@ -74,6 +69,7 @@ export const BookerPlatformWrapper = (
const bookingData = useBookerStore((state) => state.bookingData);
const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
const setSelectedMonth = useBookerStore((state) => state.setMonth);
const selectedDuration = useBookerStore((state) => state.selectedDuration);
const [isOverlayCalendarEnabled, setIsOverlayCalendarEnabled] = useState(
Boolean(localStorage?.getItem?.("overlayCalendarSwitchDefault"))
@@ -147,61 +143,13 @@ export const BookerPlatformWrapper = (
return getUsernameList(username ?? "").length > 1;
}, [username]);
const { isSuccess, isError, isPending, data } = useEventType(username, props.eventSlug, props.isTeamEvent);
const {
isSuccess: isTeamSuccess,
isError: isTeamError,
isPending: isTeamPending,
data: teamEventTypeData,
} = useTeamEventType(teamId, props.eventSlug, props.isTeamEvent, props.hostsLimit);
const event = useMemo(() => {
if (props.isTeamEvent && !isTeamPending && teamId && teamEventTypeData && teamEventTypeData.length > 0) {
return {
isSuccess: isTeamSuccess,
isError: isTeamError,
isPending: isTeamPending,
data:
teamEventTypeData && teamEventTypeData.length > 0
? transformApiTeamEventTypeForAtom(
teamEventTypeData[0],
props.entity,
props.defaultFormValues,
!!props.hostsLimit
)
: undefined,
};
}
return {
isSuccess,
isError,
isPending,
data:
data && data.length > 0
? transformApiEventTypeForAtom(data[0], props.entity, props.defaultFormValues, !!props.hostsLimit)
: undefined,
};
}, [
props.isTeamEvent,
const event = useAtomGetPublicEvent({
username,
eventSlug: props.eventSlug,
isTeamEvent: props.isTeamEvent,
teamId,
props.entity,
teamEventTypeData,
isSuccess,
isError,
isPending,
data,
isTeamPending,
isTeamSuccess,
isTeamError,
props.hostsLimit,
]);
if (isDynamic && props.duration && event.data) {
// note(Lauris): Mandatory - In case of "dynamic" event type default event duration returned by the API is 30,
// but we are re-using the dynamic event type as a team event, so we must set the event length to whatever the event length is.
event.data.length = props.duration;
}
selectedDuration,
});
const bookerLayout = useBookerLayout(event.data?.profile?.bookerLayouts);
useInitializeBookerStore({
@@ -209,7 +157,7 @@ export const BookerPlatformWrapper = (
teamMemberEmail,
crmAppSlug,
crmOwnerRecordType,
eventId: event.data?.id,
eventId: event?.data?.id,
rescheduleUid: props.rescheduleUid ?? null,
bookingUid: props.bookingUid ?? null,
layout: layout,
@@ -225,8 +173,6 @@ export const BookerPlatformWrapper = (
const month = useBookerStore((state) => state.month);
const eventSlug = useBookerStore((state) => state.eventSlug);
const selectedDuration = useBookerStore((state) => state.selectedDuration);
const { data: session } = useMe();
const hasSession = !!session;
const { name: defaultName, guests: defaultGuests, ...restFormValues } = props.defaultFormValues ?? {};
@@ -323,7 +269,7 @@ export const BookerPlatformWrapper = (
Boolean(teamId || username) &&
Boolean(month) &&
Boolean(timezone) &&
(props.isTeamEvent ? !isTeamPending : !isPending) &&
!event?.isPending &&
Boolean(event?.data?.id),
orgSlug: props.entity?.orgSlug ?? undefined,
eventTypeSlug: isDynamic ? "dynamic" : eventSlug || "",
@@ -331,7 +277,7 @@ export const BookerPlatformWrapper = (
});
const bookerForm = useBookingForm({
event: event.data,
event: event?.data,
sessionEmail:
session?.data?.email && clientId
? session.data.email.replace(`+${clientId}`, "")
@@ -356,8 +302,8 @@ export const BookerPlatformWrapper = (
schedule.refetch();
props.onCreateBookingSuccess?.(data);
if (!preventEventTypeRedirect && !!event.data?.successRedirectUrl) {
window.location.href = event.data.successRedirectUrl;
if (!preventEventTypeRedirect && !!event?.data?.successRedirectUrl) {
window.location.href = event?.data?.successRedirectUrl;
}
},
onError: props.onCreateBookingError,
@@ -376,8 +322,8 @@ export const BookerPlatformWrapper = (
schedule.refetch();
props.onCreateRecurringBookingSuccess?.(data);
if (!!event.data?.successRedirectUrl) {
window.location.href = event.data.successRedirectUrl;
if (!!event?.data?.successRedirectUrl) {
window.location.href = event?.data?.successRedirectUrl;
}
},
onError: props.onCreateRecurringBookingError,
+1 -1
View File
@@ -3,7 +3,7 @@ import type React from "react";
import type { BookerProps } from "@calcom/features/bookings/Booker";
import type { BookerStore } from "@calcom/features/bookings/Booker/store";
import type { Timezone } from "@calcom/features/bookings/Booker/types";
import type { BookingResponse } from "@calcom/platform-libraries";
import type { BookingResponse } from "@calcom/features/bookings/types";
import type {
ApiSuccessResponse,
ApiErrorResponse,
@@ -4,11 +4,11 @@ import { useState, useEffect, useMemo } from "react";
import { SingleValueComponent } from "@calcom/features/calendars/DestinationCalendarSelector";
import { OptionComponent } from "@calcom/features/calendars/DestinationCalendarSelector";
import type { ConnectedDestinationCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { ConnectedDestinationCalendars } from "@calcom/platform-libraries";
import classNames from "@calcom/ui/classNames";
import { Badge } from "@calcom/ui/components/badge";
import { Select } from "@calcom/ui/components/form";
import classNames from "@calcom/ui/classNames";
import { getPlaceholderContent } from "../lib/getPlaceholderContent";
@@ -1,407 +0,0 @@
import { defaultEvents } from "@calcom/lib/defaultEvents";
import type { SystemField } from "@calcom/lib/event-types/transformers";
import {
transformTeamLocationsApiToInternal,
transformBookingFieldsApiToInternal,
systemBeforeFieldName,
systemBeforeFieldEmail,
systemBeforeFieldLocation,
systemAfterFieldRescheduleReason,
transformRecurrenceApiToInternal,
transformIntervalLimitsApiToInternal,
transformSeatsApiToInternal,
transformEventColorsApiToInternal,
transformConfirmationPolicyApiToInternal,
transformFutureBookingLimitsApiToInternal,
} from "@calcom/lib/event-types/transformers";
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
import type {
EmailDefaultFieldOutput_2024_06_14,
EventTypeOutput_2024_06_14,
InputLocation_2024_06_14,
KnownBookingField_2024_06_14,
NameDefaultFieldOutput_2024_06_14,
TeamEventTypeOutput_2024_06_14,
} from "@calcom/platform-types";
import {
bookerLayoutOptions,
BookerLayouts,
bookerLayouts as bookerLayoutsSchema,
userMetadata as userMetadataSchema,
eventTypeBookingFields,
EventTypeMetaDataSchema,
} from "@calcom/prisma/zod-utils";
import type { BookerPlatformWrapperAtomProps } from "../../booker/types";
export function transformApiEventTypeForAtom(
eventType: Omit<EventTypeOutput_2024_06_14, "ownerId"> & { bannerUrl?: string },
entity: BookerPlatformWrapperAtomProps["entity"] | undefined,
defaultFormValues: BookerPlatformWrapperAtomProps["defaultFormValues"] | undefined,
limitHosts = false
) {
const {
lengthInMinutes,
lengthInMinutesOptions,
locations,
bookingFields,
users,
recurrence,
bookingLimitsCount,
bookingLimitsDuration,
seats,
color,
confirmationPolicy,
customName,
useDestinationCalendarEmail,
bookingWindow,
...rest
} = eventType;
const isDefault = isDefaultEvent(rest.title);
const user = users[0];
const confirmationPolicyTransformed = transformConfirmationPolicyApiToInternal(confirmationPolicy);
const defaultEventBookerLayouts = {
enabledLayouts: [...bookerLayoutOptions],
defaultLayout: BookerLayouts.MONTH_VIEW,
};
const firstUsersMetadata = userMetadataSchema.parse(user.metadata || {});
const bookerLayouts = bookerLayoutsSchema.parse(
firstUsersMetadata?.defaultBookerLayouts || defaultEventBookerLayouts
);
const metadata = EventTypeMetaDataSchema.parse(eventType.metadata);
const usersTransformed = users.map((user) => ({
...user,
metadata: undefined,
bookerUrl: getBookerBaseUrlSync(null),
profile: {
username: user.username || "",
name: user.name,
weekStart: user.weekStart,
image: "",
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
theme: null,
organization: null,
id: user.id,
organizationId: null,
userId: user.id,
upId: `usr-${user.id}`,
},
}));
return {
...rest,
length: lengthInMinutes,
locations: getLocations(locations),
bookingFields: getBookingFields(bookingFields, defaultFormValues),
isDefault,
isDynamic: false,
profile: {
username: user.username,
name: user.name,
weekStart: user.weekStart,
image: "",
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
theme: null,
bookerLayouts,
},
entity: entity
? {
...entity,
orgSlug: entity.orgSlug || null,
teamSlug: entity.teamSlug || null,
fromRedirectOfNonOrgLink: true,
name: entity.name || null,
logoUrl: entity.logoUrl || undefined,
}
: {
fromRedirectOfNonOrgLink: true,
considerUnpublished: false,
orgSlug: null,
teamSlug: null,
name: null,
logoUrl: undefined,
},
hosts: [],
subsetOfHosts: [],
users: !limitHosts ? usersTransformed : undefined,
subsetOfUsers: usersTransformed,
bookingLimits: bookingLimitsCount ? transformIntervalLimitsApiToInternal(bookingLimitsCount) : undefined,
durationLimits: bookingLimitsDuration
? transformIntervalLimitsApiToInternal(bookingLimitsDuration)
: undefined,
metadata: {
...metadata,
requiresConfirmationThreshold:
confirmationPolicyTransformed?.requiresConfirmationThreshold ?? undefined,
multipleDuration: lengthInMinutesOptions,
},
requiresConfirmation: confirmationPolicyTransformed?.requiresConfirmation ?? undefined,
requiresConfirmationWillBlockSlot:
confirmationPolicyTransformed?.requiresConfirmationWillBlockSlot ?? undefined,
eventTypeColor: transformEventColorsApiToInternal(color),
recurringEvent: recurrence ? transformRecurrenceApiToInternal(recurrence) : null,
...transformSeatsApiToInternal(seats),
eventName: customName,
useEventTypeDestinationCalendarEmail: useDestinationCalendarEmail,
...getBookingWindow(bookingWindow),
};
}
export function transformApiTeamEventTypeForAtom(
eventType: TeamEventTypeOutput_2024_06_14,
entity: BookerPlatformWrapperAtomProps["entity"] | undefined,
defaultFormValues: BookerPlatformWrapperAtomProps["defaultFormValues"] | undefined,
limitHosts = false
) {
const {
lengthInMinutes,
lengthInMinutesOptions,
locations,
hosts,
bookingFields,
recurrence,
team,
bookingLimitsCount,
bookingLimitsDuration,
seats,
color,
confirmationPolicy,
customName,
useDestinationCalendarEmail,
bookingWindow,
...rest
} = eventType;
const metadata = EventTypeMetaDataSchema.parse(eventType.metadata);
const isDefault = isDefaultEvent(rest.title);
const confirmationPolicyTransformed = transformConfirmationPolicyApiToInternal(confirmationPolicy);
const defaultEventBookerLayouts = {
enabledLayouts: [...bookerLayoutOptions],
defaultLayout: BookerLayouts.MONTH_VIEW,
};
const firstUsersMetadata = userMetadataSchema.parse({});
const bookerLayouts = bookerLayoutsSchema.parse(
firstUsersMetadata?.defaultBookerLayouts || defaultEventBookerLayouts
);
const hostTransformed = hosts.map((host) => ({
user: {
id: host.userId,
avatarUrl: null,
name: host.name,
username: "",
metadata: {},
darkBrandColor: null,
brandColor: null,
theme: null,
weekStart: "Sunday",
},
}));
const usersTransformed = hosts.map((host) => ({
...host,
metadata: undefined,
bookerUrl: getBookerBaseUrlSync(null),
profile: {
username: "",
name: host.name,
weekStart: "Sunday",
image: "",
brandColor: null,
darkBrandColor: null,
theme: null,
organization: null,
id: host.userId,
organizationId: null,
userId: host.userId,
upId: `usr-${host.userId}`,
},
}));
return {
...rest,
length: lengthInMinutes,
locations: getLocations(locations),
bookingFields: getBookingFields(bookingFields, defaultFormValues),
isDefault,
isDynamic: false,
profile: {
username: team?.slug ?? "team",
name: team?.name,
weekStart: team?.weekStart ?? "Sunday",
image: team?.logoUrl,
brandColor: team?.brandColor ?? null,
darkBrandColor: team?.darkBrandColor ?? null,
theme: team?.theme ?? null,
bookerLayouts,
},
bannerUrl: team?.bannerUrl,
entity: entity
? {
...entity,
orgSlug: entity.orgSlug || null,
teamSlug: entity.teamSlug || null,
fromRedirectOfNonOrgLink: true,
name: entity.name || null,
logoUrl: entity.logoUrl || undefined,
}
: {
fromRedirectOfNonOrgLink: true,
considerUnpublished: false,
orgSlug: null,
teamSlug: team?.slug,
name: team?.name,
logoUrl: team?.logoUrl,
},
hosts: !limitHosts ? hostTransformed : undefined,
subsetOfHosts: hostTransformed,
users: !limitHosts ? usersTransformed : undefined,
subsetOfUsers: usersTransformed,
recurringEvent: recurrence ? transformRecurrenceApiToInternal(recurrence) : null,
bookingLimits: bookingLimitsCount ? transformIntervalLimitsApiToInternal(bookingLimitsCount) : undefined,
durationLimits: bookingLimitsDuration
? transformIntervalLimitsApiToInternal(bookingLimitsDuration)
: undefined,
metadata: {
...metadata,
requiresConfirmationThreshold:
confirmationPolicyTransformed?.requiresConfirmationThreshold ?? undefined,
multipleDuration: lengthInMinutesOptions,
},
requiresConfirmation: confirmationPolicyTransformed?.requiresConfirmation ?? undefined,
requiresConfirmationWillBlockSlot:
confirmationPolicyTransformed?.requiresConfirmationWillBlockSlot ?? undefined,
eventTypeColor: transformEventColorsApiToInternal(color),
...transformSeatsApiToInternal(seats),
eventName: customName,
useEventTypeDestinationCalendarEmail: useDestinationCalendarEmail,
...getBookingWindow(bookingWindow),
};
}
function isDefaultEvent(eventSlug: string) {
const foundInDefaults = defaultEvents.find((obj) => {
return obj.slug === eventSlug;
});
return !!foundInDefaults;
}
function getLocations(locations: EventTypeOutput_2024_06_14["locations"]) {
const transformed = transformTeamLocationsApiToInternal(
locations.filter((location) => isAtomSupportedLocation(location))
);
const withPrivateHidden = transformed.map((location) => {
const { type } = location;
switch (type) {
case "inPerson":
return location?.displayLocationPublicly ? location : { ...location, address: "" };
case "link":
return location?.displayLocationPublicly ? location : { ...location, link: "" };
case "userPhone":
return location?.displayLocationPublicly
? location
: {
...location,
hostPhoneNumber: "",
};
default:
return location;
}
});
return withPrivateHidden;
}
function isAtomSupportedLocation(
location: EventTypeOutput_2024_06_14["locations"][number]
): location is InputLocation_2024_06_14 {
const supportedIntegrations = ["cal-video", "google-meet", "office365-video", "zoom"];
return (
location.type === "address" ||
location.type === "attendeeAddress" ||
location.type === "link" ||
location.type === "phone" ||
location.type === "attendeePhone" ||
location.type === "attendeeDefined" ||
location.type === "organizersDefaultApp" ||
(location.type === "integration" && supportedIntegrations.includes(location.integration))
);
}
function getBookingFields(
bookingFields: EventTypeOutput_2024_06_14["bookingFields"],
defaultFormValues: BookerPlatformWrapperAtomProps["defaultFormValues"] | undefined
) {
const transformedBookingFields = transformBookingFieldsApiToInternal(
bookingFields.filter((field) => isKnownField(field))
);
const hasNameField = transformedBookingFields.some((field) => field.name === "name");
const hasEmailField = transformedBookingFields.some((field) => field.name === "email");
const hasLocationField = transformedBookingFields.some((field) => field.name === "location");
const hasRescheduleReasonField = transformedBookingFields.some(
(field) => field.name === "rescheduleReason"
);
const systemBeforeFields: SystemField[] = [];
if (!hasNameField) {
systemBeforeFields.push(systemBeforeFieldName);
}
if (!hasEmailField) {
systemBeforeFields.push(systemBeforeFieldEmail);
}
if (!hasLocationField) {
systemBeforeFields.push(systemBeforeFieldLocation);
}
const systemAfterFields: SystemField[] = [];
if (!hasRescheduleReasonField) {
systemAfterFields.push(systemAfterFieldRescheduleReason);
}
const fieldsWithSystem = [...systemBeforeFields, ...transformedBookingFields, ...systemAfterFields];
// note(Lauris): in web app booking form values can be passed as url query params, but booker atom does not accept booking field values via url,
// so defaultFormValues act as a way to prefill booking form fields, and if the field in database has disableOnPrefill=true and value passed then its read only.
const defaultFormValuesKeys = defaultFormValues ? Object.keys(defaultFormValues) : [];
if (defaultFormValuesKeys.length) {
for (const field of fieldsWithSystem) {
if (defaultFormValuesKeys.includes(field.name) && field.disableOnPrefill) {
field.editable = "user-readonly";
}
}
}
return eventTypeBookingFields.brand<"HAS_SYSTEM_FIELDS">().parse(fieldsWithSystem);
}
function isKnownField(
field: EventTypeOutput_2024_06_14["bookingFields"][number]
): field is KnownBookingField_2024_06_14 {
return field.type !== "unknown";
}
function getBookingWindow(inputBookingWindow: EventTypeOutput_2024_06_14["bookingWindow"]) {
const res = transformFutureBookingLimitsApiToInternal(inputBookingWindow);
return !!res ? res : {};
}
function isDefaultEditableField(
field: EventTypeOutput_2024_06_14["bookingFields"][number]
): field is NameDefaultFieldOutput_2024_06_14 | EmailDefaultFieldOutput_2024_06_14 {
return field.type === "name" || field.type === "email";
}
@@ -6,8 +6,7 @@ import type { EventTypeSetup, FormValues } from "@calcom/features/eventtypes/lib
import type { User } from "@calcom/prisma/client";
import type { Availability } from "../../availability/AvailabilitySettings";
import { transformApiScheduleForAtom } from "../../availability/atom-api-transformers/transformApiScheduleForAtom";
import { useSchedule } from "../../hooks/schedules/useSchedule";
import { useAtomSchedule } from "../../hooks/schedules/useAtomSchedule";
import { useSchedules } from "../../hooks/schedules/useSchedules";
import { useTeamMembers } from "../../hooks/teams/useTeamMembers";
import { useAtomHostSchedules } from "../hooks/useAtomHostSchedules";
@@ -28,13 +27,10 @@ const EventAvailabilityTabPlatformWrapper = ({
const formMethods = useFormContext<FormValues>();
const scheduleId = formMethods.watch("schedule");
const { isLoading: isSchedulePending, data: scheduleQueryData } = useSchedule(
scheduleId || user?.defaultScheduleId || undefined
);
const { isLoading: isSchedulePending, data: atomSchedule } = useAtomSchedule(scheduleId?.toString());
const { data: schedulesQueryData, isLoading: isSchedulesPending } = useSchedules();
const atomSchedule = transformApiScheduleForAtom(user, scheduleQueryData, schedulesQueryData?.length || 0);
const hostSchedulesQuery = useAtomHostSchedules;
const { data: teamMembers } = useTeamMembers({ teamId });
@@ -1,7 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import type { BookingResponse } from "@calcom/features/bookings/types";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { BookingResponse } from "@calcom/platform-libraries";
import type { ApiResponse, ApiErrorResponse, ApiSuccessResponse } from "@calcom/platform-types";
import type { BookingCreateBody } from "@calcom/prisma/zod/custom/booking";
@@ -1,7 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import type { BookingResponse } from "@calcom/features/bookings/types";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { BookingResponse } from "@calcom/platform-libraries";
import type { ApiResponse, ApiErrorResponse, ApiSuccessResponse } from "@calcom/platform-types";
import type { BookingCreateBody } from "@calcom/prisma/zod/custom/booking";
@@ -1,7 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import type { BookingResponse, RecurringBookingCreateBody } from "@calcom/features/bookings/types";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { BookingResponse, RecurringBookingCreateBody } from "@calcom/platform-libraries";
import type { ApiResponse, ApiErrorResponse, ApiSuccessResponse } from "@calcom/platform-types";
import http from "../../lib/http";
@@ -1,7 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import type { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
import type { getBookingForReschedule } from "@calcom/platform-libraries";
import type { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
import http from "../../lib/http";
@@ -0,0 +1,59 @@
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import { SUCCESS_STATUS, V2_ENDPOINTS } from "@calcom/platform-constants";
import type { PublicEventType } from "@calcom/features/eventtypes/lib/getPublicEvent";
import type { ApiResponse } from "@calcom/platform-types";
import http from "../../../lib/http";
import { useAtomsContext } from "../../../hooks/useAtomsContext";
export const QUERY_KEY = "get-public-event";
export type UsePublicEventReturnType = ReturnType<typeof useAtomGetPublicEvent>;
type Props = {
username: string;
eventSlug: string;
isTeamEvent?: boolean;
teamId?: number;
selectedDuration: number | null;
};
export const useAtomGetPublicEvent = ({ username, eventSlug, isTeamEvent, teamId, selectedDuration }: Props) => {
const { organizationId } = useAtomsContext();
const isDynamic = useMemo(() => {
return getUsernameList(username ?? "").length > 1;
}, [username]);
const pathname = `/atoms/${V2_ENDPOINTS.eventTypes}/${eventSlug}/public`;
const event = useQuery({
queryKey: [QUERY_KEY, username, eventSlug, isTeamEvent, teamId, organizationId],
queryFn: () => {
return http?.get<ApiResponse<PublicEventType>>(pathname, {
params: {
isTeamEvent,
teamId,
orgId: organizationId,
username: getUsernameList(username?? "").join(",")
},
})
.then((res) => {
if (res.data.status === SUCCESS_STATUS) {
if (isDynamic && selectedDuration && res.data.data) {
// note(Lauris): Mandatory - In case of "dynamic" event type default event duration returned by the API is 30,
// but we are re-using the dynamic event type as a team event, so we must set the event length to whatever the event length is.
res.data.data.length = selectedDuration;
}
return res.data.data;
}
throw new Error(res.data.error.message);
});
},
});
return event;
};
@@ -0,0 +1,31 @@
import { useQuery } from "@tanstack/react-query";
import type { FindDetailedScheduleByIdReturnType } from "@calcom/lib/server/repository/schedule";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ApiResponse } from "@calcom/platform-types";
import http from "../../lib/http";
export const QUERY_KEY = "use-atom-schedule";
export const useAtomSchedule = (scheduleId?: string, isManagedEventType?: boolean) => {
const pathname = "atoms/schedules";
const { isLoading, error, data } = useQuery({
queryKey: [QUERY_KEY, scheduleId, isManagedEventType],
queryFn: () => {
return http
.get<ApiResponse<FindDetailedScheduleByIdReturnType | null>>(pathname, {
params: { scheduleId, isManagedEventType },
})
.then((res) => {
if (res.data.status === SUCCESS_STATUS) {
return res.data.data;
}
throw new Error(res.data.error?.message);
});
},
});
return { isLoading, error, data };
};
@@ -0,0 +1,49 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { UpdateScheduleResponse } from "@calcom/lib/schedules/updateSchedule";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ApiResponse, ApiErrorResponse, UpdateAtomScheduleDto } from "@calcom/platform-types";
import http from "../../lib/http";
import { QUERY_KEY as ScheduleQueryKey } from "./useAtomSchedule";
interface IUseAtomUpdateScheduleOptions {
onSuccess?: (res: ApiResponse<UpdateScheduleResponse>) => void;
onError?: (err: ApiErrorResponse) => void;
}
export const useAtomUpdateSchedule = (
{ onSuccess, onError }: IUseAtomUpdateScheduleOptions = {
onSuccess: () => {
return;
},
onError: () => {
return;
},
}
) => {
const queryClient = useQueryClient();
return useMutation<
ApiResponse<UpdateScheduleResponse>,
unknown,
{ scheduleId: number; body: UpdateAtomScheduleDto }
>({
mutationFn: async ({ scheduleId, body }: { scheduleId: number; body: UpdateAtomScheduleDto }) => {
const url = `atoms/schedules/${scheduleId}`;
const response = await http.patch<ApiResponse<UpdateScheduleResponse>>(url, body);
return response.data;
},
onSuccess: (data) => {
if (data.status === SUCCESS_STATUS) {
onSuccess?.(data);
queryClient.invalidateQueries({ queryKey: [ScheduleQueryKey] });
} else {
onError?.(data);
}
},
onError: (err) => {
onError?.(err as ApiErrorResponse);
},
});
};
@@ -5,7 +5,7 @@ import { V2_ENDPOINTS } from "@calcom/platform-constants";
import type { ApiResponse, ApiErrorResponse } from "@calcom/platform-types";
import http from "../../lib/http";
import { QUERY_KEY } from "./useSchedule";
import { QUERY_KEY } from "./useAtomSchedule";
interface IUseDeleteScheduleProps {
onSuccess?: (res: ApiResponse) => void;
@@ -1,12 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { AvailableSlotsType } from "@calcom/platform-libraries";
import type {
GetAvailableSlotsInput_2024_04_15,
ApiResponse,
ApiSuccessResponse,
} from "@calcom/platform-types";
import type { GetAvailableSlotsResponse } from "@calcom/trpc/server/routers/viewer/slots/util";
import http from "../lib/http";
@@ -33,12 +33,12 @@ export const useAvailableSlots = ({
],
queryFn: () => {
return http
.get<ApiResponse<AvailableSlotsType>>("/slots/available", {
.get<ApiResponse<GetAvailableSlotsResponse>>("/slots/available", {
params: rest,
})
.then((res) => {
if (res.data.status === SUCCESS_STATUS) {
return (res.data as ApiSuccessResponse<AvailableSlotsType>).data;
return (res.data as ApiSuccessResponse<GetAvailableSlotsResponse>).data;
}
throw new Error(res.data.error.message);
});
@@ -1,7 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import type { ConnectedDestinationCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ConnectedDestinationCalendars } from "@calcom/platform-libraries";
import type { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
import http from "../lib/http";
@@ -1,58 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import { shallow } from "zustand/shallow";
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
import { SUCCESS_STATUS, V2_ENDPOINTS } from "@calcom/platform-constants";
import type { PublicEventType } from "@calcom/platform-libraries";
import type { ApiResponse } from "@calcom/platform-types";
import http from "../lib/http";
export const QUERY_KEY = "get-public-event";
export type UsePublicEventReturnType = ReturnType<typeof usePublicEvent>;
type Props = {
username: string;
eventSlug: string;
isDynamic?: boolean;
};
export const usePublicEvent = (props: Props) => {
const [username, eventSlug] = useBookerStore((state) => [state.username, state.eventSlug], shallow);
const isTeamEvent = useBookerStore((state) => state.isTeamEvent);
const org = useBookerStore((state) => state.org);
const selectedDuration = useBookerStore((state) => state.selectedDuration);
const requestUsername = username ?? props.username;
const requestEventSlug = eventSlug ?? props.eventSlug;
const event = useQuery({
queryKey: [QUERY_KEY, username ?? props.username, eventSlug ?? props.eventSlug, props.isDynamic],
queryFn: () => {
return http
.get<ApiResponse<PublicEventType>>(
`/${V2_ENDPOINTS.eventTypes}/${requestUsername}/${requestEventSlug}/public`,
{
params: {
isTeamEvent,
org: org ?? null,
},
}
)
.then((res) => {
if (res.data.status === SUCCESS_STATUS) {
if (props.isDynamic && selectedDuration && res.data.data) {
// note(Lauris): Mandatory - In case of "dynamic" event type default event duration returned by the API is 30,
// but we are re-using the dynamic event type as a team event, so we must set the event length to whatever the event length is.
res.data.data.length = selectedDuration;
}
return res.data.data;
}
throw new Error(res.data.error.message);
});
},
});
return event;
};
-1
View File
@@ -12,7 +12,6 @@
"test": "jest"
},
"devDependencies": {
"@calcom/platform-libraries": "0.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",
"@types/jest": "^29.5.12",
"@types/node": "^20.3.1",
@@ -128,7 +128,10 @@ export default function Bookings(props: { calUsername: string; calEmail: string
},
}}
{...(isTeamEvent
? { isTeamEvent: true, teamId: teams?.[0]?.id || 0 }
? {
isTeamEvent: true,
teamId: teams?.[0]?.id || 0,
}
: { username: props.calUsername })}
hostsLimit={3}
allowUpdatingUrlParams={true}
@@ -15,7 +15,7 @@ export default function ConferencingApps(props: { calUsername: string; calEmail:
className={`flex min-h-screen flex-col ${inter.className} main text-default flex min-h-full w-full flex-col items-center overflow-visible`}>
<Navbar username={props.calUsername} />
<div className="my-8">
<ConferencingAppsSettings returnTo={callbackUri} onErrorReturnTo={callbackUri} teamId={20} />
<ConferencingAppsSettings returnTo={callbackUri} onErrorReturnTo={callbackUri} />
</div>
</main>
);
+4
View File
@@ -22,6 +22,8 @@ export { getEnabledAppsFromCredentials };
export { getConnectedApps } from "@calcom/lib/getConnectedApps";
export type { TServiceAccountKeySchema } from "@calcom/prisma/zod-utils";
export type { ConnectedApps } from "@calcom/lib/getConnectedApps";
export type { AppsStatus } from "@calcom/types/Calendar";
@@ -40,3 +42,5 @@ export {
CalendarAppDelegationCredentialClientIdNotAuthorizedError,
CalendarAppDelegationCredentialNotSetupError,
} from "@calcom/lib/CalendarAppError";
export { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/lib/server/getUsersCredentials";
+1 -48
View File
@@ -1,4 +1,4 @@
import { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent";
export { getPublicEvent, type PublicEventType } from "@calcom/features/eventtypes/lib/getPublicEvent";
export { getBulkUserEventTypes, getBulkTeamEventTypes } from "@calcom/lib/event-types/getBulkEventTypes";
@@ -9,51 +9,6 @@ export type { TUpdateInputSchema as TUpdateEventTypeInputSchema } from "@calcom/
export type { EventTypesPublic } from "@calcom/lib/event-types/getEventTypesPublic";
export { getEventTypesPublic } from "@calcom/lib/event-types/getEventTypesPublic";
export { parseEventTypeColor } from "@calcom/lib/isEventTypeColor";
export {
// note(Lauris): Api to internal
transformBookingFieldsApiToInternal,
transformLocationsApiToInternal,
transformTeamLocationsApiToInternal,
transformIntervalLimitsApiToInternal,
transformFutureBookingLimitsApiToInternal,
transformRecurrenceApiToInternal,
transformBookerLayoutsApiToInternal,
transformConfirmationPolicyApiToInternal,
transformEventColorsApiToInternal,
transformSeatsApiToInternal,
// note(Lauris): Internal to api
transformBookingFieldsInternalToApi,
transformLocationsInternalToApi,
transformIntervalLimitsInternalToApi,
transformFutureBookingLimitsInternalToApi,
transformRecurrenceInternalToApi,
transformBookerLayoutsInternalToApi,
transformRequiresConfirmationInternalToApi,
transformEventTypeColorsInternalToApi,
transformSeatsInternalToApi,
// note(Lauris): schemas
InternalLocationsSchema,
InternalLocationSchema,
BookingFieldsSchema,
BookingFieldSchema,
// note(Lauris): constants
systemBeforeFieldName,
systemBeforeFieldEmail,
systemBeforeFieldLocation,
systemAfterFieldRescheduleReason,
systemAfterFieldTitle,
systemAfterFieldNotes,
systemAfterFieldGuests,
apiToInternalintegrationsMapping,
} from "@calcom/lib/event-types/transformers";
export type {
SystemField,
CustomField,
NameSystemField,
EmailSystemField,
InternalLocation,
} from "@calcom/lib/event-types/transformers";
export {
EventTypeMetaDataSchema,
@@ -70,7 +25,5 @@ export type { EventTypesByViewer } from "@calcom/lib/event-types/getEventTypesBy
export type { UpdateEventTypeReturn } from "@calcom/trpc/server/routers/viewer/eventTypes/update.handler";
export { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries/teams";
export type PublicEventType = Awaited<ReturnType<typeof getPublicEvent>>;
export { bulkUpdateEventsToDefaultLocation } from "@calcom/lib/bulkUpdateEventsToDefaultLocation";
export { bulkUpdateTeamEventsToDefaultLocation } from "@calcom/lib/bulkUpdateTeamEventsToDefaultLocation";
export { getPublicEvent };
+3 -8
View File
@@ -22,11 +22,11 @@ import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/crede
import { paymentDataSelect } from "@calcom/prisma/selects/payment";
import { createNewUsersConnectToOrgIfExists } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/utils";
export { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
export { slugify } from "@calcom/lib/slugify";
export { getBookingForReschedule };
export type { EventBusyDate } from "@calcom/types/Calendar";
export { SchedulingType, PeriodType } from "@calcom/prisma/enums";
export { getUsernameList } from "@calcom/lib/defaultEvents";
@@ -42,15 +42,10 @@ export { handleCreatePhoneCall };
export { handleNewRecurringBooking } from "@calcom/features/bookings/lib/handleNewRecurringBooking";
export { getConnectedDestinationCalendarsAndEnsureDefaultsInDb } from "@calcom/lib/getConnectedDestinationCalendars";
export type { ConnectedDestinationCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
export { getBusyCalendarTimes } from "@calcom/lib/CalendarManager";
export type {
BookingCreateBody,
BookingResponse,
RecurringBookingCreateBody,
} from "@calcom/features/bookings/types";
export type { BookingCreateBody, BookingResponse } from "@calcom/features/bookings/types";
export { HttpError } from "@calcom/lib/http-error";
export { MINUTES_TO_BOOK } from "@calcom/lib/constants";
+5 -5
View File
@@ -1,6 +1,6 @@
import {
transformApiScheduleOverrides,
transformApiScheduleAvailability,
} from "@calcom/lib/schedules/transformers/from-api";
export {
ScheduleRepository,
type FindDetailedScheduleByIdReturnType,
} from "@calcom/lib/server/repository/schedule";
export { transformApiScheduleOverrides, transformApiScheduleAvailability };
export { updateSchedule, type UpdateScheduleResponse } from "@calcom/lib/schedules/updateSchedule";
-1
View File
@@ -1,4 +1,3 @@
import { getAvailableSlots } from "@calcom/trpc/server/routers/viewer/slots/util";
export { getAvailableSlots };
export type AvailableSlotsType = Awaited<ReturnType<typeof getAvailableSlots>>;
@@ -1,3 +1,2 @@
export * from "./inputs";
export * from "./outputs";
export * from "./transformed";
@@ -1,2 +1,3 @@
export * from "./schedules-2024-06-11";
export * from "./schedules-2024-04-15";
export * from "./update-atom-schedule-body.input";
@@ -0,0 +1,45 @@
import { Type } from "class-transformer";
import { IsString, IsOptional, IsBoolean, ValidateNested, IsArray, IsDate } from "class-validator";
export class ScheduleTimeRangeDto {
@Type(() => Date)
@IsDate()
start!: Date;
@Type(() => Date)
@IsDate()
end!: Date;
}
export class ScheduleDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => ScheduleTimeRangeDto)
times!: ScheduleTimeRangeDto[];
}
export class UpdateAtomScheduleDto {
@IsString()
@IsOptional()
timeZone?: string;
@IsString()
@IsOptional()
name?: string;
@IsBoolean()
@IsOptional()
isDefault?: boolean;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => ScheduleTimeRangeDto)
schedule?: ScheduleTimeRangeDto[][];
@IsArray()
@ValidateNested({ each: true })
@Type(() => ScheduleTimeRangeDto)
@IsOptional()
dateOverrides?: ScheduleTimeRangeDto[];
}

Some files were not shown because too many files have changed in this diff Show More