feat: add advanced tab attributes to api/v2/event-types (#16314)
* added bookerLayouts * added requiresConfirmation * added tests * undo extra changes * undo extra change * fixed imports * added requiresBookerEmailVerification * added hideCalendarNotes * added lockTimeZoneToggleOnBookingPage * added eventTypeColor * undo extra changes * added seats * added requiresConfirmationWillBlockSlot to requiresConfirmation * refactor: Implement BaseEventType class to avoid duplication across EventType input/output classes ## D-R-Y ## * temp-fix: will revert later * Correct placement of users array from EventTypeOutput to TeamEventTypeOutput * Refactor: Shifted transformation logic to controllers using interceptors and pipes for a cleaner service layer. * small fix * Refactor: Moved transformation logic from service to controller using interceptors and pipes ** organisations/event-types * feat: adde tests for `validateEventTypeInputs` * fix: failing test * fix: layout validator * renamed blockCalendarForUnconfirmedBookings to blockUnconfirmedBookingsInBooker * testing changes * added disabled state * fix merege conflict * renamed requiresConfirmation to confirmationPolicy * renamed OutputEventTypesResponseInterceptor to OutputTeamEventTypesResponseInterceptor * renamed `darkThemeColor` -> `darkThemeHex` and `lightThemeColor` -> `lightThemeHex` * renamed `eventTypeColor` -> `color` * changed `requiresConfirmation` -> `confirmationPolicy` * added tests for disabled state * added class-validators to `disabled?: false` * Revert: Split create and update input/output classes * feat: Refactor responses to use DTOs with plainToClass for explicit property control • Updated all endpoints to return response DTOs (e.g., CreateTeamEventTypeOutput, GetTeamEventTypeOutput) to ensure consistent Swagger documentation generation. • Introduced plainToClass for transforming responses, enabling explicit control over which properties are returned in the response, following Morgan’s recommendation. • Replaced generic HandlerResponse type with class-validator based DTOs for each endpoint, ensuring TypeScript enforces correct response structure. • Applied strategy: "excludeAll" to limit response properties to only those explicitly defined in the DTO, preventing unintended fields from being included in the API responses. * added pipes to transform output data * add clean script to platform-types package.json * added customName * added destination calendar * fixed type errors * added useDestinationCalendarEmail * refactor: api-reqest and api-response * Update event-type.output.ts * fixed errors * Update yarn.lock * added some missed properties to output * Update constants.ts * removed return type * fixed some type errors * reuse types * Improve readability of validation functions * Update CHANGELOG.md * chore: post publish platform libraries * Update documentation.json * fix: reset platform libraries to 0.0.0 * fixup! fix: reset platform libraries to 0.0.0 * Update event-type.tranformed.ts --------- Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
68 files changed
+2859
-6305
No files matched your search
+42
-11
@@ -1,5 +1,6 @@
|
||||
import { CreatePhoneCallInput } from "@/ee/event-types/event-types_2024_06_14/inputs/create-phone-call.input";
|
||||
import { CreatePhoneCallOutput } from "@/ee/event-types/event-types_2024_06_14/outputs/create-phone-call.output";
|
||||
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
@@ -15,7 +16,10 @@ import { DeleteTeamEventTypeOutput } from "@/modules/organizations/controllers/e
|
||||
import { GetTeamEventTypeOutput } from "@/modules/organizations/controllers/event-types/outputs/teams/get-team-event-type.output";
|
||||
import { GetTeamEventTypesOutput } from "@/modules/organizations/controllers/event-types/outputs/teams/get-team-event-types.output";
|
||||
import { UpdateTeamEventTypeOutput } from "@/modules/organizations/controllers/event-types/outputs/teams/update-team-event-type.output";
|
||||
import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/controllers/pipes/event-types/team-event-types-response.transformer";
|
||||
import { InputOrganizationsEventTypesService } from "@/modules/organizations/services/event-types/input.service";
|
||||
import { OrganizationsEventTypesService } from "@/modules/organizations/services/event-types/organizations-event-types.service";
|
||||
import { DatabaseTeamEventType } from "@/modules/organizations/services/event-types/output.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import {
|
||||
Controller,
|
||||
@@ -34,22 +38,33 @@ import {
|
||||
} from "@nestjs/common";
|
||||
import { ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { handleCreatePhoneCall } from "@calcom/platform-libraries";
|
||||
import {
|
||||
CreateTeamEventTypeInput_2024_06_14,
|
||||
GetTeamEventTypesQuery_2024_06_14,
|
||||
SkipTakePagination,
|
||||
TeamEventTypeOutput_2024_06_14,
|
||||
UpdateTeamEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export type EventTypeHandlerResponse = {
|
||||
data: DatabaseTeamEventType[] | DatabaseTeamEventType;
|
||||
status: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
};
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@DocsTags("Organizations Event Types")
|
||||
export class OrganizationsEventTypesController {
|
||||
constructor(private readonly organizationsEventTypesService: OrganizationsEventTypesService) {}
|
||||
constructor(
|
||||
private readonly organizationsEventTypesService: OrganizationsEventTypesService,
|
||||
private readonly inputService: InputOrganizationsEventTypesService,
|
||||
private readonly inputUserEventTypesService: InputEventTypesService_2024_06_14,
|
||||
private readonly outputTeamEventTypesResponsePipe: OutputTeamEventTypesResponsePipe
|
||||
) {}
|
||||
|
||||
@Roles("TEAM_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
@@ -61,16 +76,22 @@ export class OrganizationsEventTypesController {
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Body() bodyEventType: CreateTeamEventTypeInput_2024_06_14
|
||||
): Promise<CreateTeamEventTypeOutput> {
|
||||
const transformedBody = await this.inputService.transformAndValidateCreateTeamEventTypeInput(
|
||||
user.id,
|
||||
teamId,
|
||||
bodyEventType
|
||||
);
|
||||
|
||||
const eventType = await this.organizationsEventTypesService.createTeamEventType(
|
||||
user,
|
||||
teamId,
|
||||
orgId,
|
||||
bodyEventType
|
||||
transformedBody
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventType),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,7 +111,9 @@ export class OrganizationsEventTypesController {
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: (await this.outputTeamEventTypesResponsePipe.transform(
|
||||
eventType
|
||||
)) as TeamEventTypeOutput_2024_06_14,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,12 +148,13 @@ export class OrganizationsEventTypesController {
|
||||
@Query() queryParams: GetTeamEventTypesQuery_2024_06_14
|
||||
): Promise<GetTeamEventTypesOutput> {
|
||||
const { eventSlug } = queryParams;
|
||||
|
||||
if (eventSlug) {
|
||||
const eventType = await this.organizationsEventTypesService.getTeamEventTypeBySlug(teamId, eventSlug);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType ? [eventType] : [],
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventType ? [eventType] : []),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -138,7 +162,7 @@ export class OrganizationsEventTypesController {
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventTypes,
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventTypes),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,7 +179,7 @@ export class OrganizationsEventTypesController {
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventTypes,
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventTypes),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,16 +193,23 @@ export class OrganizationsEventTypesController {
|
||||
@GetUser() user: UserWithProfile,
|
||||
@Body() bodyEventType: UpdateTeamEventTypeInput_2024_06_14
|
||||
): Promise<UpdateTeamEventTypeOutput> {
|
||||
const transformedBody = await this.inputService.transformAndValidateUpdateTeamEventTypeInput(
|
||||
user.id,
|
||||
eventTypeId,
|
||||
teamId,
|
||||
bodyEventType
|
||||
);
|
||||
|
||||
const eventType = await this.organizationsEventTypesService.updateTeamEventType(
|
||||
eventTypeId,
|
||||
teamId,
|
||||
bodyEventType,
|
||||
transformedBody,
|
||||
user
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
data: await this.outputTeamEventTypesResponsePipe.transform(eventType),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -189,7 +220,7 @@ export class OrganizationsEventTypesController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async deleteTeamEventType(
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Param("eventTypeId") eventTypeId: number
|
||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number
|
||||
): Promise<DeleteTeamEventTypeOutput> {
|
||||
const eventType = await this.organizationsEventTypesService.deleteTeamEventType(teamId, eventTypeId);
|
||||
|
||||
|
||||
+37
-2
@@ -17,7 +17,12 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { BookingWindowPeriodInputTypeEnum_2024_06_14 } from "@calcom/platform-enums";
|
||||
import {
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
ConfirmationPolicyEnum,
|
||||
NoticeThresholdUnitEnum,
|
||||
} from "@calcom/platform-enums";
|
||||
import {
|
||||
ApiSuccessResponse,
|
||||
CreateTeamEventTypeInput_2024_06_14,
|
||||
@@ -276,6 +281,30 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
value: 30,
|
||||
rolling: true,
|
||||
},
|
||||
bookerLayouts: {
|
||||
enabledLayouts: [
|
||||
BookerLayoutsInputEnum_2024_06_14.column,
|
||||
BookerLayoutsInputEnum_2024_06_14.month,
|
||||
BookerLayoutsInputEnum_2024_06_14.week,
|
||||
],
|
||||
defaultLayout: BookerLayoutsInputEnum_2024_06_14.month,
|
||||
},
|
||||
|
||||
confirmationPolicy: {
|
||||
type: ConfirmationPolicyEnum.TIME,
|
||||
noticeThreshold: {
|
||||
count: 60,
|
||||
unit: NoticeThresholdUnitEnum.MINUTES,
|
||||
},
|
||||
blockUnconfirmedBookingsInBooker: true,
|
||||
},
|
||||
requiresBookerEmailVerification: true,
|
||||
hideCalendarNotes: true,
|
||||
lockTimeZoneToggleOnBookingPage: true,
|
||||
color: {
|
||||
darkThemeHex: "#292929",
|
||||
lightThemeHex: "#fafafa",
|
||||
},
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -297,6 +326,12 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
expect(data.bookingLimitsDuration).toEqual(body.bookingLimitsDuration);
|
||||
expect(data.offsetStart).toEqual(body.offsetStart);
|
||||
expect(data.bookingWindow).toEqual(body.bookingWindow);
|
||||
expect(data.bookerLayouts).toEqual(body.bookerLayouts);
|
||||
expect(data.confirmationPolicy).toEqual(body.confirmationPolicy);
|
||||
expect(data.requiresBookerEmailVerification).toEqual(body.requiresBookerEmailVerification);
|
||||
expect(data.hideCalendarNotes).toEqual(body.hideCalendarNotes);
|
||||
expect(data.lockTimeZoneToggleOnBookingPage).toEqual(body.lockTimeZoneToggleOnBookingPage);
|
||||
expect(data.color).toEqual(body.color);
|
||||
|
||||
collectiveEventType = responseBody.data;
|
||||
});
|
||||
@@ -444,7 +479,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}/event-types/999999`)
|
||||
.send(body)
|
||||
.expect(404);
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should update collective event-type", async () => {
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { OutputOrganizationsEventTypesService } from "@/modules/organizations/services/event-types/output.service";
|
||||
import { DatabaseTeamEventType } from "@/modules/organizations/services/event-types/output.service";
|
||||
import { Injectable, PipeTransform } from "@nestjs/common";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { TeamEventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class OutputTeamEventTypesResponsePipe implements PipeTransform {
|
||||
constructor(private readonly outputOrganizationsEventTypesService: OutputOrganizationsEventTypesService) {}
|
||||
|
||||
private async transformEventType(item: DatabaseTeamEventType): Promise<TeamEventTypeOutput_2024_06_14> {
|
||||
return plainToClass(
|
||||
TeamEventTypeOutput_2024_06_14,
|
||||
await this.outputOrganizationsEventTypesService.getResponseTeamEventType(item),
|
||||
{ strategy: "exposeAll" }
|
||||
);
|
||||
}
|
||||
|
||||
// Implementing function overloading to ensure correct return types based on input type:
|
||||
async transform(value: DatabaseTeamEventType[]): Promise<TeamEventTypeOutput_2024_06_14[]>;
|
||||
|
||||
async transform(value: DatabaseTeamEventType): Promise<TeamEventTypeOutput_2024_06_14>;
|
||||
|
||||
async transform(
|
||||
value: DatabaseTeamEventType | DatabaseTeamEventType[]
|
||||
): Promise<TeamEventTypeOutput_2024_06_14 | TeamEventTypeOutput_2024_06_14[]>;
|
||||
|
||||
async transform(
|
||||
value: DatabaseTeamEventType | DatabaseTeamEventType[]
|
||||
): Promise<TeamEventTypeOutput_2024_06_14 | TeamEventTypeOutput_2024_06_14[]> {
|
||||
if (Array.isArray(value)) {
|
||||
return await Promise.all(value.map((item) => this.transformEventType(item)));
|
||||
} else {
|
||||
return await this.transformEventType(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user