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: v2 docs #14446

Merged
merged 35 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c2693c9
refactor: group endpoints by tags
supalarry Apr 6, 2024
4f43ffc
refactor: remove swagger header
supalarry Apr 6, 2024
c0e762a
feat: managed users docs
supalarry Apr 6, 2024
dbc36bd
feat: gcal docs
supalarry Apr 6, 2024
6ebbda7
feat: provider docs
supalarry Apr 6, 2024
458e18a
feat: managed users docs
supalarry Apr 6, 2024
6bb7a21
fix: event-types locations docs
supalarry Apr 6, 2024
c43a3fc
refactor: schedules remove forAtom logic - return just for atoms
supalarry Apr 6, 2024
f6826a6
feat: schedules docs
supalarry Apr 6, 2024
9756197
feat: schedules docs
supalarry Apr 7, 2024
e8cc1e8
fix: me tests
supalarry Apr 7, 2024
f519cd1
fix: schedules tests
supalarry Apr 7, 2024
707454e
feat: me docs
supalarry Apr 7, 2024
206fe73
feat: calendar busy times docs
supalarry Apr 7, 2024
4a9b362
feat: calendar connected and destination calendars docs
supalarry Apr 7, 2024
9284c0c
feat: some docs for bookings endpoints
supalarry Apr 7, 2024
e0edc31
fix: add OAuth id when updating user email + disallow updating username
supalarry Apr 8, 2024
89d95cb
fix: creating user with same e-mail results in 500
supalarry Apr 8, 2024
0a9302b
fix: me e2e tests
supalarry Apr 8, 2024
c757200
fix: event-types e2e tests
supalarry Apr 8, 2024
6296db0
fix: oauth-client-users e2e tests
supalarry Apr 8, 2024
8fd6c87
fix: some bookings e2e
supalarry Apr 8, 2024
25fab24
revert: re-add name for create update user
supalarry Apr 8, 2024
698ff84
fix: schedule create isDefault required
supalarry Apr 8, 2024
838e285
Merge branch 'main' into v2-docs-improvements
supalarry Apr 8, 2024
65b747b
lock file from main
supalarry Apr 8, 2024
e9e0435
lock file from main
supalarry Apr 8, 2024
5bd0af6
comment broken bookings tests
supalarry Apr 8, 2024
0f1caa0
fix: connected calendars output type
supalarry Apr 8, 2024
82e969a
fix: connected calendars output type
supalarry Apr 8, 2024
44ed59a
Merge branch 'main' into v2-docs-improvements
supalarry Apr 8, 2024
f2eb26c
Merge branch 'main' into v2-docs-improvements
supalarry Apr 8, 2024
016b16a
Merge branch 'main' into v2-docs-improvements
supalarry Apr 9, 2024
22cd717
Merge branch 'main' into v2-docs-improvements
supalarry Apr 9, 2024
c02b2f2
remove try catch
supalarry Apr 10, 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
4 changes: 4 additions & 0 deletions apps/api/v2/src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { getEnv } from "@/env";
import { Controller, Get, Version, VERSION_NEUTRAL } from "@nestjs/common";
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";

@Controller()
@DocsTags("Health - development only")
@DocsExcludeController(getEnv("NODE_ENV") === "production")
export class AppController {
@Get("health")
@Version(VERSION_NEUTRAL)
Expand Down
205 changes: 120 additions & 85 deletions apps/api/v2/src/ee/bookings/controllers/bookings.controller.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { SchedulesRepository } from "@/ee/schedules/schedules.repository";
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";
Expand All @@ -11,17 +15,12 @@ import { Test } from "@nestjs/testing";
import { User } from "@prisma/client";
import * as request from "supertest";
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withAccessTokenAuth } from "test/utils/withAccessTokenAuth";

