feat: API endpoint to fetch bookings for standalone teams (#26818)

* inputs valid

* modules

* similar route to get bookings

* removed plan

* test case spec

* fix: e2e tests for teams booking controller

* chore: update openapi spec

* added filter

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* filter cases

* test(teams-bookings): fix eventTypeIds filter test to use correct query parameter

* fix: e2e tests

* fix: e2e tests

* fixup

---------

Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
Co-authored-by: Ryukemeister <sahalrajiv6900@gmail.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Hemanth Rachapalli
2026-01-19 09:32:27 +00:00
committed by GitHub
co-authored by cubic-dev-ai[bot] Rajiv Sahal Ryukemeister
parent 2227d990b6
commit d137787330
6 changed files with 850 additions and 0 deletions
@@ -12,6 +12,7 @@ import { OrganizationsTeamsBookingsModule } from "@/modules/organizations/teams/
import { OrganizationsUsersBookingsModule } from "@/modules/organizations/users/bookings/organizations-users-bookings.module";
import { RouterModule } from "@/modules/router/router.module";
import { StripeModule } from "@/modules/stripe/stripe.module";
import { TeamsBookingsModule } from "@/modules/teams/bookings/teams-bookings.module";
import { TeamsSchedulesModule } from "@/modules/teams/schedules/teams-schedules.module";
import { TimezoneModule } from "@/modules/timezones/timezones.module";
import { VerifiedResourcesModule } from "@/modules/verified-resources/verified-resources.module";
@@ -42,6 +43,7 @@ import { WebhooksModule } from "./webhooks/webhooks.module";
VerifiedResourcesModule,
RouterModule,
TeamsSchedulesModule,
TeamsBookingsModule,
],
})
export class EndpointsModule implements NestModule {
@@ -0,0 +1,3 @@
import { GetOrganizationsTeamsBookingsInput_2024_08_13 } from "@/modules/organizations/teams/bookings/inputs/get-organizations-teams-bookings.input";
export class GetTeamsBookingsInput_2024_08_13 extends GetOrganizationsTeamsBookingsInput_2024_08_13 {}
@@ -0,0 +1,581 @@
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
import type {
BookingOutput_2024_08_13,
CreateBookingInput_2024_08_13,
GetBookingsOutput_2024_08_13,
GetSeatedBookingOutput_2024_08_13,
RecurringBookingOutput_2024_08_13,
} from "@calcom/platform-types";
import type { Team, User } from "@calcom/prisma/client";
import type { INestApplication } from "@nestjs/common";
import type { NestExpressApplication } from "@nestjs/platform-express";
import type { TestingModule } from "@nestjs/testing";
import { Test } from "@nestjs/testing";
import request from "supertest";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { HostsRepositoryFixture } from "test/fixtures/repository/hosts.repository.fixture";
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withApiAuth } from "test/utils/withApiAuth";
import { AppModule } from "@/app.module";
import { bootstrap } from "@/bootstrap";
import type { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
import type { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { TeamsBookingsModule } from "@/modules/teams/bookings/teams-bookings.module";
type BookingData =
| BookingOutput_2024_08_13
| RecurringBookingOutput_2024_08_13
| GetSeatedBookingOutput_2024_08_13;
describe("Teams Bookings Endpoints 2024-08-13", () => {
describe("Standalone team bookings", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let schedulesService: SchedulesService_2024_04_15;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let teamRepositoryFixture: TeamRepositoryFixture;
let membershipsRepositoryFixture: MembershipRepositoryFixture;
let hostsRepositoryFixture: HostsRepositoryFixture;
const teamAdminEmail = "team-admin-bookings@api.com";
const teamOwnerEmail = "team-owner-bookings@api.com";
const teamMemberEmail = "team-member-bookings@api.com";
const nonTeamUserEmail = "non-team-user-bookings@api.com";
let teamAdmin: User;
let teamOwner: User;
let teamMember: User;
let nonTeamUser: User;
let standaloneTeam: Team;
let teamEventTypeId: number;
let teamEventTypeId2: number;
// Store created booking data for filter tests
let createdBookingUid: string;
const attendeeEmail1 = "attendee@example.com";
const attendeeName1 = "Test Attendee";
const attendeeEmail2 = "another@example.com";
const attendeeName2 = "Another Attendee";
const bookingStart1 = new Date(Date.UTC(2030, 0, 10, 10, 0, 0));
const bookingStart2 = new Date(Date.UTC(2030, 0, 11, 14, 0, 0));
async function createBookingViaApi(
eventTypeId: number,
start: Date,
attendee: { name: string; email: string }
): Promise<BookingOutput_2024_08_13> {
const body: CreateBookingInput_2024_08_13 = {
start: start.toISOString(),
eventTypeId,
attendee: {
name: attendee.name,
email: attendee.email,
timeZone: "Europe/London",
language: "en",
},
meetingUrl: "https://meet.google.com/test-meeting",
};
const response = await request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201);
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
const responseData = responseBody.data;
if (!Array.isArray(responseData) && "id" in responseData && !("bookingId" in responseData)) {
return responseData as BookingOutput_2024_08_13;
}
throw new Error("Invalid response data - expected booking but received unexpected format");
}
beforeAll(async () => {
const moduleRef: TestingModule = await withApiAuth(
teamOwnerEmail,
Test.createTestingModule({
imports: [AppModule, TeamsBookingsModule],
})
)
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
// Create a standalone team (not part of any organization)
standaloneTeam = await teamRepositoryFixture.create({
name: "Standalone Team Bookings",
isOrganization: false,
});
// Create users
teamAdmin = await userRepositoryFixture.create({
email: teamAdminEmail,
locale: "en",
name: "TeamAdminBookings",
});
teamOwner = await userRepositoryFixture.create({
email: teamOwnerEmail,
locale: "en",
name: "TeamOwnerBookings",
});
teamMember = await userRepositoryFixture.create({
email: teamMemberEmail,
locale: "en",
name: "TeamMemberBookings",
});
nonTeamUser = await userRepositoryFixture.create({
email: nonTeamUserEmail,
locale: "en",
name: "NonTeamUserBookings",
});
const userSchedule: CreateScheduleInput_2024_04_15 = {
name: "working time",
timeZone: "Europe/London",
isDefault: true,
};
await schedulesService.createUserSchedule(teamAdmin.id, userSchedule);
await schedulesService.createUserSchedule(teamOwner.id, userSchedule);
await schedulesService.createUserSchedule(teamMember.id, userSchedule);
await schedulesService.createUserSchedule(nonTeamUser.id, userSchedule);
await membershipsRepositoryFixture.create({
role: "ADMIN",
user: { connect: { id: teamAdmin.id } },
team: { connect: { id: standaloneTeam.id } },
accepted: true,
});
await membershipsRepositoryFixture.create({
role: "OWNER",
user: { connect: { id: teamOwner.id } },
team: { connect: { id: standaloneTeam.id } },
accepted: true,
});
await membershipsRepositoryFixture.create({
role: "MEMBER",
user: { connect: { id: teamMember.id } },
team: { connect: { id: standaloneTeam.id } },
accepted: true,
});
const teamEventType1 = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
connect: { id: standaloneTeam.id },
},
title: "Team Collective Event",
slug: "team-collective-event",
length: 60,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
const teamEventType2 = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "ROUND_ROBIN",
team: {
connect: { id: standaloneTeam.id },
},
title: "Team Round Robin Event",
slug: "team-round-robin-event",
length: 30,
assignAllTeamMembers: false,
bookingFields: [],
locations: [],
});
teamEventTypeId = teamEventType1.id;
teamEventTypeId2 = teamEventType2.id;
await hostsRepositoryFixture.create({
isFixed: true,
user: {
connect: {
id: teamAdmin.id,
},
},
eventType: {
connect: {
id: teamEventType1.id,
},
},
});
await hostsRepositoryFixture.create({
isFixed: true,
user: {
connect: {
id: teamOwner.id,
},
},
eventType: {
connect: {
id: teamEventType1.id,
},
},
});
await hostsRepositoryFixture.create({
isFixed: false,
user: {
connect: {
id: teamMember.id,
},
},
eventType: {
connect: {
id: teamEventType2.id,
},
},
});
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
// Create bookings for filter tests
const booking1 = await createBookingViaApi(teamEventTypeId, bookingStart1, {
name: attendeeName1,
email: attendeeEmail1,
});
createdBookingUid = booking1.uid;
await createBookingViaApi(teamEventTypeId2, bookingStart2, {
name: attendeeName2,
email: attendeeEmail2,
});
});
describe("filters", () => {
it("should filter by single status", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?status=upcoming`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
const data: (
| BookingOutput_2024_08_13
| RecurringBookingOutput_2024_08_13
| GetSeatedBookingOutput_2024_08_13
)[] = responseBody.data;
// Verify all bookings are upcoming (in the future)
data.forEach((booking) => {
if ("start" in booking) {
expect(new Date(booking.start).getTime()).toBeGreaterThan(Date.now());
}
});
});
});
it("should filter by multiple statuses", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?status=upcoming,past`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
const data: (
| BookingOutput_2024_08_13
| RecurringBookingOutput_2024_08_13
| GetSeatedBookingOutput_2024_08_13
)[] = responseBody.data;
// Verify all bookings are either upcoming (future) or past
const now = Date.now();
data.forEach((booking) => {
if (!Array.isArray(booking)) {
const bookingEnd = new Date(booking.end).getTime();
// Booking is either upcoming (starts in future) or past (ended in past)
const isUpcoming = new Date(booking.start).getTime() > now;
const isPast = bookingEnd < now;
expect(isUpcoming || isPast).toBe(true);
}
});
});
});
it("should get team bookings with eventTypeIds filter", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?eventTypeIds=${teamEventTypeId}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
const data: (
| BookingOutput_2024_08_13
| RecurringBookingOutput_2024_08_13
| GetSeatedBookingOutput_2024_08_13
)[] = responseBody.data;
expect(data.length).toBeGreaterThanOrEqual(1);
data.forEach((booking) => {
expect(booking.eventTypeId).toEqual(teamEventTypeId);
});
});
});
it("should filter by multiple eventTypeIds", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?eventTypeIds=${teamEventTypeId},${teamEventTypeId2}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const data: BookingData[] = responseBody.data;
expect(data.length).toBeGreaterThanOrEqual(2);
data.forEach((booking) => {
expect([teamEventTypeId, teamEventTypeId2]).toContain(booking.eventTypeId);
});
});
});
it("should filter by attendeeEmail", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?attendeeEmail=${attendeeEmail1}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
const data: BookingData[] = responseBody.data;
expect(data.length).toBeGreaterThanOrEqual(1);
data.forEach((booking) => {
if ("attendees" in booking) {
expect(booking.attendees.some((attendee) => attendee.email === attendeeEmail1)).toBe(true);
}
});
});
});
it("should filter by attendeeName", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?attendeeName=${encodeURIComponent(attendeeName1)}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
const data: BookingData[] = responseBody.data;
expect(data.length).toBeGreaterThanOrEqual(1);
data.forEach((booking) => {
if ("attendees" in booking) {
expect(booking.attendees.some((attendee) => attendee.name === attendeeName1)).toBe(true);
}
});
});
});
it("should filter by bookingUid", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?bookingUid=${createdBookingUid}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
const data: BookingData[] = responseBody.data;
expect(data.length).toEqual(1);
expect(data[0].uid).toEqual(createdBookingUid);
});
});
it("should filter by date range (afterStart and beforeEnd)", async () => {
const afterStartDate = new Date(Date.UTC(2030, 0, 9, 0, 0, 0)).toISOString();
const beforeEndDate = new Date(Date.UTC(2030, 0, 12, 0, 0, 0)).toISOString();
return request(app.getHttpServer())
.get(
`/v2/teams/${standaloneTeam.id}/bookings?afterStart=${afterStartDate}&beforeEnd=${beforeEndDate}`
)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.length).toBeGreaterThanOrEqual(2);
});
});
});
describe("sorting", () => {
it("should sort by start ascending", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?sortStart=asc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const data: BookingData[] = responseBody.data;
for (let i = 1; i < data.length; i++) {
const prevBooking = data[i - 1];
const currBooking = data[i];
if ("start" in prevBooking && "start" in currBooking) {
expect(new Date(prevBooking.start).getTime()).toBeLessThanOrEqual(
new Date(currBooking.start).getTime()
);
}
}
});
});
it("should sort by start descending", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?sortStart=desc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const data: BookingData[] = responseBody.data;
for (let i = 1; i < data.length; i++) {
const prevBooking = data[i - 1];
const currBooking = data[i];
if ("start" in prevBooking && "start" in currBooking) {
expect(new Date(prevBooking.start).getTime()).toBeGreaterThanOrEqual(
new Date(currBooking.start).getTime()
);
}
}
});
});
it("should sort by end ascending", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?sortEnd=asc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const data: BookingData[] = responseBody.data;
for (let i = 1; i < data.length; i++) {
const prevBooking = data[i - 1];
const currBooking = data[i];
if ("end" in prevBooking && "end" in currBooking) {
expect(new Date(prevBooking.end).getTime()).toBeLessThanOrEqual(
new Date(currBooking.end).getTime()
);
}
}
});
});
it("should sort by created ascending", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?sortCreated=asc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const data: BookingData[] = responseBody.data;
for (let i = 1; i < data.length; i++) {
const prevBooking = data[i - 1];
const currBooking = data[i];
if ("createdAt" in prevBooking && "createdAt" in currBooking) {
expect(new Date(prevBooking.createdAt).getTime()).toBeLessThanOrEqual(
new Date(currBooking.createdAt).getTime()
);
}
}
});
});
});
describe("pagination", () => {
it("should return paginated results with take parameter", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?take=1`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.length).toEqual(1);
expect(responseBody.pagination).toBeDefined();
});
});
it("should return paginated results with skip parameter", async () => {
// First get all bookings to know total
const allBookingsResponse = await request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200);
const allBookings: BookingData[] = allBookingsResponse.body.data;
const totalCount = allBookings.length;
// Skip first booking
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?skip=1`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.length).toEqual(totalCount - 1);
});
});
it("should return paginated results with take and skip parameters", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${standaloneTeam.id}/bookings?take=1&skip=1`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.length).toEqual(1);
expect(responseBody.pagination).toBeDefined();
});
});
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(teamAdminEmail);
await userRepositoryFixture.deleteByEmail(teamOwnerEmail);
await userRepositoryFixture.deleteByEmail(teamMemberEmail);
await userRepositoryFixture.deleteByEmail(nonTeamUserEmail);
await teamRepositoryFixture.delete(standaloneTeam.id);
await app.close();
});
});
});
@@ -0,0 +1,46 @@
import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { API_KEY_HEADER } from "@/lib/docs/headers";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
import { GetTeamsBookingsInput_2024_08_13 } from "@/modules/teams/bookings/inputs/get-teams-bookings.input";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Controller, UseGuards, Get, Param, ParseIntPipe, Query, HttpStatus, HttpCode } from "@nestjs/common";
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { GetBookingsOutput_2024_08_13 } from "@calcom/platform-types";
@Controller({
path: "/v2/teams/:teamId/bookings",
version: API_VERSIONS_VALUES,
})
@UseGuards(ApiAuthGuard, RolesGuard)
@DocsTags("Teams / Bookings")
@ApiHeader(API_KEY_HEADER)
export class TeamsBookingsController {
constructor(private readonly bookingsService: BookingsService_2024_08_13) {}
@Get("/")
@ApiOperation({ summary: "Get team bookings" })
@Roles("TEAM_ADMIN")
@HttpCode(HttpStatus.OK)
async getAllTeamBookings(
@Query() queryParams: GetTeamsBookingsInput_2024_08_13,
@Param("teamId", ParseIntPipe) teamId: number,
@GetUser() user: UserWithProfile
): Promise<GetBookingsOutput_2024_08_13> {
const { bookings, pagination } = await this.bookingsService.getBookings(
{ ...queryParams, teamId },
{ email: user.email, id: user.id }
);
return {
status: SUCCESS_STATUS,
data: bookings,
pagination,
};
}
}
@@ -0,0 +1,13 @@
import { BookingsModule_2024_08_13 } from "@/ee/bookings/2024-08-13/bookings.module";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { StripeModule } from "@/modules/stripe/stripe.module";
import { TeamsBookingsController } from "@/modules/teams/bookings/teams-bookings.controller";
import { Module } from "@nestjs/common";
@Module({
imports: [BookingsModule_2024_08_13, PrismaModule, StripeModule, RedisModule, MembershipsModule],
controllers: [TeamsBookingsController],
})
export class TeamsBookingsModule {}
+205
View File
@@ -15182,6 +15182,211 @@
]
}
},
"/v2/teams/{teamId}/bookings": {
"get": {
"operationId": "TeamsBookingsController_getAllTeamBookings",
"summary": "Get team bookings",
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "status",
"required": false,
"in": "query",
"description": "Filter bookings by status. If you want to filter by multiple statuses, separate them with a comma.",
"schema": {
"example": "?status=upcoming,past",
"type": "array",
"items": {
"type": "string",
"enum": [
"upcoming",
"recurring",
"past",
"cancelled",
"unconfirmed"
]
}
}
},
{
"name": "attendeeEmail",
"required": false,
"in": "query",
"description": "Filter bookings by the attendee's email address.",
"schema": {
"example": "example@domain.com",
"type": "string"
}
},
{
"name": "attendeeName",
"required": false,
"in": "query",
"description": "Filter bookings by the attendee's name.",
"schema": {
"example": "John Doe",
"type": "string"
}
},
{
"name": "bookingUid",
"required": false,
"in": "query",
"description": "Filter bookings by the booking Uid.",
"schema": {
"example": "2NtaeaVcKfpmSZ4CthFdfk",
"type": "string"
}
},
{
"name": "eventTypeIds",
"required": false,
"in": "query",
"description": "Filter bookings by event type ids belonging to the team. Event type ids must be separated by a comma.",
"schema": {
"example": "100,200",
"type": "string"
}
},
{
"name": "afterStart",
"required": false,
"in": "query",
"description": "Filter bookings that start after this date-time (ISO 8601).",
"schema": {
"example": "2024-08-13T10:00:00.000Z",
"type": "string"
}
},
{
"name": "beforeEnd",
"required": false,
"in": "query",
"description": "Filter bookings that end before this date-time (ISO 8601).",
"schema": {
"example": "2024-08-20T10:00:00.000Z",
"type": "string"
}
},
{
"name": "teamMemberIds",
"required": false,
"in": "query",
"description": "Filter bookings by team member user ids. Multiple ids can be separated by a comma. Only works for team events.",
"schema": {
"example": "10,20",
"type": "string"
}
},
{
"name": "teamMemberEmails",
"required": false,
"in": "query",
"description": "Filter bookings by team member emails. Multiple emails can be separated by a comma. Only works for team events.",
"schema": {
"example": "alice@example.com,bob@example.com",
"type": "string"
}
},
{
"name": "sortStart",
"required": false,
"in": "query",
"description": "Sort results by their start time (when booking is scheduled for) in ascending or descending order.",
"schema": {
"example": "?sortStart=asc OR ?sortStart=desc",
"enum": [
"asc",
"desc"
],
"type": "string"
}
},
{
"name": "sortEnd",
"required": false,
"in": "query",
"description": "Sort results by their end time (when booking is scheduled to end) in ascending or descending order.",
"schema": {
"example": "?sortEnd=asc OR ?sortEnd=desc",
"enum": [
"asc",
"desc"
],
"type": "string"
}
},
{
"name": "sortCreated",
"required": false,
"in": "query",
"description": "Sort results by their creation time (when booking was made) in ascending or descending order.",
"schema": {
"example": "?sortCreated=asc OR ?sortCreated=desc",
"enum": [
"asc",
"desc"
],
"type": "string"
}
},
{
"name": "take",
"required": false,
"in": "query",
"description": "The number of items to return",
"schema": {
"minimum": 1,
"maximum": 250,
"example": 10,
"type": "number"
}
},
{
"name": "skip",
"required": false,
"in": "query",
"description": "The number of items to skip",
"schema": {
"minimum": 0,
"example": 0,
"type": "number"
}
},
{
"name": "teamId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetBookingsOutput_2024_08_13"
}
}
}
}
},
"tags": [
"Teams / Bookings"
]
}
},
"/v2/teams/{teamId}/event-types": {
"post": {
"operationId": "TeamsEventTypesController_createTeamEventType",