Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: api v2 schedules #14870

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6e99f2c
api: createSchedule refactor - still need to handle overrides
supalarry May 3, 2024
749f264
createSchedule: handle overrides
supalarry May 3, 2024
15929f4
createSchedule: default schedule e2e test fix and pad hours:minutes
supalarry May 3, 2024
fcb7711
get default schedule, by id and all schedules
supalarry May 3, 2024
2968bf7
update schedule
supalarry May 6, 2024
828ce35
refactor: update handler delete only if overrides or availabilities g…
supalarry May 6, 2024
9d9b39c
remove comment
supalarry May 6, 2024
7b83b9a
refactor: move schedules inputs, outputs to platform-types + e2e test…
supalarry May 9, 2024
0be239a
refactor e2e
supalarry May 9, 2024
260b0e0
fix: overrides and test
supalarry May 9, 2024
a4447dd
Merge branch 'main' into lauris/cal-3580-apiv2-schedules-endpoints-sh…
supalarry May 10, 2024
93c8929
fix managed user create
supalarry May 10, 2024
9104999
my brain is fried but refactor good
supalarry May 10, 2024
a3b74f7
WIP: transform api schedule for atom
supalarry May 10, 2024
930d6d3
WIP: transform api schedule for atom
supalarry May 11, 2024
9075317
WIP: transform api schedule for atom
supalarry May 11, 2024
7051288
Merge branch 'main' into lauris/cal-3580-apiv2-schedules-endpoints-sh…
supalarry May 13, 2024
7e0d704
fix: re-export atom transformer from lib
supalarry May 13, 2024
eefd35b
fix: format schedule for api from atom
supalarry May 13, 2024
b945e67
fix ts ignore
supalarry May 13, 2024
287a34f
Merge branch 'main' into lauris/cal-3580-apiv2-schedules-endpoints-sh…
supalarry May 14, 2024
b0de7e6
refactor: move schedule hooks into hooks/schedules
supalarry May 14, 2024
9b25185
feat: useSchedules hook to get count of user schedules
supalarry May 14, 2024
0e275c5
tests: setup atom tests & test transformScheduleForAtom
supalarry May 14, 2024
ef5ab85
rename transformers
supalarry May 14, 2024
f6ac381
test transformAtomScheduleForApi
supalarry May 14, 2024
64124c6
reset scheduel update handler for trpc
supalarry May 14, 2024
3947902
fix: import path
supalarry May 14, 2024
ee4f157
refactor: transformers
supalarry May 14, 2024
ac5f481
swagger docs fix
supalarry May 14, 2024
fbef444
get rid of unused availabilities module
supalarry May 14, 2024
7f9449b
Merge branch 'main' into lauris/cal-3580-apiv2-schedules-endpoints-sh…
supalarry May 14, 2024
6ad321a
get rid of eslint-disable-next-line in schedules repository
supalarry May 14, 2024
c563676
refactor: remove crypto from atoms vite rollupOptions
supalarry May 15, 2024
9e8606b
refactor: comment schedules repository update logic
supalarry May 15, 2024
66af956
Merge branch 'main' into lauris/cal-3580-apiv2-schedules-endpoints-sh…
supalarry May 15, 2024
38a9adb
Merge branch 'main' into lauris/cal-3580-apiv2-schedules-endpoints-sh…
supalarry May 16, 2024
2251f3c
Merge branch 'main' into lauris/cal-3580-apiv2-schedules-endpoints-sh…
supalarry May 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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