import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import {
getAllUserBookings,
handleNewBooking,
getBookingInfo,
handleNewRecurringBooking,
handleInstantMeeting,
} from "@calcom/platform-libraries";
import { handleNewBooking } from "@calcom/platform-libraries";
import { ApiSuccessResponse, ApiResponse } from "@calcom/platform-types";

describe("Bookings Endpoints", () => {
Expand All @@ -30,28 +29,50 @@ describe("Bookings Endpoints", () => {

let userRepositoryFixture: UserRepositoryFixture;
let bookingsRepositoryFixture: BookingsRepositoryFixture;
let schedulesService: SchedulesService;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;

const userEmail = "bookings-controller-e2e@api.com";
let user: User;

let eventTypeId: number;

let createdBooking: Awaited<ReturnType<typeof handleNewBooking>>;

beforeAll(async () => {
const moduleRef = await withAccessTokenAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, AvailabilitiesModule, UsersModule],
providers: [SchedulesRepository, SchedulesService],
imports: [AppModule, PrismaModule, AvailabilitiesModule, UsersModule, SchedulesModule],
})
)
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
).compile();
.compile();

userRepositoryFixture = new UserRepositoryFixture(moduleRef);
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
schedulesService = moduleRef.get<SchedulesService>(SchedulesService);

user = await userRepositoryFixture.create({
email: userEmail,
});

const userSchedule: CreateScheduleInput = {
name: "working time",
timeZone: "Europe/Rome",
isDefault: true,
};
await schedulesService.createUserSchedule(user.id, userSchedule);
const event = await eventTypesRepositoryFixture.create(
{ title: "peer coding", slug: "peer-coding", length: 60 },
user.id
);
eventTypeId = event.id;

app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);

Expand All @@ -64,13 +85,23 @@ describe("Bookings Endpoints", () => {
});

