diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index c2dc3fd31a..3fb97f3d6f 100644 --- a/apps/api/v2/package.json +++ b/apps/api/v2/package.json @@ -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": "*", diff --git a/apps/api/v2/src/app.ts b/apps/api/v2/src/app.ts index 76fe3f209d..dd0669ca81 100644 --- a/apps/api/v2/src/app.ts +++ b/apps/api/v2/src/app.ts @@ -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; diff --git a/apps/api/v2/src/ee/bookings/controllers/bookings.controller.e2e-spec.ts b/apps/api/v2/src/ee/bookings/controllers/bookings.controller.e2e-spec.ts index bce678bc80..52acb3970d 100644 --- a/apps/api/v2/src/ee/bookings/controllers/bookings.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/bookings/controllers/bookings.controller.e2e-spec.ts @@ -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 = moduleRef.get(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, diff --git a/apps/api/v2/src/ee/me/me.controller.e2e-spec.ts b/apps/api/v2/src/ee/me/me.controller.e2e-spec.ts index d5179acd75..4acc86b17e 100644 --- a/apps/api/v2/src/ee/me/me.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/me/me.controller.e2e-spec.ts @@ -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); }); diff --git a/apps/api/v2/src/ee/me/me.controller.ts b/apps/api/v2/src/ee/me/me.controller.ts index 584ff571ff..3f2b4c5287 100644 --- a/apps/api/v2/src/ee/me/me.controller.ts +++ b/apps/api/v2/src/ee/me/me.controller.ts @@ -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("/") diff --git a/apps/api/v2/src/ee/me/me.module.ts b/apps/api/v2/src/ee/me/me.module.ts index b8802914f6..1ee07742cf 100644 --- a/apps/api/v2/src/ee/me/me.module.ts +++ b/apps/api/v2/src/ee/me/me.module.ts @@ -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 {} diff --git a/apps/api/v2/src/ee/platform-endpoints-module.ts b/apps/api/v2/src/ee/platform-endpoints-module.ts index fdf3ef34bb..67c72ab70b 100644 --- a/apps/api/v2/src/ee/platform-endpoints-module.ts +++ b/apps/api/v2/src/ee/platform-endpoints-module.ts @@ -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, diff --git a/apps/api/v2/src/ee/schedules/schedules.module.ts b/apps/api/v2/src/ee/schedules/schedules.module.ts deleted file mode 100644 index 3fe53634ee..0000000000 --- a/apps/api/v2/src/ee/schedules/schedules.module.ts +++ /dev/null @@ -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 {} diff --git a/apps/api/v2/src/ee/schedules/controllers/schedules.controller.e2e-spec.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/controllers/schedules.controller.e2e-spec.ts similarity index 82% rename from apps/api/v2/src/ee/schedules/controllers/schedules.controller.e2e-spec.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/controllers/schedules.controller.e2e-spec.ts index 7e6dec77f5..8083886f73 100644 --- a/apps/api/v2/src/ee/schedules/controllers/schedules.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/controllers/schedules.controller.e2e-spec.ts @@ -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); diff --git a/apps/api/v2/src/ee/schedules/controllers/schedules.controller.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/controllers/schedules.controller.ts similarity index 65% rename from apps/api/v2/src/ee/schedules/controllers/schedules.controller.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/controllers/schedules.controller.ts index 50cb8e0897..2bf7eae8da 100644 --- a/apps/api/v2/src/ee/schedules/controllers/schedules.controller.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/controllers/schedules.controller.ts @@ -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 { + @Body() bodySchedule: CreateScheduleInput_2024_04_15 + ): Promise { 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 { + @ApiResponse({ + status: 200, + description: "Returns the default schedule", + type: GetDefaultScheduleOutput_2024_04_15, + }) + async getDefaultSchedule( + @GetUser() user: UserWithProfile + ): Promise { 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 { + ): Promise { 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 { + async getSchedules(@GetUser() user: UserWithProfile): Promise { 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 { + ): Promise { 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 { + ): Promise { await this.schedulesService.deleteUserSchedule(userId, scheduleId); return { diff --git a/apps/api/v2/src/modules/availabilities/inputs/create-availability.input.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/inputs/create-availability.input.ts similarity index 96% rename from apps/api/v2/src/modules/availabilities/inputs/create-availability.input.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/inputs/create-availability.input.ts index f03698705b..2c3724c20c 100644 --- a/apps/api/v2/src/modules/availabilities/inputs/create-availability.input.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/inputs/create-availability.input.ts @@ -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] }) diff --git a/apps/api/v2/src/ee/schedules/inputs/create-schedule.input.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input.ts similarity index 53% rename from apps/api/v2/src/ee/schedules/inputs/create-schedule.input.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input.ts index f90c291adf..fad2d245f0 100644 --- a/apps/api/v2/src/ee/schedules/inputs/create-schedule.input.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input.ts @@ -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; diff --git a/apps/api/v2/src/ee/schedules/outputs/create-schedule.output.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/create-schedule.output.ts similarity index 80% rename from apps/api/v2/src/ee/schedules/outputs/create-schedule.output.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/create-schedule.output.ts index bbcdd17887..6a1184683e 100644 --- a/apps/api/v2/src/ee/schedules/outputs/create-schedule.output.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/create-schedule.output.ts @@ -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; diff --git a/apps/api/v2/src/ee/schedules/outputs/delete-schedule.output.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/delete-schedule.output.ts similarity index 88% rename from apps/api/v2/src/ee/schedules/outputs/delete-schedule.output.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/delete-schedule.output.ts index 023c013170..d6427fb704 100644 --- a/apps/api/v2/src/ee/schedules/outputs/delete-schedule.output.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/delete-schedule.output.ts @@ -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; diff --git a/apps/api/v2/src/ee/schedules/outputs/get-default-schedule.output.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-default-schedule.output.ts similarity index 79% rename from apps/api/v2/src/ee/schedules/outputs/get-default-schedule.output.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-default-schedule.output.ts index cf8369fa21..0d14fb7326 100644 --- a/apps/api/v2/src/ee/schedules/outputs/get-default-schedule.output.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-default-schedule.output.ts @@ -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; diff --git a/apps/api/v2/src/ee/schedules/outputs/get-schedule.output.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-schedule.output.ts similarity index 80% rename from apps/api/v2/src/ee/schedules/outputs/get-schedule.output.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-schedule.output.ts index f49e291e23..ae7709e0ba 100644 --- a/apps/api/v2/src/ee/schedules/outputs/get-schedule.output.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-schedule.output.ts @@ -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; diff --git a/apps/api/v2/src/ee/schedules/outputs/get-schedules.output.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-schedules.output.ts similarity index 81% rename from apps/api/v2/src/ee/schedules/outputs/get-schedules.output.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-schedules.output.ts index 9f7f6ce32f..81a9b911bd 100644 --- a/apps/api/v2/src/ee/schedules/outputs/get-schedules.output.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/get-schedules.output.ts @@ -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; diff --git a/apps/api/v2/src/ee/schedules/outputs/schedule-updated.output.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/schedule-updated.output.ts similarity index 77% rename from apps/api/v2/src/ee/schedules/outputs/schedule-updated.output.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/schedule-updated.output.ts index a16600db98..036e84935a 100644 --- a/apps/api/v2/src/ee/schedules/outputs/schedule-updated.output.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/schedule-updated.output.ts @@ -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; diff --git a/apps/api/v2/src/ee/schedules/outputs/schedule.output.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/schedule.output.ts similarity index 100% rename from apps/api/v2/src/ee/schedules/outputs/schedule.output.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/schedule.output.ts diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/update-schedule.output.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/update-schedule.output.ts new file mode 100644 index 0000000000..8633a77d12 --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/outputs/update-schedule.output.ts @@ -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; +} diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_04_15/schedules.module.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/schedules.module.ts new file mode 100644 index 0000000000..c6564123a5 --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/schedules.module.ts @@ -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 {} diff --git a/apps/api/v2/src/ee/schedules/schedules.repository.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/schedules.repository.ts similarity index 84% rename from apps/api/v2/src/ee/schedules/schedules.repository.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/schedules.repository.ts index b447b6b469..a17f1ae6c4 100644 --- a/apps/api/v2/src/ee/schedules/schedules.repository.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/schedules.repository.ts @@ -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: { diff --git a/apps/api/v2/src/ee/schedules/services/schedules.service.ts b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/services/schedules.service.ts similarity index 82% rename from apps/api/v2/src/ee/schedules/services/schedules.service.ts rename to apps/api/v2/src/ee/schedules/schedules_2024_04_15/services/schedules.service.ts index 2e40a5c5dd..3d5d1d4a90 100644 --- a/apps/api/v2/src/ee/schedules/services/schedules.service.ts +++ b/apps/api/v2/src/ee/schedules/schedules_2024_04_15/services/schedules.service.ts @@ -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, + }; + } } diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_06_11/controllers/schedules.controller.e2e-spec.ts b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/controllers/schedules.controller.e2e-spec.ts new file mode 100644 index 0000000000..79e03b74cd --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/controllers/schedules.controller.e2e-spec.ts @@ -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(); + }); + }); +}); diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_06_11/controllers/schedules.controller.ts b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/controllers/schedules.controller.ts new file mode 100644 index 0000000000..3efd3647cd --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/controllers/schedules.controller.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + await this.schedulesService.deleteUserSchedule(userId, scheduleId); + + return { + status: SUCCESS_STATUS, + }; + } +} diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_06_11/schedules.module.ts b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/schedules.module.ts new file mode 100644 index 0000000000..0f92ac4daf --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/schedules.module.ts @@ -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 {} diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_06_11/schedules.repository.ts b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/schedules.repository.ts new file mode 100644 index 0000000000..ad9cecc3a1 --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/schedules.repository.ts @@ -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 & { + 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) { + 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> + ) { + 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, + }, + }); + } +} diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/input-schedules.service.ts b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/input-schedules.service.ts new file mode 100644 index 0000000000..087687aecb --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/input-schedules.service.ts @@ -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); + } +} diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/output-schedules.service.ts b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/output-schedules.service.ts new file mode 100644 index 0000000000..222f423e7f --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/output-schedules.service.ts @@ -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]; +} diff --git a/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/schedules.service.ts b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/schedules.service.ts new file mode 100644 index 0000000000..d68400a572 --- /dev/null +++ b/apps/api/v2/src/ee/schedules/schedules_2024_06_11/services/schedules.service.ts @@ -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 { + 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) { + if (userId !== schedule.userId) { + throw new ForbiddenException(`User with ID=${userId} does not own schedule with ID=${schedule.id}`); + } + } +} diff --git a/apps/api/v2/src/lib/api-versions.ts b/apps/api/v2/src/lib/api-versions.ts index 57537d5e80..c220d9c997 100644 --- a/apps/api/v2/src/lib/api-versions.ts +++ b/apps/api/v2/src/lib/api-versions.ts @@ -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; diff --git a/apps/api/v2/src/modules/availabilities/availabilities.module.ts b/apps/api/v2/src/modules/availabilities/availabilities.module.ts deleted file mode 100644 index f3fe35bf6a..0000000000 --- a/apps/api/v2/src/modules/availabilities/availabilities.module.ts +++ /dev/null @@ -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 {} diff --git a/apps/api/v2/src/modules/availabilities/availabilities.service.ts b/apps/api/v2/src/modules/availabilities/availabilities.service.ts deleted file mode 100644 index fc7dc14ec4..0000000000 --- a/apps/api/v2/src/modules/availabilities/availabilities.service.ts +++ /dev/null @@ -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, - }; - } -} diff --git a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts index 8a1f4f1935..5c6bff4ede 100644 --- a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts +++ b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts @@ -22,7 +22,6 @@ import { HttpStatus, Param, Patch, - BadRequestException, Delete, Query, NotFoundException, diff --git a/apps/api/v2/src/modules/oauth-clients/oauth-client.module.ts b/apps/api/v2/src/modules/oauth-clients/oauth-client.module.ts index 52c99eca64..04393d27e7 100644 --- a/apps/api/v2/src/modules/oauth-clients/oauth-client.module.ts +++ b/apps/api/v2/src/modules/oauth-clients/oauth-client.module.ts @@ -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, diff --git a/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts b/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts index 51c27cf440..e4e8f60201 100644 --- a/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts +++ b/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts @@ -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( diff --git a/apps/api/v2/src/modules/users/users.repository.ts b/apps/api/v2/src/modules/users/users.repository.ts index e03429b192..cd77469035 100644 --- a/apps/api/v2/src/modules/users/users.repository.ts +++ b/apps/api/v2/src/modules/users/users.repository.ts @@ -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) { diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index b83aa4195d..e8fa397b89 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -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": { diff --git a/packages/lib/availability.ts b/packages/lib/availability.ts index c2763786a6..b604e07742 100644 --- a/packages/lib/availability.ts +++ b/packages/lib/availability.ts @@ -128,10 +128,10 @@ export function getWorkingHours( } export function availabilityAsString( - availability: Availability, + availability: Pick, { locale, hour12 }: { locale?: string; hour12?: boolean } ) { - const weekSpan = (availability: Availability) => { + const weekSpan = (availability: Pick) => { 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) => { 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( diff --git a/packages/lib/schedules/index.ts b/packages/lib/schedules/index.ts index abca07a1d6..a8401e8101 100644 --- a/packages/lib/schedules/index.ts +++ b/packages/lib/schedules/index.ts @@ -1 +1 @@ -export * from "./client/transformers"; +export * from "./transformers"; diff --git a/packages/lib/schedules/client/transformers.ts b/packages/lib/schedules/transformers/for-atom.ts similarity index 69% rename from packages/lib/schedules/client/transformers.ts rename to packages/lib/schedules/transformers/for-atom.ts index 1a8ba47d23..c1f9332d07 100644 --- a/packages/lib/schedules/client/transformers.ts +++ b/packages/lib/schedules/transformers/for-atom.ts @@ -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[]; +type ScheduleOverride = Pick[]; -export type ScheduleWithAvailabilitiesForWeb = Pick & { - isManaged: boolean; - workingHours: ReturnType; - schedule: AvailabilityModel[]; - availability: ReturnType; - timeZone: string; - dateOverrides: ReturnType; - 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 & { availability: AvailabilityModel[] } -) => { +export const transformScheduleToAvailabilityForAtom = (schedule: { availability: ScheduleAvailability }) => { return schedule.availability.reduce( (schedule: Schedule, availability) => { availability.days.forEach((day) => { diff --git a/packages/lib/schedules/transformers/from-api.ts b/packages/lib/schedules/transformers/from-api.ts new file mode 100644 index 0000000000..c09cbf557a --- /dev/null +++ b/packages/lib/schedules/transformers/from-api.ts @@ -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]; +} diff --git a/packages/lib/schedules/transformers/index.ts b/packages/lib/schedules/transformers/index.ts new file mode 100644 index 0000000000..832691070b --- /dev/null +++ b/packages/lib/schedules/transformers/index.ts @@ -0,0 +1,2 @@ +export * from "./for-atom"; +export * from "./from-api"; diff --git a/packages/platform/atoms/availability/AvailabilitySettings.tsx b/packages/platform/atoms/availability/AvailabilitySettings.tsx index c3fdfc2103..b16c9ced0e 100644 --- a/packages/platform/atoms/availability/AvailabilitySettings.tsx +++ b/packages/platform/atoms/availability/AvailabilitySettings.tsx @@ -62,6 +62,8 @@ export type CustomClassNames = { }; }; +export type Availability = Pick; + 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) => ( - + {availabilityAsString(availability, { locale: i18n.language, hour12: timeFormat === 12 })}
diff --git a/packages/platform/atoms/availability/atom-api-transformers/transformApiScheduleForAtom.spec.ts b/packages/platform/atoms/availability/atom-api-transformers/transformApiScheduleForAtom.spec.ts new file mode 100644 index 0000000000..b147d841cc --- /dev/null +++ b/packages/platform/atoms/availability/atom-api-transformers/transformApiScheduleForAtom.spec.ts @@ -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 = { + 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); + }); +}); diff --git a/packages/platform/atoms/availability/atom-api-transformers/transformApiScheduleForAtom.ts b/packages/platform/atoms/availability/atom-api-transformers/transformApiScheduleForAtom.ts new file mode 100644 index 0000000000..3ae579bcb8 --- /dev/null +++ b/packages/platform/atoms/availability/atom-api-transformers/transformApiScheduleForAtom.ts @@ -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 | 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, + }; +} diff --git a/packages/platform/atoms/availability/atom-api-transformers/transformAtomScheduleForApi.spec.ts b/packages/platform/atoms/availability/atom-api-transformers/transformAtomScheduleForApi.spec.ts new file mode 100644 index 0000000000..5f32c06865 --- /dev/null +++ b/packages/platform/atoms/availability/atom-api-transformers/transformAtomScheduleForApi.spec.ts @@ -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); + }); +}); diff --git a/packages/platform/atoms/availability/atom-api-transformers/transformAtomScheduleForApi.ts b/packages/platform/atoms/availability/atom-api-transformers/transformAtomScheduleForApi.ts new file mode 100644 index 0000000000..358a05ce7a --- /dev/null +++ b/packages/platform/atoms/availability/atom-api-transformers/transformAtomScheduleForApi.ts @@ -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}`; +} diff --git a/packages/platform/atoms/availability/wrappers/AvailabilitySettingsPlatformWrapper.tsx b/packages/platform/atoms/availability/wrappers/AvailabilitySettingsPlatformWrapper.tsx index f2c31a6893..b44f82d903 100644 --- a/packages/platform/atoms/availability/wrappers/AvailabilitySettingsPlatformWrapper.tsx +++ b/packages/platform/atoms/availability/wrappers/AvailabilitySettingsPlatformWrapper.tsx @@ -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; }; customClassNames?: Partial; - onUpdateSuccess?: (res: ApiResponse) => void; + onUpdateSuccess?: (res: ApiResponse) => 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; + 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
Loading...
; - if (!userSchedule) return
No user schedule present
; + if (!atomSchedule) return
No user schedule present
; return ( { - 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} diff --git a/packages/platform/atoms/cal-provider/CalProvider.tsx b/packages/platform/atoms/cal-provider/CalProvider.tsx index 9ec6d10ed0..760e64d630 100644 --- a/packages/platform/atoms/cal-provider/CalProvider.tsx +++ b/packages/platform/atoms/cal-provider/CalProvider.tsx @@ -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); diff --git a/packages/platform/atoms/hooks/useDeleteSchedule.ts b/packages/platform/atoms/hooks/schedules/useDeleteSchedule.ts similarity index 89% rename from packages/platform/atoms/hooks/useDeleteSchedule.ts rename to packages/platform/atoms/hooks/schedules/useDeleteSchedule.ts index 0322dcbb31..8da80bce20 100644 --- a/packages/platform/atoms/hooks/useDeleteSchedule.ts +++ b/packages/platform/atoms/hooks/schedules/useDeleteSchedule.ts @@ -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, 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); }, diff --git a/packages/platform/atoms/hooks/schedules/useSchedule.ts b/packages/platform/atoms/hooks/schedules/useSchedule.ts new file mode 100644 index 0000000000..21f553c8f6 --- /dev/null +++ b/packages/platform/atoms/hooks/schedules/useSchedule.ts @@ -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(pathname).then((res) => { + if (res.data.status === SUCCESS_STATUS) { + return res.data.data; + } + throw new Error(res.data.error?.message); + }); + }, + }); + + return { isLoading, error, data }; +}; diff --git a/packages/platform/atoms/hooks/schedules/useSchedules.ts b/packages/platform/atoms/hooks/schedules/useSchedules.ts new file mode 100644 index 0000000000..20c61cadd9 --- /dev/null +++ b/packages/platform/atoms/hooks/schedules/useSchedules.ts @@ -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(pathname).then((res) => { + if (res.data.status === SUCCESS_STATUS) { + return res.data.data; + } + throw new Error(res.data.error?.message); + }); + }, + }); + + return { isLoading, error, data }; +}; diff --git a/packages/platform/atoms/hooks/useUpdateSchedule.ts b/packages/platform/atoms/hooks/schedules/useUpdateSchedule.ts similarity index 61% rename from packages/platform/atoms/hooks/useUpdateSchedule.ts rename to packages/platform/atoms/hooks/schedules/useUpdateSchedule.ts index dcc953135d..87f318bfbb 100644 --- a/packages/platform/atoms/hooks/useUpdateSchedule.ts +++ b/packages/platform/atoms/hooks/schedules/useUpdateSchedule.ts @@ -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) => void; + onSuccess?: (res: ApiResponse) => void; onError?: (err: ApiErrorResponse) => void; } @@ -26,14 +26,14 @@ const useUpdateSchedule = ( const queryClient = useQueryClient(); const mutation = useMutation< - ApiResponse, + ApiResponse, 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>(pathname, data).then((res) => { + return http.patch>(pathname, data).then((res) => { return res.data; }); }, diff --git a/packages/platform/atoms/hooks/useClientSchedule.ts b/packages/platform/atoms/hooks/useClientSchedule.ts deleted file mode 100644 index edc950942b..0000000000 --- a/packages/platform/atoms/hooks/useClientSchedule.ts +++ /dev/null @@ -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>(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; diff --git a/packages/platform/atoms/jest.config.json b/packages/platform/atoms/jest.config.json new file mode 100644 index 0000000000..53b778bff5 --- /dev/null +++ b/packages/platform/atoms/jest.config.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.ts$": "ts-jest" + } +} diff --git a/packages/platform/atoms/package.json b/packages/platform/atoms/package.json index 86b486383b..ab8cc4823c 100644 --- a/packages/platform/atoms/package.json +++ b/packages/platform/atoms/package.json @@ -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", diff --git a/packages/platform/constants/api.ts b/packages/platform/constants/api.ts index 0d54cb963b..32ff91fa8e 100644 --- a/packages/platform/constants/api.ts +++ b/packages/platform/constants/api.ts @@ -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"; diff --git a/packages/platform/libraries/index.ts b/packages/platform/libraries/index.ts index e4790782d0..36c73383ad 100644 --- a/packages/platform/libraries/index.ts +++ b/packages/platform/libraries/index.ts @@ -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, diff --git a/packages/platform/types/index.ts b/packages/platform/types/index.ts index 634d5f211d..91b03c3138 100644 --- a/packages/platform/types/index.ts +++ b/packages/platform/types/index.ts @@ -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"; diff --git a/packages/platform/types/schedules/index.ts b/packages/platform/types/schedules/index.ts new file mode 100644 index 0000000000..c6de942b32 --- /dev/null +++ b/packages/platform/types/schedules/index.ts @@ -0,0 +1,2 @@ +export * from "./schedules-2024-06-11"; +export * from "./schedules-2024-04-15"; diff --git a/packages/platform/types/schedules/schedules-2024-04-15/index.ts b/packages/platform/types/schedules/schedules-2024-04-15/index.ts new file mode 100644 index 0000000000..7e6e7b459a --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-04-15/index.ts @@ -0,0 +1 @@ +export * from "./schedules"; diff --git a/packages/platform/types/schedules.ts b/packages/platform/types/schedules/schedules-2024-04-15/schedules.ts similarity index 88% rename from packages/platform/types/schedules.ts rename to packages/platform/types/schedules/schedules-2024-04-15/schedules.ts index f9fe1578ed..26964bd52a 100644 --- a/packages/platform/types/schedules.ts +++ b/packages/platform/types/schedules/schedules-2024-04-15/schedules.ts @@ -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; +export type ScheduleResponse = z.infer; class ScheduleItem { @IsString() @@ -56,7 +56,7 @@ class DateOverride { end!: Date; } -export class UpdateScheduleInput { +export class UpdateScheduleInput_2024_04_15 { @IsString() @IsOptional() @DocsProperty() diff --git a/packages/platform/types/schedules/schedules-2024-06-11/constants.ts b/packages/platform/types/schedules/schedules-2024-06-11/constants.ts new file mode 100644 index 0000000000..23ce20f2f4 --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/constants.ts @@ -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)$/; diff --git a/packages/platform/types/schedules/schedules-2024-06-11/index.ts b/packages/platform/types/schedules/schedules-2024-06-11/index.ts new file mode 100644 index 0000000000..2d351e9bd3 --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/index.ts @@ -0,0 +1,3 @@ +export * from "./inputs"; +export * from "./outputs"; +export * from "./constants"; diff --git a/packages/platform/types/schedules/schedules-2024-06-11/inputs/create-schedule.input.ts b/packages/platform/types/schedules/schedules-2024-06-11/inputs/create-schedule.input.ts new file mode 100644 index 0000000000..4dd63e9999 --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/inputs/create-schedule.input.ts @@ -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[]; +} diff --git a/packages/platform/types/schedules/schedules-2024-06-11/inputs/index.ts b/packages/platform/types/schedules/schedules-2024-06-11/inputs/index.ts new file mode 100644 index 0000000000..061eb32a22 --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/inputs/index.ts @@ -0,0 +1,2 @@ +export * from "./create-schedule.input"; +export * from "./update-schedule.input"; diff --git a/packages/platform/types/schedules/schedules-2024-06-11/inputs/update-schedule.input.ts b/packages/platform/types/schedules/schedules-2024-06-11/inputs/update-schedule.input.ts new file mode 100644 index 0000000000..cb53aa2a68 --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/inputs/update-schedule.input.ts @@ -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[]; +} diff --git a/apps/api/v2/src/ee/schedules/outputs/update-schedule.output.ts b/packages/platform/types/schedules/schedules-2024-06-11/outputs/create-schedule.output.ts similarity index 68% rename from apps/api/v2/src/ee/schedules/outputs/update-schedule.output.ts rename to packages/platform/types/schedules/schedules-2024-06-11/outputs/create-schedule.output.ts index 70ebe9ba9b..8276ba45e3 100644 --- a/apps/api/v2/src/ee/schedules/outputs/update-schedule.output.ts +++ b/packages/platform/types/schedules/schedules-2024-06-11/outputs/create-schedule.output.ts @@ -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; } diff --git a/packages/platform/types/schedules/schedules-2024-06-11/outputs/delete-schedule.output.ts b/packages/platform/types/schedules/schedules-2024-06-11/outputs/delete-schedule.output.ts new file mode 100644 index 0000000000..59ad6c1c7f --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/outputs/delete-schedule.output.ts @@ -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; +} diff --git a/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-default-schedule.output.ts b/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-default-schedule.output.ts new file mode 100644 index 0000000000..13b7f0c24e --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-default-schedule.output.ts @@ -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; +} diff --git a/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-schedule.output.ts b/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-schedule.output.ts new file mode 100644 index 0000000000..244e17f243 --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-schedule.output.ts @@ -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; +} diff --git a/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-schedules.output.ts b/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-schedules.output.ts new file mode 100644 index 0000000000..15661a1629 --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/outputs/get-schedules.output.ts @@ -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; +} diff --git a/packages/platform/types/schedules/schedules-2024-06-11/outputs/index.ts b/packages/platform/types/schedules/schedules-2024-06-11/outputs/index.ts new file mode 100644 index 0000000000..c53dcae82b --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/outputs/index.ts @@ -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"; diff --git a/packages/platform/types/schedules/schedules-2024-06-11/outputs/schedule.output.ts b/packages/platform/types/schedules/schedules-2024-06-11/outputs/schedule.output.ts new file mode 100644 index 0000000000..13c1bcbf39 --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/outputs/schedule.output.ts @@ -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[]; +} diff --git a/packages/platform/types/schedules/schedules-2024-06-11/outputs/update-schedule.output.ts b/packages/platform/types/schedules/schedules-2024-06-11/outputs/update-schedule.output.ts new file mode 100644 index 0000000000..fdd2306e2a --- /dev/null +++ b/packages/platform/types/schedules/schedules-2024-06-11/outputs/update-schedule.output.ts @@ -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; +} diff --git a/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts b/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts index 42fbf5a78b..0ede8713f0 100644 --- a/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts @@ -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 => { +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 { }, }); - const userAvailability = transformScheduleToAvailabilityForClient(schedule); + const userAvailability = transformScheduleToAvailabilityForAtom(schedule); return { schedule, diff --git a/yarn.lock b/yarn.lock index 9d8337d757..a5301b0b5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"