Skip to content

Commit

Permalink
refactor: api v2 schedules (#14870)
Browse files Browse the repository at this point in the history
* api: createSchedule refactor - still need to handle overrides

* createSchedule: handle overrides

* createSchedule: default schedule e2e test fix and pad hours:minutes

* get default schedule, by id and all schedules

* update schedule

* refactor: update handler delete only if overrides or availabilities given

* remove comment

* refactor: move schedules inputs, outputs to platform-types + e2e tests refactor

* refactor e2e

* fix: overrides and test

* fix managed user create

* my brain is fried but refactor good

* WIP: transform api schedule for atom

* WIP: transform api schedule for atom

* WIP: transform api schedule for atom

* fix: re-export atom transformer from lib

* fix: format schedule for api from atom

* fix ts ignore

* refactor: move schedule hooks into hooks/schedules

* feat: useSchedules hook to get count of user schedules

* tests: setup atom tests & test transformScheduleForAtom

* rename transformers

* test transformAtomScheduleForApi

* reset scheduel update handler for trpc

* fix: import path

* refactor: transformers

* swagger docs fix

* get rid of unused availabilities module

* get rid of eslint-disable-next-line in schedules repository

* refactor: remove crypto from atoms vite rollupOptions

* refactor: comment schedules repository update logic
  • Loading branch information
supalarry committed May 16, 2024
1 parent ca05e70 commit c25c835
Show file tree
Hide file tree
Showing 55 changed files with 1,389 additions and 1,207 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ 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";
Expand All @@ -21,6 +19,7 @@ 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", () => {
Expand All @@ -43,7 +42,7 @@ describe("Bookings Endpoints", () => {
const moduleRef = await withAccessTokenAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, AvailabilitiesModule, UsersModule, SchedulesModule],
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule],
})
)
.overrideGuard(PermissionsGuard)
Expand Down
14 changes: 3 additions & 11 deletions apps/api/v2/src/ee/me/me.controller.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ 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";
Expand Down Expand Up @@ -34,14 +33,7 @@ describe("Me Endpoints", () => {
const moduleRef = await withAccessTokenAuth(
userEmail,
Test.createTestingModule({
imports: [
AppModule,
PrismaModule,
AvailabilitiesModule,
UsersModule,
TokensModule,
SchedulesModule,
],
imports: [AppModule, PrismaModule, UsersModule, TokensModule, SchedulesModule],
})
)
.overrideGuard(PermissionsGuard)
Expand Down Expand Up @@ -118,13 +110,13 @@ describe("Me Endpoints", () => {
});

it("should not update user associated with access token given invalid time format", async () => {
const bodyWithIncorrectTimeFormat: UpdateManagedUserInput = { timeFormat: 100 };
const bodyWithIncorrectTimeFormat = { 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: UpdateManagedUserInput = { weekStart: "waba luba dub dub" };
const bodyWithIncorrectWeekStart = { weekStart: "waba luba dub dub" };

return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectWeekStart).expect(400);
});
Expand Down
2 changes: 1 addition & 1 deletion apps/api/v2/src/ee/me/me.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, user.defaultScheduleId, {
await this.schedulesRepository.updateUserSchedule(user.id, user.defaultScheduleId, {
timeZone: bodySchedule.timeZone,
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
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";
Expand All @@ -20,6 +15,14 @@ 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", () => {
Expand All @@ -32,23 +35,27 @@ 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,
AvailabilitiesModule,
UsersModule,
TokensModule,
SchedulesModule,
],
imports: [AppModule, PrismaModule, UsersModule, TokensModule, SchedulesModule],
})
)
.overrideGuard(PermissionsGuard)
Expand All @@ -75,60 +82,66 @@ 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(body)
.send(createScheduleInput)
.expect(201)
.then(async (response) => {
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 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 scheduleUser = schedule?.[0].userId
? await userRepositoryFixture.get(schedule?.[0].userId)
const responseBody: CreateScheduleOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
createdSchedule = response.body.data;

const expectedSchedule = {
...createScheduleInput,
availability: defaultAvailability,
overrides: [],
};
outputScheduleMatchesExpected(createdSchedule, expectedSchedule, 1);

const scheduleOwner = createdSchedule.ownerId
? await userRepositoryFixture.get(createdSchedule.ownerId)
: null;
expect(scheduleUser?.defaultScheduleId).toEqual(responseData.data.id);
createdSchedule = responseData.data;
expect(scheduleOwner?.defaultScheduleId).toEqual(createdSchedule.id);
});
});

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 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 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 responseBody: GetScheduleOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const outputSchedule = responseBody.data;

const expectedSchedule = {
...createScheduleInput,
availability: defaultAvailability,
overrides: [],
};
outputScheduleMatchesExpected(outputSchedule, expectedSchedule, 1);
});
});

Expand All @@ -137,23 +150,21 @@ describe("Schedules Endpoints", () => {
.get(`/api/v2/schedules`)
.expect(200)
.then((response) => {
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 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);
const responseBody: GetSchedulesOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const outputSchedule = responseBody.data[0];

const expectedSchedule = {
...createScheduleInput,
availability: defaultAvailability,
overrides: [],
};
outputScheduleMatchesExpected(outputSchedule, expectedSchedule, 1);
});
});

it("should update schedule name", async () => {
const newScheduleName = "new-schedule-name";
const newScheduleName = "updated-schedule-name";

const body: UpdateScheduleInput = {
name: newScheduleName,
Expand All @@ -166,19 +177,41 @@ describe("Schedules Endpoints", () => {
.then((response: any) => {
const responseData: UpdateScheduleOutput = response.body;
expect(responseData.status).toEqual(SUCCESS_STATUS);
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 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.name = newScheduleName;
const responseSchedule = responseData.data;

const expectedSchedule = { ...createdSchedule, name: newScheduleName };
outputScheduleMatchesExpected(responseSchedule, expectedSchedule, 1);

createdSchedule = responseSchedule;
});
});

it("should add overrides", async () => {
const overrides = [
{
date: "2026-05-05",
startTime: "10:00",
endTime: "12:00",
},
];

const body: UpdateScheduleInput = {
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;
});
});

Expand Down

0 comments on commit c25c835

Please sign in to comment.