refactor: v2 schedules (#15403)

* refactor: platform-types schedule types

* refactor: lib add bidirectional schedule transformers api <-> atom

* refactor: trpc handlers use renamed lib transformers

* refactor: platform-libraries export new bi-directional transformers

* chore: v2 update platform-libraries

* chore: enable also old platform-libraries

* fix: platform-types support previous types

* refactor: versioning date

* chore: version old schedules as schedules_2024_04_15 module

* chore: version old schedules as schedules_2024_04_15 module

* feat: new 2024_06_11 schedules module

* feat: atoms use refactored schedule api

* refactor: remove ?for=atom in schedules hooks

* fix: unit test
This commit is contained in:
Lauris Skraucis
2024-06-11 14:35:53 +00:00
committed by GitHub
parent 357a42b512
commit 68ce9524eb
79 changed files with 2664 additions and 522 deletions
+1
View File
@@ -26,6 +26,7 @@
"dependencies": {
"@calcom/platform-constants": "*",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-libraries-0.0.4": "npm:@calcom/platform-libraries@0.0.4",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
"@calcom/prisma": "*",
+2
View File
@@ -30,7 +30,9 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
type: VersioningType.CUSTOM,
extractor: (request: unknown) => {
const headerVersion = (request as Request)?.headers[CAL_API_VERSION_HEADER] as string | undefined;
console.log("asap header headerVersion", headerVersion);
if (headerVersion && API_VERSIONS.includes(headerVersion as API_VERSIONS_ENUM)) {
console.log("asap return header headerVersion", headerVersion);
return headerVersion;
}
return VERSION_2024_04_15;
@@ -2,11 +2,10 @@ import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { GetBookingOutput } from "@/ee/bookings/outputs/get-booking.output";
import { GetBookingsOutput } from "@/ee/bookings/outputs/get-bookings.output";
import { CreateScheduleInput } from "@/ee/schedules/inputs/create-schedule.input";
import { SchedulesModule } from "@/ee/schedules/schedules.module";
import { SchedulesService } from "@/ee/schedules/services/schedules.service";
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { AvailabilitiesModule } from "@/modules/availabilities/availabilities.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
@@ -29,7 +28,7 @@ describe("Bookings Endpoints", () => {
let userRepositoryFixture: UserRepositoryFixture;
let bookingsRepositoryFixture: BookingsRepositoryFixture;
let schedulesService: SchedulesService;
let schedulesService: SchedulesService_2024_04_15;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
const userEmail = "bookings-controller-e2e@api.com";
@@ -43,7 +42,7 @@ describe("Bookings Endpoints", () => {
const moduleRef = await withAccessTokenAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, AvailabilitiesModule, UsersModule, SchedulesModule],
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
})
)
.overrideGuard(PermissionsGuard)
@@ -55,13 +54,13 @@ describe("Bookings Endpoints", () => {
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
schedulesService = moduleRef.get<SchedulesService>(SchedulesService);
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
user = await userRepositoryFixture.create({
email: userEmail,
});
const userSchedule: CreateScheduleInput = {
const userSchedule: CreateScheduleInput_2024_04_15 = {
name: "working time",
timeZone: "Europe/Rome",
isDefault: true,
@@ -1,8 +1,7 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { SchedulesModule } from "@/ee/schedules/schedules.module";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { AvailabilitiesModule } from "@/modules/availabilities/availabilities.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
@@ -34,14 +33,7 @@ describe("Me Endpoints", () => {
const moduleRef = await withAccessTokenAuth(
userEmail,
Test.createTestingModule({
imports: [
AppModule,
PrismaModule,
AvailabilitiesModule,
UsersModule,
TokensModule,
SchedulesModule,
],
imports: [AppModule, PrismaModule, UsersModule, TokensModule, SchedulesModule_2024_04_15],
})
)
.overrideGuard(PermissionsGuard)
@@ -118,13 +110,13 @@ describe("Me Endpoints", () => {
});
it("should not update user associated with access token given invalid time format", async () => {
const bodyWithIncorrectTimeFormat: UpdateManagedUserInput = { timeFormat: 100 };
const bodyWithIncorrectTimeFormat: UpdateManagedUserInput = { timeFormat: 100 as any };
return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectTimeFormat).expect(400);
});
it("should not update user associated with access token given invalid week start", async () => {
const bodyWithIncorrectWeekStart: UpdateManagedUserInput = { weekStart: "waba luba dub dub" };
const bodyWithIncorrectWeekStart: UpdateManagedUserInput = { weekStart: "waba luba dub dub" as any };
return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectWeekStart).expect(400);
});
+2 -2
View File
@@ -1,6 +1,6 @@
import { GetMeOutput } from "@/ee/me/outputs/get-me.output";
import { UpdateMeOutput } from "@/ee/me/outputs/update-me.output";
import { SchedulesService } from "@/ee/schedules/services/schedules.service";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
@@ -23,7 +23,7 @@ import { userSchemaResponse } from "@calcom/platform-types";
export class MeController {
constructor(
private readonly usersRepository: UsersRepository,
private readonly schedulesRepository: SchedulesService
private readonly schedulesRepository: SchedulesService_2024_04_15
) {}
@Get("/")
+2 -2
View File
@@ -1,11 +1,11 @@
import { MeController } from "@/ee/me/me.controller";
import { SchedulesModule } from "@/ee/schedules/schedules.module";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
@Module({
imports: [UsersModule, SchedulesModule, TokensModule],
imports: [UsersModule, SchedulesModule_2024_04_15, TokensModule],
controllers: [MeController],
})
export class MeModule {}
@@ -4,7 +4,8 @@ import { EventTypesModule } from "@/ee/event-types/event-types.module";
import { GcalModule } from "@/ee/gcal/gcal.module";
import { MeModule } from "@/ee/me/me.module";
import { ProviderModule } from "@/ee/provider/provider.module";
import { SchedulesModule } from "@/ee/schedules/schedules.module";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
import { SlotsModule } from "@/modules/slots/slots.module";
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
import { Module } from "@nestjs/common";
@@ -13,7 +14,8 @@ import { Module } from "@nestjs/common";
imports: [
GcalModule,
ProviderModule,
SchedulesModule,
SchedulesModule_2024_04_15,
SchedulesModule_2024_06_11,
MeModule,
EventTypesModule,
CalendarsModule,
@@ -1,16 +0,0 @@
import { SchedulesController } from "@/ee/schedules/controllers/schedules.controller";
import { SchedulesRepository } from "@/ee/schedules/schedules.repository";
import { SchedulesService } from "@/ee/schedules/services/schedules.service";
import { AvailabilitiesModule } from "@/modules/availabilities/availabilities.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, AvailabilitiesModule, UsersModule, TokensModule],
providers: [SchedulesRepository, SchedulesService],
controllers: [SchedulesController],
exports: [SchedulesService, SchedulesRepository],
})
export class SchedulesModule {}
@@ -1,12 +1,11 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { CreateScheduleInput } from "@/ee/schedules/inputs/create-schedule.input";
import { CreateScheduleOutput } from "@/ee/schedules/outputs/create-schedule.output";
import { GetSchedulesOutput } from "@/ee/schedules/outputs/get-schedules.output";
import { UpdateScheduleOutput } from "@/ee/schedules/outputs/update-schedule.output";
import { SchedulesModule } from "@/ee/schedules/schedules.module";
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
import { CreateScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/create-schedule.output";
import { GetSchedulesOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/get-schedules.output";
import { UpdateScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/update-schedule.output";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { AvailabilitiesModule } from "@/modules/availabilities/availabilities.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
@@ -19,8 +18,8 @@ import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.r
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withAccessTokenAuth } from "test/utils/withAccessTokenAuth";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { UpdateScheduleInput } from "@calcom/platform-types";
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_04_15 } from "@calcom/platform-constants";
import { UpdateScheduleInput_2024_04_15 } from "@calcom/platform-types";
describe("Schedules Endpoints", () => {
describe("User Authentication", () => {
@@ -32,7 +31,7 @@ describe("Schedules Endpoints", () => {
const userEmail = "schedules-controller-e2e@api.com";
let user: User;
let createdSchedule: CreateScheduleOutput["data"];
let createdSchedule: CreateScheduleOutput_2024_04_15["data"];
const defaultAvailabilityDays = [1, 2, 3, 4, 5];
const defaultAvailabilityStartTime = "1970-01-01T09:00:00.000Z";
const defaultAvailabilityEndTime = "1970-01-01T17:00:00.000Z";
@@ -41,14 +40,7 @@ describe("Schedules Endpoints", () => {
const moduleRef = await withAccessTokenAuth(
userEmail,
Test.createTestingModule({
imports: [
AppModule,
PrismaModule,
AvailabilitiesModule,
UsersModule,
TokensModule,
SchedulesModule,
],
imports: [AppModule, PrismaModule, UsersModule, TokensModule, SchedulesModule_2024_04_15],
})
)
.overrideGuard(PermissionsGuard)
@@ -79,7 +71,7 @@ describe("Schedules Endpoints", () => {
const scheduleTimeZone = "Europe/Rome";
const isDefault = true;
const body: CreateScheduleInput = {
const body: CreateScheduleInput_2024_04_15 = {
name: scheduleName,
timeZone: scheduleTimeZone,
isDefault,
@@ -87,10 +79,11 @@ describe("Schedules Endpoints", () => {
return request(app.getHttpServer())
.post("/api/v2/schedules")
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
.send(body)
.expect(201)
.then(async (response) => {
const responseData: CreateScheduleOutput = response.body;
const responseData: CreateScheduleOutput_2024_04_15 = response.body;
expect(responseData.status).toEqual(SUCCESS_STATUS);
expect(responseData.data).toBeDefined();
expect(responseData.data.isDefault).toEqual(isDefault);
@@ -117,7 +110,7 @@ describe("Schedules Endpoints", () => {
.get("/api/v2/schedules/default")
.expect(200)
.then(async (response) => {
const responseData: CreateScheduleOutput = response.body;
const responseData: CreateScheduleOutput_2024_04_15 = response.body;
expect(responseData.status).toEqual(SUCCESS_STATUS);
expect(responseData.data).toBeDefined();
expect(responseData.data.id).toEqual(createdSchedule.id);
@@ -137,7 +130,7 @@ describe("Schedules Endpoints", () => {
.get(`/api/v2/schedules`)
.expect(200)
.then((response) => {
const responseData: GetSchedulesOutput = response.body;
const responseData: GetSchedulesOutput_2024_04_15 = response.body;
expect(responseData.status).toEqual(SUCCESS_STATUS);
expect(responseData.data).toBeDefined();
expect(responseData.data?.[0].id).toEqual(createdSchedule.id);
@@ -155,16 +148,17 @@ describe("Schedules Endpoints", () => {
it("should update schedule name", async () => {
const newScheduleName = "new-schedule-name";
const body: UpdateScheduleInput = {
const body: UpdateScheduleInput_2024_04_15 = {
name: newScheduleName,
};
return request(app.getHttpServer())
.patch(`/api/v2/schedules/${createdSchedule.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
.send(body)
.expect(200)
.then((response: any) => {
const responseData: UpdateScheduleOutput = response.body;
const responseData: UpdateScheduleOutput_2024_04_15 = response.body;
expect(responseData.status).toEqual(SUCCESS_STATUS);
expect(responseData.data).toBeDefined();
expect(responseData.data.schedule.name).toEqual(newScheduleName);
@@ -1,11 +1,11 @@
import { CreateScheduleOutput } from "@/ee/schedules/outputs/create-schedule.output";
import { DeleteScheduleOutput } from "@/ee/schedules/outputs/delete-schedule.output";
import { GetDefaultScheduleOutput } from "@/ee/schedules/outputs/get-default-schedule.output";
import { GetScheduleOutput } from "@/ee/schedules/outputs/get-schedule.output";
import { GetSchedulesOutput } from "@/ee/schedules/outputs/get-schedules.output";
import { UpdateScheduleOutput } from "@/ee/schedules/outputs/update-schedule.output";
import { SchedulesService } from "@/ee/schedules/services/schedules.service";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { CreateScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/create-schedule.output";
import { DeleteScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/delete-schedule.output";
import { GetDefaultScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/get-default-schedule.output";
import { GetScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/get-schedule.output";
import { GetSchedulesOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/get-schedules.output";
import { UpdateScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/update-schedule.output";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
import { VERSION_2024_04_15_VALUE } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
@@ -27,25 +27,25 @@ import { ApiResponse, ApiTags as DocsTags } from "@nestjs/swagger";
import { Throttle } from "@nestjs/throttler";
import { SCHEDULE_READ, SCHEDULE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
import { UpdateScheduleInput } from "@calcom/platform-types";
import { UpdateScheduleInput_2024_04_15 } from "@calcom/platform-types";
import { CreateScheduleInput } from "../inputs/create-schedule.input";
import { CreateScheduleInput_2024_04_15 } from "../inputs/create-schedule.input";
@Controller({
path: "/v2/schedules",
version: API_VERSIONS_VALUES,
version: VERSION_2024_04_15_VALUE,
})
@UseGuards(AccessTokenGuard, PermissionsGuard)
@DocsTags("Schedules")
export class SchedulesController {
constructor(private readonly schedulesService: SchedulesService) {}
export class SchedulesController_2024_04_15 {
constructor(private readonly schedulesService: SchedulesService_2024_04_15) {}
@Post("/")
@Permissions([SCHEDULE_WRITE])
async createSchedule(
@GetUser() user: UserWithProfile,
@Body() bodySchedule: CreateScheduleInput
): Promise<CreateScheduleOutput> {
@Body() bodySchedule: CreateScheduleInput_2024_04_15
): Promise<CreateScheduleOutput_2024_04_15> {
const schedule = await this.schedulesService.createUserSchedule(user.id, bodySchedule);
const scheduleFormatted = await this.schedulesService.formatScheduleForAtom(user, schedule);
@@ -57,8 +57,14 @@ export class SchedulesController {
@Get("/default")
@Permissions([SCHEDULE_READ])
@ApiResponse({ status: 200, description: "Returns the default schedule", type: GetDefaultScheduleOutput })
async getDefaultSchedule(@GetUser() user: UserWithProfile): Promise<GetDefaultScheduleOutput | null> {
@ApiResponse({
status: 200,
description: "Returns the default schedule",
type: GetDefaultScheduleOutput_2024_04_15,
})
async getDefaultSchedule(
@GetUser() user: UserWithProfile
): Promise<GetDefaultScheduleOutput_2024_04_15 | null> {
const schedule = await this.schedulesService.getUserScheduleDefault(user.id);
const scheduleFormatted = schedule
? await this.schedulesService.formatScheduleForAtom(user, schedule)
@@ -76,7 +82,7 @@ export class SchedulesController {
async getSchedule(
@GetUser() user: UserWithProfile,
@Param("scheduleId") scheduleId: number
): Promise<GetScheduleOutput> {
): Promise<GetScheduleOutput_2024_04_15> {
const schedule = await this.schedulesService.getUserSchedule(user.id, scheduleId);
const scheduleFormatted = await this.schedulesService.formatScheduleForAtom(user, schedule);
@@ -88,7 +94,7 @@ export class SchedulesController {
@Get("/")
@Permissions([SCHEDULE_READ])
async getSchedules(@GetUser() user: UserWithProfile): Promise<GetSchedulesOutput> {
async getSchedules(@GetUser() user: UserWithProfile): Promise<GetSchedulesOutput_2024_04_15> {
const schedules = await this.schedulesService.getUserSchedules(user.id);
const schedulesFormatted = await this.schedulesService.formatSchedulesForAtom(user, schedules);
@@ -103,9 +109,9 @@ export class SchedulesController {
@Permissions([SCHEDULE_WRITE])
async updateSchedule(
@GetUser() user: UserWithProfile,
@Body() bodySchedule: UpdateScheduleInput,
@Body() bodySchedule: UpdateScheduleInput_2024_04_15,
@Param("scheduleId") scheduleId: string
): Promise<UpdateScheduleOutput> {
): Promise<UpdateScheduleOutput_2024_04_15> {
const updatedSchedule = await this.schedulesService.updateUserSchedule(
user,
Number(scheduleId),
@@ -124,7 +130,7 @@ export class SchedulesController {
async deleteSchedule(
@GetUser("id") userId: number,
@Param("scheduleId") scheduleId: number
): Promise<DeleteScheduleOutput> {
): Promise<DeleteScheduleOutput_2024_04_15> {
await this.schedulesService.deleteUserSchedule(userId, scheduleId);
return {
@@ -3,7 +3,7 @@ import { ApiProperty } from "@nestjs/swagger";
import { Transform, TransformFnParams } from "class-transformer";
import { IsArray, IsDate, IsNumber } from "class-validator";
export class CreateAvailabilityInput {
export class CreateAvailabilityInput_2024_04_15 {
@IsArray()
@IsNumber({}, { each: true })
@ApiProperty({ example: [1, 2] })
@@ -1,8 +1,8 @@
import { CreateAvailabilityInput } from "@/modules/availabilities/inputs/create-availability.input";
import { CreateAvailabilityInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-availability.input";
import { Type } from "class-transformer";
import { IsArray, IsBoolean, IsTimeZone, IsOptional, IsString, ValidateNested } from "class-validator";
export class CreateScheduleInput {
export class CreateScheduleInput_2024_04_15 {
@IsString()
name!: string;
@@ -11,9 +11,9 @@ export class CreateScheduleInput {
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateAvailabilityInput)
@Type(() => CreateAvailabilityInput_2024_04_15)
@IsOptional()
availabilities?: CreateAvailabilityInput[];
availabilities?: CreateAvailabilityInput_2024_04_15[];
@IsBoolean()
isDefault!: boolean;
@@ -1,11 +1,11 @@
import { ScheduleOutput } from "@/ee/schedules/outputs/schedule.output";
import { ScheduleOutput } from "@/ee/schedules/schedules_2024_04_15/outputs/schedule.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class CreateScheduleOutput {
export class CreateScheduleOutput_2024_04_15 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@@ -3,7 +3,7 @@ import { IsEnum } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class DeleteScheduleOutput {
export class DeleteScheduleOutput_2024_04_15 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@@ -1,11 +1,11 @@
import { ScheduleOutput } from "@/ee/schedules/outputs/schedule.output";
import { ScheduleOutput } from "@/ee/schedules/schedules_2024_04_15/outputs/schedule.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class GetDefaultScheduleOutput {
export class GetDefaultScheduleOutput_2024_04_15 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@@ -1,11 +1,11 @@
import { ScheduleOutput } from "@/ee/schedules/outputs/schedule.output";
import { ScheduleOutput } from "@/ee/schedules/schedules_2024_04_15/outputs/schedule.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class GetScheduleOutput {
export class GetScheduleOutput_2024_04_15 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@@ -1,11 +1,11 @@
import { ScheduleOutput } from "@/ee/schedules/outputs/schedule.output";
import { ScheduleOutput } from "@/ee/schedules/schedules_2024_04_15/outputs/schedule.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsArray, IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class GetSchedulesOutput {
export class GetSchedulesOutput_2024_04_15 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@@ -1,7 +1,7 @@
import { Type } from "class-transformer";
import { IsBoolean, IsInt, IsOptional, IsString, ValidateNested, IsArray } from "class-validator";
class EventTypeModel {
class EventTypeModel_2024_04_15 {
@IsInt()
id!: number;
@@ -10,7 +10,7 @@ class EventTypeModel {
eventName?: string | null;
}
class AvailabilityModel {
class AvailabilityModel_2024_04_15 {
@IsInt()
id!: number;
@@ -46,7 +46,7 @@ class AvailabilityModel {
date?: Date | null;
}
class ScheduleModel {
class ScheduleModel_2024_04_15 {
@IsInt()
id!: number;
@@ -62,21 +62,21 @@ class ScheduleModel {
@IsOptional()
@ValidateNested({ each: true })
@Type(() => EventTypeModel)
@Type(() => EventTypeModel_2024_04_15)
@IsArray()
eventType?: EventTypeModel[];
eventType?: EventTypeModel_2024_04_15[];
@IsOptional()
@ValidateNested({ each: true })
@Type(() => AvailabilityModel)
@Type(() => AvailabilityModel_2024_04_15)
@IsArray()
availability?: AvailabilityModel[];
availability?: AvailabilityModel_2024_04_15[];
}
export class UpdatedScheduleOutput {
export class UpdatedScheduleOutput_2024_04_15 {
@ValidateNested()
@Type(() => ScheduleModel)
schedule!: ScheduleModel;
@Type(() => ScheduleModel_2024_04_15)
schedule!: ScheduleModel_2024_04_15;
@IsBoolean()
isDefault!: boolean;
@@ -0,0 +1,20 @@
import { UpdatedScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/schedule-updated.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class UpdateScheduleOutput_2024_04_15 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: UpdatedScheduleOutput_2024_04_15,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => UpdatedScheduleOutput_2024_04_15)
data!: UpdatedScheduleOutput_2024_04_15;
}
@@ -0,0 +1,15 @@
import { SchedulesController_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/controllers/schedules.controller";
import { SchedulesRepository_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.repository";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, UsersModule, TokensModule],
providers: [SchedulesRepository_2024_04_15, SchedulesService_2024_04_15],
controllers: [SchedulesController_2024_04_15],
exports: [SchedulesService_2024_04_15, SchedulesRepository_2024_04_15],
})
export class SchedulesModule_2024_04_15 {}
@@ -1,18 +1,18 @@
import { CreateScheduleInput } from "@/ee/schedules/inputs/create-schedule.input";
import { CreateAvailabilityInput } from "@/modules/availabilities/inputs/create-availability.input";
import { CreateAvailabilityInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-availability.input";
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client";
@Injectable()
export class SchedulesRepository {
export class SchedulesRepository_2024_04_15 {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async createScheduleWithAvailabilities(
userId: number,
schedule: CreateScheduleInput,
availabilities: CreateAvailabilityInput[]
schedule: CreateScheduleInput_2024_04_15,
availabilities: CreateAvailabilityInput_2024_04_15[]
) {
const createScheduleData: Prisma.ScheduleCreateInput = {
user: {
@@ -1,7 +1,7 @@
import { CreateScheduleInput } from "@/ee/schedules/inputs/create-schedule.input";
import { ScheduleOutput } from "@/ee/schedules/outputs/schedule.output";
import { SchedulesRepository } from "@/ee/schedules/schedules.repository";
import { AvailabilitiesService } from "@/modules/availabilities/availabilities.service";
import { CreateAvailabilityInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-availability.input";
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
import { ScheduleOutput } from "@/ee/schedules/schedules_2024_04_15/outputs/schedule.output";
import { SchedulesRepository_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.repository";
import { UserWithProfile, UsersRepository } from "@/modules/users/users.repository";
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
import { Schedule } from "@prisma/client";
@@ -14,13 +14,12 @@ import {
transformAvailabilityForClient,
transformDateOverridesForClient,
} from "@calcom/platform-libraries-0.0.2";
import { UpdateScheduleInput } from "@calcom/platform-types";
import { UpdateScheduleInput_2024_04_15 } from "@calcom/platform-types";
@Injectable()
export class SchedulesService {
export class SchedulesService_2024_04_15 {
constructor(
private readonly schedulesRepository: SchedulesRepository,
private readonly availabilitiesService: AvailabilitiesService,
private readonly schedulesRepository: SchedulesRepository_2024_04_15,
private readonly usersRepository: UsersRepository
) {}
@@ -34,10 +33,10 @@ export class SchedulesService {
return this.createUserSchedule(userId, schedule);
}
async createUserSchedule(userId: number, schedule: CreateScheduleInput) {
async createUserSchedule(userId: number, schedule: CreateScheduleInput_2024_04_15) {
const availabilities = schedule.availabilities?.length
? schedule.availabilities
: [this.availabilitiesService.getDefaultAvailabilityInput()];
: [this.getDefaultAvailabilityInput()];
const createdSchedule = await this.schedulesRepository.createScheduleWithAvailabilities(
userId,
@@ -76,7 +75,11 @@ export class SchedulesService {
return this.schedulesRepository.getSchedulesByUserId(userId);
}
async updateUserSchedule(user: UserWithProfile, scheduleId: number, bodySchedule: UpdateScheduleInput) {
async updateUserSchedule(
user: UserWithProfile,
scheduleId: number,
bodySchedule: UpdateScheduleInput_2024_04_15
) {
const existingSchedule = await this.schedulesRepository.getScheduleById(scheduleId);
if (!existingSchedule) {
@@ -158,4 +161,15 @@ export class SchedulesService {
throw new ForbiddenException(`User with ID=${userId} does not own schedule with ID=${schedule.id}`);
}
}
getDefaultAvailabilityInput(): CreateAvailabilityInput_2024_04_15 {
const startTime = new Date(new Date().setUTCHours(9, 0, 0, 0));
const endTime = new Date(new Date().setUTCHours(17, 0, 0, 0));
return {
days: [1, 2, 3, 4, 5],
startTime,
endTime,
};
}
}
@@ -0,0 +1,238 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { User } from "@prisma/client";
import * as request from "supertest";
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withAccessTokenAuth } from "test/utils/withAccessTokenAuth";
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_11 } from "@calcom/platform-constants";
import {
CreateScheduleInput_2024_06_11,
CreateScheduleOutput_2024_06_11,
GetScheduleOutput_2024_06_11,
GetSchedulesOutput_2024_06_11,
ScheduleOutput_2024_06_11,
UpdateScheduleOutput_2024_06_11,
} from "@calcom/platform-types";
import { UpdateScheduleInput_2024_06_11 } from "@calcom/platform-types";
describe("Schedules Endpoints", () => {
describe("User Authentication", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let scheduleRepositoryFixture: SchedulesRepositoryFixture;
const userEmail = "schedules-controller-e2e@api.com";
let user: User;
const createScheduleInput: CreateScheduleInput_2024_06_11 = {
name: "work",
timeZone: "Europe/Rome",
isDefault: true,
};
const defaultAvailability: CreateScheduleInput_2024_06_11["availability"] = [
{
days: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
startTime: "09:00",
endTime: "17:00",
},
];
let createdSchedule: CreateScheduleOutput_2024_06_11["data"];
beforeAll(async () => {
const moduleRef = await withAccessTokenAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, TokensModule, SchedulesModule_2024_06_11],
})
)
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
scheduleRepositoryFixture = new SchedulesRepositoryFixture(moduleRef);
user = await userRepositoryFixture.create({
email: userEmail,
});
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
it("should be defined", () => {
expect(userRepositoryFixture).toBeDefined();
expect(user).toBeDefined();
});
it("should create a default schedule", async () => {
return request(app.getHttpServer())
.post("/api/v2/schedules")
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_11)
.send(createScheduleInput)
.expect(201)
.then(async (response) => {
const responseBody: CreateScheduleOutput_2024_06_11 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
createdSchedule = response.body.data;
const expectedSchedule = {
...createScheduleInput,
availability: defaultAvailability,
overrides: [],
};
outputScheduleMatchesExpected(createdSchedule, expectedSchedule, 1);
const scheduleOwner = createdSchedule.ownerId
? await userRepositoryFixture.get(createdSchedule.ownerId)
: null;
expect(scheduleOwner?.defaultScheduleId).toEqual(createdSchedule.id);
});
});
function outputScheduleMatchesExpected(
outputSchedule: ScheduleOutput_2024_06_11 | null,
expected: CreateScheduleInput_2024_06_11 & {
availability: CreateScheduleInput_2024_06_11["availability"];
} & {
overrides: CreateScheduleInput_2024_06_11["overrides"];
},
expectedAvailabilityLength: number
) {
expect(outputSchedule).toBeTruthy();
expect(outputSchedule?.name).toEqual(expected.name);
expect(outputSchedule?.timeZone).toEqual(expected.timeZone);
expect(outputSchedule?.isDefault).toEqual(expected.isDefault);
expect(outputSchedule?.availability.length).toEqual(expectedAvailabilityLength);
const outputScheduleAvailability = outputSchedule?.availability[0];
expect(outputScheduleAvailability).toBeDefined();
expect(outputScheduleAvailability?.days).toEqual(expected.availability?.[0].days);
expect(outputScheduleAvailability?.startTime).toEqual(expected.availability?.[0].startTime);
expect(outputScheduleAvailability?.endTime).toEqual(expected.availability?.[0].endTime);
expect(JSON.stringify(outputSchedule?.overrides)).toEqual(JSON.stringify(expected.overrides));
}
it("should get default schedule", async () => {
return request(app.getHttpServer())
.get("/api/v2/schedules/default")
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_11)
.expect(200)
.then(async (response) => {
const responseBody: GetScheduleOutput_2024_06_11 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const outputSchedule = responseBody.data;
const expectedSchedule = {
...createScheduleInput,
availability: defaultAvailability,
overrides: [],
};
outputScheduleMatchesExpected(outputSchedule, expectedSchedule, 1);
});
});
it("should get schedules", async () => {
return request(app.getHttpServer())
.get(`/api/v2/schedules`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_11)
.expect(200)
.then((response) => {
const responseBody: GetSchedulesOutput_2024_06_11 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const outputSchedule = responseBody.data[0];
const expectedSchedule = {
...createScheduleInput,
availability: defaultAvailability,
overrides: [],
};
outputScheduleMatchesExpected(outputSchedule, expectedSchedule, 1);
});
});
it("should update schedule name", async () => {
const newScheduleName = "updated-schedule-name";
const body: UpdateScheduleInput_2024_06_11 = {
name: newScheduleName,
};
return request(app.getHttpServer())
.patch(`/api/v2/schedules/${createdSchedule.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_11)
.send(body)
.expect(200)
.then((response: any) => {
const responseData: UpdateScheduleOutput_2024_06_11 = response.body;
expect(responseData.status).toEqual(SUCCESS_STATUS);
const responseSchedule = responseData.data;
const expectedSchedule = { ...createdSchedule, name: newScheduleName };
outputScheduleMatchesExpected(responseSchedule, expectedSchedule, 1);
createdSchedule = responseSchedule;
});
});
it("should add overrides", async () => {
const overrides = [
{
date: "2026-05-05",
startTime: "10:00",
endTime: "12:00",
},
];
const body: UpdateScheduleInput_2024_06_11 = {
overrides,
};
return request(app.getHttpServer())
.patch(`/api/v2/schedules/${createdSchedule.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_11)
.send(body)
.expect(200)
.then((response: any) => {
const responseData: UpdateScheduleOutput_2024_06_11 = response.body;
expect(responseData.status).toEqual(SUCCESS_STATUS);
const responseSchedule = responseData.data;
const expectedSchedule = { ...createdSchedule, overrides };
outputScheduleMatchesExpected(responseSchedule, expectedSchedule, 1);
createdSchedule = responseSchedule;
});
});
it("should delete schedule", async () => {
return request(app.getHttpServer()).delete(`/api/v2/schedules/${createdSchedule.id}`).expect(200);
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(user.email);
try {
await scheduleRepositoryFixture.deleteById(createdSchedule.id);
} catch (e) {}
await app.close();
});
});
});
@@ -0,0 +1,132 @@
import { SchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/schedules.service";
import { VERSION_2024_06_11_VALUE } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Post,
Patch,
UseGuards,
} from "@nestjs/common";
import { ApiResponse, ApiTags as DocsTags } from "@nestjs/swagger";
import { Throttle } from "@nestjs/throttler";
import { SCHEDULE_READ, SCHEDULE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
import {
CreateScheduleOutput_2024_06_11,
CreateScheduleInput_2024_06_11,
UpdateScheduleInput_2024_06_11,
GetScheduleOutput_2024_06_11,
UpdateScheduleOutput_2024_06_11,
GetDefaultScheduleOutput_2024_06_11,
DeleteScheduleOutput_2024_06_11,
GetSchedulesOutput_2024_06_11,
} from "@calcom/platform-types";
@Controller({
path: "/v2/schedules",
version: VERSION_2024_06_11_VALUE,
})
@UseGuards(AccessTokenGuard, PermissionsGuard)
@DocsTags("Schedules")
export class SchedulesController_2024_06_11 {
constructor(private readonly schedulesService: SchedulesService_2024_06_11) {}
@Post("/")
@Permissions([SCHEDULE_WRITE])
async createSchedule(
@GetUser() user: UserWithProfile,
@Body() bodySchedule: CreateScheduleInput_2024_06_11
): Promise<CreateScheduleOutput_2024_06_11> {
const schedule = await this.schedulesService.createUserSchedule(user.id, bodySchedule);
return {
status: SUCCESS_STATUS,
data: schedule,
};
}
@Get("/default")
@Permissions([SCHEDULE_READ])
@ApiResponse({
status: 200,
description: "Returns the default schedule",
type: GetDefaultScheduleOutput_2024_06_11,
})
async getDefaultSchedule(@GetUser() user: UserWithProfile): Promise<GetScheduleOutput_2024_06_11> {
const schedule = await this.schedulesService.getUserScheduleDefault(user.id);
return {
status: SUCCESS_STATUS,
data: schedule,
};
}
@Get("/:scheduleId")
@Permissions([SCHEDULE_READ])
@Throttle({ default: { limit: 10, ttl: 60000 } }) // allow 10 requests per minute (for :scheduleId)
async getSchedule(
@GetUser() user: UserWithProfile,
@Param("scheduleId") scheduleId: number
): Promise<GetScheduleOutput_2024_06_11> {
const schedule = await this.schedulesService.getUserSchedule(user.id, scheduleId);
return {
status: SUCCESS_STATUS,
data: schedule,
};
}
@Get("/")
@Permissions([SCHEDULE_READ])
async getSchedules(@GetUser() user: UserWithProfile): Promise<GetSchedulesOutput_2024_06_11> {
const schedules = await this.schedulesService.getUserSchedules(user.id);
return {
status: SUCCESS_STATUS,
data: schedules,
};
}
@Patch("/:scheduleId")
@Permissions([SCHEDULE_WRITE])
async updateSchedule(
@GetUser() user: UserWithProfile,
@Body() bodySchedule: UpdateScheduleInput_2024_06_11,
@Param("scheduleId") scheduleId: string
): Promise<UpdateScheduleOutput_2024_06_11> {
const updatedSchedule = await this.schedulesService.updateUserSchedule(
user.id,
Number(scheduleId),
bodySchedule
);
return {
status: SUCCESS_STATUS,
data: updatedSchedule,
};
}
@Delete("/:scheduleId")
@HttpCode(HttpStatus.OK)
@Permissions([SCHEDULE_WRITE])
async deleteSchedule(
@GetUser("id") userId: number,
@Param("scheduleId") scheduleId: number
): Promise<DeleteScheduleOutput_2024_06_11> {
await this.schedulesService.deleteUserSchedule(userId, scheduleId);
return {
status: SUCCESS_STATUS,
};
}
}
@@ -0,0 +1,22 @@
import { SchedulesController_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/controllers/schedules.controller";
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
import { InputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/input-schedules.service";
import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/output-schedules.service";
import { SchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/schedules.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, UsersModule, TokensModule],
providers: [
SchedulesRepository_2024_06_11,
SchedulesService_2024_06_11,
InputSchedulesService_2024_06_11,
OutputSchedulesService_2024_06_11,
],
controllers: [SchedulesController_2024_06_11],
exports: [SchedulesService_2024_06_11, SchedulesRepository_2024_06_11],
})
export class SchedulesModule_2024_06_11 {}
@@ -0,0 +1,208 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import type { CreateScheduleInput_2024_06_11 } from "@calcom/platform-types";
type InputScheduleAvailabilityTransformed = {
days: number[];
startTime: Date;
endTime: Date;
};
type InputScheduleOverrideTransformed = {
date: Date;
startTime: Date;
endTime: Date;
};
type InputScheduleTransformed = Omit<CreateScheduleInput_2024_06_11, "availability" | "overrides"> & {
availability: InputScheduleAvailabilityTransformed[];
overrides: InputScheduleOverrideTransformed[];
};
@Injectable()
export class SchedulesRepository_2024_06_11 {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async createSchedule(userId: number, schedule: Omit<InputScheduleTransformed, "isDefault">) {
const { availability, overrides } = schedule;
const createScheduleData: Prisma.ScheduleCreateInput = {
user: {
connect: {
id: userId,
},
},
name: schedule.name,
timeZone: schedule.timeZone,
};
const availabilitiesAndOverrides: Prisma.AvailabilityCreateManyInput[] = [];
if (availability && availability.length > 0) {
availability.forEach((availability) => {
availabilitiesAndOverrides.push({
days: availability.days,
startTime: availability.startTime,
endTime: availability.endTime,
userId,
});
});
}
if (overrides && overrides.length > 0) {
overrides.forEach((override) => {
availabilitiesAndOverrides.push({
date: override.date,
startTime: override.startTime,
endTime: override.endTime,
userId,
});
});
}
if (availabilitiesAndOverrides.length > 0) {
createScheduleData.availability = {
createMany: {
data: availabilitiesAndOverrides,
},
};
}
const createdSchedule = await this.dbWrite.prisma.schedule.create({
data: {
...createScheduleData,
},
include: {
availability: true,
},
});
return createdSchedule;
}
async getScheduleById(scheduleId: number) {
const schedule = await this.dbRead.prisma.schedule.findUnique({
where: {
id: scheduleId,
},
include: {
availability: true,
},
});
return schedule;
}
async updateSchedule(
userId: number,
scheduleId: number,
schedule: Partial<Omit<InputScheduleTransformed, "isDefault">>
) {
const { availability, overrides } = schedule;
const updateScheduleData: Prisma.ScheduleUpdateInput = {
name: schedule.name,
timeZone: schedule.timeZone,
};
const availabilitiesAndOverrides: Prisma.AvailabilityCreateManyInput[] = [];
const deleteConditions = [];
if (availability) {
// note(Lauris): availabilities and overrides are stored in the same "Availability" table,
// but availabilities have "date" field as null, while overrides have it as not null, so delete
// condition below results in deleting only rows from Availability table that are availabilities.
deleteConditions.push({
scheduleId: { equals: scheduleId },
date: null,
});
}
if (overrides) {
// note(Lauris): availabilities and overrides are stored in the same "Availability" table,
// but overrides have "date" field as not-null, while availabilities have it as null, so delete
// condition below results in deleting only rows from Availability table that are overrides.
deleteConditions.push({
scheduleId: { equals: scheduleId },
NOT: { date: null },
});
}
if (availability && availability.length > 0) {
availability.forEach((availability) => {
availabilitiesAndOverrides.push({
days: availability.days,
startTime: availability.startTime,
endTime: availability.endTime,
userId,
});
});
}
if (overrides && overrides.length > 0) {
overrides.forEach((override) => {
availabilitiesAndOverrides.push({
date: override.date,
startTime: override.startTime,
endTime: override.endTime,
userId,
});
});
}
if (availabilitiesAndOverrides.length > 0) {
updateScheduleData.availability = {
deleteMany: deleteConditions,
createMany: {
data: availabilitiesAndOverrides,
},
};
}
const updatedSchedule = await this.dbWrite.prisma.schedule.update({
where: {
id: scheduleId,
},
data: {
...updateScheduleData,
},
include: {
availability: true,
},
});
return updatedSchedule;
}
async getSchedulesByUserId(userId: number) {
const schedules = await this.dbRead.prisma.schedule.findMany({
where: {
userId,
},
include: {
availability: true,
},
});
return schedules;
}
async deleteScheduleById(scheduleId: number) {
return this.dbWrite.prisma.schedule.delete({
where: {
id: scheduleId,
},
});
}
async getUserSchedulesCount(userId: number) {
return this.dbRead.prisma.schedule.count({
where: {
userId,
},
});
}
}
@@ -0,0 +1,43 @@
import { Injectable } from "@nestjs/common";
import {
transformApiScheduleOverrides,
transformApiScheduleAvailability,
} from "@calcom/platform-libraries-0.0.4";
import { CreateScheduleInput_2024_06_11, ScheduleAvailabilityInput_2024_06_11 } from "@calcom/platform-types";
import { ScheduleOverrideInput_2024_06_11 } from "@calcom/platform-types";
@Injectable()
export class InputSchedulesService_2024_06_11 {
transformInputCreateSchedule(inputSchedule: CreateScheduleInput_2024_06_11) {
const defaultAvailability: ScheduleAvailabilityInput_2024_06_11[] = [
{
days: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
startTime: "09:00",
endTime: "17:00",
},
];
const defaultOverrides: ScheduleOverrideInput_2024_06_11[] = [];
const availability = this.transformInputScheduleAvailability(
inputSchedule.availability || defaultAvailability
);
const overrides = this.transformInputOverrides(inputSchedule.overrides || defaultOverrides);
const internalCreateSchedule = {
...inputSchedule,
availability,
overrides,
};
return internalCreateSchedule;
}
transformInputScheduleAvailability(inputAvailability: ScheduleAvailabilityInput_2024_06_11[]) {
return transformApiScheduleAvailability(inputAvailability);
}
transformInputOverrides(inputOverrides: ScheduleOverrideInput_2024_06_11[]) {
return transformApiScheduleOverrides(inputOverrides);
}
}
@@ -0,0 +1,78 @@
import { UsersRepository } from "@/modules/users/users.repository";
import { Injectable } from "@nestjs/common";
import type { Availability, Schedule } from "@prisma/client";
import { WeekDay } from "@calcom/platform-types";
@Injectable()
export class OutputSchedulesService_2024_06_11 {
constructor(private readonly usersRepository: UsersRepository) {}
async getResponseSchedule(databaseSchedule: Schedule & { availability: Availability[] }) {
if (!databaseSchedule.timeZone) {
throw new Error("Failed to create schedule because its timezone is not set.");
}
const ownerDefaultScheduleId = await this.usersRepository.getUserScheduleDefaultId(
databaseSchedule.userId
);
const createdScheduleAvailabilities = databaseSchedule.availability.filter(
(availability) => !!availability.days.length
);
const createdScheduleOverrides = databaseSchedule.availability.filter((override) => !!override.date);
return {
id: databaseSchedule.id,
ownerId: databaseSchedule.userId,
name: databaseSchedule.name,
timeZone: databaseSchedule.timeZone,
availability: createdScheduleAvailabilities.map((availability) => ({
days: availability.days.map(transformNumberToDay),
startTime: this.padHoursMinutesWithZeros(
availability.startTime.getUTCHours() + ":" + availability.startTime.getUTCMinutes()
),
endTime: this.padHoursMinutesWithZeros(
availability.endTime.getUTCHours() + ":" + availability.endTime.getUTCMinutes()
),
})),
isDefault: databaseSchedule.id === ownerDefaultScheduleId,
overrides: createdScheduleOverrides.map((override) => ({
date:
override.date?.getUTCFullYear() +
"-" +
(override.date ? override.date.getUTCMonth() + 1 : "").toString().padStart(2, "0") +
"-" +
override.date?.getUTCDate().toString().padStart(2, "0"),
startTime: this.padHoursMinutesWithZeros(
override.startTime.getUTCHours() + ":" + override.startTime.getUTCMinutes()
),
endTime: this.padHoursMinutesWithZeros(
override.endTime.getUTCHours() + ":" + override.endTime.getUTCMinutes()
),
})),
};
}
padHoursMinutesWithZeros(hhMM: string) {
const [hours, minutes] = hhMM.split(":");
const formattedHours = hours.padStart(2, "0");
const formattedMinutes = minutes.padStart(2, "0");
return `${formattedHours}:${formattedMinutes}`;
}
}
function transformNumberToDay(day: number): WeekDay {
const weekMap: { [key: number]: WeekDay } = {
0: "Sunday",
1: "Monday",
2: "Tuesday",
3: "Wednesday",
4: "Thursday",
5: "Friday",
6: "Saturday",
};
return weekMap[day];
}
@@ -0,0 +1,123 @@
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
import { InputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/input-schedules.service";
import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/output-schedules.service";
import { UsersRepository } from "@/modules/users/users.repository";
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
import { Schedule } from "@prisma/client";
import { CreateScheduleInput_2024_06_11, ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
import { UpdateScheduleInput_2024_06_11 } from "@calcom/platform-types";
@Injectable()
export class SchedulesService_2024_06_11 {
constructor(
private readonly schedulesRepository: SchedulesRepository_2024_06_11,
private readonly inputSchedulesService: InputSchedulesService_2024_06_11,
private readonly outputSchedulesService: OutputSchedulesService_2024_06_11,
private readonly usersRepository: UsersRepository
) {}
async createUserDefaultSchedule(userId: number, timeZone: string) {
const defaultSchedule = {
isDefault: true,
name: "Default schedule",
timeZone,
};
return this.createUserSchedule(userId, defaultSchedule);
}
async createUserSchedule(
userId: number,
scheduleInput: CreateScheduleInput_2024_06_11
): Promise<ScheduleOutput_2024_06_11> {
const schedule = this.inputSchedulesService.transformInputCreateSchedule(scheduleInput);
const createdSchedule = await this.schedulesRepository.createSchedule(userId, schedule);
if (schedule.isDefault) {
await this.usersRepository.setDefaultSchedule(userId, createdSchedule.id);
}
return this.outputSchedulesService.getResponseSchedule(createdSchedule);
}
async getUserScheduleDefault(userId: number) {
const user = await this.usersRepository.findById(userId);
if (!user?.defaultScheduleId) return null;
const defaultSchedule = await this.schedulesRepository.getScheduleById(user.defaultScheduleId);
if (!defaultSchedule) return null;
return this.outputSchedulesService.getResponseSchedule(defaultSchedule);
}
async getUserSchedule(userId: number, scheduleId: number) {
const existingSchedule = await this.schedulesRepository.getScheduleById(scheduleId);
if (!existingSchedule) {
throw new NotFoundException(`Schedule with ID=${scheduleId} does not exist.`);
}
this.checkUserOwnsSchedule(userId, existingSchedule);
return this.outputSchedulesService.getResponseSchedule(existingSchedule);
}
async getUserSchedules(userId: number) {
const schedules = await this.schedulesRepository.getSchedulesByUserId(userId);
return Promise.all(
schedules.map(async (schedule) => {
return this.outputSchedulesService.getResponseSchedule(schedule);
})
);
}
async updateUserSchedule(userId: number, scheduleId: number, bodySchedule: UpdateScheduleInput_2024_06_11) {
const existingSchedule = await this.schedulesRepository.getScheduleById(scheduleId);
if (!existingSchedule) {
throw new NotFoundException(`Schedule with ID=${scheduleId} does not exist.`);
}
this.checkUserOwnsSchedule(userId, existingSchedule);
const availability = bodySchedule.availability
? this.inputSchedulesService.transformInputScheduleAvailability(bodySchedule.availability)
: undefined;
const overrides = bodySchedule.overrides
? this.inputSchedulesService.transformInputOverrides(bodySchedule.overrides)
: undefined;
if (bodySchedule.isDefault) {
await this.usersRepository.setDefaultSchedule(userId, scheduleId);
}
const updatedSchedule = await this.schedulesRepository.updateSchedule(userId, scheduleId, {
...bodySchedule,
availability,
overrides,
});
return this.outputSchedulesService.getResponseSchedule(updatedSchedule);
}
async deleteUserSchedule(userId: number, scheduleId: number) {
const existingSchedule = await this.schedulesRepository.getScheduleById(scheduleId);
if (!existingSchedule) {
throw new BadRequestException(`Schedule with ID=${scheduleId} does not exist.`);
}
this.checkUserOwnsSchedule(userId, existingSchedule);
return this.schedulesRepository.deleteScheduleById(scheduleId);
}
checkUserOwnsSchedule(userId: number, schedule: Pick<Schedule, "id" | "userId">) {
if (userId !== schedule.userId) {
throw new ForbiddenException(`User with ID=${userId} does not own schedule with ID=${schedule.id}`);
}
}
}
+7 -1
View File
@@ -1,7 +1,13 @@
import { VersionValue } from "@nestjs/common/interfaces";
import { API_VERSIONS, VERSION_2024_04_15, VERSION_2024_05_21 } from "@calcom/platform-constants";
import {
API_VERSIONS,
VERSION_2024_04_15,
VERSION_2024_05_21,
VERSION_2024_06_11,
} from "@calcom/platform-constants";
export const API_VERSIONS_VALUES: VersionValue = API_VERSIONS as unknown as VersionValue;
export const VERSION_2024_06_11_VALUE: VersionValue = VERSION_2024_06_11 as unknown as VersionValue;
export const VERSION_2024_05_21_VALUE: VersionValue = VERSION_2024_05_21 as unknown as VersionValue;
export const VERSION_2024_04_15_VALUE: VersionValue = VERSION_2024_04_15 as unknown as VersionValue;
@@ -1,10 +0,0 @@
import { AvailabilitiesService } from "@/modules/availabilities/availabilities.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule],
providers: [AvailabilitiesService],
exports: [AvailabilitiesService],
})
export class AvailabilitiesModule {}
@@ -1,16 +0,0 @@
import { CreateAvailabilityInput } from "@/modules/availabilities/inputs/create-availability.input";
import { Injectable } from "@nestjs/common";
@Injectable()
export class AvailabilitiesService {
getDefaultAvailabilityInput(): CreateAvailabilityInput {
const startTime = new Date(new Date().setUTCHours(9, 0, 0, 0));
const endTime = new Date(new Date().setUTCHours(17, 0, 0, 0));
return {
days: [1, 2, 3, 4, 5],
startTime,
endTime,
};
}
}
@@ -22,7 +22,6 @@ import {
HttpStatus,
Param,
Patch,
BadRequestException,
Delete,
Query,
NotFoundException,
@@ -1,5 +1,5 @@
import { EventTypesModule } from "@/ee/event-types/event-types.module";
import { SchedulesModule } from "@/ee/schedules/schedules.module";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { AuthModule } from "@/modules/auth/auth.module";
import { BillingModule } from "@/modules/billing/billing.module";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
@@ -32,7 +32,7 @@ import { Global, Module } from "@nestjs/common";
OrganizationsModule,
StripeModule,
BillingModule,
SchedulesModule,
SchedulesModule_2024_04_15,
],
providers: [
OAuthClientRepository,
@@ -1,5 +1,5 @@
import { EventTypesService } from "@/ee/event-types/services/event-types.service";
import { SchedulesService } from "@/ee/schedules/services/schedules.service";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
@@ -15,7 +15,7 @@ export class OAuthClientUsersService {
private readonly userRepository: UsersRepository,
private readonly tokensRepository: TokensRepository,
private readonly eventTypesService: EventTypesService,
private readonly schedulesService: SchedulesService
private readonly schedulesService: SchedulesService_2024_04_15
) {}
async createOauthClientUser(
@@ -173,6 +173,14 @@ export class UsersRepository {
},
});
}
async getUserScheduleDefaultId(userId: number) {
const user = await this.findById(userId);
if (!user?.defaultScheduleId) return null;
return user?.defaultScheduleId;
}
}
function capitalizeTimezone(timezone: string) {
+417 -31
View File
@@ -808,14 +808,14 @@
},
"/v2/schedules": {
"post": {
"operationId": "SchedulesController_createSchedule",
"operationId": "SchedulesController_2024_06_11_createSchedule",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScheduleInput"
"$ref": "#/components/schemas/CreateScheduleInput_2024_06_11"
}
}
}
@@ -826,7 +826,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScheduleOutput"
"$ref": "#/components/schemas/CreateScheduleOutput_2024_06_11"
}
}
}
@@ -837,7 +837,7 @@
]
},
"get": {
"operationId": "SchedulesController_getSchedules",
"operationId": "SchedulesController_2024_06_11_getSchedules",
"parameters": [],
"responses": {
"200": {
@@ -845,7 +845,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetSchedulesOutput"
"$ref": "#/components/schemas/GetSchedulesOutput_2024_06_11"
}
}
}
@@ -858,7 +858,7 @@
},
"/v2/schedules/default": {
"get": {
"operationId": "SchedulesController_getDefaultSchedule",
"operationId": "SchedulesController_2024_06_11_getDefaultSchedule",
"parameters": [],
"responses": {
"200": {
@@ -866,7 +866,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetDefaultScheduleOutput"
"$ref": "#/components/schemas/GetDefaultScheduleOutput_2024_06_11"
}
}
}
@@ -879,7 +879,7 @@
},
"/v2/schedules/{scheduleId}": {
"get": {
"operationId": "SchedulesController_getSchedule",
"operationId": "SchedulesController_2024_06_11_getSchedule",
"parameters": [
{
"name": "scheduleId",
@@ -896,7 +896,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetScheduleOutput"
"$ref": "#/components/schemas/GetScheduleOutput_2024_06_11"
}
}
}
@@ -907,7 +907,7 @@
]
},
"patch": {
"operationId": "SchedulesController_updateSchedule",
"operationId": "SchedulesController_2024_06_11_updateSchedule",
"parameters": [
{
"name": "scheduleId",
@@ -923,7 +923,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScheduleInput"
"$ref": "#/components/schemas/UpdateScheduleInput_2024_06_11"
}
}
}
@@ -934,7 +934,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScheduleOutput"
"$ref": "#/components/schemas/UpdateScheduleOutput_2024_06_11"
}
}
}
@@ -945,7 +945,7 @@
]
},
"delete": {
"operationId": "SchedulesController_deleteSchedule",
"operationId": "SchedulesController_2024_06_11_deleteSchedule",
"parameters": [
{
"name": "scheduleId",
@@ -962,7 +962,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeleteScheduleOutput"
"$ref": "#/components/schemas/DeleteScheduleOutput_2024_06_11"
}
}
}
@@ -3002,7 +3002,7 @@
"data"
]
},
"CreateAvailabilityInput": {
"CreateAvailabilityInput_2024_04_15": {
"type": "object",
"properties": {
"days": {
@@ -3030,7 +3030,7 @@
"endTime"
]
},
"CreateScheduleInput": {
"CreateScheduleInput_2024_04_15": {
"type": "object",
"properties": {
"name": {
@@ -3042,7 +3042,7 @@
"availabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CreateAvailabilityInput"
"$ref": "#/components/schemas/CreateAvailabilityInput_2024_04_15"
}
},
"isDefault": {
@@ -3214,7 +3214,7 @@
"readOnly"
]
},
"CreateScheduleOutput": {
"CreateScheduleOutput_2024_04_15": {
"type": "object",
"properties": {
"status": {
@@ -3234,7 +3234,7 @@
"data"
]
},
"GetDefaultScheduleOutput": {
"GetDefaultScheduleOutput_2024_04_15": {
"type": "object",
"properties": {
"status": {
@@ -3259,7 +3259,7 @@
"data"
]
},
"GetScheduleOutput": {
"GetScheduleOutput_2024_04_15": {
"type": "object",
"properties": {
"status": {
@@ -3279,7 +3279,7 @@
"data"
]
},
"GetSchedulesOutput": {
"GetSchedulesOutput_2024_04_15": {
"type": "object",
"properties": {
"status": {
@@ -3299,7 +3299,7 @@
"data"
]
},
"UpdateScheduleInput": {
"UpdateScheduleInput_2024_04_15": {
"type": "object",
"properties": {
"timeZone": {
@@ -3359,7 +3359,7 @@
"schedule"
]
},
"EventTypeModel": {
"EventTypeModel_2024_04_15": {
"type": "object",
"properties": {
"id": {
@@ -3374,7 +3374,50 @@
"id"
]
},
"ScheduleModel": {
"AvailabilityModel_2024_04_15": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"userId": {
"type": "number",
"nullable": true
},
"scheduleId": {
"type": "number",
"nullable": true
},
"eventTypeId": {
"type": "number",
"nullable": true
},
"days": {
"type": "array",
"items": {
"type": "number"
}
},
"startTime": {
"format": "date-time",
"type": "string"
},
"endTime": {
"format": "date-time",
"type": "string"
},
"date": {
"format": "date-time",
"type": "string",
"nullable": true
}
},
"required": [
"id",
"days"
]
},
"ScheduleModel_2024_04_15": {
"type": "object",
"properties": {
"id": {
@@ -3393,13 +3436,13 @@
"eventType": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EventTypeModel"
"$ref": "#/components/schemas/EventTypeModel_2024_04_15"
}
},
"availability": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AvailabilityModel"
"$ref": "#/components/schemas/AvailabilityModel_2024_04_15"
}
}
},
@@ -3409,11 +3452,11 @@
"name"
]
},
"UpdatedScheduleOutput": {
"UpdatedScheduleOutput_2024_04_15": {
"type": "object",
"properties": {
"schedule": {
"$ref": "#/components/schemas/ScheduleModel"
"$ref": "#/components/schemas/ScheduleModel_2024_04_15"
},
"isDefault": {
"type": "boolean"
@@ -3435,7 +3478,7 @@
"isDefault"
]
},
"UpdateScheduleOutput": {
"UpdateScheduleOutput_2024_04_15": {
"type": "object",
"properties": {
"status": {
@@ -3447,7 +3490,7 @@
]
},
"data": {
"$ref": "#/components/schemas/UpdatedScheduleOutput"
"$ref": "#/components/schemas/UpdatedScheduleOutput_2024_04_15"
}
},
"required": [
@@ -3455,7 +3498,7 @@
"data"
]
},
"DeleteScheduleOutput": {
"DeleteScheduleOutput_2024_04_15": {
"type": "object",
"properties": {
"status": {
@@ -3561,6 +3604,349 @@
"status"
]
},
"ScheduleAvailabilityInput_2024_06_11": {
"type": "object",
"properties": {
"days": {
"example": [
"Monday",
"Tuesday"
],
"type": "array",
"items": {
"type": "string"
}
},
"startTime": {
"type": "string",
"example": "09:00"
},
"endTime": {
"type": "string",
"example": "10:00"
}
},
"required": [
"days",
"startTime",
"endTime"
]
},
"ScheduleOverrideInput_2024_06_11": {
"type": "object",
"properties": {
"date": {
"type": "string",
"example": "2024-05-20"
},
"startTime": {
"type": "string",
"example": "12:00"
},
"endTime": {
"type": "string",
"example": "13:00"
}
},
"required": [
"date",
"startTime",
"endTime"
]
},
"CreateScheduleInput_2024_06_11": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "One-on-one coaching"
},
"timeZone": {
"type": "string",
"example": "Europe/Rome"
},
"availability": {
"example": [
{
"days": [
"Monday",
"Tuesday"
],
"startTime": "09:00",
"endTime": "10:00"
}
],
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleAvailabilityInput_2024_06_11"
}
},
"isDefault": {
"type": "boolean",
"example": true
},
"overrides": {
"example": [
{
"date": "2024-05-20",
"startTime": "12:00",
"endTime": "14:00"
}
],
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleOverrideInput_2024_06_11"
}
}
},
"required": [
"name",
"timeZone",
"isDefault"
]
},
"ScheduleOutput_2024_06_11": {
"type": "object",
"properties": {
"id": {
"type": "number",
"example": 254
},
"ownerId": {
"type": "number",
"example": 478
},
"name": {
"type": "string",
"example": "One-on-one coaching"
},
"timeZone": {
"type": "string",
"example": "Europe/Rome"
},
"availability": {
"example": [
{
"days": [
"Monday",
"Tuesday"
],
"startTime": "09:00",
"endTime": "10:00"
}
],
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleAvailabilityInput_2024_06_11"
}
},
"isDefault": {
"type": "boolean",
"example": true
},
"overrides": {
"example": [
{
"date": "2024-05-20",
"startTime": "12:00",
"endTime": "13:00"
}
],
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleOverrideInput_2024_06_11"
}
}
},
"required": [
"id",
"ownerId",
"name",
"timeZone",
"availability",
"isDefault",
"overrides"
]
},
"CreateScheduleOutput_2024_06_11": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/ScheduleOutput_2024_06_11"
}
},
"required": [
"status",
"data"
]
},
"GetDefaultScheduleOutput_2024_06_11": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/ScheduleOutput_2024_06_11"
}
},
"required": [
"status",
"data"
]
},
"GetScheduleOutput_2024_06_11": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"nullable": true,
"allOf": [
{
"$ref": "#/components/schemas/ScheduleOutput_2024_06_11"
}
]
},
"error": {
"type": "object"
}
},
"required": [
"status",
"data"
]
},
"GetSchedulesOutput_2024_06_11": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleOutput_2024_06_11"
}
},
"error": {
"type": "object"
}
},
"required": [
"status",
"data"
]
},
"UpdateScheduleInput_2024_06_11": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "One-on-one coaching"
},
"timeZone": {
"type": "string",
"example": "Europe/Rome"
},
"availability": {
"example": [
{
"days": [
"Monday",
"Tuesday"
],
"startTime": "09:00",
"endTime": "10:00"
}
],
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleAvailabilityInput_2024_06_11"
}
},
"isDefault": {
"type": "boolean",
"example": true
},
"overrides": {
"example": [
{
"date": "2024-05-20",
"startTime": "12:00",
"endTime": "14:00"
}
],
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleOverrideInput_2024_06_11"
}
}
}
},
"UpdateScheduleOutput_2024_06_11": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/ScheduleOutput_2024_06_11"
},
"error": {
"type": "object"
}
},
"required": [
"status",
"data"
]
},
"DeleteScheduleOutput_2024_06_11": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
}
},
"required": [
"status"
]
},
"MeOutput": {
"type": "object",
"properties": {
+3 -3
View File
@@ -128,10 +128,10 @@ export function getWorkingHours(
}
export function availabilityAsString(
availability: Availability,
availability: Pick<Availability, "days" | "startTime" | "endTime">,
{ locale, hour12 }: { locale?: string; hour12?: boolean }
) {
const weekSpan = (availability: Availability) => {
const weekSpan = (availability: Pick<Availability, "days" | "startTime" | "endTime">) => {
const days = availability.days.slice(1).reduce(
(days, day) => {
if (days[days.length - 1].length === 1 && days[days.length - 1][0] === day - 1) {
@@ -153,7 +153,7 @@ export function availabilityAsString(
.join(", ");
};
const timeSpan = (availability: Availability) => {
const timeSpan = (availability: Pick<Availability, "days" | "startTime" | "endTime">) => {
return `${new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "numeric", hour12 }).format(
new Date(availability.startTime.toISOString().slice(0, -1))
)} - ${new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "numeric", hour12 }).format(
+1 -1
View File
@@ -1 +1 @@
export * from "./client/transformers";
export * from "./transformers";
@@ -1,33 +1,25 @@
import type { Availability as AvailabilityModel, Schedule as ScheduleModel } from "@prisma/client";
import type { Availability } from "@prisma/client";
import dayjs from "@calcom/dayjs";
import { getWorkingHours } from "@calcom/lib/availability";
import { yyyymmdd } from "@calcom/lib/date-fns";
import type { Schedule, TimeRange } from "@calcom/types/schedule";
export type ScheduleWithAvailabilities = ScheduleModel & { availability: AvailabilityModel[] };
type ScheduleAvailability = Pick<Availability, "days" | "startTime" | "endTime">[];
type ScheduleOverride = Pick<Availability, "date" | "startTime" | "endTime">[];
export type ScheduleWithAvailabilitiesForWeb = Pick<ScheduleModel, "id" | "name"> & {
isManaged: boolean;
workingHours: ReturnType<typeof transformWorkingHoursForClient>;
schedule: AvailabilityModel[];
availability: ReturnType<typeof transformAvailabilityForClient>;
timeZone: string;
dateOverrides: ReturnType<typeof transformDateOverridesForClient>;
isDefault: boolean;
isLastSchedule: boolean;
readOnly: boolean;
};
export function transformWorkingHoursForClient(schedule: ScheduleWithAvailabilities) {
export function transformWorkingHoursForAtom(schedule: {
timeZone: string | null;
availability: ScheduleAvailability;
}) {
return getWorkingHours(
{ timeZone: schedule.timeZone || undefined, utcOffset: 0 },
schedule.availability || []
);
}
export function transformAvailabilityForClient(schedule: ScheduleWithAvailabilities) {
return transformScheduleToAvailabilityForClient(schedule).map((a) =>
export function transformAvailabilityForAtom(schedule: { availability: ScheduleAvailability }) {
return transformScheduleToAvailabilityForAtom(schedule).map((a) =>
a.map((startAndEnd) => ({
...startAndEnd,
end: new Date(startAndEnd.end.toISOString().replace("23:59:00.000Z", "23:59:59.999Z")),
@@ -35,7 +27,10 @@ export function transformAvailabilityForClient(schedule: ScheduleWithAvailabilit
);
}
export function transformDateOverridesForClient(schedule: ScheduleWithAvailabilities, timeZone: string) {
export function transformDateOverridesForAtom(
schedule: { availability: ScheduleOverride },
timeZone: string
) {
return schedule.availability.reduce((acc, override) => {
// only if future date override
const currentUtcOffset = dayjs().tz(timeZone).utcOffset();
@@ -70,9 +65,7 @@ export function transformDateOverridesForClient(schedule: ScheduleWithAvailabili
}, [] as { ranges: TimeRange[] }[]);
}
export const transformScheduleToAvailabilityForClient = (
schedule: Partial<ScheduleModel> & { availability: AvailabilityModel[] }
) => {
export const transformScheduleToAvailabilityForAtom = (schedule: { availability: ScheduleAvailability }) => {
return schedule.availability.reduce(
(schedule: Schedule, availability) => {
availability.days.forEach((day) => {
@@ -0,0 +1,54 @@
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];
}
@@ -0,0 +1,2 @@
export * from "./for-atom";
export * from "./from-api";
@@ -62,6 +62,8 @@ export type CustomClassNames = {
};
};
export type Availability = Pick<Schedule, "days" | "startTime" | "endTime">;
type AvailabilitySettingsProps = {
skeletonLabel?: string;
schedule: {
@@ -73,7 +75,7 @@ type AvailabilitySettingsProps = {
workingHours: WorkingHours[];
dateOverrides: { ranges: TimeRange[] }[];
timeZone: string;
schedule: Schedule[];
schedule: Availability[];
};
travelSchedules?: RouterOutputs["viewer"]["getTravelSchedules"];
handleDelete: () => void;
@@ -289,7 +291,9 @@ export function AvailabilitySettings({
schedule.schedule
.filter((availability) => !!availability.days.length)
.map((availability) => (
<span key={availability.id} className={cn(customClassNames?.subtitlesClassName)}>
<span
key={availability.startTime.valueOf().toString()}
className={cn(customClassNames?.subtitlesClassName)}>
{availabilityAsString(availability, { locale: i18n.language, hour12: timeFormat === 12 })}
<br />
</span>
@@ -0,0 +1,152 @@
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);
});
});
@@ -0,0 +1,59 @@
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,
};
});
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,
};
}
@@ -0,0 +1,72 @@
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);
});
});
@@ -0,0 +1,66 @@
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) => ({
date: `${range.start.getUTCFullYear}-${range.start.getUTCMonth}-${range.start.getUTCDate}`,
startTime: `${range.start.getUTCHours}-${range.start.getUTCMinutes}`,
endTime: `${range.end.getUTCHours}-${range.end.getUTCMinutes}`,
})) ?? []
) ?? [];
const availability = formatScheduleTime(schedule);
return { name, timeZone, isDefault, availability, overrides };
}
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,16 +1,18 @@
import type { ScheduleLabelsType } from "@calcom/features/schedules/components/Schedule";
import type { ScheduleWithAvailabilitiesForWeb, UpdateScheduleOutputType } from "@calcom/platform-libraries";
import type { ApiErrorResponse, ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
import type { ApiErrorResponse, ApiResponse, ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
import useClientSchedule from "../../hooks/useClientSchedule";
import useDeleteSchedule from "../../hooks/useDeleteSchedule";
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 useUpdateSchedule from "../../hooks/useUpdateSchedule";
import { AtomsWrapper } from "../../src/components/atoms-wrapper";
import { useToast } from "../../src/components/ui/use-toast";
import type { Schedule } from "../AvailabilitySettings";
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 = {
@@ -19,7 +21,7 @@ type AvailabilitySettingsPlatformWrapperProps = {
tooltips: Partial<ScheduleLabelsType>;
};
customClassNames?: Partial<CustomClassNames>;
onUpdateSuccess?: (res: ApiResponse<UpdateScheduleOutputType>) => void;
onUpdateSuccess?: (res: ApiResponse<ScheduleOutput_2024_06_11>) => void;
onUpdateError?: (err: ApiErrorResponse) => void;
onDeleteSuccess?: (res: ApiResponse) => void;
onDeleteError?: (err: ApiErrorResponse) => void;
@@ -35,10 +37,10 @@ export const AvailabilitySettingsPlatformWrapper = ({
onUpdateSuccess,
disableEditableHeading = false,
}: AvailabilitySettingsPlatformWrapperProps) => {
const { isLoading, data: schedule } = useClientSchedule(id);
const mySchedule = schedule as ApiSuccessResponse<ScheduleWithAvailabilitiesForWeb>;
const { isLoading, data: schedule } = useSchedule(id);
const { data: schedules } = useSchedules();
const { data: me } = useMe();
const userSchedule = mySchedule?.data;
const atomSchedule = transformApiScheduleForAtom(me?.data, schedule, schedules?.length || 0);
const { timeFormat } = me?.data || { timeFormat: null };
const { toast } = useToast();
@@ -77,48 +79,43 @@ export const AvailabilitySettingsPlatformWrapper = ({
};
const handleUpdate = async (id: number, body: AvailabilityFormValues) => {
const transformedDateOverrides =
body.dateOverrides.flatMap(
(dateOverridesRanges) =>
dateOverridesRanges?.ranges?.map((range) => ({ start: range.start, end: range.end })) ?? []
) ?? [];
await updateSchedule({ ...body, scheduleId: id, dateOverrides: transformedDateOverrides });
const updateBody = transformAtomScheduleForApi(body);
updateSchedule({ id, ...updateBody });
};
if (isLoading) return <div className="px-10 py-4 text-xl">Loading...</div>;
if (!userSchedule) return <div className="px-10 py-4 text-xl">No user schedule present</div>;
if (!atomSchedule) return <div className="px-10 py-4 text-xl">No user schedule present</div>;
return (
<AtomsWrapper>
<AvailabilitySettings
disableEditableHeading={disableEditableHeading}
handleDelete={() => {
userSchedule.id && handleDelete(userSchedule.id);
atomSchedule.id && handleDelete(atomSchedule.id);
}}
handleSubmit={async (data) => {
userSchedule.id && handleUpdate(userSchedule.id, data);
atomSchedule.id && handleUpdate(atomSchedule.id, data);
}}
weekStart="Sunday"
timeFormat={timeFormat}
isLoading={isLoading}
schedule={{
name: userSchedule.name,
id: userSchedule.id,
isLastSchedule: userSchedule.isLastSchedule,
isDefault: userSchedule.isDefault,
workingHours: userSchedule.workingHours,
dateOverrides: userSchedule.dateOverrides,
timeZone: userSchedule.timeZone,
availability: userSchedule.availability,
name: atomSchedule.name,
id: atomSchedule.id,
isLastSchedule: atomSchedule.isLastSchedule,
isDefault: atomSchedule.isDefault,
workingHours: atomSchedule.workingHours,
dateOverrides: atomSchedule.dateOverrides,
timeZone: atomSchedule.timeZone,
availability: atomSchedule.availability,
schedule:
userSchedule.schedule.reduce(
(acc: Schedule[], avail: Schedule) => [
atomSchedule.schedule.reduce(
(acc: Availability[], avail: Availability) => [
...acc,
{ ...avail, startTime: new Date(avail.startTime), endTime: new Date(avail.endTime) },
{ days: avail.days, startTime: new Date(avail.startTime), endTime: new Date(avail.endTime) },
],
[] as Schedule[]
[]
) || [],
}}
isDeleting={isDeletionInProgress}
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useEffect, type ReactNode } from "react";
import type { API_VERSIONS_ENUM } from "@calcom/platform-constants";
import { VERSION_2024_04_15 } from "@calcom/platform-constants";
import { VERSION_2024_06_11 } from "@calcom/platform-constants";
import http from "../lib/http";
import { BaseCalProvider } from "./BaseCalProvider";
@@ -40,7 +40,7 @@ export function CalProvider({
children,
autoUpdateTimezone = true,
onTimezoneChange,
version = VERSION_2024_04_15,
version = VERSION_2024_06_11,
}: CalProviderProps) {
useEffect(() => {
http.setVersionHeader(version);
@@ -4,8 +4,8 @@ import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { V2_ENDPOINTS } from "@calcom/platform-constants";
import type { ApiResponse, ApiErrorResponse } from "@calcom/platform-types";
import http from "../lib/http";
import { QUERY_KEY } from "./useClientSchedule";
import http from "../../lib/http";
import { QUERY_KEY } from "./useSchedule";
interface IUseDeleteScheduleProps {
onSuccess?: (res: ApiResponse) => void;
@@ -31,7 +31,7 @@ const useDeleteSchedule = (
const mutation = useMutation<ApiResponse<undefined>, unknown, DeleteScheduleInput>({
mutationFn: (data) => {
const { id } = data;
const pathname = `/${V2_ENDPOINTS.availability}/${id}?for=atom`;
const pathname = `/${V2_ENDPOINTS.availability}/${id}`;
return http?.delete(pathname).then((res) => res.data);
},
@@ -0,0 +1,26 @@
import { useQuery } from "@tanstack/react-query";
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
import type { GetScheduleOutput_2024_06_11 } from "@calcom/platform-types";
import http from "../../lib/http";
export const QUERY_KEY = "user-schedule";
export const useSchedule = (id?: string) => {
const pathname = id ? `/${V2_ENDPOINTS.availability}/${id}` : `/${V2_ENDPOINTS.availability}/default`;
const { isLoading, error, data } = useQuery({
queryKey: [QUERY_KEY, id],
queryFn: () => {
return http.get<GetScheduleOutput_2024_06_11>(pathname).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,26 @@
import { useQuery } from "@tanstack/react-query";
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
import type { GetSchedulesOutput_2024_06_11 } from "@calcom/platform-types";
import http from "../../lib/http";
export const QUERY_KEY = "user-schedules";
export const useSchedules = () => {
const pathname = `/${V2_ENDPOINTS.availability}`;
const { isLoading, error, data } = useQuery({
queryKey: [QUERY_KEY],
queryFn: () => {
return http.get<GetSchedulesOutput_2024_06_11>(pathname).then((res) => {
if (res.data.status === SUCCESS_STATUS) {
return res.data.data;
}
throw new Error(res.data.error?.message);
});
},
});
return { isLoading, error, data };
};
@@ -2,14 +2,14 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { V2_ENDPOINTS } from "@calcom/platform-constants";
import type { UpdateScheduleOutputType } from "@calcom/platform-libraries";
import type { ApiResponse, UpdateScheduleInput, ApiErrorResponse } from "@calcom/platform-types";
import type { ApiResponse, UpdateScheduleInput_2024_06_11, ApiErrorResponse } from "@calcom/platform-types";
import type { ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
import http from "../lib/http";
import { QUERY_KEY as ScheduleQueryKey } from "./useClientSchedule";
import http from "../../lib/http";
import { QUERY_KEY as ScheduleQueryKey } from "./useSchedule";
interface IPUpdateOAuthClient {
onSuccess?: (res: ApiResponse<UpdateScheduleOutputType>) => void;
onSuccess?: (res: ApiResponse<ScheduleOutput_2024_06_11>) => void;
onError?: (err: ApiErrorResponse) => void;
}
@@ -26,14 +26,14 @@ const useUpdateSchedule = (
const queryClient = useQueryClient();
const mutation = useMutation<
ApiResponse<UpdateScheduleOutputType>,
ApiResponse<ScheduleOutput_2024_06_11>,
unknown,
UpdateScheduleInput & { scheduleId: number }
UpdateScheduleInput_2024_06_11 & { id: number }
>({
mutationFn: (data) => {
const pathname = `/${V2_ENDPOINTS.availability}/${data.scheduleId}?for=atom`;
const pathname = `/${V2_ENDPOINTS.availability}/${data.id}`;
return http.patch<ApiResponse<UpdateScheduleOutputType>>(pathname, data).then((res) => {
return http.patch<ApiResponse<ScheduleOutput_2024_06_11>>(pathname, data).then((res) => {
return res.data;
});
},
@@ -1,31 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ScheduleWithAvailabilitiesForWeb } from "@calcom/platform-libraries";
import type { ApiResponse } from "@calcom/platform-types";
import http from "../lib/http";
export const QUERY_KEY = "user-schedule";
const useClientSchedule = (id?: string) => {
const pathname = id
? `/${V2_ENDPOINTS.availability}/${id}?for=atom`
: `/${V2_ENDPOINTS.availability}/default?for=atom`;
const { isLoading, error, data } = useQuery({
queryKey: [QUERY_KEY, id],
queryFn: () => {
return http.get<ApiResponse<ScheduleWithAvailabilitiesForWeb>>(pathname).then((res) => {
if (res.data.status === SUCCESS_STATUS) {
return res.data;
}
throw new Error(res.data.error.message);
});
},
});
return { isLoading, error, data };
};
export default useClientSchedule;
+9
View File
@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.ts$": "ts-jest"
}
}
+5 -1
View File
@@ -8,22 +8,26 @@
"scripts": {
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
"build": "yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
"publish": "rm -rf dist && yarn build && npm publish --access public"
"publish": "rm -rf dist && yarn build && npm publish --access public",
"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",
"@types/react": "18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^2.2.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"autoprefixer": "^10.4.19",
"jest": "^29.7.0",
"postcss": "^8.4.38",
"postcss-import": "^16.1.0",
"postcss-prefixer": "^3.0.0",
"postcss-prefixwrap": "1.46.0",
"rollup-plugin-node-builtins": "^2.1.2",
"ts-jest": "^29.1.2",
"typescript": "^4.9.4",
"vite": "^5.0.10",
"vite-plugin-dts": "^3.7.3",
+3 -2
View File
@@ -51,9 +51,10 @@ export const X_CAL_CLIENT_ID = "x-cal-client-id";
// HTTP status codes
export const HTTP_CODE_TOKEN_EXPIRED = 498;
export const VERSION_2024_04_15 = "2024-04-15";
export const VERSION_2024_06_11 = "2024-06-11";
export const VERSION_2024_05_21 = "2024-05-21";
export const API_VERSIONS = [VERSION_2024_04_15, VERSION_2024_05_21] as const;
export const VERSION_2024_04_15 = "2024-04-15";
export const API_VERSIONS = [VERSION_2024_06_11, VERSION_2024_04_15, VERSION_2024_05_21] as const;
export type API_VERSIONS_ENUM = (typeof API_VERSIONS)[number];
export type API_VERSIONS_TYPE = typeof API_VERSIONS;
export const CAL_API_VERSION_HEADER = "cal-api-version";
+7 -8
View File
@@ -48,14 +48,13 @@ export type { ConnectedDestinationCalendars } from "@calcom/lib/getConnectedDest
export { getBusyCalendarTimes } from "@calcom/core/CalendarManager";
export {
transformWorkingHoursForClient,
transformAvailabilityForClient,
transformDateOverridesForClient,
} from "@calcom/lib/schedules/client/transformers";
export type {
ScheduleWithAvailabilities,
ScheduleWithAvailabilitiesForWeb,
} from "@calcom/lib/schedules/client/transformers";
transformWorkingHoursForAtom,
transformAvailabilityForAtom,
transformDateOverridesForAtom,
transformApiScheduleAvailability,
transformApiScheduleOverrides,
} from "@calcom/lib/schedules/transformers";
export type {
BookingCreateBody,
BookingResponse,
+1 -1
View File
@@ -3,6 +3,6 @@ export * from "./api";
export * from "./oauth-clients";
export * from "./slots";
export * from "./calendars";
export * from "./schedules";
export * from "./bookings";
export * from "./billing";
export * from "./schedules";
@@ -0,0 +1,2 @@
export * from "./schedules-2024-06-11";
export * from "./schedules-2024-04-15";
@@ -0,0 +1 @@
export * from "./schedules";
@@ -4,27 +4,27 @@ import { IsString, IsBoolean, IsOptional, ValidateNested, IsArray, IsDate } from
import { DateTime } from "luxon";
import { z } from "zod";
const scheduleSchema = z.object({
const scheduleSchema_2024_04_15 = z.object({
id: z.number().int(),
userId: z.number().int(),
name: z.string(),
timeZone: z.string().nullish(),
});
const availabilitySchema = z.object({
const availabilitySchema_2024_04_15 = z.object({
id: z.number().int(),
days: z.number().int().array(),
startTime: z.date(),
endTime: z.date(),
});
export const schemaScheduleResponse = z
export const schemaScheduleResponse_2024_04_15 = z
.object({})
.merge(scheduleSchema)
.merge(scheduleSchema_2024_04_15)
.merge(
z.object({
availability: z
.array(availabilitySchema)
.array(availabilitySchema_2024_04_15)
.transform((availabilities) =>
availabilities.map((availability) => ({
...availability,
@@ -36,7 +36,7 @@ export const schemaScheduleResponse = z
})
);
export type ScheduleResponse = z.infer<typeof schemaScheduleResponse>;
export type ScheduleResponse = z.infer<typeof schemaScheduleResponse_2024_04_15>;
class ScheduleItem {
@IsString()
@@ -56,7 +56,7 @@ class DateOverride {
end!: Date;
}
export class UpdateScheduleInput {
export class UpdateScheduleInput_2024_04_15 {
@IsString()
@IsOptional()
@DocsProperty()
@@ -0,0 +1,13 @@
export const WEEK_DAYS = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
] as const;
export type WeekDay = (typeof WEEK_DAYS)[number];
export const TIME_FORMAT_HH_MM = /^([01]\d|2[0-3]):([0-5]\d)$/;
@@ -0,0 +1,3 @@
export * from "./inputs";
export * from "./outputs";
export * from "./constants";
@@ -0,0 +1,97 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import {
IsBoolean,
IsOptional,
IsString,
ValidateNested,
IsArray,
Matches,
IsISO8601,
IsTimeZone,
IsIn,
} from "class-validator";
import type { WeekDay } from "../constants";
import { TIME_FORMAT_HH_MM, WEEK_DAYS } from "../constants";
export class ScheduleAvailabilityInput_2024_06_11 {
@IsArray()
@IsIn(WEEK_DAYS, { each: true })
@ApiProperty({ example: ["Monday", "Tuesday"] })
days!: WeekDay[];
@IsString()
@Matches(TIME_FORMAT_HH_MM, { message: "startTime must be a valid time format HH:MM" })
@ApiProperty({ example: "09:00" })
startTime!: string;
@IsString()
@Matches(TIME_FORMAT_HH_MM, { message: "endTime must be a valid time format HH:MM" })
@ApiProperty({ example: "10:00" })
endTime!: string;
}
export class ScheduleOverrideInput_2024_06_11 {
@IsISO8601({ strict: true })
@ApiProperty({ example: "2024-05-20" })
date!: string;
@IsString()
@Matches(TIME_FORMAT_HH_MM, { message: "startTime must be a valid time format HH:MM" })
@ApiProperty({ example: "12:00" })
startTime!: string;
@IsString()
@Matches(TIME_FORMAT_HH_MM, { message: "endTime must be a valid time format HH:MM" })
@ApiProperty({ example: "13:00" })
endTime!: string;
}
export class CreateScheduleInput_2024_06_11 {
@IsString()
@ApiProperty({ example: "One-on-one coaching" })
name!: string;
@IsTimeZone()
@ApiProperty({ example: "Europe/Rome" })
timeZone!: string;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => ScheduleAvailabilityInput_2024_06_11)
@ApiProperty({
type: [ScheduleAvailabilityInput_2024_06_11],
example: [
{
days: ["Monday", "Tuesday"],
startTime: "09:00",
endTime: "10:00",
},
],
required: false,
})
availability?: ScheduleAvailabilityInput_2024_06_11[];
@IsBoolean()
@ApiProperty({ example: true })
isDefault!: boolean;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => ScheduleOverrideInput_2024_06_11)
@ApiProperty({
type: [ScheduleOverrideInput_2024_06_11],
example: [
{
date: "2024-05-20",
startTime: "12:00",
endTime: "14:00",
},
],
required: false,
})
overrides?: ScheduleOverrideInput_2024_06_11[];
}
@@ -0,0 +1,2 @@
export * from "./create-schedule.input";
export * from "./update-schedule.input";
@@ -0,0 +1,59 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsString, IsBoolean, IsOptional, ValidateNested, IsArray, IsTimeZone } from "class-validator";
import {
ScheduleAvailabilityInput_2024_06_11,
ScheduleOverrideInput_2024_06_11,
} from "./create-schedule.input";
export class UpdateScheduleInput_2024_06_11 {
@IsString()
@IsOptional()
@ApiProperty({ example: "One-on-one coaching", required: false })
name?: string;
@IsTimeZone()
@IsOptional()
@ApiProperty({ example: "Europe/Rome", required: false })
timeZone?: string;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => ScheduleAvailabilityInput_2024_06_11)
@ApiProperty({
type: [ScheduleAvailabilityInput_2024_06_11],
example: [
{
days: ["Monday", "Tuesday"],
startTime: "09:00",
endTime: "10:00",
},
],
required: false,
})
availability?: ScheduleAvailabilityInput_2024_06_11[];
@IsBoolean()
@IsOptional()
@ApiProperty({ example: true, required: false })
isDefault?: boolean;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => ScheduleOverrideInput_2024_06_11)
@ApiProperty({
type: [ScheduleOverrideInput_2024_06_11],
example: [
{
date: "2024-05-20",
startTime: "12:00",
endTime: "14:00",
},
],
required: false,
})
overrides?: ScheduleOverrideInput_2024_06_11[];
}
@@ -1,20 +1,20 @@
import { UpdatedScheduleOutput } from "@/ee/schedules/outputs/schedule-updated.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class UpdateScheduleOutput {
import { ScheduleOutput_2024_06_11 } from "./schedule.output";
export class CreateScheduleOutput_2024_06_11 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: UpdatedScheduleOutput,
type: ScheduleOutput_2024_06_11,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => UpdatedScheduleOutput)
data!: UpdatedScheduleOutput;
@Type(() => ScheduleOutput_2024_06_11)
data!: ScheduleOutput_2024_06_11;
}
@@ -0,0 +1,10 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsEnum } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class DeleteScheduleOutput_2024_06_11 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
}
@@ -0,0 +1,20 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { ScheduleOutput_2024_06_11 } from "./schedule.output";
export class GetDefaultScheduleOutput_2024_06_11 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: ScheduleOutput_2024_06_11,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => ScheduleOutput_2024_06_11)
data!: ScheduleOutput_2024_06_11 | null;
}
@@ -0,0 +1,23 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { ScheduleOutput_2024_06_11 } from "./schedule.output";
export class GetScheduleOutput_2024_06_11 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: ScheduleOutput_2024_06_11,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => ScheduleOutput_2024_06_11)
data!: ScheduleOutput_2024_06_11 | null;
error?: Error;
}
@@ -0,0 +1,22 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsArray, IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { ScheduleOutput_2024_06_11 } from "./schedule.output";
export class GetSchedulesOutput_2024_06_11 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({ type: [ScheduleOutput_2024_06_11] })
@IsNotEmptyObject()
@ValidateNested({ each: true })
@Type(() => ScheduleOutput_2024_06_11)
@IsArray()
data!: ScheduleOutput_2024_06_11[];
error?: Error;
}
@@ -0,0 +1,7 @@
export * from "./schedule.output";
export * from "./get-schedule.output";
export * from "./get-schedules.output";
export * from "./get-default-schedule.output";
export * from "./create-schedule.output";
export * from "./update-schedule.output";
export * from "./delete-schedule.output";
@@ -0,0 +1,69 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import {
IsBoolean,
IsOptional,
IsString,
ValidateNested,
IsArray,
IsTimeZone,
IsNumber,
} from "class-validator";
import {
ScheduleAvailabilityInput_2024_06_11,
ScheduleOverrideInput_2024_06_11,
} from "../inputs/create-schedule.input";
export class ScheduleOutput_2024_06_11 {
@IsNumber()
@ApiProperty({ example: 254 })
id!: number;
@IsNumber()
@ApiProperty({ example: 478 })
ownerId!: number;
@IsString()
@ApiProperty({ example: "One-on-one coaching" })
name!: string;
@IsTimeZone()
@ApiProperty({ example: "Europe/Rome" })
timeZone!: string;
@IsArray()
@ValidateNested({ each: true })
@Type(() => ScheduleAvailabilityInput_2024_06_11)
@ApiProperty({
type: [ScheduleAvailabilityInput_2024_06_11],
example: [
{
days: ["Monday", "Tuesday"],
startTime: "09:00",
endTime: "10:00",
},
],
})
availability!: ScheduleAvailabilityInput_2024_06_11[];
@IsBoolean()
@ApiProperty({ example: true })
isDefault!: boolean;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => ScheduleOverrideInput_2024_06_11)
@ApiProperty({
type: [ScheduleOverrideInput_2024_06_11],
example: [
{
date: "2024-05-20",
startTime: "12:00",
endTime: "13:00",
},
],
})
overrides!: ScheduleOverrideInput_2024_06_11[];
}
@@ -0,0 +1,23 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { ScheduleOutput_2024_06_11 } from "./schedule.output";
export class UpdateScheduleOutput_2024_06_11 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: ScheduleOutput_2024_06_11,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => ScheduleOutput_2024_06_11)
data!: ScheduleOutput_2024_06_11;
error?: Error;
}
@@ -1,8 +1,7 @@
import type { ScheduleWithAvailabilitiesForWeb } from "@calcom/lib";
import {
transformAvailabilityForClient,
transformDateOverridesForClient,
transformWorkingHoursForClient,
transformAvailabilityForAtom,
transformDateOverridesForAtom,
transformWorkingHoursForAtom,
} from "@calcom/lib";
import { hasReadPermissionsForUserId } from "@calcom/lib/hasEditPermissionForUser";
import { prisma } from "@calcom/prisma";
@@ -20,7 +19,7 @@ type GetOptions = {
input: TGetInputSchema;
};
export const getHandler = async ({ ctx, input }: GetOptions): Promise<ScheduleWithAvailabilitiesForWeb> => {
export const getHandler = async ({ ctx, input }: GetOptions) => {
const { user } = ctx;
const schedule = await prisma.schedule.findUnique({
@@ -66,11 +65,11 @@ export const getHandler = async ({ ctx, input }: GetOptions): Promise<ScheduleWi
id: schedule.id,
name: schedule.name,
isManaged: schedule.userId !== user.id,
workingHours: transformWorkingHoursForClient(schedule),
workingHours: transformWorkingHoursForAtom(schedule),
schedule: schedule.availability,
availability: transformAvailabilityForClient(schedule),
availability: transformAvailabilityForAtom(schedule),
timeZone,
dateOverrides: transformDateOverridesForClient(schedule, timeZone),
dateOverrides: transformDateOverridesForAtom(schedule, timeZone),
isDefault: !input.scheduleId || user.defaultScheduleId === schedule.id,
isLastSchedule: schedulesCount <= 1,
readOnly: schedule.userId !== user.id && !input.isManagedEventType,
@@ -1,4 +1,4 @@
import { transformScheduleToAvailabilityForClient } from "@calcom/lib";
import { transformScheduleToAvailabilityForAtom } from "@calcom/lib";
import { getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { hasEditPermissionForUserID } from "@calcom/lib/hasEditPermissionForUser";
import { prisma } from "@calcom/prisma";
@@ -118,7 +118,7 @@ export const updateHandler = async ({ input, ctx }: UpdateOptions) => {
},
});
const userAvailability = transformScheduleToAvailabilityForClient(schedule);
const userAvailability = transformScheduleToAvailabilityForAtom(schedule);
return {
schedule,
+306 -193
View File
@@ -165,6 +165,13 @@ __metadata:
languageName: node
linkType: hard
"@antfu/utils@npm:^0.7.7":
version: 0.7.8
resolution: "@antfu/utils@npm:0.7.8"
checksum: 504146b6606b53b2ff1ee9df406fd855a1854c566aa5ef4ccf4cd10c0ccdbf084a934d29a9561c5585b73f03090ef1146fd52698fab33e623cebd97e1f236f5f
languageName: node
linkType: hard
"@anthropic-ai/sdk@npm:^0.9.1":
version: 0.9.1
resolution: "@anthropic-ai/sdk@npm:0.9.1"
@@ -3830,6 +3837,7 @@ __metadata:
dependencies:
"@calcom/platform-constants": "*"
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2"
"@calcom/platform-libraries-0.0.4": "npm:@calcom/platform-libraries@0.0.4"
"@calcom/platform-types": "*"
"@calcom/platform-utils": "*"
"@calcom/prisma": "*"
@@ -3980,7 +3988,7 @@ __metadata:
languageName: unknown
linkType: soft
"@calcom/atoms@*, @calcom/atoms@workspace:^, @calcom/atoms@workspace:packages/platform/atoms":
"@calcom/atoms@*, @calcom/atoms@workspace:packages/platform/atoms":
version: 0.0.0-use.local
resolution: "@calcom/atoms@workspace:packages/platform/atoms"
dependencies:
@@ -3990,13 +3998,16 @@ __metadata:
"@radix-ui/react-toast": ^1.1.5
"@rollup/plugin-node-resolve": ^15.0.1
"@tanstack/react-query": ^5.17.15
"@types/jest": ^29.5.12
"@types/node": ^20.3.1
"@types/react": 18.0.26
"@types/react-dom": ^18.0.9
"@vitejs/plugin-react": ^2.2.0
"@vitejs/plugin-react-swc": ^3.7.0
autoprefixer: ^10.4.19
class-variance-authority: ^0.4.0
clsx: ^2.0.0
jest: ^29.7.0
lucide-react: ^0.364.0
postcss: ^8.4.38
postcss-import: ^16.1.0
@@ -4007,9 +4018,11 @@ __metadata:
tailwind-merge: ^1.13.2
tailwindcss: ^3.3.3
tailwindcss-animate: ^1.0.6
ts-jest: ^29.1.2
typescript: ^4.9.4
vite: ^5.0.10
vite-plugin-dts: ^3.7.3
vite-plugin-inspect: ^0.8.4
languageName: unknown
linkType: soft
@@ -4785,6 +4798,17 @@ __metadata:
languageName: node
linkType: hard
"@calcom/platform-libraries-0.0.4@npm:@calcom/platform-libraries@0.0.4":
version: 0.0.4
resolution: "@calcom/platform-libraries@npm:0.0.4"
dependencies:
"@calcom/core": "*"
"@calcom/features": "*"
"@calcom/lib": "*"
checksum: 3e8cba76f767398f9ffb95575913514925b7a51a15791741a521284eabdaba5d52f85fbcd9f032b3f2f3a6130520e67049cd8ba6f96ae4f87597de5019710c7f
languageName: node
linkType: hard
"@calcom/platform-libraries@workspace:packages/platform/libraries":
version: 0.0.0-use.local
resolution: "@calcom/platform-libraries@workspace:packages/platform/libraries"
@@ -5384,7 +5408,6 @@ __metadata:
dependencies:
"@algora/sdk": ^0.1.2
"@calcom/app-store": "*"
"@calcom/atoms": "workspace:^"
"@calcom/config": "*"
"@calcom/dayjs": "*"
"@calcom/embed-react": "workspace:^"
@@ -5430,7 +5453,6 @@ __metadata:
"@vercel/og": ^0.5.0
autoprefixer: ^10.4.12
bcryptjs: ^2.4.3
class-variance-authority: ^0.7.0
clsx: ^1.2.1
cobe: ^0.4.1
concurrently: ^7.6.0
@@ -5443,7 +5465,6 @@ __metadata:
env-cmd: ^10.1.0
eslint: ^8.34.0
fathom-client: ^3.5.0
framer-motion: ^11.0.25
globby: ^13.1.3
graphql: ^16.8.0
graphql-codegen: ^0.4.0
@@ -5453,7 +5474,7 @@ __metadata:
i18n-unused: ^0.13.0
iframe-resizer-react: ^1.1.0
keen-slider: ^6.8.0
lucide-react: ^0.364.0
lucide-react: ^0.171.0
micro: ^10.0.1
next: ^14.1.3
next-auth: ^4.22.1
@@ -5465,7 +5486,7 @@ __metadata:
prism-react-renderer: ^1.3.5
react: ^18.2.0
react-confetti: ^6.0.1
react-datocms: ^5.0.3
react-datocms: ^3.1.0
react-device-detect: ^2.2.2
react-dom: ^18.2.0
react-fast-marquee: ^1.6.4
@@ -5475,10 +5496,8 @@ __metadata:
react-live-chat-loader: ^2.8.1
react-markdown: ^9.0.1
react-merge-refs: 1.1.0
react-parallax-tilt: ^1.7.226
react-resize-detector: ^9.1.0
react-twemoji: ^0.3.0
react-twitter-embed: ^4.0.4
react-use-measure: ^2.1.1
react-wrap-balancer: ^1.0.0
remark: ^14.0.2
@@ -9150,59 +9169,6 @@ __metadata:
languageName: node
linkType: hard
"@mux/mux-player-react@npm:*":
version: 2.7.0
resolution: "@mux/mux-player-react@npm:2.7.0"
dependencies:
"@mux/mux-player": 2.7.0
"@mux/playback-core": 0.24.0
prop-types: ^15.7.2
peerDependencies:
"@types/react": ^17.0.0 || ^18
react: ^17.0.2 || ^18
react-dom: ^17.0.2 || ^18
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 15ea8a8b9b2c82252e0647c22f8a1fbe7a04efaf1eb7aa61d10e4665b3dad45057c97c47cd04b8da42d02aad8288555feb9de98a8b9059b730420a8d12cada73
languageName: node
linkType: hard
"@mux/mux-player@npm:2.7.0":
version: 2.7.0
resolution: "@mux/mux-player@npm:2.7.0"
dependencies:
"@mux/mux-video": 0.19.0
"@mux/playback-core": 0.24.0
media-chrome: ~3.2.1
checksum: d3254c5d82f42d26e4ec3c27e50b6438a7dc74863381ddd9003bdc92947f6db4fc45b8caee21de9125aa80e71536268426d7159617dbace739de99f8f0ed259f
languageName: node
linkType: hard
"@mux/mux-video@npm:0.19.0":
version: 0.19.0
resolution: "@mux/mux-video@npm:0.19.0"
dependencies:
"@mux/playback-core": 0.24.0
castable-video: ~1.0.6
custom-media-element: ~1.2.3
media-tracks: ~0.3.0
checksum: 342d6f29fc60a9ea9694a8d794f679306e194f5de5d5cf85ed67391dce8d70330c9d78ee2681aee0e332ab1d2583a8a0174addf1182d0cf1c08fb85a01da849d
languageName: node
linkType: hard
"@mux/playback-core@npm:0.24.0":
version: 0.24.0
resolution: "@mux/playback-core@npm:0.24.0"
dependencies:
hls.js: ~1.5.8
mux-embed: ~5.2.0
checksum: b9783c05b1c009fb296a84de61d7b693fd81996bd35ef26aca7342a09eee476e4d8840fde1ef89273b01db2d4f3b57692101ae7bcc4cb5b15d4d6786bd0ba961
languageName: node
linkType: hard
"@ndelangen/get-tarball@npm:^3.0.7":
version: 3.0.9
resolution: "@ndelangen/get-tarball@npm:3.0.9"
@@ -10471,6 +10437,13 @@ __metadata:
languageName: node
linkType: hard
"@polka/url@npm:^1.0.0-next.24":
version: 1.0.0-next.25
resolution: "@polka/url@npm:1.0.0-next.25"
checksum: 4ab1d7a37163139c0e7bfc9d1e3f6a2a0db91a78b9f0a21f571d6aec2cdaeaacced744d47886c117aa7579aa5694b303fe3e0bd1922bb9cb3ce6bf7c2dc09801
languageName: node
linkType: hard
"@prisma/client@npm:5.4.2, @prisma/client@npm:^5.4.2":
version: 5.4.2
resolution: "@prisma/client@npm:5.4.2"
@@ -14967,6 +14940,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-darwin-arm64@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-darwin-arm64@npm:1.5.28"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@swc/core-darwin-x64@npm:1.3.100":
version: 1.3.100
resolution: "@swc/core-darwin-x64@npm:1.3.100"
@@ -14974,6 +14954,20 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-darwin-x64@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-darwin-x64@npm:1.5.28"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@swc/core-linux-arm-gnueabihf@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.5.28"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@swc/core-linux-arm64-gnu@npm:1.3.100":
version: 1.3.100
resolution: "@swc/core-linux-arm64-gnu@npm:1.3.100"
@@ -14981,6 +14975,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-linux-arm64-gnu@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-linux-arm64-gnu@npm:1.5.28"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-arm64-musl@npm:1.3.100":
version: 1.3.100
resolution: "@swc/core-linux-arm64-musl@npm:1.3.100"
@@ -14988,6 +14989,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-linux-arm64-musl@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-linux-arm64-musl@npm:1.5.28"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@swc/core-linux-x64-gnu@npm:1.3.100":
version: 1.3.100
resolution: "@swc/core-linux-x64-gnu@npm:1.3.100"
@@ -14995,6 +15003,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-linux-x64-gnu@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-linux-x64-gnu@npm:1.5.28"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-x64-musl@npm:1.3.100":
version: 1.3.100
resolution: "@swc/core-linux-x64-musl@npm:1.3.100"
@@ -15002,6 +15017,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-linux-x64-musl@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-linux-x64-musl@npm:1.5.28"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@swc/core-win32-arm64-msvc@npm:1.3.100":
version: 1.3.100
resolution: "@swc/core-win32-arm64-msvc@npm:1.3.100"
@@ -15009,6 +15031,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-win32-arm64-msvc@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-win32-arm64-msvc@npm:1.5.28"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@swc/core-win32-ia32-msvc@npm:1.3.100":
version: 1.3.100
resolution: "@swc/core-win32-ia32-msvc@npm:1.3.100"
@@ -15016,6 +15045,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-win32-ia32-msvc@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-win32-ia32-msvc@npm:1.5.28"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@swc/core-win32-x64-msvc@npm:1.3.100":
version: 1.3.100
resolution: "@swc/core-win32-x64-msvc@npm:1.3.100"
@@ -15023,6 +15059,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-win32-x64-msvc@npm:1.5.28":
version: 1.5.28
resolution: "@swc/core-win32-x64-msvc@npm:1.5.28"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@swc/core@npm:^1.3.82":
version: 1.3.100
resolution: "@swc/core@npm:1.3.100"
@@ -15066,6 +15109,52 @@ __metadata:
languageName: node
linkType: hard
"@swc/core@npm:^1.5.7":
version: 1.5.28
resolution: "@swc/core@npm:1.5.28"
dependencies:
"@swc/core-darwin-arm64": 1.5.28
"@swc/core-darwin-x64": 1.5.28
"@swc/core-linux-arm-gnueabihf": 1.5.28
"@swc/core-linux-arm64-gnu": 1.5.28
"@swc/core-linux-arm64-musl": 1.5.28
"@swc/core-linux-x64-gnu": 1.5.28
"@swc/core-linux-x64-musl": 1.5.28
"@swc/core-win32-arm64-msvc": 1.5.28
"@swc/core-win32-ia32-msvc": 1.5.28
"@swc/core-win32-x64-msvc": 1.5.28
"@swc/counter": ^0.1.3
"@swc/types": ^0.1.8
peerDependencies:
"@swc/helpers": "*"
dependenciesMeta:
"@swc/core-darwin-arm64":
optional: true
"@swc/core-darwin-x64":
optional: true
"@swc/core-linux-arm-gnueabihf":
optional: true
"@swc/core-linux-arm64-gnu":
optional: true
"@swc/core-linux-arm64-musl":
optional: true
"@swc/core-linux-x64-gnu":
optional: true
"@swc/core-linux-x64-musl":
optional: true
"@swc/core-win32-arm64-msvc":
optional: true
"@swc/core-win32-ia32-msvc":
optional: true
"@swc/core-win32-x64-msvc":
optional: true
peerDependenciesMeta:
"@swc/helpers":
optional: true
checksum: 9bd81dff298989d840859e957121740d8dc3154d574092c7faca874382866143b2d8279b9d6a20355221de05b2d58305581d09cf229357d97acaa5d55221b14a
languageName: node
linkType: hard
"@swc/counter@npm:^0.1.1":
version: 0.1.2
resolution: "@swc/counter@npm:0.1.2"
@@ -15073,6 +15162,13 @@ __metadata:
languageName: node
linkType: hard
"@swc/counter@npm:^0.1.3":
version: 0.1.3
resolution: "@swc/counter@npm:0.1.3"
checksum: df8f9cfba9904d3d60f511664c70d23bb323b3a0803ec9890f60133954173047ba9bdeabce28cd70ba89ccd3fd6c71c7b0bd58be85f611e1ffbe5d5c18616598
languageName: node
linkType: hard
"@swc/helpers@npm:0.5.2":
version: 0.5.2
resolution: "@swc/helpers@npm:0.5.2"
@@ -15089,6 +15185,15 @@ __metadata:
languageName: node
linkType: hard
"@swc/types@npm:^0.1.8":
version: 0.1.8
resolution: "@swc/types@npm:0.1.8"
dependencies:
"@swc/counter": ^0.1.3
checksum: e564d0e37b0e28546973c6d50c7a179395912a97168d695cfe9cf1051199c8b828680cdafcb8d43948f76d3703873bafb88dfb5bc2dfe0596b4ad18fcaf90c80
languageName: node
linkType: hard
"@szmarczak/http-timer@npm:^4.0.5":
version: 4.0.6
resolution: "@szmarczak/http-timer@npm:4.0.6"
@@ -16164,7 +16269,7 @@ __metadata:
languageName: node
linkType: hard
"@types/jest@npm:^29.5.10":
"@types/jest@npm:^29.5.10, @types/jest@npm:^29.5.12":
version: 29.5.12
resolution: "@types/jest@npm:29.5.12"
dependencies:
@@ -17314,6 +17419,17 @@ __metadata:
languageName: node
linkType: hard
"@vitejs/plugin-react-swc@npm:^3.7.0":
version: 3.7.0
resolution: "@vitejs/plugin-react-swc@npm:3.7.0"
dependencies:
"@swc/core": ^1.5.7
peerDependencies:
vite: ^4 || ^5
checksum: 87ee71cc7d261a0047a1a080c81081cb163edd555e1c8c60ff3372d14b7d76a19a28a7a03334417f622e704abd67e39e89a59d45f4742ec7036ca2988ee6651f
languageName: node
linkType: hard
"@vitejs/plugin-react@npm:^2.2.0":
version: 2.2.0
resolution: "@vitejs/plugin-react@npm:2.2.0"
@@ -20422,15 +20538,6 @@ __metadata:
languageName: node
linkType: hard
"castable-video@npm:~1.0.6":
version: 1.0.9
resolution: "castable-video@npm:1.0.9"
dependencies:
custom-media-element: ~1.3.1
checksum: 85600d7a257458e3ea4771a056209abada26e6a7a97e42b03402e78acd0c622923d9a55d58332937693346433c4264f578a4bfc0a49f7817cd83dd65ffe7d877
languageName: node
linkType: hard
"ccount@npm:^2.0.0":
version: 2.0.1
resolution: "ccount@npm:2.0.1"
@@ -20888,15 +20995,6 @@ __metadata:
languageName: node
linkType: hard
"class-variance-authority@npm:^0.7.0":
version: 0.7.0
resolution: "class-variance-authority@npm:0.7.0"
dependencies:
clsx: 2.0.0
checksum: e7fd1fab433ef06f52a1b7b241b70b4a185864deef199d3b0a2c3412f1cc179517288264c383f3b971a00d76811625fc8f7ffe709e6170219e88cd7368f08a20
languageName: node
linkType: hard
"classnames@npm:^2.2.5, classnames@npm:^2.2.6":
version: 2.3.2
resolution: "classnames@npm:2.3.2"
@@ -21169,13 +21267,6 @@ __metadata:
languageName: node
linkType: hard
"clsx@npm:2.0.0":
version: 2.0.0
resolution: "clsx@npm:2.0.0"
checksum: a2cfb2351b254611acf92faa0daf15220f4cd648bdf96ce369d729813b85336993871a4bf6978ddea2b81b5a130478339c20d9d0b5c6fc287e5147f0c059276e
languageName: node
linkType: hard
"clsx@npm:^1.1.1":
version: 1.1.1
resolution: "clsx@npm:1.1.1"
@@ -22481,20 +22572,6 @@ __metadata:
languageName: node
linkType: hard
"custom-media-element@npm:~1.2.3":
version: 1.2.3
resolution: "custom-media-element@npm:1.2.3"
checksum: da43680c35c870cd80ea02b9fce3efe25e9502c7088de5ab0e47ef2734aa9ce7e147c95f78a10abd4c13c47a81eae4712a7651ad18f87e023bc371082ba09842
languageName: node
linkType: hard
"custom-media-element@npm:~1.3.1":
version: 1.3.1
resolution: "custom-media-element@npm:1.3.1"
checksum: d8dd50cca0e93746182e4c06290f5d96d7bf0b76d6d97fb2edbf74fd8245285e5e1c40a83f8b752aad8e9a140aff0f1a2fbbb7385c6e9f6c8d05053fd2aefeb1
languageName: node
linkType: hard
"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6":
version: 3.2.3
resolution: "d3-array@npm:3.2.3"
@@ -24007,6 +24084,13 @@ __metadata:
languageName: node
linkType: hard
"error-stack-parser-es@npm:^0.1.1":
version: 0.1.4
resolution: "error-stack-parser-es@npm:0.1.4"
checksum: 360da046245a42e0dbfe004d8549a224763ed249410041e8280e764743e04f405bd5167ed866d29037003516329349bd500dbb99591f04006b66697a9eb8ddd8
languageName: node
linkType: hard
"error-stack-parser@npm:^2.0.6":
version: 2.1.4
resolution: "error-stack-parser@npm:2.1.4"
@@ -26528,26 +26612,6 @@ __metadata:
languageName: node
linkType: hard
"framer-motion@npm:^11.0.25":
version: 11.1.7
resolution: "framer-motion@npm:11.1.7"
dependencies:
tslib: ^2.4.0
peerDependencies:
"@emotion/is-prop-valid": "*"
react: ^18.0.0
react-dom: ^18.0.0
peerDependenciesMeta:
"@emotion/is-prop-valid":
optional: true
react:
optional: true
react-dom:
optional: true
checksum: ba4f2f0823eec7b78fa87180545cf8c832929ce52de8bab34312a67bfd6dee4ab6ad17143b9df2efcd4ede7cb87fc326843a1101a6cad3031c89a6ad2160e037
languageName: node
linkType: hard
"fresh@npm:0.5.2, fresh@npm:^0.5.2":
version: 0.5.2
resolution: "fresh@npm:0.5.2"
@@ -28220,13 +28284,6 @@ __metadata:
languageName: node
linkType: hard
"hls.js@npm:~1.5.8":
version: 1.5.9
resolution: "hls.js@npm:1.5.9"
checksum: cc98c40079df0a4b454954bd551696487573235109fb39261fc7b6728ae1fb187391a703dbed404174aef384a6eb115c82e2cd0ded99cbbb86cba0e0b6f99a2b
languageName: node
linkType: hard
"hmac-drbg@npm:^1.0.1":
version: 1.0.1
resolution: "hmac-drbg@npm:1.0.1"
@@ -33007,6 +33064,15 @@ __metadata:
languageName: node
linkType: hard
"lucide-react@npm:^0.171.0":
version: 0.171.0
resolution: "lucide-react@npm:0.171.0"
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0
checksum: 768ffe368c52a518ee339203d86ff4479989ab4d79c0716f721900c4bb7392ef6ff7a14807f6a685abd74d27f4c1778170bff77a0ab4c3e06c17944b557d8300
languageName: node
linkType: hard
"lucide-react@npm:^0.364.0":
version: 0.364.0
resolution: "lucide-react@npm:0.364.0"
@@ -33575,20 +33641,6 @@ __metadata:
languageName: node
linkType: hard
"media-chrome@npm:~3.2.1":
version: 3.2.3
resolution: "media-chrome@npm:3.2.3"
checksum: 073757ff6952045052f9c63fbb688ac8ff9f54787f5e26eaa52f47d72a8c53df65a1b62a30e2c7d90631c8c59a0ec2b960e746ab3e6dca389a3f936b42c858f2
languageName: node
linkType: hard
"media-tracks@npm:~0.3.0":
version: 0.3.2
resolution: "media-tracks@npm:0.3.2"
checksum: c08ea931ded4e6b3db7e0fd47259622448d9d0e965795610953f32a2d6a52bc0025870f051c2c7934c823d4b1675dac736e428445e7cbcba4326b45bb59969cb
languageName: node
linkType: hard
"media-typer@npm:0.3.0":
version: 0.3.0
resolution: "media-typer@npm:0.3.0"
@@ -34825,6 +34877,13 @@ __metadata:
languageName: node
linkType: hard
"mrmime@npm:^2.0.0":
version: 2.0.0
resolution: "mrmime@npm:2.0.0"
checksum: f6fe11ec667c3d96f1ce5fd41184ed491d5f0a5f4045e82446a471ccda5f84c7f7610dff61d378b73d964f73a320bd7f89788f9e6b9403e32cc4be28ba99f569
languageName: node
linkType: hard
"ms@npm:2.0.0":
version: 2.0.0
resolution: "ms@npm:2.0.0"
@@ -34943,13 +35002,6 @@ __metadata:
languageName: node
linkType: hard
"mux-embed@npm:~5.2.0":
version: 5.2.1
resolution: "mux-embed@npm:5.2.1"
checksum: ec34d3e003e2520be6d1386ca2383daa198a1fa3a469ca3f06c11a742d6ab136eef4944c37e8f2dcaa0585e1b0a6dfc119e3cf2302c7eb0e7f2ebc7c1fc95fbd
languageName: node
linkType: hard
"mysql2@npm:3.9.1":
version: 3.9.1
resolution: "mysql2@npm:3.9.1"
@@ -36443,6 +36495,18 @@ __metadata:
languageName: node
linkType: hard
"open@npm:^10.1.0":
version: 10.1.0
resolution: "open@npm:10.1.0"
dependencies:
default-browser: ^5.2.1
define-lazy-prop: ^3.0.0
is-inside-container: ^1.0.0
is-wsl: ^3.1.0
checksum: 079b0771616bac13b08129b0300032dc9328d72f345e460dd0416b8a8196a5bdf5e0251fefec8aa2a6a97c736734ac65dd8f1d29ab3fc9a13e85624aa5bc4470
languageName: node
linkType: hard
"open@npm:^8.0.4":
version: 8.4.2
resolution: "open@npm:8.4.2"
@@ -37479,6 +37543,13 @@ __metadata:
languageName: node
linkType: hard
"perfect-debounce@npm:^1.0.0":
version: 1.0.0
resolution: "perfect-debounce@npm:1.0.0"
checksum: 220343acf52976947958fef3599849471605316e924fe19c633ae2772576298e9d38f02cefa8db46f06607505ce7b232cbb35c9bfd477bd0329bd0a2ce37c594
languageName: node
linkType: hard
"performance-now@npm:^2.1.0":
version: 2.1.0
resolution: "performance-now@npm:2.1.0"
@@ -39222,21 +39293,20 @@ __metadata:
languageName: node
linkType: hard
"react-datocms@npm:^5.0.3":
version: 5.0.3
resolution: "react-datocms@npm:5.0.3"
"react-datocms@npm:^3.1.0":
version: 3.1.4
resolution: "react-datocms@npm:3.1.4"
dependencies:
"@mux/mux-player-react": "*"
datocms-listen: ^0.1.9
datocms-structured-text-generic-html-renderer: ^2.0.1
datocms-structured-text-utils: ^2.0.1
react-intersection-observer: ^9.4.3
react-intersection-observer: ^8.33.1
react-string-replace: ^1.1.0
universal-base64: ^2.1.0
use-deep-compare-effect: ^1.6.1
peerDependencies:
react: ">= 16.12.0"
checksum: 22c20152afb54424acfe967a2c8c525cd9f132a33374f2aba0231f16ea64dade389b096e2dac8de9ffded612bc32e9891d725609ee947639fe1cef907cb143f5
checksum: 54aba12aef4937175c2011548a8a576c96c8d8a596e84d191826910624c1d596e76a49782689dc236388a10803b02e700ac820cb7500cca7fd147a81f6c544c3
languageName: node
linkType: hard
@@ -39488,16 +39558,12 @@ __metadata:
languageName: node
linkType: hard
"react-intersection-observer@npm:^9.4.3":
version: 9.10.2
resolution: "react-intersection-observer@npm:9.10.2"
"react-intersection-observer@npm:^8.33.1":
version: 8.34.0
resolution: "react-intersection-observer@npm:8.34.0"
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
react-dom:
optional: true
checksum: fea1104ae5515f01b6c13f8f8259bf04e44fc624e8ff5aecb1b24cca25cc04b62f7551586acd838a2c475d55fb07d9c042a412f90cd4c8e39ff4575d5a5a76ac
react: ^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0
checksum: 7713fecfd1512c7f5a60f9f0bf15403b8f8bbd4110bcafaeaea6de36a0e0eb60368c3638f99e9c97b75ad8fc787ea48c241dcb5c694f821d7f2976f709082cc5
languageName: node
linkType: hard
@@ -39607,16 +39673,6 @@ __metadata:
languageName: node
linkType: hard
"react-parallax-tilt@npm:^1.7.226":
version: 1.7.227
resolution: "react-parallax-tilt@npm:1.7.227"
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 07a1ef95e56d35a40942976f7e9960d985f50a3330d626884b8ef75860bac1bea4c40b09b610a44c0aaa200603845ffe4975deda56c540cc364b8a26feef703d
languageName: node
linkType: hard
"react-phone-input-2@npm:^2.15.1":
version: 2.15.1
resolution: "react-phone-input-2@npm:2.15.1"
@@ -40031,18 +40087,6 @@ __metadata:
languageName: node
linkType: hard
"react-twitter-embed@npm:^4.0.4":
version: 4.0.4
resolution: "react-twitter-embed@npm:4.0.4"
dependencies:
scriptjs: ^2.5.9
peerDependencies:
react: ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: cdb3c5bd04c4da0efa767476be47c0a3865fb6335f2a1b9e242170167b51615c38164223278cef60c77143c4bac27ba582cbea054d0af3f138104fa5ec537c4c
languageName: node
linkType: hard
"react-universal-interface@npm:^0.6.2":
version: 0.6.2
resolution: "react-universal-interface@npm:0.6.2"
@@ -41870,13 +41914,6 @@ __metadata:
languageName: node
linkType: hard
"scriptjs@npm:^2.5.9":
version: 2.5.9
resolution: "scriptjs@npm:2.5.9"
checksum: fc84cb6b60b6fb9aa6f1b3bc59fc94b233bd5241ed3a04233579014382b5eb60640269c87d8657902acc09f9b785ee33230c218627cea00e653564bda8f5acb6
languageName: node
linkType: hard
"scuid@npm:^1.1.0":
version: 1.1.0
resolution: "scuid@npm:1.1.0"
@@ -42387,6 +42424,17 @@ __metadata:
languageName: node
linkType: hard
"sirv@npm:^2.0.4":
version: 2.0.4
resolution: "sirv@npm:2.0.4"
dependencies:
"@polka/url": ^1.0.0-next.24
mrmime: ^2.0.0
totalist: ^3.0.0
checksum: 6853384a51d6ee9377dd657e2b257e0e98b29abbfbfa6333e105197f0f100c8c56a4520b47028b04ab1833cf2312526206f38fcd4f891c6df453f40da1a15a57
languageName: node
linkType: hard
"sisteransi@npm:^1.0.5":
version: 1.0.5
resolution: "sisteransi@npm:1.0.5"
@@ -44616,6 +44664,13 @@ __metadata:
languageName: node
linkType: hard
"totalist@npm:^3.0.0":
version: 3.0.1
resolution: "totalist@npm:3.0.1"
checksum: 5132d562cf88ff93fd710770a92f31dbe67cc19b5c6ccae2efc0da327f0954d211bbfd9456389655d726c624f284b4a23112f56d1da931ca7cfabbe1f45e778a
languageName: node
linkType: hard
"tough-cookie@npm:*, tough-cookie@npm:^4.1.2":
version: 4.1.2
resolution: "tough-cookie@npm:4.1.2"
@@ -44782,6 +44837,42 @@ __metadata:
languageName: node
linkType: hard
"ts-jest@npm:^29.1.2":
version: 29.1.4
resolution: "ts-jest@npm:29.1.4"
dependencies:
bs-logger: 0.x
fast-json-stable-stringify: 2.x
jest-util: ^29.0.0
json5: ^2.2.3
lodash.memoize: 4.x
make-error: 1.x
semver: ^7.5.3
yargs-parser: ^21.0.1
peerDependencies:
"@babel/core": ">=7.0.0-beta.0 <8"
"@jest/transform": ^29.0.0
"@jest/types": ^29.0.0
babel-jest: ^29.0.0
jest: ^29.0.0
typescript: ">=4.3 <6"
peerDependenciesMeta:
"@babel/core":
optional: true
"@jest/transform":
optional: true
"@jest/types":
optional: true
babel-jest:
optional: true
esbuild:
optional: true
bin:
ts-jest: cli.js
checksum: e36cba389adbb3700b46422e883c8d25e76febcc01c4a39c801ef15e6edbd6da1695bdd144100153e992f98a754aea4099906955b1b9a83c3c72d77009c3d7e2
languageName: node
linkType: hard
"ts-loader@npm:^9.4.3":
version: 9.5.1
resolution: "ts-loader@npm:9.5.1"
@@ -46578,6 +46669,28 @@ __metadata:
languageName: node
linkType: hard
"vite-plugin-inspect@npm:^0.8.4":
version: 0.8.4
resolution: "vite-plugin-inspect@npm:0.8.4"
dependencies:
"@antfu/utils": ^0.7.7
"@rollup/pluginutils": ^5.1.0
debug: ^4.3.4
error-stack-parser-es: ^0.1.1
fs-extra: ^11.2.0
open: ^10.1.0
perfect-debounce: ^1.0.0
picocolors: ^1.0.0
sirv: ^2.0.4
peerDependencies:
vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0
peerDependenciesMeta:
"@nuxt/kit":
optional: true
checksum: ba1a828ed13da03763f858452b7d514d5804c81abd97ba63366c85265dcdf148b3c93f92ddc6439ccef135768a5c7d74a89a4f2c5cc73bf2373135c151326562
languageName: node
linkType: hard
"vite@npm:^3.0.0 || ^4.0.0 || ^5.0.0-0, vite@npm:^3.1.0 || ^4.0.0 || ^5.0.0-0, vite@npm:^5.0.10, vite@npm:^5.0.12":
version: 5.1.6
resolution: "vite@npm:5.1.6"