This reverts commit c25c835b76.
This commit is contained in:
@@ -2,9 +2,11 @@ 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 { 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";
|
||||
@@ -19,7 +21,6 @@ import { withAccessTokenAuth } from "test/utils/withAccessTokenAuth";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
import { handleNewBooking } from "@calcom/platform-libraries";
|
||||
import { CreateScheduleInput } from "@calcom/platform-types";
|
||||
import { ApiSuccessResponse, ApiResponse } from "@calcom/platform-types";
|
||||
|
||||
describe("Bookings Endpoints", () => {
|
||||
@@ -42,7 +43,7 @@ describe("Bookings Endpoints", () => {
|
||||
const moduleRef = await withAccessTokenAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule],
|
||||
imports: [AppModule, PrismaModule, AvailabilitiesModule, UsersModule, SchedulesModule],
|
||||
})
|
||||
)
|
||||
.overrideGuard(PermissionsGuard)
|
||||
|
||||
@@ -2,6 +2,7 @@ import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { SchedulesModule } from "@/ee/schedules/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";
|
||||
@@ -33,7 +34,14 @@ describe("Me Endpoints", () => {
|
||||
const moduleRef = await withAccessTokenAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule, SchedulesModule],
|
||||
imports: [
|
||||
AppModule,
|
||||
PrismaModule,
|
||||
AvailabilitiesModule,
|
||||
UsersModule,
|
||||
TokensModule,
|
||||
SchedulesModule,
|
||||
],
|
||||
})
|
||||
)
|
||||
.overrideGuard(PermissionsGuard)
|
||||
@@ -110,13 +118,13 @@ describe("Me Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should not update user associated with access token given invalid time format", async () => {
|
||||
const bodyWithIncorrectTimeFormat = { timeFormat: 100 };
|
||||
const bodyWithIncorrectTimeFormat: UpdateManagedUserInput = { timeFormat: 100 };
|
||||
|
||||
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 = { weekStart: "waba luba dub dub" };
|
||||
const bodyWithIncorrectWeekStart: UpdateManagedUserInput = { weekStart: "waba luba dub dub" };
|
||||
|
||||
return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectWeekStart).expect(400);
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ export class MeController {
|
||||
): Promise<UpdateMeOutput> {
|
||||
const updatedUser = await this.usersRepository.update(user.id, bodySchedule);
|
||||
if (bodySchedule.timeZone && user.defaultScheduleId) {
|
||||
await this.schedulesRepository.updateUserSchedule(user.id, user.defaultScheduleId, {
|
||||
await this.schedulesRepository.updateUserSchedule(user, user.defaultScheduleId, {
|
||||
timeZone: bodySchedule.timeZone,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
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 { 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";
|
||||
@@ -15,14 +20,6 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
|
||||
import { withAccessTokenAuth } from "test/utils/withAccessTokenAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import {
|
||||
CreateScheduleInput,
|
||||
CreateScheduleOutput,
|
||||
GetScheduleOutput,
|
||||
GetSchedulesOutput,
|
||||
ScheduleOutput,
|
||||
UpdateScheduleOutput,
|
||||
} from "@calcom/platform-types";
|
||||
import { UpdateScheduleInput } from "@calcom/platform-types";
|
||||
|
||||
describe("Schedules Endpoints", () => {
|
||||
@@ -35,27 +32,23 @@ describe("Schedules Endpoints", () => {
|
||||
const userEmail = "schedules-controller-e2e@api.com";
|
||||
let user: User;
|
||||
|
||||
const createScheduleInput: CreateScheduleInput = {
|
||||
name: "work",
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
|
||||
const defaultAvailability: CreateScheduleInput["availability"] = [
|
||||
{
|
||||
days: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
||||
startTime: "09:00",
|
||||
endTime: "17:00",
|
||||
},
|
||||
];
|
||||
|
||||
let createdSchedule: CreateScheduleOutput["data"];
|
||||
const defaultAvailabilityDays = [1, 2, 3, 4, 5];
|
||||
const defaultAvailabilityStartTime = "1970-01-01T09:00:00.000Z";
|
||||
const defaultAvailabilityEndTime = "1970-01-01T17:00:00.000Z";
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withAccessTokenAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule, SchedulesModule],
|
||||
imports: [
|
||||
AppModule,
|
||||
PrismaModule,
|
||||
AvailabilitiesModule,
|
||||
UsersModule,
|
||||
TokensModule,
|
||||
SchedulesModule,
|
||||
],
|
||||
})
|
||||
)
|
||||
.overrideGuard(PermissionsGuard)
|
||||
@@ -82,66 +75,60 @@ describe("Schedules Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create a default schedule", async () => {
|
||||
const scheduleName = "schedule-name";
|
||||
const scheduleTimeZone = "Europe/Rome";
|
||||
const isDefault = true;
|
||||
|
||||
const body: CreateScheduleInput = {
|
||||
name: scheduleName,
|
||||
timeZone: scheduleTimeZone,
|
||||
isDefault,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post("/api/v2/schedules")
|
||||
.send(createScheduleInput)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: CreateScheduleOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
createdSchedule = response.body.data;
|
||||
const responseData: CreateScheduleOutput = response.body;
|
||||
expect(responseData.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseData.data).toBeDefined();
|
||||
expect(responseData.data.isDefault).toEqual(isDefault);
|
||||
expect(responseData.data.timeZone).toEqual(scheduleTimeZone);
|
||||
expect(responseData.data.name).toEqual(scheduleName);
|
||||
|
||||
const expectedSchedule = {
|
||||
...createScheduleInput,
|
||||
availability: defaultAvailability,
|
||||
overrides: [],
|
||||
};
|
||||
outputScheduleMatchesExpected(createdSchedule, expectedSchedule, 1);
|
||||
const schedule = responseData.data.schedule;
|
||||
expect(schedule).toBeDefined();
|
||||
expect(schedule.length).toEqual(1);
|
||||
expect(schedule?.[0]?.days).toEqual(defaultAvailabilityDays);
|
||||
expect(schedule?.[0]?.startTime).toEqual(defaultAvailabilityStartTime);
|
||||
expect(schedule?.[0]?.endTime).toEqual(defaultAvailabilityEndTime);
|
||||
|
||||
const scheduleOwner = createdSchedule.ownerId
|
||||
? await userRepositoryFixture.get(createdSchedule.ownerId)
|
||||
const scheduleUser = schedule?.[0].userId
|
||||
? await userRepositoryFixture.get(schedule?.[0].userId)
|
||||
: null;
|
||||
expect(scheduleOwner?.defaultScheduleId).toEqual(createdSchedule.id);
|
||||
expect(scheduleUser?.defaultScheduleId).toEqual(responseData.data.id);
|
||||
createdSchedule = responseData.data;
|
||||
});
|
||||
});
|
||||
|
||||
function outputScheduleMatchesExpected(
|
||||
outputSchedule: ScheduleOutput | null,
|
||||
expected: CreateScheduleInput & { availability: CreateScheduleInput["availability"] } & {
|
||||
overrides: CreateScheduleInput["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")
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetScheduleOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const outputSchedule = responseBody.data;
|
||||
const responseData: CreateScheduleOutput = response.body;
|
||||
expect(responseData.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseData.data).toBeDefined();
|
||||
expect(responseData.data.id).toEqual(createdSchedule.id);
|
||||
expect(responseData.data.schedule?.[0].userId).toEqual(createdSchedule.schedule[0].userId);
|
||||
|
||||
const expectedSchedule = {
|
||||
...createScheduleInput,
|
||||
availability: defaultAvailability,
|
||||
overrides: [],
|
||||
};
|
||||
outputScheduleMatchesExpected(outputSchedule, expectedSchedule, 1);
|
||||
const schedule = responseData.data.schedule;
|
||||
expect(schedule).toBeDefined();
|
||||
expect(schedule.length).toEqual(1);
|
||||
expect(schedule?.[0]?.days).toEqual(defaultAvailabilityDays);
|
||||
expect(schedule?.[0]?.startTime).toEqual(defaultAvailabilityStartTime);
|
||||
expect(schedule?.[0]?.endTime).toEqual(defaultAvailabilityEndTime);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,21 +137,23 @@ describe("Schedules Endpoints", () => {
|
||||
.get(`/api/v2/schedules`)
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody: GetSchedulesOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const outputSchedule = responseBody.data[0];
|
||||
const responseData: GetSchedulesOutput = response.body;
|
||||
expect(responseData.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseData.data).toBeDefined();
|
||||
expect(responseData.data?.[0].id).toEqual(createdSchedule.id);
|
||||
expect(responseData.data?.[0].schedule?.[0].userId).toEqual(createdSchedule.schedule[0].userId);
|
||||
|
||||
const expectedSchedule = {
|
||||
...createScheduleInput,
|
||||
availability: defaultAvailability,
|
||||
overrides: [],
|
||||
};
|
||||
outputScheduleMatchesExpected(outputSchedule, expectedSchedule, 1);
|
||||
const schedule = responseData.data?.[0].schedule;
|
||||
expect(schedule).toBeDefined();
|
||||
expect(schedule.length).toEqual(1);
|
||||
expect(schedule?.[0]?.days).toEqual(defaultAvailabilityDays);
|
||||
expect(schedule?.[0]?.startTime).toEqual(defaultAvailabilityStartTime);
|
||||
expect(schedule?.[0]?.endTime).toEqual(defaultAvailabilityEndTime);
|
||||
});
|
||||
});
|
||||
|
||||
it("should update schedule name", async () => {
|
||||
const newScheduleName = "updated-schedule-name";
|
||||
const newScheduleName = "new-schedule-name";
|
||||
|
||||
const body: UpdateScheduleInput = {
|
||||
name: newScheduleName,
|
||||
@@ -177,41 +166,19 @@ describe("Schedules Endpoints", () => {
|
||||
.then((response: any) => {
|
||||
const responseData: UpdateScheduleOutput = response.body;
|
||||
expect(responseData.status).toEqual(SUCCESS_STATUS);
|
||||
const responseSchedule = responseData.data;
|
||||
expect(responseData.data).toBeDefined();
|
||||
expect(responseData.data.schedule.name).toEqual(newScheduleName);
|
||||
expect(responseData.data.schedule.id).toEqual(createdSchedule.id);
|
||||
expect(responseData.data.schedule.userId).toEqual(createdSchedule.schedule[0].userId);
|
||||
|
||||
const expectedSchedule = { ...createdSchedule, name: newScheduleName };
|
||||
outputScheduleMatchesExpected(responseSchedule, expectedSchedule, 1);
|
||||
const availability = responseData.data.schedule.availability;
|
||||
expect(availability).toBeDefined();
|
||||
expect(availability?.length).toEqual(1);
|
||||
expect(availability?.[0]?.days).toEqual(defaultAvailabilityDays);
|
||||
expect(availability?.[0]?.startTime).toEqual(defaultAvailabilityStartTime);
|
||||
expect(availability?.[0]?.endTime).toEqual(defaultAvailabilityEndTime);
|
||||
|
||||
createdSchedule = responseSchedule;
|
||||
});
|
||||
});
|
||||
|
||||
it("should add overrides", async () => {
|
||||
const overrides = [
|
||||
{
|
||||
date: "2026-05-05",
|
||||
startTime: "10:00",
|
||||
endTime: "12:00",
|
||||
},
|
||||
];
|
||||
|
||||
const body: UpdateScheduleInput = {
|
||||
overrides,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/schedules/${createdSchedule.id}`)
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.then((response: any) => {
|
||||
const responseData: UpdateScheduleOutput = response.body;
|
||||
expect(responseData.status).toEqual(SUCCESS_STATUS);
|
||||
const responseSchedule = responseData.data;
|
||||
|
||||
const expectedSchedule = { ...createdSchedule, overrides };
|
||||
outputScheduleMatchesExpected(responseSchedule, expectedSchedule, 1);
|
||||
|
||||
createdSchedule = responseSchedule;
|
||||
createdSchedule.name = newScheduleName;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
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 { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||
@@ -20,16 +26,9 @@ 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,
|
||||
CreateScheduleInput,
|
||||
UpdateScheduleInput,
|
||||
GetScheduleOutput,
|
||||
UpdateScheduleOutput,
|
||||
GetDefaultScheduleOutput,
|
||||
DeleteScheduleOutput,
|
||||
GetSchedulesOutput,
|
||||
} from "@calcom/platform-types";
|
||||
import { UpdateScheduleInput } from "@calcom/platform-types";
|
||||
|
||||
import { CreateScheduleInput } from "../inputs/create-schedule.input";
|
||||
|
||||
@Controller({
|
||||
path: "schedules",
|
||||
@@ -47,22 +46,26 @@ export class SchedulesController {
|
||||
@Body() bodySchedule: CreateScheduleInput
|
||||
): Promise<CreateScheduleOutput> {
|
||||
const schedule = await this.schedulesService.createUserSchedule(user.id, bodySchedule);
|
||||
const scheduleFormatted = await this.schedulesService.formatScheduleForAtom(user, schedule);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: schedule,
|
||||
data: scheduleFormatted,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("/default")
|
||||
@Permissions([SCHEDULE_READ])
|
||||
@ApiResponse({ status: 200, description: "Returns the default schedule", type: GetDefaultScheduleOutput })
|
||||
async getDefaultSchedule(@GetUser() user: UserWithProfile): Promise<GetScheduleOutput> {
|
||||
async getDefaultSchedule(@GetUser() user: UserWithProfile): Promise<GetDefaultScheduleOutput | null> {
|
||||
const schedule = await this.schedulesService.getUserScheduleDefault(user.id);
|
||||
const scheduleFormatted = schedule
|
||||
? await this.schedulesService.formatScheduleForAtom(user, schedule)
|
||||
: null;
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: schedule,
|
||||
data: scheduleFormatted,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,10 +77,11 @@ export class SchedulesController {
|
||||
@Param("scheduleId") scheduleId: number
|
||||
): Promise<GetScheduleOutput> {
|
||||
const schedule = await this.schedulesService.getUserSchedule(user.id, scheduleId);
|
||||
const scheduleFormatted = await this.schedulesService.formatScheduleForAtom(user, schedule);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: schedule,
|
||||
data: scheduleFormatted,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,13 +89,15 @@ export class SchedulesController {
|
||||
@Permissions([SCHEDULE_READ])
|
||||
async getSchedules(@GetUser() user: UserWithProfile): Promise<GetSchedulesOutput> {
|
||||
const schedules = await this.schedulesService.getUserSchedules(user.id);
|
||||
const schedulesFormatted = await this.schedulesService.formatSchedulesForAtom(user, schedules);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: schedules,
|
||||
data: schedulesFormatted,
|
||||
};
|
||||
}
|
||||
|
||||
// note(Lauris): currently this endpoint is atoms only
|
||||
@Patch("/:scheduleId")
|
||||
@Permissions([SCHEDULE_WRITE])
|
||||
async updateSchedule(
|
||||
@@ -100,7 +106,7 @@ export class SchedulesController {
|
||||
@Param("scheduleId") scheduleId: string
|
||||
): Promise<UpdateScheduleOutput> {
|
||||
const updatedSchedule = await this.schedulesService.updateUserSchedule(
|
||||
user.id,
|
||||
user,
|
||||
Number(scheduleId),
|
||||
bodySchedule
|
||||
);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { CreateAvailabilityInput } from "@/modules/availabilities/inputs/create-availability.input";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsArray, IsBoolean, IsTimeZone, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||
|
||||
export class CreateScheduleInput {
|
||||
@IsString()
|
||||
name!: string;
|
||||
|
||||
@IsTimeZone()
|
||||
timeZone!: string;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateAvailabilityInput)
|
||||
@IsOptional()
|
||||
availabilities?: CreateAvailabilityInput[];
|
||||
|
||||
@IsBoolean()
|
||||
isDefault!: boolean;
|
||||
}
|
||||
+1
-2
@@ -1,11 +1,10 @@
|
||||
import { ScheduleOutput } from "@/ee/schedules/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";
|
||||
|
||||
import { ScheduleOutput } from "./schedule.output";
|
||||
|
||||
export class CreateScheduleOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
+1
-2
@@ -1,11 +1,10 @@
|
||||
import { ScheduleOutput } from "@/ee/schedules/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";
|
||||
|
||||
import { ScheduleOutput } from "./schedule.output";
|
||||
|
||||
export class GetDefaultScheduleOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
+2
-5
@@ -1,11 +1,10 @@
|
||||
import { ScheduleOutput } from "@/ee/schedules/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";
|
||||
|
||||
import { ScheduleOutput } from "./schedule.output";
|
||||
|
||||
export class GetScheduleOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
@@ -17,7 +16,5 @@ export class GetScheduleOutput {
|
||||
@IsNotEmptyObject()
|
||||
@ValidateNested()
|
||||
@Type(() => ScheduleOutput)
|
||||
data!: ScheduleOutput | null;
|
||||
|
||||
error?: Error;
|
||||
data!: ScheduleOutput;
|
||||
}
|
||||
+4
-5
@@ -1,22 +1,21 @@
|
||||
import { ScheduleOutput } from "@/ee/schedules/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";
|
||||
|
||||
import { ScheduleOutput } from "./schedule.output";
|
||||
|
||||
export class GetSchedulesOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({ type: [ScheduleOutput] })
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ScheduleOutput)
|
||||
@IsArray()
|
||||
data!: ScheduleOutput[];
|
||||
|
||||
error?: Error;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Type } from "class-transformer";
|
||||
import { IsBoolean, IsInt, IsOptional, IsString, ValidateNested, IsArray } from "class-validator";
|
||||
|
||||
class EventTypeModel {
|
||||
@IsInt()
|
||||
id!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
eventName?: string | null;
|
||||
}
|
||||
|
||||
class AvailabilityModel {
|
||||
@IsInt()
|
||||
id!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
userId?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
scheduleId?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
eventTypeId?: number | null;
|
||||
|
||||
@IsArray()
|
||||
@IsInt({ each: true })
|
||||
days!: number[];
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Date)
|
||||
@IsString() // Assuming date is serialized/deserialized appropriately
|
||||
startTime?: Date;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Date)
|
||||
@IsString() // Assuming date is serialized/deserialized appropriately
|
||||
endTime?: Date;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Date)
|
||||
@IsString() // Assuming date is serialized/deserialized appropriately
|
||||
date?: Date | null;
|
||||
}
|
||||
|
||||
class ScheduleModel {
|
||||
@IsInt()
|
||||
id!: number;
|
||||
|
||||
@IsInt()
|
||||
userId!: number;
|
||||
|
||||
@IsString()
|
||||
name!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
timeZone?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => EventTypeModel)
|
||||
@IsArray()
|
||||
eventType?: EventTypeModel[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AvailabilityModel)
|
||||
@IsArray()
|
||||
availability?: AvailabilityModel[];
|
||||
}
|
||||
|
||||
export class UpdatedScheduleOutput {
|
||||
@ValidateNested()
|
||||
@Type(() => ScheduleModel)
|
||||
schedule!: ScheduleModel;
|
||||
|
||||
@IsBoolean()
|
||||
isDefault!: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
timeZone?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
prevDefaultId?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
currentDefaultId?: number | null;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Type } from "class-transformer";
|
||||
import { IsDate, IsOptional, IsArray, IsBoolean, IsInt, IsString, ValidateNested } from "class-validator";
|
||||
|
||||
class AvailabilityModel {
|
||||
@IsInt()
|
||||
id!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
userId?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
eventTypeId?: number | null;
|
||||
|
||||
@IsArray()
|
||||
@IsInt({ each: true })
|
||||
days!: number[];
|
||||
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
startTime!: Date;
|
||||
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
endTime!: Date;
|
||||
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
date?: Date | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
scheduleId?: number | null;
|
||||
}
|
||||
|
||||
class WorkingHours {
|
||||
@IsArray()
|
||||
@IsInt({ each: true })
|
||||
days!: number[];
|
||||
|
||||
@IsInt()
|
||||
startTime!: number;
|
||||
|
||||
@IsInt()
|
||||
endTime!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
userId?: number | null;
|
||||
}
|
||||
|
||||
class TimeRange {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
userId?: number | null;
|
||||
|
||||
@IsDate()
|
||||
start!: Date;
|
||||
|
||||
@IsDate()
|
||||
end!: Date;
|
||||
}
|
||||
|
||||
export class ScheduleOutput {
|
||||
@IsInt()
|
||||
id!: number;
|
||||
|
||||
@IsString()
|
||||
name!: string;
|
||||
|
||||
@IsBoolean()
|
||||
isManaged!: boolean;
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => WorkingHours)
|
||||
workingHours!: WorkingHours[];
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AvailabilityModel)
|
||||
@IsArray()
|
||||
schedule!: AvailabilityModel[];
|
||||
|
||||
availability!: TimeRange[][];
|
||||
|
||||
@IsString()
|
||||
timeZone!: string;
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@IsArray()
|
||||
// note(Lauris) it should be
|
||||
// dateOverrides!: { ranges: TimeRange[] }[];
|
||||
// but docs aren't generating correctly it results in array of strings
|
||||
dateOverrides!: unknown[];
|
||||
|
||||
@IsBoolean()
|
||||
isDefault!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
isLastSchedule!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
readOnly!: boolean;
|
||||
}
|
||||
+4
-7
@@ -1,23 +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";
|
||||
|
||||
import { ScheduleOutput } from "./schedule.output";
|
||||
|
||||
export class UpdateScheduleOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
type: UpdatedScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@ValidateNested()
|
||||
@Type(() => ScheduleOutput)
|
||||
data!: ScheduleOutput;
|
||||
|
||||
error?: Error;
|
||||
@Type(() => UpdatedScheduleOutput)
|
||||
data!: UpdatedScheduleOutput;
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
import { SchedulesController } from "@/ee/schedules/controllers/schedules.controller";
|
||||
import { SchedulesRepository } from "@/ee/schedules/schedules.repository";
|
||||
import { InputSchedulesService } from "@/ee/schedules/services/input-schedules.service";
|
||||
import { OutputSchedulesService } from "@/ee/schedules/services/output-schedules.service";
|
||||
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, UsersModule, TokensModule],
|
||||
providers: [SchedulesRepository, SchedulesService, InputSchedulesService, OutputSchedulesService],
|
||||
imports: [PrismaModule, AvailabilitiesModule, UsersModule, TokensModule],
|
||||
providers: [SchedulesRepository, SchedulesService],
|
||||
controllers: [SchedulesController],
|
||||
exports: [SchedulesService, SchedulesRepository],
|
||||
})
|
||||
|
||||
@@ -1,34 +1,19 @@
|
||||
import { CreateScheduleInput } from "@/ee/schedules/inputs/create-schedule.input";
|
||||
import { CreateAvailabilityInput } from "@/modules/availabilities/inputs/create-availability.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";
|
||||
|
||||
import type { CreateScheduleInput } from "@calcom/platform-types";
|
||||
|
||||
type InputScheduleAvailabilityTransformed = {
|
||||
days: number[];
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
};
|
||||
|
||||
type InputScheduleOverrideTransformed = {
|
||||
date: Date;
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
};
|
||||
|
||||
type InputScheduleTransformed = Omit<CreateScheduleInput, "availability" | "overrides"> & {
|
||||
availability: InputScheduleAvailabilityTransformed[];
|
||||
overrides: InputScheduleOverrideTransformed[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SchedulesRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async createSchedule(userId: number, schedule: Omit<InputScheduleTransformed, "isDefault">) {
|
||||
const { availability, overrides } = schedule;
|
||||
|
||||
async createScheduleWithAvailabilities(
|
||||
userId: number,
|
||||
schedule: CreateScheduleInput,
|
||||
availabilities: CreateAvailabilityInput[]
|
||||
) {
|
||||
const createScheduleData: Prisma.ScheduleCreateInput = {
|
||||
user: {
|
||||
connect: {
|
||||
@@ -39,34 +24,17 @@ export class SchedulesRepository {
|
||||
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) {
|
||||
if (availabilities.length > 0) {
|
||||
createScheduleData.availability = {
|
||||
createMany: {
|
||||
data: availabilitiesAndOverrides,
|
||||
data: availabilities.map((availability) => {
|
||||
return {
|
||||
days: availability.days,
|
||||
startTime: availability.startTime,
|
||||
endTime: availability.endTime,
|
||||
userId,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -96,87 +64,6 @@ export class SchedulesRepository {
|
||||
return schedule;
|
||||
}
|
||||
|
||||
async updateSchedule(
|
||||
userId: number,
|
||||
scheduleId: number,
|
||||
schedule: Partial<Omit<InputScheduleTransformed, "isDefault">>
|
||||
) {
|
||||
const { availability, overrides } = schedule;
|
||||
|
||||
const updateScheduleData: Prisma.ScheduleUpdateInput = {
|
||||
name: schedule.name,
|
||||
timeZone: schedule.timeZone,
|
||||
};
|
||||
|
||||
const availabilitiesAndOverrides: Prisma.AvailabilityCreateManyInput[] = [];
|
||||
|
||||
const deleteConditions = [];
|
||||
if (availability) {
|
||||
// note(Lauris): availabilities and overrides are stored in the same "Availability" table,
|
||||
// but availabilities have "date" field as null, while overrides have it as not null, so delete
|
||||
// condition below results in deleting only rows from Availability table that are availabilities.
|
||||
deleteConditions.push({
|
||||
scheduleId: { equals: scheduleId },
|
||||
date: null,
|
||||
});
|
||||
}
|
||||
|
||||
if (overrides) {
|
||||
// note(Lauris): availabilities and overrides are stored in the same "Availability" table,
|
||||
// but overrides have "date" field as not-null, while availabilities have it as null, so delete
|
||||
// condition below results in deleting only rows from Availability table that are overrides.
|
||||
deleteConditions.push({
|
||||
scheduleId: { equals: scheduleId },
|
||||
NOT: { date: null },
|
||||
});
|
||||
}
|
||||
|
||||
if (availability && availability.length > 0) {
|
||||
availability.forEach((availability) => {
|
||||
availabilitiesAndOverrides.push({
|
||||
days: availability.days,
|
||||
startTime: availability.startTime,
|
||||
endTime: availability.endTime,
|
||||
userId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (overrides && overrides.length > 0) {
|
||||
overrides.forEach((override) => {
|
||||
availabilitiesAndOverrides.push({
|
||||
date: override.date,
|
||||
startTime: override.startTime,
|
||||
endTime: override.endTime,
|
||||
userId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (availabilitiesAndOverrides.length > 0) {
|
||||
updateScheduleData.availability = {
|
||||
deleteMany: deleteConditions,
|
||||
createMany: {
|
||||
data: availabilitiesAndOverrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const updatedSchedule = await this.dbWrite.prisma.schedule.update({
|
||||
where: {
|
||||
id: scheduleId,
|
||||
},
|
||||
data: {
|
||||
...updateScheduleData,
|
||||
},
|
||||
include: {
|
||||
availability: true,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedSchedule;
|
||||
}
|
||||
|
||||
async getSchedulesByUserId(userId: number) {
|
||||
const schedules = await this.dbRead.prisma.schedule.findMany({
|
||||
where: {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { transformApiScheduleOverrides, transformApiScheduleAvailability } from "@calcom/platform-libraries";
|
||||
import { CreateScheduleInput, ScheduleAvailabilityInput } from "@calcom/platform-types";
|
||||
import { ScheduleOverrideInput } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class InputSchedulesService {
|
||||
transformInputCreateSchedule(inputSchedule: CreateScheduleInput) {
|
||||
const defaultAvailability: ScheduleAvailabilityInput[] = [
|
||||
{
|
||||
days: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
||||
startTime: "09:00",
|
||||
endTime: "17:00",
|
||||
},
|
||||
];
|
||||
const defaultOverrides: ScheduleOverrideInput[] = [];
|
||||
|
||||
const availability = this.transformInputScheduleAvailability(
|
||||
inputSchedule.availability || defaultAvailability
|
||||
);
|
||||
const overrides = this.transformInputOverrides(inputSchedule.overrides || defaultOverrides);
|
||||
|
||||
const internalCreateSchedule = {
|
||||
...inputSchedule,
|
||||
availability,
|
||||
overrides,
|
||||
};
|
||||
|
||||
return internalCreateSchedule;
|
||||
}
|
||||
|
||||
transformInputScheduleAvailability(inputAvailability: ScheduleAvailabilityInput[]) {
|
||||
return transformApiScheduleAvailability(inputAvailability);
|
||||
}
|
||||
|
||||
transformInputOverrides(inputOverrides: ScheduleOverrideInput[]) {
|
||||
return transformApiScheduleOverrides(inputOverrides);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
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 {
|
||||
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];
|
||||
}
|
||||
@@ -1,42 +1,55 @@
|
||||
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 { InputSchedulesService } from "@/ee/schedules/services/input-schedules.service";
|
||||
import { OutputSchedulesService } from "@/ee/schedules/services/output-schedules.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { AvailabilitiesService } from "@/modules/availabilities/availabilities.service";
|
||||
import { UserWithProfile, UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Schedule } from "@prisma/client";
|
||||
import { User } from "@prisma/client";
|
||||
|
||||
import { CreateScheduleInput, ScheduleOutput } from "@calcom/platform-types";
|
||||
import type { ScheduleWithAvailabilities } from "@calcom/platform-libraries";
|
||||
import { updateScheduleHandler } from "@calcom/platform-libraries";
|
||||
import {
|
||||
transformWorkingHoursForClient,
|
||||
transformAvailabilityForClient,
|
||||
transformDateOverridesForClient,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { UpdateScheduleInput } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class SchedulesService {
|
||||
constructor(
|
||||
private readonly schedulesRepository: SchedulesRepository,
|
||||
private readonly inputSchedulesService: InputSchedulesService,
|
||||
private readonly outputSchedulesService: OutputSchedulesService,
|
||||
private readonly availabilitiesService: AvailabilitiesService,
|
||||
private readonly usersRepository: UsersRepository
|
||||
) {}
|
||||
|
||||
async createUserDefaultSchedule(userId: number, timeZone: string) {
|
||||
const defaultSchedule = {
|
||||
const schedule = {
|
||||
isDefault: true,
|
||||
name: "Default schedule",
|
||||
timeZone,
|
||||
};
|
||||
|
||||
return this.createUserSchedule(userId, defaultSchedule);
|
||||
return this.createUserSchedule(userId, schedule);
|
||||
}
|
||||
|
||||
async createUserSchedule(userId: number, scheduleInput: CreateScheduleInput): Promise<ScheduleOutput> {
|
||||
const schedule = this.inputSchedulesService.transformInputCreateSchedule(scheduleInput);
|
||||
async createUserSchedule(userId: number, schedule: CreateScheduleInput) {
|
||||
const availabilities = schedule.availabilities?.length
|
||||
? schedule.availabilities
|
||||
: [this.availabilitiesService.getDefaultAvailabilityInput()];
|
||||
|
||||
const createdSchedule = await this.schedulesRepository.createSchedule(userId, schedule);
|
||||
const createdSchedule = await this.schedulesRepository.createScheduleWithAvailabilities(
|
||||
userId,
|
||||
schedule,
|
||||
availabilities
|
||||
);
|
||||
|
||||
if (schedule.isDefault) {
|
||||
await this.usersRepository.setDefaultSchedule(userId, createdSchedule.id);
|
||||
}
|
||||
|
||||
return this.outputSchedulesService.getResponseSchedule(createdSchedule);
|
||||
return createdSchedule;
|
||||
}
|
||||
|
||||
async getUserScheduleDefault(userId: number) {
|
||||
@@ -44,10 +57,7 @@ export class SchedulesService {
|
||||
|
||||
if (!user?.defaultScheduleId) return null;
|
||||
|
||||
const defaultSchedule = await this.schedulesRepository.getScheduleById(user.defaultScheduleId);
|
||||
|
||||
if (!defaultSchedule) return null;
|
||||
return this.outputSchedulesService.getResponseSchedule(defaultSchedule);
|
||||
return this.schedulesRepository.getScheduleById(user.defaultScheduleId);
|
||||
}
|
||||
|
||||
async getUserSchedule(userId: number, scheduleId: number) {
|
||||
@@ -59,45 +69,38 @@ export class SchedulesService {
|
||||
|
||||
this.checkUserOwnsSchedule(userId, existingSchedule);
|
||||
|
||||
return this.outputSchedulesService.getResponseSchedule(existingSchedule);
|
||||
return existingSchedule;
|
||||
}
|
||||
|
||||
async getUserSchedules(userId: number) {
|
||||
const schedules = await this.schedulesRepository.getSchedulesByUserId(userId);
|
||||
return Promise.all(
|
||||
schedules.map(async (schedule) => {
|
||||
return this.outputSchedulesService.getResponseSchedule(schedule);
|
||||
})
|
||||
);
|
||||
return this.schedulesRepository.getSchedulesByUserId(userId);
|
||||
}
|
||||
|
||||
async updateUserSchedule(userId: number, scheduleId: number, bodySchedule: UpdateScheduleInput) {
|
||||
async updateUserSchedule(user: UserWithProfile, scheduleId: number, bodySchedule: UpdateScheduleInput) {
|
||||
const existingSchedule = await this.schedulesRepository.getScheduleById(scheduleId);
|
||||
|
||||
if (!existingSchedule) {
|
||||
throw new NotFoundException(`Schedule with ID=${scheduleId} does not exist.`);
|
||||
}
|
||||
|
||||
this.checkUserOwnsSchedule(userId, existingSchedule);
|
||||
this.checkUserOwnsSchedule(user.id, existingSchedule);
|
||||
|
||||
const availability = bodySchedule.availability
|
||||
? this.inputSchedulesService.transformInputScheduleAvailability(bodySchedule.availability)
|
||||
: undefined;
|
||||
const overrides = bodySchedule.overrides
|
||||
? this.inputSchedulesService.transformInputOverrides(bodySchedule.overrides)
|
||||
: undefined;
|
||||
const schedule = await this.getUserSchedule(user.id, Number(scheduleId));
|
||||
const scheduleFormatted = await this.formatScheduleForAtom(user, schedule);
|
||||
|
||||
if (bodySchedule.isDefault) {
|
||||
await this.usersRepository.setDefaultSchedule(userId, scheduleId);
|
||||
if (!bodySchedule.schedule) {
|
||||
// note(Lauris): When updating an availability in cal web app, lets say only its name, also
|
||||
// the schedule is sent and then passed to the update handler. Notably, availability is passed too
|
||||
// and they have same shape, so to match shapes I attach "scheduleFormatted.availability" to reflect
|
||||
// schedule that would be passed by the web app. If we don't, then updating schedule name will erase
|
||||
// schedule.
|
||||
bodySchedule.schedule = scheduleFormatted.availability;
|
||||
}
|
||||
|
||||
const updatedSchedule = await this.schedulesRepository.updateSchedule(userId, scheduleId, {
|
||||
...bodySchedule,
|
||||
availability,
|
||||
overrides,
|
||||
return updateScheduleHandler({
|
||||
input: { scheduleId: Number(scheduleId), ...bodySchedule },
|
||||
ctx: { user },
|
||||
});
|
||||
|
||||
return this.outputSchedulesService.getResponseSchedule(updatedSchedule);
|
||||
}
|
||||
|
||||
async deleteUserSchedule(userId: number, scheduleId: number) {
|
||||
@@ -112,6 +115,44 @@ export class SchedulesService {
|
||||
return this.schedulesRepository.deleteScheduleById(scheduleId);
|
||||
}
|
||||
|
||||
async formatScheduleForAtom(user: User, schedule: ScheduleWithAvailabilities): Promise<ScheduleOutput> {
|
||||
const usersSchedulesCount = await this.schedulesRepository.getUserSchedulesCount(user.id);
|
||||
return this.transformScheduleForAtom(schedule, usersSchedulesCount, user);
|
||||
}
|
||||
|
||||
async formatSchedulesForAtom(
|
||||
user: User,
|
||||
schedules: ScheduleWithAvailabilities[]
|
||||
): Promise<ScheduleOutput[]> {
|
||||
const usersSchedulesCount = await this.schedulesRepository.getUserSchedulesCount(user.id);
|
||||
return Promise.all(
|
||||
schedules.map((schedule) => this.transformScheduleForAtom(schedule, usersSchedulesCount, user))
|
||||
);
|
||||
}
|
||||
|
||||
async transformScheduleForAtom(
|
||||
schedule: ScheduleWithAvailabilities,
|
||||
userSchedulesCount: number,
|
||||
user: Pick<User, "id" | "defaultScheduleId" | "timeZone">
|
||||
): Promise<ScheduleOutput> {
|
||||
const timeZone = schedule.timeZone || user.timeZone;
|
||||
const defaultSchedule = await this.getUserScheduleDefault(user.id);
|
||||
|
||||
return {
|
||||
id: schedule.id,
|
||||
name: schedule.name,
|
||||
isManaged: schedule.userId !== user.id,
|
||||
workingHours: transformWorkingHoursForClient(schedule),
|
||||
schedule: schedule.availability,
|
||||
availability: transformAvailabilityForClient(schedule),
|
||||
timeZone,
|
||||
dateOverrides: transformDateOverridesForClient(schedule, timeZone),
|
||||
isDefault: defaultSchedule?.id === schedule.id,
|
||||
isLastSchedule: userSchedulesCount <= 1,
|
||||
readOnly: schedule.userId !== user.id,
|
||||
};
|
||||
}
|
||||
|
||||
checkUserOwnsSchedule(userId: number, schedule: Pick<Schedule, "id" | "userId">) {
|
||||
if (userId !== schedule.userId) {
|
||||
throw new ForbiddenException(`User with ID=${userId} does not own schedule with ID=${schedule.id}`);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
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 {}
|
||||
@@ -0,0 +1,16 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Transform, TransformFnParams } from "class-transformer";
|
||||
import { IsArray, IsDate, IsNumber } from "class-validator";
|
||||
|
||||
export class CreateAvailabilityInput {
|
||||
@IsArray()
|
||||
@IsNumber({}, { each: true })
|
||||
@ApiProperty({ example: [1, 2] })
|
||||
days!: number[];
|
||||
|
||||
@IsDate()
|
||||
@Transform(({ value, key }: TransformFnParams) => transformStringToDate(value, key))
|
||||
startTime!: Date;
|
||||
|
||||
@IsDate()
|
||||
@Transform(({ value, key }: TransformFnParams) => transformStringToDate(value, key))
|
||||
endTime!: Date;
|
||||
}
|
||||
|
||||
function transformStringToDate(value: string, key: string): Date {
|
||||
// note(Lauris): incoming value is ISO8061 e.g. 2025-0412T13:17:56.324Z
|
||||
const dateTimeParts = value.split("T");
|
||||
|
||||
const timePart = dateTimeParts[1].split(".")[0]; // Removes milliseconds
|
||||
const parts = timePart.split(":");
|
||||
|
||||
if (parts.length !== 3) {
|
||||
throw new BadRequestException(
|
||||
`Invalid time format. Expected format(ISO8061): 2025-0412T13:17:56.324Z. Received: ${value}`
|
||||
);
|
||||
}
|
||||
const [hours, minutes, seconds] = parts.map(Number);
|
||||
|
||||
if (hours < 0 || hours > 23) {
|
||||
throw new BadRequestException(`Invalid ${key} hours. Expected value between 0 and 23`);
|
||||
}
|
||||
|
||||
if (minutes < 0 || minutes > 59) {
|
||||
throw new BadRequestException(`Invalid ${key} minutes. Expected value between 0 and 59`);
|
||||
}
|
||||
|
||||
if (seconds < 0 || seconds > 59) {
|
||||
throw new BadRequestException(`Invalid ${key} seconds. Expected value between 0 and 59`);
|
||||
}
|
||||
|
||||
return new Date(new Date().setUTCHours(hours, minutes, seconds, 0));
|
||||
}
|
||||
+1
@@ -21,6 +21,7 @@ import {
|
||||
HttpStatus,
|
||||
Param,
|
||||
Patch,
|
||||
BadRequestException,
|
||||
Delete,
|
||||
Query,
|
||||
NotFoundException,
|
||||
|
||||
@@ -173,14 +173,6 @@ 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) {
|
||||
|
||||
@@ -2883,26 +2883,26 @@
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"ScheduleAvailabilityInput": {
|
||||
"CreateAvailabilityInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"days": {
|
||||
"example": [
|
||||
"Monday",
|
||||
"Tuesday"
|
||||
1,
|
||||
2
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"startTime": {
|
||||
"type": "string",
|
||||
"example": "09:00"
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"endTime": {
|
||||
"type": "string",
|
||||
"example": "10:00"
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2911,71 +2911,23 @@
|
||||
"endTime"
|
||||
]
|
||||
},
|
||||
"ScheduleOverrideInput": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "One-on-one coaching"
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string",
|
||||
"example": "Europe/Rome"
|
||||
"type": "string"
|
||||
},
|
||||
"availability": {
|
||||
"example": [
|
||||
{
|
||||
"days": [
|
||||
"Monday",
|
||||
"Tuesday"
|
||||
],
|
||||
"startTime": "09:00",
|
||||
"endTime": "10:00"
|
||||
}
|
||||
],
|
||||
"availabilities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ScheduleAvailabilityInput"
|
||||
"$ref": "#/components/schemas/CreateAvailabilityInput"
|
||||
}
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"overrides": {
|
||||
"example": [
|
||||
{
|
||||
"date": "2024-05-20",
|
||||
"startTime": "12:00",
|
||||
"endTime": "14:00"
|
||||
}
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ScheduleOverrideInput"
|
||||
}
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2984,67 +2936,163 @@
|
||||
"isDefault"
|
||||
]
|
||||
},
|
||||
"ScheduleOutput": {
|
||||
"WorkingHours": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"startTime": {
|
||||
"type": "number"
|
||||
},
|
||||
"endTime": {
|
||||
"type": "number"
|
||||
},
|
||||
"userId": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"days",
|
||||
"startTime",
|
||||
"endTime"
|
||||
]
|
||||
},
|
||||
"AvailabilityModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"userId": {
|
||||
"type": "number",
|
||||
"example": 254
|
||||
"nullable": true
|
||||
},
|
||||
"ownerId": {
|
||||
"eventTypeId": {
|
||||
"type": "number",
|
||||
"example": 478
|
||||
"nullable": true
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ScheduleAvailabilityInput"
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
"startTime": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"overrides": {
|
||||
"example": [
|
||||
{
|
||||
"date": "2024-05-20",
|
||||
"startTime": "12:00",
|
||||
"endTime": "13:00"
|
||||
}
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ScheduleOverrideInput"
|
||||
}
|
||||
"endTime": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"scheduleId": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"days",
|
||||
"startTime",
|
||||
"endTime"
|
||||
]
|
||||
},
|
||||
"TimeRange": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"userId": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"start": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"end": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"start",
|
||||
"end"
|
||||
]
|
||||
},
|
||||
"ScheduleOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"isManaged": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"workingHours": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/WorkingHours"
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AvailabilityModel"
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"required": true,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TimeRange"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
},
|
||||
"dateOverrides": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isLastSchedule": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"readOnly": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"ownerId",
|
||||
"name",
|
||||
"timeZone",
|
||||
"isManaged",
|
||||
"workingHours",
|
||||
"schedule",
|
||||
"availability",
|
||||
"timeZone",
|
||||
"dateOverrides",
|
||||
"isDefault",
|
||||
"overrides"
|
||||
"isLastSchedule",
|
||||
"readOnly"
|
||||
]
|
||||
},
|
||||
"CreateScheduleOutput": {
|
||||
@@ -3079,7 +3127,12 @@
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/ScheduleOutput"
|
||||
"nullable": true,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ScheduleOutput"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3099,15 +3152,7 @@
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"nullable": true,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ScheduleOutput"
|
||||
}
|
||||
]
|
||||
},
|
||||
"error": {
|
||||
"type": "object"
|
||||
"$ref": "#/components/schemas/ScheduleOutput"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3127,13 +3172,7 @@
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ScheduleOutput"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"type": "object"
|
||||
"$ref": "#/components/schemas/ScheduleOutput"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3144,48 +3183,138 @@
|
||||
"UpdateScheduleInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"schedule": {
|
||||
"example": [
|
||||
[],
|
||||
[
|
||||
{
|
||||
"start": "2022-01-01T00:00:00.000Z",
|
||||
"end": "2022-01-02T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
],
|
||||
"items": {
|
||||
"type": "array"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"dateOverrides": {
|
||||
"example": [
|
||||
[],
|
||||
[
|
||||
{
|
||||
"start": "2022-01-01T00:00:00.000Z",
|
||||
"end": "2022-01-02T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
],
|
||||
"items": {
|
||||
"type": "array"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"timeZone",
|
||||
"name",
|
||||
"isDefault",
|
||||
"schedule"
|
||||
]
|
||||
},
|
||||
"EventTypeModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"eventName": {
|
||||
"type": "string",
|
||||
"example": "One-on-one coaching"
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"ScheduleModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"userId": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string",
|
||||
"example": "Europe/Rome"
|
||||
"nullable": true
|
||||
},
|
||||
"eventType": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/EventTypeModel"
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"example": [
|
||||
{
|
||||
"days": [
|
||||
"Monday",
|
||||
"Tuesday"
|
||||
],
|
||||
"startTime": "09:00",
|
||||
"endTime": "10:00"
|
||||
}
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ScheduleAvailabilityInput"
|
||||
}
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"overrides": {
|
||||
"example": [
|
||||
{
|
||||
"date": "2024-05-20",
|
||||
"startTime": "12:00",
|
||||
"endTime": "14:00"
|
||||
}
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ScheduleOverrideInput"
|
||||
"$ref": "#/components/schemas/AvailabilityModel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"userId",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"UpdatedScheduleOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schedule": {
|
||||
"$ref": "#/components/schemas/ScheduleModel"
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
},
|
||||
"prevDefaultId": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"currentDefaultId": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"schedule",
|
||||
"isDefault"
|
||||
]
|
||||
},
|
||||
"UpdateScheduleOutput": {
|
||||
"type": "object",
|
||||
@@ -3199,10 +3328,7 @@
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/ScheduleOutput"
|
||||
},
|
||||
"error": {
|
||||
"type": "object"
|
||||
"$ref": "#/components/schemas/UpdatedScheduleOutput"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -128,10 +128,10 @@ export function getWorkingHours(
|
||||
}
|
||||
|
||||
export function availabilityAsString(
|
||||
availability: Pick<Availability, "days" | "startTime" | "endTime">,
|
||||
availability: Availability,
|
||||
{ locale, hour12 }: { locale?: string; hour12?: boolean }
|
||||
) {
|
||||
const weekSpan = (availability: Pick<Availability, "days" | "startTime" | "endTime">) => {
|
||||
const weekSpan = (availability: Availability) => {
|
||||
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: Pick<Availability, "days" | "startTime" | "endTime">) => {
|
||||
const timeSpan = (availability: Availability) => {
|
||||
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(
|
||||
|
||||
+21
-14
@@ -1,25 +1,33 @@
|
||||
import type { Availability } from "@prisma/client";
|
||||
import type { Availability as AvailabilityModel, Schedule as ScheduleModel } 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";
|
||||
|
||||
type ScheduleAvailability = Pick<Availability, "days" | "startTime" | "endTime">[];
|
||||
type ScheduleOverride = Pick<Availability, "date" | "startTime" | "endTime">[];
|
||||
export type ScheduleWithAvailabilities = ScheduleModel & { availability: AvailabilityModel[] };
|
||||
|
||||
export function transformWorkingHoursForAtom(schedule: {
|
||||
timeZone: string | null;
|
||||
availability: ScheduleAvailability;
|
||||
}) {
|
||||
export type ScheduleWithAvailabilitiesForWeb = Pick<ScheduleModel, "id" | "name"> & {
|
||||
isManaged: boolean;
|
||||
workingHours: ReturnType<typeof transformWorkingHoursForClient>;
|
||||
schedule: AvailabilityModel[];
|
||||
availability: ReturnType<typeof transformAvailabilityForClient>;
|
||||
timeZone: string;
|
||||
dateOverrides: ReturnType<typeof transformDateOverridesForClient>;
|
||||
isDefault: boolean;
|
||||
isLastSchedule: boolean;
|
||||
readOnly: boolean;
|
||||
};
|
||||
|
||||
export function transformWorkingHoursForClient(schedule: ScheduleWithAvailabilities) {
|
||||
return getWorkingHours(
|
||||
{ timeZone: schedule.timeZone || undefined, utcOffset: 0 },
|
||||
schedule.availability || []
|
||||
);
|
||||
}
|
||||
|
||||
export function transformAvailabilityForAtom(schedule: { availability: ScheduleAvailability }) {
|
||||
return transformScheduleToAvailabilityForAtom(schedule).map((a) =>
|
||||
export function transformAvailabilityForClient(schedule: ScheduleWithAvailabilities) {
|
||||
return transformScheduleToAvailabilityForClient(schedule).map((a) =>
|
||||
a.map((startAndEnd) => ({
|
||||
...startAndEnd,
|
||||
end: new Date(startAndEnd.end.toISOString().replace("23:59:00.000Z", "23:59:59.999Z")),
|
||||
@@ -27,10 +35,7 @@ export function transformAvailabilityForAtom(schedule: { availability: ScheduleA
|
||||
);
|
||||
}
|
||||
|
||||
export function transformDateOverridesForAtom(
|
||||
schedule: { availability: ScheduleOverride },
|
||||
timeZone: string
|
||||
) {
|
||||
export function transformDateOverridesForClient(schedule: ScheduleWithAvailabilities, timeZone: string) {
|
||||
return schedule.availability.reduce((acc, override) => {
|
||||
// only iff future date override
|
||||
if (!override.date || dayjs.tz(override.date, timeZone).isBefore(dayjs(), "day")) {
|
||||
@@ -62,7 +67,9 @@ export function transformDateOverridesForAtom(
|
||||
}, [] as { ranges: TimeRange[] }[]);
|
||||
}
|
||||
|
||||
export const transformScheduleToAvailabilityForAtom = (schedule: { availability: ScheduleAvailability }) => {
|
||||
export const transformScheduleToAvailabilityForClient = (
|
||||
schedule: Partial<ScheduleModel> & { availability: AvailabilityModel[] }
|
||||
) => {
|
||||
return schedule.availability.reduce(
|
||||
(schedule: Schedule, availability) => {
|
||||
availability.days.forEach((day) => {
|
||||
@@ -1 +1 @@
|
||||
export * from "./transformers";
|
||||
export * from "./client/transformers";
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import type { ScheduleAvailabilityInput } from "@calcom/platform-types";
|
||||
import type { ScheduleOverrideInput } from "@calcom/platform-types";
|
||||
import type { WeekDay } from "@calcom/platform-types";
|
||||
|
||||
export function transformApiScheduleAvailability(inputAvailability: ScheduleAvailabilityInput[]) {
|
||||
return inputAvailability.map((availability) => ({
|
||||
days: availability.days.map(transformDayToNumber),
|
||||
startTime: createDateFromHoursMinutes(availability.startTime),
|
||||
endTime: createDateFromHoursMinutes(availability.endTime),
|
||||
}));
|
||||
}
|
||||
|
||||
export function transformApiScheduleOverrides(inputOverrides: ScheduleOverrideInput[]) {
|
||||
return inputOverrides.map((override) => ({
|
||||
date: new Date(override.date),
|
||||
startTime: createDateFromHoursMinutes(override.startTime),
|
||||
endTime: createDateFromHoursMinutes(override.endTime),
|
||||
}));
|
||||
}
|
||||
|
||||
function createDateFromHoursMinutes(hoursMinutes: string): Date {
|
||||
const parts = hoursMinutes.split(":");
|
||||
|
||||
if (parts.length < 2) {
|
||||
throw new Error("Invalid time format. Please use 'hh:mm'.");
|
||||
}
|
||||
|
||||
const hours = parseInt(parts[0], 10);
|
||||
const minutes = parseInt(parts[1], 10);
|
||||
|
||||
if (isNaN(hours) || hours < 0 || hours > 23) {
|
||||
throw new Error("Hours must be a number between 0 and 23.");
|
||||
}
|
||||
if (isNaN(minutes) || minutes < 0 || minutes > 59) {
|
||||
throw new Error("Minutes must be a number between 0 and 59.");
|
||||
}
|
||||
|
||||
const utcDate = new Date(Date.UTC(1970, 0, 1, hours, minutes));
|
||||
|
||||
return utcDate;
|
||||
}
|
||||
|
||||
function transformDayToNumber(day: WeekDay): number {
|
||||
const weekMap: { [key in WeekDay]: number } = {
|
||||
Sunday: 0,
|
||||
Monday: 1,
|
||||
Tuesday: 2,
|
||||
Wednesday: 3,
|
||||
Thursday: 4,
|
||||
Friday: 5,
|
||||
Saturday: 6,
|
||||
};
|
||||
return weekMap[day];
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./for-atom";
|
||||
export * from "./from-api";
|
||||
@@ -62,8 +62,6 @@ export type CustomClassNames = {
|
||||
};
|
||||
};
|
||||
|
||||
export type Availability = Pick<Schedule, "days" | "startTime" | "endTime">;
|
||||
|
||||
type AvailabilitySettingsProps = {
|
||||
skeletonLabel?: string;
|
||||
schedule: {
|
||||
@@ -75,7 +73,7 @@ type AvailabilitySettingsProps = {
|
||||
workingHours: WorkingHours[];
|
||||
dateOverrides: { ranges: TimeRange[] }[];
|
||||
timeZone: string;
|
||||
schedule: Availability[];
|
||||
schedule: Schedule[];
|
||||
};
|
||||
travelSchedules?: RouterOutputs["viewer"]["getTravelSchedules"];
|
||||
handleDelete: () => void;
|
||||
@@ -276,9 +274,7 @@ export function AvailabilitySettings({
|
||||
schedule.schedule
|
||||
.filter((availability) => !!availability.days.length)
|
||||
.map((availability) => (
|
||||
<span
|
||||
key={availability.startTime.valueOf().toString()}
|
||||
className={cn(customClassNames?.subtitlesClassName)}>
|
||||
<span key={availability.id} className={cn(customClassNames?.subtitlesClassName)}>
|
||||
{availabilityAsString(availability, { locale: i18n.language, hour12: timeFormat === 12 })}
|
||||
<br />
|
||||
</span>
|
||||
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
import type { ScheduleOutput } from "@calcom/platform-types";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
|
||||
import { transformApiScheduleForAtom } from "./transformApiScheduleForAtom";
|
||||
|
||||
const SCHEDULE_OWNER_ID = 256;
|
||||
|
||||
describe("transformScheduleForAtom", () => {
|
||||
const user: Pick<User, "id" | "defaultScheduleId" | "timeZone"> = {
|
||||
id: SCHEDULE_OWNER_ID,
|
||||
defaultScheduleId: 139,
|
||||
timeZone: "America/New_York",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should return null if user is not provided", () => {
|
||||
const schedule: ScheduleOutput | 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 = {
|
||||
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 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("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-14T09:00:00.000Z"),
|
||||
end: new Date("2024-05-14T17:00:00.000Z"),
|
||||
},
|
||||
],
|
||||
[],
|
||||
],
|
||||
timeZone: "America/Cancun",
|
||||
dateOverrides: [],
|
||||
isDefault: true,
|
||||
isLastSchedule: true,
|
||||
readOnly: false,
|
||||
};
|
||||
|
||||
const result = transformApiScheduleForAtom(user, schedule, 1);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
import {
|
||||
transformAvailabilityForAtom,
|
||||
transformDateOverridesForAtom,
|
||||
transformApiScheduleAvailability,
|
||||
transformApiScheduleOverrides,
|
||||
transformWorkingHoursForAtom,
|
||||
} from "@calcom/lib/schedules/transformers";
|
||||
import type { ScheduleOutput } from "@calcom/platform-types";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
|
||||
export function transformApiScheduleForAtom(
|
||||
user: Pick<User, "id" | "defaultScheduleId" | "timeZone"> | undefined,
|
||||
schedule: ScheduleOutput | 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,
|
||||
};
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
import type { UpdateScheduleInput } 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 = {
|
||||
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);
|
||||
});
|
||||
});
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
import type { ScheduleAvailabilityInput, UpdateScheduleInput, WeekDay } from "@calcom/platform-types";
|
||||
|
||||
import type { AvailabilityFormValues } from "../types";
|
||||
|
||||
export function transformAtomScheduleForApi(body: AvailabilityFormValues): UpdateScheduleInput {
|
||||
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["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 } = {};
|
||||
|
||||
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}`;
|
||||
}
|
||||
+32
-29
@@ -1,18 +1,16 @@
|
||||
import type { ScheduleLabelsType } from "@calcom/features/schedules/components/Schedule";
|
||||
import type { ApiErrorResponse, ApiResponse, ScheduleOutput } from "@calcom/platform-types";
|
||||
import type { ScheduleWithAvailabilitiesForWeb, UpdateScheduleOutputType } from "@calcom/platform-libraries";
|
||||
import type { ApiErrorResponse, ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
|
||||
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 useClientSchedule from "../../hooks/useClientSchedule";
|
||||
import useDeleteSchedule from "../../hooks/useDeleteSchedule";
|
||||
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 { Availability } from "../AvailabilitySettings";
|
||||
import type { Schedule } 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 PlatformAvailabilitySettingsWrapperProps = {
|
||||
@@ -21,7 +19,7 @@ type PlatformAvailabilitySettingsWrapperProps = {
|
||||
tooltips: Partial<ScheduleLabelsType>;
|
||||
};
|
||||
customClassNames?: Partial<CustomClassNames>;
|
||||
onUpdateSuccess?: (res: ApiResponse<ScheduleOutput>) => void;
|
||||
onUpdateSuccess?: (res: ApiResponse<UpdateScheduleOutputType>) => void;
|
||||
onUpdateError?: (err: ApiErrorResponse) => void;
|
||||
onDeleteSuccess?: (res: ApiResponse) => void;
|
||||
onDeleteError?: (err: ApiErrorResponse) => void;
|
||||
@@ -37,10 +35,10 @@ export const PlatformAvailabilitySettingsWrapper = ({
|
||||
onUpdateSuccess,
|
||||
disableEditableHeading = false,
|
||||
}: PlatformAvailabilitySettingsWrapperProps) => {
|
||||
const { isLoading, data: schedule } = useSchedule(id);
|
||||
const { data: schedules } = useSchedules();
|
||||
const { isLoading, data: schedule } = useClientSchedule(id);
|
||||
const mySchedule = schedule as ApiSuccessResponse<ScheduleWithAvailabilitiesForWeb>;
|
||||
const { data: me } = useMe();
|
||||
const atomSchedule = transformApiScheduleForAtom(me?.data, schedule, schedules?.length || 0);
|
||||
const userSchedule = mySchedule?.data;
|
||||
const { timeFormat } = me?.data || { timeFormat: null };
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -79,43 +77,48 @@ export const PlatformAvailabilitySettingsWrapper = ({
|
||||
};
|
||||
|
||||
const handleUpdate = async (id: number, body: AvailabilityFormValues) => {
|
||||
const updateBody = transformAtomScheduleForApi(body);
|
||||
updateSchedule({ id, ...updateBody });
|
||||
const transformedDateOverrides =
|
||||
body.dateOverrides.flatMap(
|
||||
(dateOverridesRanges) =>
|
||||
dateOverridesRanges?.ranges?.map((range) => ({ start: range.start, end: range.end })) ?? []
|
||||
) ?? [];
|
||||
|
||||
await updateSchedule({ ...body, scheduleId: id, dateOverrides: transformedDateOverrides });
|
||||
};
|
||||
|
||||
if (isLoading) return <div className="px-10 py-4 text-xl">Loading...</div>;
|
||||
|
||||
if (!atomSchedule) return <div className="px-10 py-4 text-xl">No user schedule present</div>;
|
||||
if (!userSchedule) return <div className="px-10 py-4 text-xl">No user schedule present</div>;
|
||||
|
||||
return (
|
||||
<AtomsWrapper>
|
||||
<AvailabilitySettings
|
||||
disableEditableHeading={disableEditableHeading}
|
||||
handleDelete={() => {
|
||||
atomSchedule.id && handleDelete(atomSchedule.id);
|
||||
userSchedule.id && handleDelete(userSchedule.id);
|
||||
}}
|
||||
handleSubmit={async (data) => {
|
||||
atomSchedule.id && handleUpdate(atomSchedule.id, data);
|
||||
userSchedule.id && handleUpdate(userSchedule.id, data);
|
||||
}}
|
||||
weekStart="Sunday"
|
||||
timeFormat={timeFormat}
|
||||
isLoading={isLoading}
|
||||
schedule={{
|
||||
name: atomSchedule.name,
|
||||
id: atomSchedule.id,
|
||||
isLastSchedule: atomSchedule.isLastSchedule,
|
||||
isDefault: atomSchedule.isDefault,
|
||||
workingHours: atomSchedule.workingHours,
|
||||
dateOverrides: atomSchedule.dateOverrides,
|
||||
timeZone: atomSchedule.timeZone,
|
||||
availability: atomSchedule.availability,
|
||||
name: userSchedule.name,
|
||||
id: userSchedule.id,
|
||||
isLastSchedule: userSchedule.isLastSchedule,
|
||||
isDefault: userSchedule.isDefault,
|
||||
workingHours: userSchedule.workingHours,
|
||||
dateOverrides: userSchedule.dateOverrides,
|
||||
timeZone: userSchedule.timeZone,
|
||||
availability: userSchedule.availability,
|
||||
schedule:
|
||||
atomSchedule.schedule.reduce(
|
||||
(acc: Availability[], avail: Availability) => [
|
||||
userSchedule.schedule.reduce(
|
||||
(acc: Schedule[], avail: Schedule) => [
|
||||
...acc,
|
||||
{ days: avail.days, startTime: new Date(avail.startTime), endTime: new Date(avail.endTime) },
|
||||
{ ...avail, startTime: new Date(avail.startTime), endTime: new Date(avail.endTime) },
|
||||
],
|
||||
[]
|
||||
[] as Schedule[]
|
||||
) || [],
|
||||
}}
|
||||
isDeleting={isDeletionInProgress}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { GetScheduleOutput } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "user-schedule";
|
||||
|
||||
export const useSchedule = (id?: string) => {
|
||||
const pathname = id ? `/${V2_ENDPOINTS.availability}/${id}` : `/${V2_ENDPOINTS.availability}/default`;
|
||||
|
||||
const { isLoading, error, data } = useQuery({
|
||||
queryKey: [QUERY_KEY, id],
|
||||
queryFn: () => {
|
||||
return http.get<GetScheduleOutput>(pathname).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return res.data.data;
|
||||
}
|
||||
throw new Error(res.data.error?.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { isLoading, error, data };
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { GetSchedulesOutput } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "user-schedules";
|
||||
|
||||
export const useSchedules = () => {
|
||||
const pathname = `/${V2_ENDPOINTS.availability}`;
|
||||
|
||||
const { isLoading, error, data } = useQuery({
|
||||
queryKey: [QUERY_KEY],
|
||||
queryFn: () => {
|
||||
return http.get<GetSchedulesOutput>(pathname).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return res.data.data;
|
||||
}
|
||||
throw new Error(res.data.error?.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { isLoading, error, data };
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { ScheduleWithAvailabilitiesForWeb } from "@calcom/platform-libraries";
|
||||
import type { ApiResponse } from "@calcom/platform-types";
|
||||
|
||||
import http from "../lib/http";
|
||||
|
||||
export const QUERY_KEY = "user-schedule";
|
||||
|
||||
const useClientSchedule = (id?: string) => {
|
||||
const pathname = id
|
||||
? `/${V2_ENDPOINTS.availability}/${id}?for=atom`
|
||||
: `/${V2_ENDPOINTS.availability}/default?for=atom`;
|
||||
|
||||
const { isLoading, error, data } = useQuery({
|
||||
queryKey: [QUERY_KEY, id],
|
||||
queryFn: () => {
|
||||
return http.get<ApiResponse<ScheduleWithAvailabilitiesForWeb>>(pathname).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return res.data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { isLoading, error, data };
|
||||
};
|
||||
|
||||
export default useClientSchedule;
|
||||
+2
-2
@@ -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 "./useSchedule";
|
||||
import http from "../lib/http";
|
||||
import { QUERY_KEY } from "./useClientSchedule";
|
||||
|
||||
interface IUseDeleteScheduleProps {
|
||||
onSuccess?: (res: ApiResponse) => void;
|
||||
+11
-7
@@ -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 { ScheduleOutput } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../lib/http";
|
||||
import { QUERY_KEY as ScheduleQueryKey } from "./useSchedule";
|
||||
import http from "../lib/http";
|
||||
import { QUERY_KEY as ScheduleQueryKey } from "./useClientSchedule";
|
||||
|
||||
interface IPUpdateOAuthClient {
|
||||
onSuccess?: (res: ApiResponse<ScheduleOutput>) => void;
|
||||
onSuccess?: (res: ApiResponse<UpdateScheduleOutputType>) => void;
|
||||
onError?: (err: ApiErrorResponse) => void;
|
||||
}
|
||||
|
||||
@@ -25,11 +25,15 @@ const useUpdateSchedule = (
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation<ApiResponse<ScheduleOutput>, unknown, UpdateScheduleInput & { id: number }>({
|
||||
const mutation = useMutation<
|
||||
ApiResponse<UpdateScheduleOutputType>,
|
||||
unknown,
|
||||
UpdateScheduleInput & { scheduleId: number }
|
||||
>({
|
||||
mutationFn: (data) => {
|
||||
const pathname = `/${V2_ENDPOINTS.availability}/${data.id}?for=atom`;
|
||||
const pathname = `/${V2_ENDPOINTS.availability}/${data.scheduleId}?for=atom`;
|
||||
|
||||
return http.patch<ApiResponse<ScheduleOutput>>(pathname, data).then((res) => {
|
||||
return http.patch<ApiResponse<UpdateScheduleOutputType>>(pathname, data).then((res) => {
|
||||
return res.data;
|
||||
});
|
||||
},
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,7 @@
|
||||
"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",
|
||||
"test": "jest"
|
||||
"publish": "rm -rf dist && yarn build && npm publish --access public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
@@ -53,15 +52,12 @@
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@tanstack/react-query": "^5.17.15",
|
||||
"@types/jest": "^29.5.12",
|
||||
"class-variance-authority": "^0.4.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"lucide-react": "^0.364.0",
|
||||
"react-use": "^17.4.2",
|
||||
"tailwind-merge": "^1.13.2",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tailwindcss-animate": "^1.0.6",
|
||||
"ts-jest": "^29.1.2"
|
||||
"tailwindcss-animate": "^1.0.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,12 +48,14 @@ export type { ConnectedDestinationCalendars } from "@calcom/lib/getConnectedDest
|
||||
export { getBusyCalendarTimes } from "@calcom/core/CalendarManager";
|
||||
|
||||
export {
|
||||
transformWorkingHoursForAtom,
|
||||
transformAvailabilityForAtom,
|
||||
transformDateOverridesForAtom,
|
||||
transformApiScheduleAvailability,
|
||||
transformApiScheduleOverrides,
|
||||
} from "@calcom/lib/schedules/transformers";
|
||||
transformWorkingHoursForClient,
|
||||
transformAvailabilityForClient,
|
||||
transformDateOverridesForClient,
|
||||
} from "@calcom/lib/schedules/client/transformers";
|
||||
export type {
|
||||
ScheduleWithAvailabilities,
|
||||
ScheduleWithAvailabilitiesForWeb,
|
||||
} from "@calcom/lib/schedules/client/transformers";
|
||||
export type {
|
||||
BookingCreateBody,
|
||||
BookingResponse,
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { ApiProperty as DocsProperty, ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsString, IsBoolean, IsOptional, ValidateNested, IsArray, IsDate } from "class-validator";
|
||||
import { DateTime } from "luxon";
|
||||
import { z } from "zod";
|
||||
|
||||
const scheduleSchema = z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
name: z.string(),
|
||||
timeZone: z.string().nullish(),
|
||||
});
|
||||
|
||||
const availabilitySchema = z.object({
|
||||
id: z.number().int(),
|
||||
days: z.number().int().array(),
|
||||
startTime: z.date(),
|
||||
endTime: z.date(),
|
||||
});
|
||||
|
||||
export const schemaScheduleResponse = z
|
||||
.object({})
|
||||
.merge(scheduleSchema)
|
||||
.merge(
|
||||
z.object({
|
||||
availability: z
|
||||
.array(availabilitySchema)
|
||||
.transform((availabilities) =>
|
||||
availabilities.map((availability) => ({
|
||||
...availability,
|
||||
startTime: DateTime.fromJSDate(availability.startTime).toUTC().toFormat("HH:mm:ss"),
|
||||
endTime: DateTime.fromJSDate(availability.endTime).toUTC().toFormat("HH:mm:ss"),
|
||||
}))
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
);
|
||||
|
||||
export type ScheduleResponse = z.infer<typeof schemaScheduleResponse>;
|
||||
|
||||
class ScheduleItem {
|
||||
@IsString()
|
||||
start!: Date;
|
||||
|
||||
@IsString()
|
||||
end!: Date;
|
||||
}
|
||||
|
||||
class DateOverride {
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
start!: Date;
|
||||
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
end!: Date;
|
||||
}
|
||||
|
||||
export class UpdateScheduleInput {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
timeZone?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
name?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
isDefault?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ScheduleItem)
|
||||
@DocsProperty()
|
||||
@IsArray()
|
||||
@ApiProperty({
|
||||
type: [[ScheduleItem]],
|
||||
example: [
|
||||
[],
|
||||
[{ start: "2022-01-01T00:00:00.000Z", end: "2022-01-02T00:00:00.000Z" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
isArray: true,
|
||||
})
|
||||
schedule?: ScheduleItem[][];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => DateOverride)
|
||||
@IsArray()
|
||||
@DocsProperty()
|
||||
@ApiProperty({
|
||||
type: [DateOverride],
|
||||
example: [
|
||||
[],
|
||||
[{ start: "2022-01-01T00:00:00.000Z", end: "2022-01-02T00:00:00.000Z" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
isArray: true,
|
||||
required: false,
|
||||
})
|
||||
dateOverrides?: DateOverride[];
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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)$/;
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from "./inputs";
|
||||
export * from "./outputs";
|
||||
export * from "./constants";
|
||||
@@ -1,97 +0,0 @@
|
||||
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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@IsString()
|
||||
@ApiProperty({ example: "One-on-one coaching" })
|
||||
name!: string;
|
||||
|
||||
@IsTimeZone()
|
||||
@ApiProperty({ example: "Europe/Rome" })
|
||||
timeZone!: string;
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ScheduleAvailabilityInput)
|
||||
@ApiProperty({
|
||||
type: [ScheduleAvailabilityInput],
|
||||
example: [
|
||||
{
|
||||
days: ["Monday", "Tuesday"],
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
})
|
||||
availability?: ScheduleAvailabilityInput[];
|
||||
|
||||
@IsBoolean()
|
||||
@ApiProperty({ example: true })
|
||||
isDefault!: boolean;
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ScheduleOverrideInput)
|
||||
@ApiProperty({
|
||||
type: [ScheduleOverrideInput],
|
||||
example: [
|
||||
{
|
||||
date: "2024-05-20",
|
||||
startTime: "12:00",
|
||||
endTime: "14:00",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
})
|
||||
overrides?: ScheduleOverrideInput[];
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./create-schedule.input";
|
||||
export * from "./update-schedule.input";
|
||||
@@ -1,56 +0,0 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsString, IsBoolean, IsOptional, ValidateNested, IsArray, IsTimeZone } from "class-validator";
|
||||
|
||||
import { ScheduleAvailabilityInput, ScheduleOverrideInput } from "./create-schedule.input";
|
||||
|
||||
export class UpdateScheduleInput {
|
||||
@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)
|
||||
@ApiProperty({
|
||||
type: [ScheduleAvailabilityInput],
|
||||
example: [
|
||||
{
|
||||
days: ["Monday", "Tuesday"],
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
})
|
||||
availability?: ScheduleAvailabilityInput[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty({ example: true, required: false })
|
||||
isDefault?: boolean;
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ScheduleOverrideInput)
|
||||
@ApiProperty({
|
||||
type: [ScheduleOverrideInput],
|
||||
example: [
|
||||
{
|
||||
date: "2024-05-20",
|
||||
startTime: "12:00",
|
||||
endTime: "14:00",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
})
|
||||
overrides?: ScheduleOverrideInput[];
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
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";
|
||||
@@ -1,66 +0,0 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
IsArray,
|
||||
IsTimeZone,
|
||||
IsNumber,
|
||||
} from "class-validator";
|
||||
|
||||
import { ScheduleAvailabilityInput, ScheduleOverrideInput } from "../inputs/create-schedule.input";
|
||||
|
||||
export class ScheduleOutput {
|
||||
@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)
|
||||
@ApiProperty({
|
||||
type: [ScheduleAvailabilityInput],
|
||||
example: [
|
||||
{
|
||||
days: ["Monday", "Tuesday"],
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
},
|
||||
],
|
||||
})
|
||||
availability!: ScheduleAvailabilityInput[];
|
||||
|
||||
@IsBoolean()
|
||||
@ApiProperty({ example: true })
|
||||
isDefault!: boolean;
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ScheduleOverrideInput)
|
||||
@ApiProperty({
|
||||
type: [ScheduleOverrideInput],
|
||||
example: [
|
||||
{
|
||||
date: "2024-05-20",
|
||||
startTime: "12:00",
|
||||
endTime: "13:00",
|
||||
},
|
||||
],
|
||||
})
|
||||
overrides!: ScheduleOverrideInput[];
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { ScheduleWithAvailabilitiesForWeb } from "@calcom/lib";
|
||||
import {
|
||||
transformAvailabilityForAtom,
|
||||
transformDateOverridesForAtom,
|
||||
transformWorkingHoursForAtom,
|
||||
transformAvailabilityForClient,
|
||||
transformDateOverridesForClient,
|
||||
transformWorkingHoursForClient,
|
||||
} from "@calcom/lib";
|
||||
import { hasReadPermissionsForUserId } from "@calcom/lib/hasEditPermissionForUser";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
@@ -19,7 +20,7 @@ type GetOptions = {
|
||||
input: TGetInputSchema;
|
||||
};
|
||||
|
||||
export const getHandler = async ({ ctx, input }: GetOptions) => {
|
||||
export const getHandler = async ({ ctx, input }: GetOptions): Promise<ScheduleWithAvailabilitiesForWeb> => {
|
||||
const { user } = ctx;
|
||||
|
||||
const schedule = await prisma.schedule.findUnique({
|
||||
@@ -65,11 +66,11 @@ export const getHandler = async ({ ctx, input }: GetOptions) => {
|
||||
id: schedule.id,
|
||||
name: schedule.name,
|
||||
isManaged: schedule.userId !== user.id,
|
||||
workingHours: transformWorkingHoursForAtom(schedule),
|
||||
workingHours: transformWorkingHoursForClient(schedule),
|
||||
schedule: schedule.availability,
|
||||
availability: transformAvailabilityForAtom(schedule),
|
||||
availability: transformAvailabilityForClient(schedule),
|
||||
timeZone,
|
||||
dateOverrides: transformDateOverridesForAtom(schedule, timeZone),
|
||||
dateOverrides: transformDateOverridesForClient(schedule, timeZone),
|
||||
isDefault: !input.scheduleId || user.defaultScheduleId === schedule.id,
|
||||
isLastSchedule: schedulesCount <= 1,
|
||||
readOnly: schedule.userId !== user.id && !input.isManagedEventType,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { transformScheduleToAvailabilityForAtom } from "@calcom/lib";
|
||||
import { transformScheduleToAvailabilityForClient } from "@calcom/lib";
|
||||
import { getAvailabilityFromSchedule } from "@calcom/lib/availability";
|
||||
import { hasEditPermissionForUserID } from "@calcom/lib/hasEditPermissionForUser";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
@@ -118,7 +118,7 @@ export const updateHandler = async ({ input, ctx }: UpdateOptions) => {
|
||||
},
|
||||
});
|
||||
|
||||
const userAvailability = transformScheduleToAvailabilityForAtom(schedule);
|
||||
const userAvailability = transformScheduleToAvailabilityForClient(schedule);
|
||||
|
||||
return {
|
||||
schedule,
|
||||
|
||||
@@ -4006,7 +4006,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/atoms@*, @calcom/atoms@workspace:packages/platform/atoms":
|
||||
"@calcom/atoms@*, @calcom/atoms@workspace:^, @calcom/atoms@workspace:packages/platform/atoms":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@calcom/atoms@workspace:packages/platform/atoms"
|
||||
dependencies:
|
||||
@@ -4015,7 +4015,6 @@ __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
|
||||
@@ -4023,7 +4022,6 @@ __metadata:
|
||||
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
|
||||
@@ -4034,7 +4032,6 @@ __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
|
||||
@@ -5391,6 +5388,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@algora/sdk": ^0.1.2
|
||||
"@calcom/app-store": "*"
|
||||
"@calcom/atoms": "workspace:^"
|
||||
"@calcom/config": "*"
|
||||
"@calcom/dayjs": "*"
|
||||
"@calcom/embed-react": "workspace:^"
|
||||
@@ -5436,6 +5434,7 @@ __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
|
||||
@@ -5448,6 +5447,7 @@ __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
|
||||
@@ -5457,7 +5457,7 @@ __metadata:
|
||||
i18n-unused: ^0.13.0
|
||||
iframe-resizer-react: ^1.1.0
|
||||
keen-slider: ^6.8.0
|
||||
lucide-react: ^0.171.0
|
||||
lucide-react: ^0.364.0
|
||||
micro: ^10.0.1
|
||||
next: ^14.1.3
|
||||
next-auth: ^4.22.1
|
||||
@@ -5469,7 +5469,7 @@ __metadata:
|
||||
prism-react-renderer: ^1.3.5
|
||||
react: ^18.2.0
|
||||
react-confetti: ^6.0.1
|
||||
react-datocms: ^3.1.0
|
||||
react-datocms: ^5.0.3
|
||||
react-device-detect: ^2.2.2
|
||||
react-dom: ^18.2.0
|
||||
react-fast-marquee: ^1.6.4
|
||||
@@ -5481,6 +5481,7 @@ __metadata:
|
||||
react-merge-refs: 1.1.0
|
||||
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
|
||||
@@ -9180,6 +9181,59 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mux/mux-player-react@npm:*":
|
||||
version: 2.5.0
|
||||
resolution: "@mux/mux-player-react@npm:2.5.0"
|
||||
dependencies:
|
||||
"@mux/mux-player": 2.5.0
|
||||
"@mux/playback-core": 0.23.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: 9b2854793a47928d964fbbd46d4c2e659d1dea29993bf74febcdbd3dfd2729abdb9d9d926e67113376ea7a9d99da9c275705d702996503deade6d35128e7f6ac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mux/mux-player@npm:2.5.0":
|
||||
version: 2.5.0
|
||||
resolution: "@mux/mux-player@npm:2.5.0"
|
||||
dependencies:
|
||||
"@mux/mux-video": 0.18.0
|
||||
"@mux/playback-core": 0.23.0
|
||||
media-chrome: ~3.2.1
|
||||
checksum: 566bcb3372fb0f6ac4e37ce82707d565693681d010a7ae6d9890ae6b74320d8464af7bb1f8582709a7263c2e09e9e58137c2caf5f2c2eb3db91a137fb625d064
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mux/mux-video@npm:0.18.0":
|
||||
version: 0.18.0
|
||||
resolution: "@mux/mux-video@npm:0.18.0"
|
||||
dependencies:
|
||||
"@mux/playback-core": 0.23.0
|
||||
castable-video: ~1.0.6
|
||||
custom-media-element: ~1.2.3
|
||||
media-tracks: ~0.3.0
|
||||
checksum: a6a8137cfbfd04304f11434cc8e2eff3c1f3e72fd758cd4b2bf4eea4ce29a29525116a491e4be3ce4c995eba483704a5020ab4db5960017b22c5a913bfb04279
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mux/playback-core@npm:0.23.0":
|
||||
version: 0.23.0
|
||||
resolution: "@mux/playback-core@npm:0.23.0"
|
||||
dependencies:
|
||||
hls.js: ~1.5.8
|
||||
mux-embed: ~5.2.0
|
||||
checksum: c5bee62359ee79094ceca839972a7ecbcaf7339cb68ab5f404bbb6b3b7e25d1066c69bc4d92aa9912af5fa6d6a0e4eabe745e3589fffcc92f8b78171a6af58eb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ndelangen/get-tarball@npm:^3.0.7":
|
||||
version: 3.0.9
|
||||
resolution: "@ndelangen/get-tarball@npm:3.0.9"
|
||||
@@ -16143,7 +16197,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/jest@npm:^29.5.10, @types/jest@npm:^29.5.12":
|
||||
"@types/jest@npm:^29.5.10":
|
||||
version: 29.5.12
|
||||
resolution: "@types/jest@npm:29.5.12"
|
||||
dependencies:
|
||||
@@ -20429,6 +20483,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"castable-video@npm:~1.0.6":
|
||||
version: 1.0.6
|
||||
resolution: "castable-video@npm:1.0.6"
|
||||
dependencies:
|
||||
custom-media-element: ~1.2.2
|
||||
checksum: 873ea75b35c594ed3755bacedd33628c070c751e34e0a64e42f98a4e9e0fda7f988ecac0ec723a4f7a25f332ba4650d4686e826614d81663554b9b31298cc648
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ccount@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "ccount@npm:2.0.1"
|
||||
@@ -20893,6 +20956,15 @@ __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"
|
||||
@@ -21165,6 +21237,13 @@ __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"
|
||||
@@ -22477,6 +22556,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"custom-media-element@npm:~1.2.2, 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
|
||||
|
||||
"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"
|
||||
@@ -26532,6 +26618,26 @@ __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"
|
||||
@@ -28213,6 +28319,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hls.js@npm:~1.5.8":
|
||||
version: 1.5.8
|
||||
resolution: "hls.js@npm:1.5.8"
|
||||
checksum: 93781b38859ce852952244e9671d536a73acb93a20cfbc3a68e372056d024789e48c7d67354b6b9ac35acab4a161d0d15adcf64279a0832f46465233cdbc8be0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hmac-drbg@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "hmac-drbg@npm:1.0.1"
|
||||
@@ -33007,15 +33120,6 @@ __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"
|
||||
@@ -33584,6 +33688,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"media-chrome@npm:~3.2.1":
|
||||
version: 3.2.1
|
||||
resolution: "media-chrome@npm:3.2.1"
|
||||
checksum: 0e7d8a6850d0c4844be7b7975bcf80886aad5f2ae3cff1081aa5a7b7920ddf9f15b2da4df875f83ffb5f083d3fc1d337a8ac204317e40391f8311eb7f2f58c5e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"media-tracks@npm:~0.3.0":
|
||||
version: 0.3.0
|
||||
resolution: "media-tracks@npm:0.3.0"
|
||||
checksum: 217880bdf566cd070f22fce2cba132cd602ae2838a20f1c75aeb5f928edda283133d474b1f427deb2758cd9b64911f88eb6380921662566bbeb7051adbb81629
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"media-typer@npm:0.3.0":
|
||||
version: 0.3.0
|
||||
resolution: "media-typer@npm:0.3.0"
|
||||
@@ -34938,6 +35056,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mux-embed@npm:~5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "mux-embed@npm:5.2.0"
|
||||
checksum: c90f6e216a8239ef15534b26feb2299cbac51dff82766e23c89be6e30bf4348fe711fb092968b607fd45d1b509e75882efc2e12e0b34039483c1bf1b7c6690bb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mysql2@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "mysql2@npm:3.9.1"
|
||||
@@ -39219,20 +39344,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-datocms@npm:^3.1.0":
|
||||
version: 3.1.4
|
||||
resolution: "react-datocms@npm:3.1.4"
|
||||
"react-datocms@npm:^5.0.3":
|
||||
version: 5.0.3
|
||||
resolution: "react-datocms@npm:5.0.3"
|
||||
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: ^8.33.1
|
||||
react-intersection-observer: ^9.4.3
|
||||
react-string-replace: ^1.1.0
|
||||
universal-base64: ^2.1.0
|
||||
use-deep-compare-effect: ^1.6.1
|
||||
peerDependencies:
|
||||
react: ">= 16.12.0"
|
||||
checksum: 54aba12aef4937175c2011548a8a576c96c8d8a596e84d191826910624c1d596e76a49782689dc236388a10803b02e700ac820cb7500cca7fd147a81f6c544c3
|
||||
checksum: 22c20152afb54424acfe967a2c8c525cd9f132a33374f2aba0231f16ea64dade389b096e2dac8de9ffded612bc32e9891d725609ee947639fe1cef907cb143f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -39484,12 +39610,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-intersection-observer@npm:^8.33.1":
|
||||
version: 8.34.0
|
||||
resolution: "react-intersection-observer@npm:8.34.0"
|
||||
"react-intersection-observer@npm:^9.4.3":
|
||||
version: 9.8.2
|
||||
resolution: "react-intersection-observer@npm:9.8.2"
|
||||
peerDependencies:
|
||||
react: ^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0
|
||||
checksum: 7713fecfd1512c7f5a60f9f0bf15403b8f8bbd4110bcafaeaea6de36a0e0eb60368c3638f99e9c97b75ad8fc787ea48c241dcb5c694f821d7f2976f709082cc5
|
||||
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
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 0e77226cf458499959773da9db9e00ae33a6eb95c2fa1ec3f506bbcba00955c1c27d2320fba8cdd740cadb1c7d6cc396386cbd9c359298cff196cac660dbee49
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -40013,6 +40143,18 @@ __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"
|
||||
@@ -41840,6 +41982,13 @@ __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"
|
||||
@@ -44726,7 +44875,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-jest@npm:^29.1.1, ts-jest@npm:^29.1.2":
|
||||
"ts-jest@npm:^29.1.1":
|
||||
version: 29.1.2
|
||||
resolution: "ts-jest@npm:29.1.2"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user