feat: v2 book team events using teamSlug & eventTypeSlug (#20585)

* feat: v2 book team events using teamSlug & eventTypeSlug

* fix: tests
This commit is contained in:
Lauris Skraucis authored and GitHub committed 2025-04-17 13:23:25 +03:00
1 parent a11c2cc52c
commit 8974f8f9de
9 files changed
+226 -30

No files matched your search

@@ -19,9 +19,14 @@ import { CredentialsRepository } from "@/modules/credentials/credentials.reposit
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
import { OrganizationsTeamsRepository } from "@/modules/organizations/teams/index/organizations-teams.repository";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { StripeModule } from "@/modules/stripe/stripe.module";
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
import { TeamsModule } from "@/modules/teams/teams/teams.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { UsersModule } from "@/modules/users/users.module";
@@ -38,6 +43,9 @@ import { Module } from "@nestjs/common";
SchedulesModule_2024_04_15,
EventTypesModule_2024_04_15,
EventTypesModule_2024_06_14,
StripeModule,
TeamsModule,
TeamsEventTypesModule,
],
providers: [
TokensRepository,
@@ -57,6 +65,8 @@ import { Module } from "@nestjs/common";
AppsRepository,
CalendarsRepository,
SelectedCalendarsRepository,
OrganizationsTeamsRepository,
OrganizationsRepository,
],
controllers: [BookingsController_2024_08_13],
exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13],
@@ -100,9 +100,13 @@ export class BookingsController_2024_08_13 {
The start needs to be in UTC aka if the timezone is GMT+2 in Rome and meeting should start at 11, then UTC time should have hours 09:00 aka without time zone.
Finally, there are 2 ways to book an event type:
Finally, there are 2 ways to book an event type belonging to an individual user:
1. Provide \`eventTypeId\` in the request body.
2. Provide \`eventTypeSlug\` and \`username\` and optionally \`organizationSlug\` if the user with the username is within an organization.
And 2 ways to book and event type belonging to a team:
1. Provide \`eventTypeId\` in the request body.
2. Provide \`eventTypeSlug\` and \`teamSlug\` and optionally \`organizationSlug\` if the team with the teamSlug is within an organization.
`,
})
@ApiBody({
@@ -39,7 +39,10 @@ import { PlatformOAuthClient, Team } from "@calcom/prisma/client";
describe("Bookings Endpoints 2024-08-13", () => {
describe("Team bookings", () => {
let app: INestApplication;
const organizationSlug = `team-bookings-organization-${randomString()}`;
let organization: Team;
const team1Slug = `team-bookings-team1-${randomString()}`;
const team2Slug = `team-bookings-team2-${randomString()}`;
let team1: Team;
let team2: Team;
@@ -95,12 +98,15 @@ describe("Bookings Endpoints 2024-08-13", () => {
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
organization = await organizationsRepositoryFixture.create({
name: `team-bookings-organization-${randomString()}`,
name: organizationSlug,
slug: organizationSlug,
isOrganization: true,
});
oAuthClient = await createOAuthClient(organization.id);
team1 = await teamRepositoryFixture.create({
name: `team-bookings-team1-${randomString()}`,
name: team1Slug,
slug: team1Slug,
isOrganization: false,
parent: { connect: { id: organization.id } },
createdByOAuthClient: {
@@ -111,7 +117,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
team2 = await teamRepositoryFixture.create({
name: `team-bookings-team2-${randomString()}`,
name: team2Slug,
slug: team2Slug,
isOrganization: false,
parent: { connect: { id: organization.id } },
createdByOAuthClient: {
@@ -668,6 +675,84 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
describe("book using teamSlug, eventTypeSlug and organizationSlug", () => {
it("should not be able to book if missing organizationSlug", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2040, 0, 9, 13, 0, 0)).toISOString(),
teamSlug: team1Slug,
eventTypeSlug: team1EventTypeSlug,
attendee: {
name: "alice",
email: "alice@gmail.com",
timeZone: "Europe/Madrid",
language: "es",
},
meetingUrl: "https://meet.google.com/abc-def-ghi",
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(404);
});
it("should book using teamSlug and eventTypeSlug and organizationSlug", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2040, 0, 9, 13, 0, 0)).toISOString(),
teamSlug: team1Slug,
eventTypeSlug: team1EventTypeSlug,
organizationSlug: organizationSlug,
attendee: {
name: "alice",
email: "alice@gmail.com",
timeZone: "Europe/Madrid",
language: "es",
},
meetingUrl: "https://meet.google.com/abc-def-ghi",
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsBooking(responseBody.data)).toBe(true);
if (responseDataIsBooking(responseBody.data)) {
const data: BookingOutput_2024_08_13 = responseBody.data;
expect(data.id).toBeDefined();
expect(data.uid).toBeDefined();
expect(data.hosts.length).toEqual(1);
expect(data.hosts[0].id).toEqual(teamUser.id);
expect(data.status).toEqual("accepted");
expect(data.start).toEqual(body.start);
expect(data.end).toEqual(new Date(Date.UTC(2040, 0, 9, 14, 0, 0)).toISOString());
expect(data.duration).toEqual(60);
expect(data.eventTypeId).toEqual(team1EventTypeId);
expect(data.attendees.length).toEqual(1);
expect(data.attendees[0]).toEqual({
name: body.attendee.name,
email: body.attendee.email,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
});
expect(data.meetingUrl).toEqual(body.meetingUrl);
expect(data.absentHost).toEqual(false);
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibly recurring bookings"
);
}
});
});
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
@@ -34,6 +34,7 @@ import {
} from "@calcom/platform-constants";
import {
CreateEventTypeInput_2024_06_14,
FAILED_EVENT_TYPE_IDENTIFICATION_ERROR_MESSAGE,
GetBookingOutput_2024_08_13,
GetBookingsOutput_2024_08_13,
GetSeatedBookingOutput_2024_08_13,
@@ -1692,11 +1693,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(400);
expect(
response.body.error.message.includes(
"Either eventTypeId OR (eventTypeSlug + username) must be provided"
)
).toBe(true);
expect(response.body.error.message.includes(FAILED_EVENT_TYPE_IDENTIFICATION_ERROR_MESSAGE)).toBe(
true
);
});
it("should create a booking by username and event type slug", async () => {
@@ -8,7 +8,11 @@ import { BillingService } from "@/modules/billing/services/billing.service";
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
import { OrganizationsTeamsRepository } from "@/modules/organizations/teams/index/organizations-teams.repository";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
import { UsersService } from "@/modules/users/services/users.service";
import { UsersRepository, UserWithProfile } from "@/modules/users/users.repository";
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
@@ -77,7 +81,11 @@ export class BookingsService_2024_08_13 {
private readonly usersService: UsersService,
private readonly usersRepository: UsersRepository,
private readonly platformBookingsService: PlatformBookingsService,
private readonly oAuthClientRepository: OAuthClientRepository
private readonly oAuthClientRepository: OAuthClientRepository,
private readonly organizationsTeamsRepository: OrganizationsTeamsRepository,
private readonly organizationsRepository: OrganizationsRepository,
private readonly teamsRepository: TeamsRepository,
private readonly teamsEventTypesRepository: TeamsEventTypesRepository
) {}
async createBooking(request: Request, body: CreateBookingInput) {
@@ -94,6 +102,16 @@ export class BookingsService_2024_08_13 {
`Event type with slug ${body.eventTypeSlug} belonging to user ${body.username} within organization ${body.organizationSlug} not found.`
);
}
if (body.teamSlug && body.eventTypeSlug && !body.organizationSlug) {
throw new NotFoundException(
`Event type with slug ${body.eventTypeSlug} belonging to team ${body.teamSlug} not found.`
);
}
if (body.teamSlug && body.eventTypeSlug && body.organizationSlug) {
throw new NotFoundException(
`Event type with slug ${body.eventTypeSlug} belonging to team ${body.teamSlug} within organization ${body.organizationSlug} not found.`
);
}
throw new NotFoundException(`Event type with id ${body.eventTypeId} not found.`);
}
@@ -141,10 +159,34 @@ export class BookingsService_2024_08_13 {
user.id,
body.eventTypeSlug
);
} else if (body.teamSlug && body.eventTypeSlug) {
const team = await this.getBookedEventTypeTeam(body.teamSlug, body.organizationSlug);
if (!team) {
throw new NotFoundException(`Team with slug ${body.teamSlug} not found`);
}
return await this.teamsEventTypesRepository.getEventTypeByTeamIdAndSlugWithOwnerAndTeam(
team.id,
body.eventTypeSlug
);
}
return null;
}
async getBookedEventTypeTeam(teamSlug: string, organizationSlug: string | undefined) {
if (!organizationSlug) {
return await this.teamsRepository.findTeamBySlug(teamSlug);
}
const organization = await this.organizationsRepository.findOrgBySlug(organizationSlug);
if (!organization) {
throw new NotFoundException(
`slots-input.service.ts: Organization with slug ${organizationSlug} not found`
);
}
return await this.organizationsTeamsRepository.findOrgTeamBySlug(organization.id, teamSlug);
}
async hasRequiredBookingFieldsResponses(body: CreateBookingInput, eventType: EventType | null) {
const bookingFields = { ...body.bookingFieldsResponses, attendeePhoneNumber: body.attendee.phoneNumber };
if (!eventType?.bookingFields) {
@@ -60,6 +60,18 @@ export class TeamsEventTypesRepository {
});
}
async getEventTypeByTeamIdAndSlugWithOwnerAndTeam(teamId: number, eventTypeSlug: string) {
return this.dbRead.prisma.eventType.findUnique({
where: {
teamId_slug: {
teamId,
slug: eventTypeSlug,
},
},
include: { owner: true, team: true },
});
}
async getTeamEventTypes(teamId: number) {
return this.dbRead.prisma.eventType.findMany({
where: {
+22 -7
View File
@@ -6353,7 +6353,7 @@
"post": {
"operationId": "BookingsController_2024_08_13_createBooking",
"summary": "Create a booking",
"description": "\n POST /v2/bookings is used to create regular bookings, recurring bookings and instant bookings. The request bodies for all 3 are almost the same except:\n If eventTypeId in the request body is id of a regular event, then regular booking is created.\n\n If it is an id of a recurring event type, then recurring booking is created.\n\n Meaning that the request bodies are equal but the outcome depends on what kind of event type it is with the goal of making it as seamless for developers as possible.\n\n For team event types it is possible to create instant meeting. To do that just pass `\"instant\": true` to the request body.\n\n The start needs to be in UTC aka if the timezone is GMT+2 in Rome and meeting should start at 11, then UTC time should have hours 09:00 aka without time zone.\n\n Finally, there are 2 ways to book an event type:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `username` and optionally `organizationSlug` if the user with the username is within an organization.\n ",
"description": "\n POST /v2/bookings is used to create regular bookings, recurring bookings and instant bookings. The request bodies for all 3 are almost the same except:\n If eventTypeId in the request body is id of a regular event, then regular booking is created.\n\n If it is an id of a recurring event type, then recurring booking is created.\n\n Meaning that the request bodies are equal but the outcome depends on what kind of event type it is with the goal of making it as seamless for developers as possible.\n\n For team event types it is possible to create instant meeting. To do that just pass `\"instant\": true` to the request body.\n\n The start needs to be in UTC aka if the timezone is GMT+2 in Rome and meeting should start at 11, then UTC time should have hours 09:00 aka without time zone.\n\n Finally, there are 2 ways to book an event type belonging to an individual user:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `username` and optionally `organizationSlug` if the user with the username is within an organization.\n\n And 2 ways to book and event type belonging to a team:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `teamSlug` and optionally `organizationSlug` if the team with the teamSlug is within an organization.\n ",
"parameters": [
{
"name": "cal-api-version",
@@ -20666,7 +20666,7 @@
},
"eventTypeSlug": {
"type": "string",
"description": "The slug of the event type. Required along with username and optionally organizationSlug if eventTypeId is not provided.",
"description": "The slug of the event type. Required along with username / teamSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "my-event-type"
},
"username": {
@@ -20674,9 +20674,14 @@
"description": "The username of the event owner. Required along with eventTypeSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "john-doe"
},
"teamSlug": {
"type": "string",
"description": "Team slug for team that owns event type for which slots are fetched. Required along with eventTypeSlug and optionally organizationSlug if the team is part of organization",
"example": "john-doe"
},
"organizationSlug": {
"type": "string",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username.",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp"
},
"attendee": {
@@ -20773,7 +20778,7 @@
},
"eventTypeSlug": {
"type": "string",
"description": "The slug of the event type. Required along with username and optionally organizationSlug if eventTypeId is not provided.",
"description": "The slug of the event type. Required along with username / teamSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "my-event-type"
},
"username": {
@@ -20781,9 +20786,14 @@
"description": "The username of the event owner. Required along with eventTypeSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "john-doe"
},
"teamSlug": {
"type": "string",
"description": "Team slug for team that owns event type for which slots are fetched. Required along with eventTypeSlug and optionally organizationSlug if the team is part of organization",
"example": "john-doe"
},
"organizationSlug": {
"type": "string",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username.",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp"
},
"attendee": {
@@ -20886,7 +20896,7 @@
},
"eventTypeSlug": {
"type": "string",
"description": "The slug of the event type. Required along with username and optionally organizationSlug if eventTypeId is not provided.",
"description": "The slug of the event type. Required along with username / teamSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "my-event-type"
},
"username": {
@@ -20894,9 +20904,14 @@
"description": "The username of the event owner. Required along with eventTypeSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "john-doe"
},
"teamSlug": {
"type": "string",
"description": "Team slug for team that owns event type for which slots are fetched. Required along with eventTypeSlug and optionally organizationSlug if the team is part of organization",
"example": "john-doe"
},
"organizationSlug": {
"type": "string",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username.",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp"
},
"attendee": {
+22 -7
View File
@@ -6073,7 +6073,7 @@
"post": {
"operationId": "BookingsController_2024_08_13_createBooking",
"summary": "Create a booking",
"description": "\n POST /v2/bookings is used to create regular bookings, recurring bookings and instant bookings. The request bodies for all 3 are almost the same except:\n If eventTypeId in the request body is id of a regular event, then regular booking is created.\n\n If it is an id of a recurring event type, then recurring booking is created.\n\n Meaning that the request bodies are equal but the outcome depends on what kind of event type it is with the goal of making it as seamless for developers as possible.\n\n For team event types it is possible to create instant meeting. To do that just pass `\"instant\": true` to the request body.\n\n The start needs to be in UTC aka if the timezone is GMT+2 in Rome and meeting should start at 11, then UTC time should have hours 09:00 aka without time zone.\n\n Finally, there are 2 ways to book an event type:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `username` and optionally `organizationSlug` if the user with the username is within an organization.\n ",
"description": "\n POST /v2/bookings is used to create regular bookings, recurring bookings and instant bookings. The request bodies for all 3 are almost the same except:\n If eventTypeId in the request body is id of a regular event, then regular booking is created.\n\n If it is an id of a recurring event type, then recurring booking is created.\n\n Meaning that the request bodies are equal but the outcome depends on what kind of event type it is with the goal of making it as seamless for developers as possible.\n\n For team event types it is possible to create instant meeting. To do that just pass `\"instant\": true` to the request body.\n\n The start needs to be in UTC aka if the timezone is GMT+2 in Rome and meeting should start at 11, then UTC time should have hours 09:00 aka without time zone.\n\n Finally, there are 2 ways to book an event type belonging to an individual user:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `username` and optionally `organizationSlug` if the user with the username is within an organization.\n\n And 2 ways to book and event type belonging to a team:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `teamSlug` and optionally `organizationSlug` if the team with the teamSlug is within an organization.\n ",
"parameters": [
{
"name": "cal-api-version",
@@ -18860,7 +18860,7 @@
},
"eventTypeSlug": {
"type": "string",
"description": "The slug of the event type. Required along with username and optionally organizationSlug if eventTypeId is not provided.",
"description": "The slug of the event type. Required along with username / teamSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "my-event-type"
},
"username": {
@@ -18868,9 +18868,14 @@
"description": "The username of the event owner. Required along with eventTypeSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "john-doe"
},
"teamSlug": {
"type": "string",
"description": "Team slug for team that owns event type for which slots are fetched. Required along with eventTypeSlug and optionally organizationSlug if the team is part of organization",
"example": "john-doe"
},
"organizationSlug": {
"type": "string",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username.",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp"
},
"attendee": {
@@ -18961,7 +18966,7 @@
},
"eventTypeSlug": {
"type": "string",
"description": "The slug of the event type. Required along with username and optionally organizationSlug if eventTypeId is not provided.",
"description": "The slug of the event type. Required along with username / teamSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "my-event-type"
},
"username": {
@@ -18969,9 +18974,14 @@
"description": "The username of the event owner. Required along with eventTypeSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "john-doe"
},
"teamSlug": {
"type": "string",
"description": "Team slug for team that owns event type for which slots are fetched. Required along with eventTypeSlug and optionally organizationSlug if the team is part of organization",
"example": "john-doe"
},
"organizationSlug": {
"type": "string",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username.",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp"
},
"attendee": {
@@ -19067,7 +19077,7 @@
},
"eventTypeSlug": {
"type": "string",
"description": "The slug of the event type. Required along with username and optionally organizationSlug if eventTypeId is not provided.",
"description": "The slug of the event type. Required along with username / teamSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "my-event-type"
},
"username": {
@@ -19075,9 +19085,14 @@
"description": "The username of the event owner. Required along with eventTypeSlug and optionally organizationSlug if eventTypeId is not provided.",
"example": "john-doe"
},
"teamSlug": {
"type": "string",
"description": "Team slug for team that owns event type for which slots are fetched. Required along with eventTypeSlug and optionally organizationSlug if the team is part of organization",
"example": "john-doe"
},
"organizationSlug": {
"type": "string",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username.",
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp"
},
"attendee": {
@@ -36,6 +36,9 @@ import {
} from "./location.input";
import { ValidateMetadata } from "./validators/validate-metadata";
export const FAILED_EVENT_TYPE_IDENTIFICATION_ERROR_MESSAGE =
"Either eventTypeId or eventTypeSlug + username or eventTypeSlug + teamSlug must be provided";
function RequireEventTypeIdentification(validationOptions?: ValidationOptions) {
return function (object: any) {
registerDecorator({
@@ -48,16 +51,16 @@ function RequireEventTypeIdentification(validationOptions?: ValidationOptions) {
validate(_value: any, args: ValidationArguments) {
const obj = args.object as CreateBookingInput_2024_08_13;
// Check if eventTypeId is provided
const hasEventTypeId = !!obj?.eventTypeId;
// Check if we have both eventTypeSlug and username
const hasSlugAndUsername = !!obj?.eventTypeSlug && !!obj?.username;
return hasEventTypeId || hasSlugAndUsername;
const hasSlugAndTeamSlug = !!obj?.eventTypeSlug && !!obj?.teamSlug;
return hasEventTypeId || hasSlugAndUsername || hasSlugAndTeamSlug;
},
defaultMessage(): string {
return "Either eventTypeId OR (eventTypeSlug + username) must be provided";
return FAILED_EVENT_TYPE_IDENTIFICATION_ERROR_MESSAGE;
},
},
});
@@ -182,7 +185,7 @@ export class CreateBookingInput_2024_08_13 {
@ApiPropertyOptional({
type: String,
description:
"The slug of the event type. Required along with username and optionally organizationSlug if eventTypeId is not provided.",
"The slug of the event type. Required along with username / teamSlug and optionally organizationSlug if eventTypeId is not provided.",
example: "my-event-type",
})
@IsOptional()
@@ -201,7 +204,18 @@ export class CreateBookingInput_2024_08_13 {
@ApiPropertyOptional({
type: String,
description: "The organization slug. Optional, only used when booking with eventTypeSlug + username.",
description:
"Team slug for team that owns event type for which slots are fetched. Required along with eventTypeSlug and optionally organizationSlug if the team is part of organization",
example: "john-doe",
})
@IsOptional()
@IsString()
teamSlug?: string;
@ApiPropertyOptional({
type: String,
description:
"The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
example: "acme-corp",
})
@IsOptional()