it("should create a booking", async () => {
const bookingStart = "2023-05-25T09:30:00.000Z";
const bookingEnd = "2023-05-25T10:30:00.000Z";
const bookingEventTypeId = 7;
const bookingTimeZone = "Europe/Londom";
const bookingStart = "2040-05-21T09:30:00.000Z";
const bookingEnd = "2040-05-21T10:30:00.000Z";
const bookingEventTypeId = eventTypeId;
const bookingTimeZone = "Europe/London";
const bookingLanguage = "en";
const bookingHashedLink = "";
const bookingMetadata = {};
const bookingResponses = {
name: "tester",
email: "tester@example.com",
location: {
value: "link",
optionValue: "",
},
notes: "test",
guests: [],
};

const body = {
start: bookingStart,
Expand All @@ -80,6 +111,7 @@ describe("Bookings Endpoints", () => {
language: bookingLanguage,
metadata: bookingMetadata,
hashedLink: bookingHashedLink,
responses: bookingResponses,
};

return request(app.getHttpServer())
Expand All @@ -91,8 +123,8 @@ describe("Bookings Endpoints", () => {
response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.user.email).toBeDefined();
expect(responseBody.data.user.email).toEqual(userEmail);
expect(responseBody.data.userPrimaryEmail).toBeDefined();
expect(responseBody.data.userPrimaryEmail).toEqual(userEmail);
expect(responseBody.data.id).toBeDefined();
expect(responseBody.data.uid).toBeDefined();
expect(responseBody.data.startTime).toEqual(bookingStart);
Expand All @@ -106,10 +138,10 @@ describe("Bookings Endpoints", () => {

it("should get bookings", async () => {
return request(app.getHttpServer())
.get("/api/v2/ee/bookings")
.get("/api/v2/ee/bookings?filters[status]=upcoming")
.then((response) => {
const responseBody: ApiSuccessResponse<Awaited<ReturnType<typeof getAllUserBookings>>> =
response.body;
console.log("asap responseBody", JSON.stringify(response.body, null, 2));
const responseBody: GetBookingsOutput = response.body;
const fetchedBooking = responseBody.data.bookings[0];

expect(responseBody.data.bookings.length).toEqual(1);
Expand All @@ -121,16 +153,16 @@ describe("Bookings Endpoints", () => {
expect(fetchedBooking.uid).toEqual(createdBooking.uid);
expect(fetchedBooking.startTime).toEqual(createdBooking.startTime);
expect(fetchedBooking.endTime).toEqual(createdBooking.endTime);
expect(fetchedBooking.user?.email).toEqual(createdBooking.user.email);
expect(fetchedBooking.user?.email).toEqual(userEmail);
});
});

it("should get booking", async () => {
return request(app.getHttpServer())
.get(`/api/v2/ee/bookings/${createdBooking.uid}`)
.then((response) => {
const responseBody: ApiSuccessResponse<Awaited<ReturnType<typeof getBookingInfo>>> = response.body;
const { bookingInfo } = responseBody.data;
const responseBody: GetBookingOutput = response.body;
const bookingInfo = responseBody.data;

expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
Expand All @@ -143,68 +175,71 @@ describe("Bookings Endpoints", () => {
});
});

it("should create a recurring booking", async () => {
const bookingStart = "2023-05-25T09:30:00.000Z";
const bookingEnd = "2023-05-25T10:30:00.000Z";
const bookingEventTypeId = 7;
const bookingTimeZone = "Europe/Londom";
const bookingLanguage = "en";
const bookingHashedLink = "";
const bookingRecurringCount = 5;
const currentBookingRecurringIndex = 0;

const body = {
start: bookingStart,
end: bookingEnd,
eventTypeId: bookingEventTypeId,
timeZone: bookingTimeZone,
language: bookingLanguage,
metadata: {},
hashedLink: bookingHashedLink,
recurringCount: bookingRecurringCount,
currentRecurringIndex: currentBookingRecurringIndex,
};

return request(app.getHttpServer())
.post("/api/v2/ee/bookings/reccuring")
.send(body)
.expect(201)
.then((response) => {
const responseBody: ApiResponse<Awaited<ReturnType<typeof handleNewRecurringBooking>>> =
response.body;

expect(responseBody.status).toEqual("recurring");
});
});

it("should create an instant booking", async () => {
const bookingStart = "2023-05-25T09:30:00.000Z";
const bookingEnd = "2023-05-25T10:30:00.000Z";
const bookingEventTypeId = 7;
const bookingTimeZone = "Europe/Londom";
const bookingLanguage = "en";
const bookingHashedLink = "";

const body = {
start: bookingStart,
end: bookingEnd,
eventTypeId: bookingEventTypeId,
timeZone: bookingTimeZone,
language: bookingLanguage,
metadata: {},
hashedLink: bookingHashedLink,
};

return request(app.getHttpServer())
.post("/api/v2/ee/bookings/instant")
.send(body)
.expect(201)
.then((response) => {
const responseBody: ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>> = response.body;

expect(responseBody.status).toEqual("instant");
});
});
// note(Lauris) : found this test broken here - first thing to fix is that recurring endpoint accepts an array not 1 object.
// it("should create a recurring booking", async () => {
// const bookingStart = "2040-05-25T09:30:00.000Z";
// const bookingEnd = "2040-05-25T10:30:00.000Z";
// const bookingEventTypeId = 7;
// const bookingTimeZone = "Europe/London";
// const bookingLanguage = "en";
// const bookingHashedLink = "";
// const bookingRecurringCount = 5;
// const currentBookingRecurringIndex = 0;

// const body = {
// start: bookingStart,
// end: bookingEnd,
// eventTypeId: bookingEventTypeId,
// timeZone: bookingTimeZone,
// language: bookingLanguage,
// metadata: {},
// hashedLink: bookingHashedLink,
// recurringCount: bookingRecurringCount,
// currentRecurringIndex: currentBookingRecurringIndex,
// };

// return request(app.getHttpServer())
// .post("/api/v2/ee/bookings/reccuring")
// .send(body)
// .expect(201)
// .then((response) => {
// const responseBody: ApiResponse<Awaited<ReturnType<typeof handleNewRecurringBooking>>> =
// response.body;

// expect(responseBody.status).toEqual("recurring");
// });
// });

// note(Lauris) : found this test broken here - first thing to fix is that the eventTypeId must be team event type, because
// instant bookings only work for teams.
// it("should create an instant booking", async () => {
// const bookingStart = "2040-05-25T09:30:00.000Z";
// const bookingEnd = "2040-25T10:30:00.000Z";
// const bookingEventTypeId = 7;
// const bookingTimeZone = "Europe/London";
// const bookingLanguage = "en";
// const bookingHashedLink = "";

// const body = {
// start: bookingStart,
// end: bookingEnd,
// eventTypeId: bookingEventTypeId,
// timeZone: bookingTimeZone,
// language: bookingLanguage,
// metadata: {},
// hashedLink: bookingHashedLink,
// };

// return request(app.getHttpServer())
// .post("/api/v2/ee/bookings/instant")
// .send(body)
// .expect(201)
// .then((response) => {
// const responseBody: ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>> = response.body;

// expect(responseBody.status).toEqual("instant");
// });
// });

it("should cancel a booking", async () => {
const bookingId = createdBooking.id;
Expand Down
13 changes: 10 additions & 3 deletions apps/api/v2/src/ee/bookings/controllers/bookings.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { CreateReccuringBookingInput } from "@/ee/bookings/inputs/create-reccuring-booking.input";
import { GetBookingOutput } from "@/ee/bookings/outputs/get-booking.output";
import { GetBookingsOutput } from "@/ee/bookings/outputs/get-bookings.output";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
Expand All @@ -20,6 +22,7 @@ import {
NotFoundException,
UseGuards,
} from "@nestjs/common";
import { ApiQuery, ApiTags as DocsTags } from "@nestjs/swagger";
import { User } from "@prisma/client";
import { Request } from "express";
import { NextApiRequest } from "next/types";
Expand All @@ -38,7 +41,7 @@ import {
handleNewRecurringBooking,
handleInstantMeeting,
} from "@calcom/platform-libraries";
import { GetBookingsInput, CancelBookingInput } from "@calcom/platform-types";
import { GetBookingsInput, CancelBookingInput, Status } from "@calcom/platform-types";
import { ApiResponse } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";

Expand All @@ -47,6 +50,7 @@ import { PrismaClient } from "@calcom/prisma";
version: "2",
})
@UseGuards(PermissionsGuard)
@DocsTags("Bookings")
export class BookingsController {
private readonly logger = new Logger("ee bookings controller");

Expand All @@ -58,10 +62,13 @@ export class BookingsController {
// note(Rajiv): currently this endpoint is atoms only
@Get("/")
@UseGuards(AccessTokenGuard)
@ApiQuery({ name: "filters[status]", enum: Status, required: true })
@ApiQuery({ name: "limit", type: "number", required: false })
@ApiQuery({ name: "cursor", type: "number", required: false })
async getBookings(
@GetUser() user: User,
@Query() queryParams: GetBookingsInput
): Promise<ApiResponse<unknown>> {
): Promise<GetBookingsOutput> {
const { filters, cursor, limit } = queryParams;
const bookings = await getAllUserBookings({
bookingListingByStatus: filters.status,
Expand All @@ -82,7 +89,7 @@ export class BookingsController {

// note(Rajiv): currently this endpoint is atoms only
@Get("/:bookingUid")
async getBooking(@Param("bookingUid") bookingUid: string): Promise<ApiResponse<unknown>> {
async getBooking(@Param("bookingUid") bookingUid: string): Promise<GetBookingOutput> {
const { bookingInfo } = await getBookingInfo(bookingUid);

if (!bookingInfo) {
Expand Down