From e437dfa000b3ebf0b9abf92b5c8ec1ec0b68fe83 Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Wed, 26 Feb 2025 16:06:08 +0100 Subject: [PATCH] refactor: platform default event types (#19225) * feat: add areDefaultEventTypesEnabled to PlatformOAuthClient * feat: specify areDefaultEventTypesEnabled when creating OAuth client * feat: specify areDefaultEventTypesEnabled when updating OAuth client * feat: display areDefaultEventTypesEnabled in OAuth clients list * refactor: set areDefaultEventTypesEnabled by default to false on API level * feat: v2 API CREATE managed user toggle default event types * refactor: centralize OAuth inputs and outputs in platform/types * fix: correct response types for OAuth hooks * refactor: web/lib/hooks/settings/platform/oauth-clients/useOAuthClients.ts * refactor: web/lib/hooks/settings/platform/oauth-clients/useOAuthClients.ts * refactor: split web OAuth hooks into separate files * refactor: split web OAuth hooks into separate files * docs: v2 OAuth client inputs and outputs * refactor: update and create oauth client inputs --- .../oauth-client-users.controller.e2e-spec.ts | 69 +++++++++++++++++-- .../oauth-client-users.controller.ts | 11 ++- .../oauth-clients/oauth-clients.controller.ts | 3 +- .../CreateOAuthClientResponse.dto.ts | 21 ++---- .../services/oauth-clients-users.service.ts | 25 +++---- .../oauth-clients-output.service.ts | 1 + apps/api/v2/swagger/documentation.json | 30 ++++++-- .../dashboard/oauth-clients-list/index.tsx | 1 + .../platform/hooks/useGetUserAttributes.ts | 2 +- .../oauth-clients/OAuthClientCard.tsx | 6 ++ .../oauth-clients/oauth-client-form/index.tsx | 26 ++++++- .../pricing/platform-pricing/index.tsx | 6 +- .../platform/billing/useCheckTeamBilling.ts | 20 ++++++ .../billing/useSubscribeTeamToStripe.ts | 41 +++++++++++ .../useUpgradeTeamSubscriptionInStripe.ts | 41 +++++++++++ .../oauth-clients/useCreateOAuthClient.ts | 40 +++++++++++ .../oauth-clients/useDeleteOAuthClient.ts | 39 +++++++++++ .../platform/oauth-clients/useOAuthClient.ts | 39 +++++++++++ .../platform/oauth-clients/useOAuthClients.ts | 66 ------------------ .../oauth-clients/useUpdateOAuthClient.ts | 43 ++++++++++++ .../[clientId]/edit/edit-view.tsx | 6 +- .../oauth-clients/create-new-view.tsx | 3 +- .../settings/platform/platform-view.tsx | 2 +- docs/api-reference/v2/openapi.json | 38 ++++++++-- packages/platform/types/index.ts | 1 + packages/platform/types/me/index.ts | 1 + packages/platform/types/me/outputs/index.ts | 1 + packages/platform/types/me/outputs/me.ts | 15 ++++ .../inputs/create-oauth-client.input.ts | 13 ++++ .../inputs/update-oauth-client.input.ts | 10 +++ .../outputs/create-oauth-client.output.ts | 16 +++++ .../types/oauth-clients/outputs/index.ts | 1 + .../outputs/oauth-client.output.ts | 17 +++-- .../migration.sql | 2 + packages/prisma/schema.prisma | 1 + 35 files changed, 519 insertions(+), 138 deletions(-) create mode 100644 apps/web/lib/hooks/settings/platform/billing/useCheckTeamBilling.ts create mode 100644 apps/web/lib/hooks/settings/platform/billing/useSubscribeTeamToStripe.ts create mode 100644 apps/web/lib/hooks/settings/platform/billing/useUpgradeTeamSubscriptionInStripe.ts create mode 100644 apps/web/lib/hooks/settings/platform/oauth-clients/useCreateOAuthClient.ts create mode 100644 apps/web/lib/hooks/settings/platform/oauth-clients/useDeleteOAuthClient.ts create mode 100644 apps/web/lib/hooks/settings/platform/oauth-clients/useOAuthClient.ts create mode 100644 apps/web/lib/hooks/settings/platform/oauth-clients/useUpdateOAuthClient.ts create mode 100644 packages/platform/types/me/index.ts create mode 100644 packages/platform/types/me/outputs/index.ts create mode 100644 packages/platform/types/me/outputs/me.ts create mode 100644 packages/platform/types/oauth-clients/outputs/create-oauth-client.output.ts create mode 100644 packages/prisma/migrations/20250210114958_add_default_event_types_enabled_to_platform_oauth_client/migration.sql diff --git a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.e2e-spec.ts b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.e2e-spec.ts index 533c7af03d..3c56506430 100644 --- a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.e2e-spec.ts +++ b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.e2e-spec.ts @@ -74,6 +74,7 @@ describe("OAuth Client Users Endpoints", () => { let app: INestApplication; let oAuthClient: PlatformOAuthClient; + let oAuthClientEventTypesDisabled: PlatformOAuthClient; let organization: Team; let userRepositoryFixture: UserRepositoryFixture; let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; @@ -84,6 +85,7 @@ describe("OAuth Client Users Endpoints", () => { let membershipsRepositoryFixture: MembershipRepositoryFixture; let postResponseData: CreateManagedUserOutput["data"]; + let postResponseData2: CreateManagedUserOutput["data"]; const platformAdminEmail = `oauth-client-users-admin-${randomString()}@api.com`; let platformAdmin: User; @@ -91,6 +93,9 @@ describe("OAuth Client Users Endpoints", () => { const userEmail = `oauth-client-users-user-${randomString()}@api.com`; const userTimeZone = "Europe/Rome"; + const userEmail2 = `oauth-client-users-user2-${randomString()}@api.com`; + const userTimeZone2 = "America/New_York"; + beforeAll(async () => { const moduleRef = await Test.createTestingModule({ providers: [PrismaExceptionFilter, HttpExceptionFilter], @@ -116,6 +121,7 @@ describe("OAuth Client Users Endpoints", () => { isOrganization: true, }); oAuthClient = await createOAuthClient(organization.id); + oAuthClientEventTypesDisabled = await createOAuthClient(organization.id, false); await profilesRepositoryFixture.create({ uid: "asd1qwwqeqw-asddsadasd", @@ -136,12 +142,13 @@ describe("OAuth Client Users Endpoints", () => { await app.init(); }); - async function createOAuthClient(organizationId: number) { + async function createOAuthClient(organizationId: number, areDefaultEventTypesEnabled?: boolean) { const data = { logo: "logo-url", name: "name", redirectUris: [CLIENT_REDIRECT_URI], permissions: 32, + areDefaultEventTypesEnabled, }; const secret = "secret"; @@ -185,7 +192,7 @@ describe("OAuth Client Users Endpoints", () => { .expect(400); }); - it(`/POST`, async () => { + it(`/POST with default event types`, async () => { const requestBody: CreateManagedUserInput = { email: userEmail, timeZone: userTimeZone, @@ -218,14 +225,55 @@ describe("OAuth Client Users Endpoints", () => { expect(responseBody.data.accessToken).toBeDefined(); expect(responseBody.data.refreshToken).toBeDefined(); - await userConnectedToOAuth(responseBody.data.user.email); + await userConnectedToOAuth(oAuthClient.id, responseBody.data.user.email); await userHasDefaultEventTypes(responseBody.data.user.id); await userHasDefaultSchedule(responseBody.data.user.id, responseBody.data.user.defaultScheduleId); await userHasOnlyOneSchedule(responseBody.data.user.id); }); - async function userConnectedToOAuth(userEmail: string) { - const oAuthUsers = await oauthClientRepositoryFixture.getUsers(oAuthClient.id); + it(`/POST without default event types`, async () => { + const requestBody: CreateManagedUserInput = { + email: userEmail2, + timeZone: userTimeZone2, + weekStart: "Monday", + timeFormat: 24, + locale: Locales.FR, + name: "Alice Smith", + avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png", + }; + + const response = await request(app.getHttpServer()) + .post(`/api/v2/oauth-clients/${oAuthClientEventTypesDisabled.id}/users`) + .set("x-cal-secret-key", oAuthClientEventTypesDisabled.secret) + .send(requestBody) + .expect(201); + + const responseBody: CreateManagedUserOutput = response.body; + + postResponseData2 = responseBody.data; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data.user.email).toEqual( + getOAuthUserEmail(oAuthClientEventTypesDisabled.id, requestBody.email) + ); + expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone); + expect(responseBody.data.user.name).toEqual(requestBody.name); + expect(responseBody.data.user.weekStart).toEqual(requestBody.weekStart); + expect(responseBody.data.user.timeFormat).toEqual(requestBody.timeFormat); + expect(responseBody.data.user.locale).toEqual(requestBody.locale); + expect(responseBody.data.user.avatarUrl).toEqual(requestBody.avatarUrl); + expect(responseBody.data.accessToken).toBeDefined(); + expect(responseBody.data.refreshToken).toBeDefined(); + + await userConnectedToOAuth(oAuthClientEventTypesDisabled.id, responseBody.data.user.email); + await userDoesNotHaveDefaultEventTypes(responseBody.data.user.id); + await userHasDefaultSchedule(responseBody.data.user.id, responseBody.data.user.defaultScheduleId); + await userHasOnlyOneSchedule(responseBody.data.user.id); + }); + + async function userConnectedToOAuth(oAuthClientId: string, userEmail: string) { + const oAuthUsers = await oauthClientRepositoryFixture.getUsers(oAuthClientId); const newOAuthUser = oAuthUsers?.find((user) => user.email === userEmail); expect(oAuthUsers?.length).toEqual(1); @@ -251,6 +299,11 @@ describe("OAuth Client Users Endpoints", () => { ).toBeTruthy(); } + async function userDoesNotHaveDefaultEventTypes(userId: number) { + const defaultEventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId); + expect(defaultEventTypes?.length).toEqual(0); + } + async function userHasDefaultSchedule(userId: number, scheduleId: number | null) { expect(scheduleId).toBeDefined(); expect(scheduleId).not.toBeNull(); @@ -358,12 +411,18 @@ describe("OAuth Client Users Endpoints", () => { afterAll(async () => { await oauthClientRepositoryFixture.delete(oAuthClient.id); + await oauthClientRepositoryFixture.delete(oAuthClientEventTypesDisabled.id); await teamRepositoryFixture.delete(organization.id); try { await userRepositoryFixture.delete(postResponseData.user.id); } catch (e) { // User might have been deleted by the test } + try { + await userRepositoryFixture.delete(postResponseData2.user.id); + } catch (e) { + // User might have been deleted by the test + } try { await userRepositoryFixture.delete(platformAdmin.id); } catch (e) { diff --git a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts index b935146406..21f766f82e 100644 --- a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts +++ b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller.ts @@ -85,14 +85,11 @@ export class OAuthClientUsersController { `Creating user with data: ${JSON.stringify(body, null, 2)} for OAuth Client with ID ${oAuthClientId}` ); const client = await this.oauthRepository.getOAuthClient(oAuthClientId); + if (!client) { + throw new NotFoundException(`OAuth Client with ID ${oAuthClientId} not found`); + } - const isPlatformManaged = true; - const { user, tokens } = await this.oAuthClientUsersService.createOauthClientUser( - oAuthClientId, - body, - isPlatformManaged, - client?.organizationId - ); + const { user, tokens } = await this.oAuthClientUsersService.createOAuthClientUser(client, body); return { status: SUCCESS_STATUS, diff --git a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-clients/oauth-clients.controller.ts b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-clients/oauth-clients.controller.ts index 84ecaa6927..c36db1b615 100644 --- a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-clients/oauth-clients.controller.ts +++ b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-clients/oauth-clients.controller.ts @@ -37,8 +37,7 @@ import { import { User, MembershipRole } from "@prisma/client"; import { SUCCESS_STATUS } from "@calcom/platform-constants"; -import { CreateOAuthClientInput, UpdateOAuthClientInput } from "@calcom/platform-types"; -import { Pagination } from "@calcom/platform-types"; +import { CreateOAuthClientInput, UpdateOAuthClientInput, Pagination } from "@calcom/platform-types"; const AUTH_DOCUMENTATION = `⚠️ First, this endpoint requires \`Cookie: next-auth.session-token=eyJhbGciOiJ\` header. Log into Cal web app using owner of organization that was created after visiting \`/settings/organizations/new\`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard. Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.`; diff --git a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-clients/responses/CreateOAuthClientResponse.dto.ts b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-clients/responses/CreateOAuthClientResponse.dto.ts index c7eaee2584..ffb8df15e6 100644 --- a/apps/api/v2/src/modules/oauth-clients/controllers/oauth-clients/responses/CreateOAuthClientResponse.dto.ts +++ b/apps/api/v2/src/modules/oauth-clients/controllers/oauth-clients/responses/CreateOAuthClientResponse.dto.ts @@ -1,22 +1,9 @@ import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; -import { IsIn, ValidateNested, IsNotEmptyObject, IsString } from "class-validator"; +import { IsIn, ValidateNested, IsNotEmptyObject } from "class-validator"; import { SUCCESS_STATUS, ERROR_STATUS, REDIRECT_STATUS } from "@calcom/platform-constants"; - -class DataDto { - @ApiProperty({ - example: "clsx38nbl0001vkhlwin9fmt0", - }) - @IsString() - clientId!: string; - - @ApiProperty({ - example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoib2F1dGgtY2xpZW50Iiwi", - }) - @IsString() - clientSecret!: string; -} +import { CreateOAuthClientOutput } from "@calcom/platform-types"; export class CreateOAuthClientResponseDto { @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) @@ -31,8 +18,8 @@ export class CreateOAuthClientResponseDto { }) @IsNotEmptyObject() @ValidateNested() - @Type(() => DataDto) - data!: DataDto; + @Type(() => CreateOAuthClientOutput) + data!: CreateOAuthClientOutput; } export class CreateOauthClientRedirect { diff --git a/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts b/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts index b6762e6541..809eb37bbb 100644 --- a/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts +++ b/apps/api/v2/src/modules/oauth-clients/services/oauth-clients-users.service.ts @@ -6,7 +6,7 @@ import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-us import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input"; import { UsersRepository } from "@/modules/users/users.repository"; import { BadRequestException, ConflictException, Injectable } from "@nestjs/common"; -import { User, CreationSource } from "@prisma/client"; +import { User, CreationSource, PlatformOAuthClient } from "@prisma/client"; import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries"; @@ -16,16 +16,13 @@ export class OAuthClientUsersService { private readonly userRepository: UsersRepository, private readonly tokensRepository: TokensRepository, private readonly eventTypesService: EventTypesService_2024_04_15, - private readonly schedulesService: SchedulesService_2024_04_15, - private readonly organizationsTeamsService: OrganizationsTeamsService + private readonly schedulesService: SchedulesService_2024_04_15 ) {} - async createOauthClientUser( - oAuthClientId: string, - body: CreateManagedUserInput, - isPlatformManaged: boolean, - organizationId?: number - ) { + async createOAuthClientUser(oAuthClient: PlatformOAuthClient, body: CreateManagedUserInput) { + const oAuthClientId = oAuthClient.id; + const organizationId = oAuthClient.organizationId; + const existingUser = await this.getExistingUserByEmail(oAuthClientId, body.email); if (existingUser) { throw new ConflictException( @@ -35,7 +32,9 @@ export class OAuthClientUsersService { let user: User; if (!organizationId) { - throw new BadRequestException("You cannot create a managed user outside of an organization"); + throw new BadRequestException( + "You cannot create a managed user outside of an organization - the OAuth client does not belong to any organization." + ); } else { const email = this.getOAuthUserEmail(oAuthClientId, body.email); user = ( @@ -57,7 +56,7 @@ export class OAuthClientUsersService { autoAccept: true, }, }, - isPlatformManaged, + isPlatformManaged: true, timeFormat: body.timeFormat, weekStart: body.weekStart, timeZone: body.timeZone, @@ -79,7 +78,9 @@ export class OAuthClientUsersService { user.id ); - await this.eventTypesService.createUserDefaultEventTypes(user.id); + if (oAuthClient.areDefaultEventTypesEnabled) { + await this.eventTypesService.createUserDefaultEventTypes(user.id); + } if (body.timeZone) { const defaultSchedule = await this.schedulesService.createUserDefaultSchedule(user.id, body.timeZone); diff --git a/apps/api/v2/src/modules/oauth-clients/services/oauth-clients/oauth-clients-output.service.ts b/apps/api/v2/src/modules/oauth-clients/services/oauth-clients/oauth-clients-output.service.ts index 280f76e975..ddd0285f91 100644 --- a/apps/api/v2/src/modules/oauth-clients/services/oauth-clients/oauth-clients-output.service.ts +++ b/apps/api/v2/src/modules/oauth-clients/services/oauth-clients/oauth-clients-output.service.ts @@ -33,6 +33,7 @@ export class OAuthClientsOutputService { bookingCancelRedirectUri: client.bookingCancelRedirectUri ?? undefined, bookingRescheduleRedirectUri: client.bookingRescheduleRedirectUri ?? undefined, areEmailsEnabled: client.areEmailsEnabled, + areDefaultEventTypesEnabled: client.areDefaultEventTypesEnabled, }; } diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index 127447be79..90a3b40ecd 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -7635,6 +7635,11 @@ }, "areEmailsEnabled": { "type": "boolean" + }, + "areDefaultEventTypesEnabled": { + "type": "boolean", + "default": false, + "description": "If true, when creating a managed user the managed user will have 4 default event types: 30 and 60 minutes without Cal video, 30 and 60 minutes with Cal video. Set this as false if you want to create a managed user and then manually create event types for the user." } }, "required": [ @@ -7643,7 +7648,7 @@ "permissions" ] }, - "DataDto": { + "CreateOAuthClientOutput": { "type": "object", "properties": { "clientId": { @@ -7678,7 +7683,7 @@ }, "allOf": [ { - "$ref": "#/components/schemas/DataDto" + "$ref": "#/components/schemas/CreateOAuthClientOutput" } ] } @@ -7748,6 +7753,15 @@ "type": "string", "example": "2024-03-23T08:33:21.851Z" }, + "areEmailsEnabled": { + "type": "boolean", + "example": true + }, + "areDefaultEventTypesEnabled": { + "type": "boolean", + "example": true, + "description": "If enabled, when creating a managed user the managed user will have 4 default event types: 30 and 60 minutes without Cal video, 30 and 60 minutes with Cal video. Leave this disabled if you want to create a managed user and then manually create event types for the user." + }, "bookingRedirectUri": { "type": "string", "example": "https://example.com/booking-redirect" @@ -7759,10 +7773,6 @@ "bookingRescheduleRedirectUri": { "type": "string", "example": "https://example.com/booking-reschedule" - }, - "areEmailsEnabled": { - "type": "boolean", - "example": false } }, "required": [ @@ -7772,7 +7782,9 @@ "permissions", "redirectUris", "organizationId", - "createdAt" + "createdAt", + "areEmailsEnabled", + "areDefaultEventTypesEnabled" ] }, "GetOAuthClientsResponseDto": { @@ -7844,6 +7856,10 @@ }, "areEmailsEnabled": { "type": "boolean" + }, + "areDefaultEventTypesEnabled": { + "type": "boolean", + "description": "If true, when creating a managed user the managed user will have 4 default event types: 30 and 60 minutes without Cal video, 30 and 60 minutes with Cal video. Set this as false if you want to create a managed user and then manually create event types for the user." } } }, diff --git a/apps/web/components/settings/platform/dashboard/oauth-clients-list/index.tsx b/apps/web/components/settings/platform/dashboard/oauth-clients-list/index.tsx index 52c2bc0225..f5a84d89cd 100644 --- a/apps/web/components/settings/platform/dashboard/oauth-clients-list/index.tsx +++ b/apps/web/components/settings/platform/dashboard/oauth-clients-list/index.tsx @@ -46,6 +46,7 @@ export const OAuthClientsList = ({ oauthClients, isDeleting, handleDelete }: OAu isLoading={isDeleting} onDelete={handleDelete} areEmailsEnabled={client.areEmailsEnabled} + areDefaultEventTypesEnabled={client.areDefaultEventTypesEnabled} organizationId={client.organizationId} /> ); diff --git a/apps/web/components/settings/platform/hooks/useGetUserAttributes.ts b/apps/web/components/settings/platform/hooks/useGetUserAttributes.ts index e837f115fd..34a6d468a6 100644 --- a/apps/web/components/settings/platform/hooks/useGetUserAttributes.ts +++ b/apps/web/components/settings/platform/hooks/useGetUserAttributes.ts @@ -1,4 +1,4 @@ -import { useCheckTeamBilling } from "@calcom/web/lib/hooks/settings/platform/oauth-clients/usePersistOAuthClient"; +import { useCheckTeamBilling } from "@calcom/web/lib/hooks/settings/platform/billing/useCheckTeamBilling"; import { usePlatformMe } from "./usePlatformMe"; diff --git a/apps/web/components/settings/platform/oauth-clients/OAuthClientCard.tsx b/apps/web/components/settings/platform/oauth-clients/OAuthClientCard.tsx index 56daa78b27..a961cd053f 100644 --- a/apps/web/components/settings/platform/oauth-clients/OAuthClientCard.tsx +++ b/apps/web/components/settings/platform/oauth-clients/OAuthClientCard.tsx @@ -15,6 +15,7 @@ type OAuthClientCardProps = { bookingCancelRedirectUri: string | null | undefined; bookingRescheduleRedirectUri: string | null | undefined; areEmailsEnabled: boolean | undefined; + areDefaultEventTypesEnabled: boolean; permissions: Array; lastItem: boolean; id: string; @@ -38,6 +39,7 @@ export const OAuthClientCard = ({ onDelete, isLoading, areEmailsEnabled, + areDefaultEventTypesEnabled, organizationId, }: OAuthClientCardProps) => { const router = useRouter(); @@ -165,6 +167,10 @@ export const OAuthClientCard = ({
Emails enabled: {areEmailsEnabled ? "Yes" : "No"}
+
+ Default event types enabled:{" "} + {areDefaultEventTypesEnabled ? "Yes" : "No"} +
+
+
+ + + +
+
+
+
+

Permissions

diff --git a/apps/web/components/settings/platform/pricing/platform-pricing/index.tsx b/apps/web/components/settings/platform/pricing/platform-pricing/index.tsx index 3acf1a326e..e0a5820608 100644 --- a/apps/web/components/settings/platform/pricing/platform-pricing/index.tsx +++ b/apps/web/components/settings/platform/pricing/platform-pricing/index.tsx @@ -5,10 +5,8 @@ import type { ReactNode } from "react"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { showToast } from "@calcom/ui"; -import { - useSubscribeTeamToStripe, - useUpgradeTeamSubscriptionInStripe, -} from "@lib/hooks/settings/platform/oauth-clients/usePersistOAuthClient"; +import { useSubscribeTeamToStripe } from "@lib/hooks/settings/platform/billing/useSubscribeTeamToStripe"; +import { useUpgradeTeamSubscriptionInStripe } from "@lib/hooks/settings/platform/billing/useUpgradeTeamSubscriptionInStripe"; import { platformPlans } from "@components/settings/platform/platformUtils"; import { PlatformBillingCard } from "@components/settings/platform/pricing/billing-card"; diff --git a/apps/web/lib/hooks/settings/platform/billing/useCheckTeamBilling.ts b/apps/web/lib/hooks/settings/platform/billing/useCheckTeamBilling.ts new file mode 100644 index 0000000000..d26cbedd26 --- /dev/null +++ b/apps/web/lib/hooks/settings/platform/billing/useCheckTeamBilling.ts @@ -0,0 +1,20 @@ +import { useQuery } from "@tanstack/react-query"; + +export const useCheckTeamBilling = (teamId?: number | null, isPlatformTeam?: boolean | null) => { + const QUERY_KEY = "check-team-billing"; + const isTeamBilledAlready = useQuery({ + queryKey: [QUERY_KEY, teamId], + queryFn: async () => { + const response = await fetch(`/api/v2/billing/${teamId}/check`, { + method: "get", + headers: { "Content-type": "application/json" }, + }); + const data = await response.json(); + + return data.data; + }, + enabled: !!teamId && !!isPlatformTeam, + }); + + return isTeamBilledAlready; +}; diff --git a/apps/web/lib/hooks/settings/platform/billing/useSubscribeTeamToStripe.ts b/apps/web/lib/hooks/settings/platform/billing/useSubscribeTeamToStripe.ts new file mode 100644 index 0000000000..07eda93427 --- /dev/null +++ b/apps/web/lib/hooks/settings/platform/billing/useSubscribeTeamToStripe.ts @@ -0,0 +1,41 @@ +import { useMutation } from "@tanstack/react-query"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import type { ApiResponse, SubscribeTeamInput } from "@calcom/platform-types"; + +export const useSubscribeTeamToStripe = ( + { + onSuccess, + onError, + teamId, + }: { teamId?: number | null; onSuccess: (redirectUrl: string) => void; onError: () => void } = { + onSuccess: () => { + return; + }, + onError: () => { + return; + }, + } +) => { + const mutation = useMutation, unknown, SubscribeTeamInput>({ + mutationFn: (data) => { + return fetch(`/api/v2/billing/${teamId}/subscribe`, { + method: "post", + headers: { "Content-type": "application/json" }, + body: JSON.stringify(data), + }).then((res) => res?.json()); + }, + onSuccess: (data) => { + if (data.status === SUCCESS_STATUS) { + onSuccess?.(data.data?.url); + } else { + onError?.(); + } + }, + onError: () => { + onError?.(); + }, + }); + + return mutation; +}; diff --git a/apps/web/lib/hooks/settings/platform/billing/useUpgradeTeamSubscriptionInStripe.ts b/apps/web/lib/hooks/settings/platform/billing/useUpgradeTeamSubscriptionInStripe.ts new file mode 100644 index 0000000000..14a456e069 --- /dev/null +++ b/apps/web/lib/hooks/settings/platform/billing/useUpgradeTeamSubscriptionInStripe.ts @@ -0,0 +1,41 @@ +import { useMutation } from "@tanstack/react-query"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import type { ApiResponse, SubscribeTeamInput } from "@calcom/platform-types"; + +export const useUpgradeTeamSubscriptionInStripe = ( + { + onSuccess, + onError, + teamId, + }: { teamId?: number | null; onSuccess: (redirectUrl: string) => void; onError: () => void } = { + onSuccess: () => { + return; + }, + onError: () => { + return; + }, + } +) => { + const mutation = useMutation, unknown, SubscribeTeamInput>({ + mutationFn: (data) => { + return fetch(`/api/v2/billing/${teamId}/upgrade`, { + method: "post", + headers: { "Content-type": "application/json" }, + body: JSON.stringify(data), + }).then((res) => res?.json()); + }, + onSuccess: (data) => { + if (data.status === SUCCESS_STATUS) { + onSuccess?.(data.data?.url); + } else { + onError?.(); + } + }, + onError: () => { + onError?.(); + }, + }); + + return mutation; +}; diff --git a/apps/web/lib/hooks/settings/platform/oauth-clients/useCreateOAuthClient.ts b/apps/web/lib/hooks/settings/platform/oauth-clients/useCreateOAuthClient.ts new file mode 100644 index 0000000000..7869f86d36 --- /dev/null +++ b/apps/web/lib/hooks/settings/platform/oauth-clients/useCreateOAuthClient.ts @@ -0,0 +1,40 @@ +import { useMutation } from "@tanstack/react-query"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import type { ApiResponse, CreateOAuthClientInput, CreateOAuthClientOutput } from "@calcom/platform-types"; + +export interface IPersistOAuthClient { + onSuccess?: () => void; + onError?: () => void; +} + +export const useCreateOAuthClient = ( + { onSuccess, onError }: IPersistOAuthClient = { + onSuccess: () => { + return; + }, + onError: () => { + return; + }, + } +) => { + return useMutation, unknown, CreateOAuthClientInput>({ + mutationFn: async (data) => { + return fetch("/api/v2/oauth-clients", { + method: "post", + headers: { "Content-type": "application/json" }, + body: JSON.stringify(data), + }).then((res) => res.json()); + }, + onSuccess: (data) => { + if (data.status === SUCCESS_STATUS) { + onSuccess?.(); + } else { + onError?.(); + } + }, + onError: () => { + onError?.(); + }, + }); +}; diff --git a/apps/web/lib/hooks/settings/platform/oauth-clients/useDeleteOAuthClient.ts b/apps/web/lib/hooks/settings/platform/oauth-clients/useDeleteOAuthClient.ts new file mode 100644 index 0000000000..88bf708d35 --- /dev/null +++ b/apps/web/lib/hooks/settings/platform/oauth-clients/useDeleteOAuthClient.ts @@ -0,0 +1,39 @@ +import { useMutation } from "@tanstack/react-query"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import type { ApiResponse, DeleteOAuthClientInput, PlatformOAuthClientDto } from "@calcom/platform-types"; + +import type { IPersistOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/useCreateOAuthClient"; + +export const useDeleteOAuthClient = ( + { onSuccess, onError }: IPersistOAuthClient = { + onSuccess: () => { + return; + }, + onError: () => { + return; + }, + } +) => { + const mutation = useMutation, unknown, DeleteOAuthClientInput>({ + mutationFn: async (data) => { + const { id } = data; + return fetch(`/api/v2/oauth-clients/${id}`, { + method: "delete", + headers: { "Content-type": "application/json" }, + }).then((res) => res?.json()); + }, + onSuccess: (data) => { + if (data.status === SUCCESS_STATUS) { + onSuccess?.(); + } else { + onError?.(); + } + }, + onError: () => { + onError?.(); + }, + }); + + return mutation; +}; diff --git a/apps/web/lib/hooks/settings/platform/oauth-clients/useOAuthClient.ts b/apps/web/lib/hooks/settings/platform/oauth-clients/useOAuthClient.ts new file mode 100644 index 0000000000..f8f19e48b6 --- /dev/null +++ b/apps/web/lib/hooks/settings/platform/oauth-clients/useOAuthClient.ts @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query"; + +import type { ApiSuccessResponse, PlatformOAuthClientDto } from "@calcom/platform-types"; + +export const useOAuthClient = (clientId?: string) => { + const { + isLoading, + error, + data: response, + isFetched, + isError, + isFetching, + isSuccess, + isFetchedAfterMount, + refetch, + } = useQuery>({ + queryKey: ["oauth-client", clientId], + queryFn: () => { + return fetch(`/api/v2/oauth-clients/${clientId}`, { + method: "get", + headers: { "Content-type": "application/json" }, + }).then((res) => res.json()); + }, + enabled: Boolean(clientId), + staleTime: Infinity, + }); + + return { + isLoading, + error, + data: response?.data, + isFetched, + isError, + isFetching, + isSuccess, + isFetchedAfterMount, + refetch, + }; +}; diff --git a/apps/web/lib/hooks/settings/platform/oauth-clients/useOAuthClients.ts b/apps/web/lib/hooks/settings/platform/oauth-clients/useOAuthClients.ts index 13ea78c120..c815d0b0b6 100644 --- a/apps/web/lib/hooks/settings/platform/oauth-clients/useOAuthClients.ts +++ b/apps/web/lib/hooks/settings/platform/oauth-clients/useOAuthClients.ts @@ -2,17 +2,6 @@ import { useQuery } from "@tanstack/react-query"; import type { ApiSuccessResponse, PlatformOAuthClientDto } from "@calcom/platform-types"; -export type ManagedUser = { - id: number; - email: string; - username: string | null; - timeZone: string; - weekStart: string; - createdDate: Date; - timeFormat: number | null; - defaultScheduleId: number | null; -}; - export const useOAuthClients = () => { const query = useQuery>({ queryKey: ["oauth-clients"], @@ -26,58 +15,3 @@ export const useOAuthClients = () => { return { ...query, data: query.data?.data ?? [] }; }; - -export const useOAuthClient = (clientId?: string) => { - const { - isLoading, - error, - data: response, - isFetched, - isError, - isFetching, - isSuccess, - isFetchedAfterMount, - refetch, - } = useQuery>({ - queryKey: ["oauth-client", clientId], - queryFn: () => { - return fetch(`/api/v2/oauth-clients/${clientId}`, { - method: "get", - headers: { "Content-type": "application/json" }, - }).then((res) => res.json()); - }, - enabled: Boolean(clientId), - staleTime: Infinity, - }); - - return { - isLoading, - error, - data: response?.data, - isFetched, - isError, - isFetching, - isSuccess, - isFetchedAfterMount, - refetch, - }; -}; -export const useGetOAuthClientManagedUsers = (clientId: string) => { - const { - isLoading, - error, - data: response, - refetch, - } = useQuery>({ - queryKey: ["oauth-client-managed-users", clientId], - queryFn: () => { - return fetch(`/api/v2/oauth-clients/${clientId}/managed-users`, { - method: "get", - headers: { "Content-type": "application/json" }, - }).then((res) => res.json()); - }, - enabled: Boolean(clientId), - }); - - return { isLoading, error, data: response?.data, refetch }; -}; diff --git a/apps/web/lib/hooks/settings/platform/oauth-clients/useUpdateOAuthClient.ts b/apps/web/lib/hooks/settings/platform/oauth-clients/useUpdateOAuthClient.ts new file mode 100644 index 0000000000..955372ced6 --- /dev/null +++ b/apps/web/lib/hooks/settings/platform/oauth-clients/useUpdateOAuthClient.ts @@ -0,0 +1,43 @@ +import { useMutation } from "@tanstack/react-query"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import type { ApiResponse, CreateOAuthClientInput, PlatformOAuthClientDto } from "@calcom/platform-types"; + +import type { IPersistOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/useCreateOAuthClient"; + +export const useUpdateOAuthClient = ( + { onSuccess, onError, clientId }: IPersistOAuthClient & { clientId?: string } = { + onSuccess: () => { + return; + }, + onError: () => { + return; + }, + } +) => { + const mutation = useMutation< + ApiResponse, + unknown, + Omit + >({ + mutationFn: async (data) => { + return fetch(`/api/v2/oauth-clients/${clientId}`, { + method: "PATCH", + headers: { "Content-type": "application/json" }, + body: JSON.stringify(data), + }).then((res) => res?.json()); + }, + onSuccess: (data) => { + if (data.status === SUCCESS_STATUS) { + onSuccess?.(); + } else { + onError?.(); + } + }, + onError: () => { + onError?.(); + }, + }); + + return mutation; +}; diff --git a/apps/web/modules/settings/platform/oauth-clients/[clientId]/edit/edit-view.tsx b/apps/web/modules/settings/platform/oauth-clients/[clientId]/edit/edit-view.tsx index d88f7faadd..4e47722d1c 100644 --- a/apps/web/modules/settings/platform/oauth-clients/[clientId]/edit/edit-view.tsx +++ b/apps/web/modules/settings/platform/oauth-clients/[clientId]/edit/edit-view.tsx @@ -9,8 +9,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants"; import { showToast } from "@calcom/ui"; -import { useOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/useOAuthClients"; -import { useUpdateOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/usePersistOAuthClient"; +import { useOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/useOAuthClient"; +import { useUpdateOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/useUpdateOAuthClient"; import NoPlatformPlan from "@components/settings/platform/dashboard/NoPlatformPlan"; import { useGetUserAttributes } from "@components/settings/platform/hooks/useGetUserAttributes"; @@ -59,6 +59,7 @@ export default function EditOAuthClient() { bookingCancelRedirectUri: data.bookingCancelRedirectUri, bookingRescheduleRedirectUri: data.bookingRescheduleRedirectUri, areEmailsEnabled: data.areEmailsEnabled, + areDefaultEventTypesEnabled: data.areDefaultEventTypesEnabled, }); }; @@ -85,6 +86,7 @@ export default function EditOAuthClient() { defaultValues={{ name: data?.name ?? "", areEmailsEnabled: data.areEmailsEnabled ?? false, + areDefaultEventTypesEnabled: data.areDefaultEventTypesEnabled ?? false, redirectUris: data?.redirectUris?.map((uri) => ({ uri })) ?? [{ uri: "" }], bookingRedirectUri: data?.bookingRedirectUri ?? "", bookingCancelRedirectUri: data?.bookingCancelRedirectUri ?? "", diff --git a/apps/web/modules/settings/platform/oauth-clients/create-new-view.tsx b/apps/web/modules/settings/platform/oauth-clients/create-new-view.tsx index c08abade80..27cb4a0c57 100644 --- a/apps/web/modules/settings/platform/oauth-clients/create-new-view.tsx +++ b/apps/web/modules/settings/platform/oauth-clients/create-new-view.tsx @@ -10,7 +10,7 @@ import type { PERMISSION_MAP } from "@calcom/platform-constants"; import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants/permissions"; import { showToast } from "@calcom/ui"; -import { useCreateOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/usePersistOAuthClient"; +import { useCreateOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/useCreateOAuthClient"; import NoPlatformPlan from "@components/settings/platform/dashboard/NoPlatformPlan"; import { useGetUserAttributes } from "@components/settings/platform/hooks/useGetUserAttributes"; @@ -55,6 +55,7 @@ export default function CreateOAuthClient() { bookingCancelRedirectUri: data.bookingCancelRedirectUri, bookingRescheduleRedirectUri: data.bookingRescheduleRedirectUri, areEmailsEnabled: data.areEmailsEnabled, + areDefaultEventTypesEnabled: data.areDefaultEventTypesEnabled, }); }; diff --git a/apps/web/modules/settings/platform/platform-view.tsx b/apps/web/modules/settings/platform/platform-view.tsx index e58946d37f..4d46ae10e2 100644 --- a/apps/web/modules/settings/platform/platform-view.tsx +++ b/apps/web/modules/settings/platform/platform-view.tsx @@ -7,8 +7,8 @@ import Shell from "@calcom/features/shell/Shell"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { showToast } from "@calcom/ui"; +import { useDeleteOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/useDeleteOAuthClient"; import { useOAuthClients } from "@lib/hooks/settings/platform/oauth-clients/useOAuthClients"; -import { useDeleteOAuthClient } from "@lib/hooks/settings/platform/oauth-clients/usePersistOAuthClient"; import { HelpCards } from "@components/settings/platform/dashboard/HelpCards"; import NoPlatformPlan from "@components/settings/platform/dashboard/NoPlatformPlan"; diff --git a/docs/api-reference/v2/openapi.json b/docs/api-reference/v2/openapi.json index e5b1905581..982868bbc6 100644 --- a/docs/api-reference/v2/openapi.json +++ b/docs/api-reference/v2/openapi.json @@ -7181,11 +7181,16 @@ }, "areEmailsEnabled": { "type": "boolean" + }, + "areDefaultEventTypesEnabled": { + "type": "boolean", + "default": false, + "description": "If true, when creating a managed user the managed user will have 4 default event types: 30 and 60 minutes without Cal video, 30 and 60 minutes with Cal video. Set this as false if you want to create a managed user and then manually create event types for the user." } }, "required": ["name", "redirectUris", "permissions"] }, - "DataDto": { + "CreateOAuthClientOutput": { "type": "object", "properties": { "clientId": { @@ -7214,7 +7219,7 @@ }, "allOf": [ { - "$ref": "#/components/schemas/DataDto" + "$ref": "#/components/schemas/CreateOAuthClientOutput" } ] } @@ -7276,6 +7281,15 @@ "type": "string", "example": "2024-03-23T08:33:21.851Z" }, + "areEmailsEnabled": { + "type": "boolean", + "example": true + }, + "areDefaultEventTypesEnabled": { + "type": "boolean", + "example": true, + "description": "If enabled, when creating a managed user the managed user will have 4 default event types: 30 and 60 minutes without Cal video, 30 and 60 minutes with Cal video. Leave this disabled if you want to create a managed user and then manually create event types for the user." + }, "bookingRedirectUri": { "type": "string", "example": "https://example.com/booking-redirect" @@ -7287,13 +7301,19 @@ "bookingRescheduleRedirectUri": { "type": "string", "example": "https://example.com/booking-reschedule" - }, - "areEmailsEnabled": { - "type": "boolean", - "example": false } }, - "required": ["id", "name", "secret", "permissions", "redirectUris", "organizationId", "createdAt"] + "required": [ + "id", + "name", + "secret", + "permissions", + "redirectUris", + "organizationId", + "createdAt", + "areEmailsEnabled", + "areDefaultEventTypesEnabled" + ] }, "GetOAuthClientsResponseDto": { "type": "object", @@ -7352,6 +7372,10 @@ }, "areEmailsEnabled": { "type": "boolean" + }, + "areDefaultEventTypesEnabled": { + "type": "boolean", + "description": "If true, when creating a managed user the managed user will have 4 default event types: 30 and 60 minutes without Cal video, 30 and 60 minutes with Cal video. Set this as false if you want to create a managed user and then manually create event types for the user." } } }, diff --git a/packages/platform/types/index.ts b/packages/platform/types/index.ts index de15a562f5..0a4590491a 100644 --- a/packages/platform/types/index.ts +++ b/packages/platform/types/index.ts @@ -10,5 +10,6 @@ export * from "./event-types"; export * from "./organizations"; export * from "./teams"; export * from "./embed"; +export * from "./oauth-clients"; export * from "./routing-forms"; export * from "./me"; diff --git a/packages/platform/types/me/index.ts b/packages/platform/types/me/index.ts new file mode 100644 index 0000000000..fb53cfa187 --- /dev/null +++ b/packages/platform/types/me/index.ts @@ -0,0 +1 @@ +export * from "./outputs"; diff --git a/packages/platform/types/me/outputs/index.ts b/packages/platform/types/me/outputs/index.ts new file mode 100644 index 0000000000..3c25512c54 --- /dev/null +++ b/packages/platform/types/me/outputs/index.ts @@ -0,0 +1 @@ +export * from "./me"; diff --git a/packages/platform/types/me/outputs/me.ts b/packages/platform/types/me/outputs/me.ts new file mode 100644 index 0000000000..e500ed0aab --- /dev/null +++ b/packages/platform/types/me/outputs/me.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; + +export const userSchemaResponse = z.object({ + id: z.number().int(), + email: z.string(), + timeFormat: z.number().int().default(12), + defaultScheduleId: z.number().int().nullable(), + weekStart: z.string(), + timeZone: z.string().default("Europe/London"), + username: z.string(), + organizationId: z.number().nullable(), + organization: z.object({ isPlatform: z.boolean(), id: z.number() }).optional(), +}); + +export type UserResponse = z.infer; diff --git a/packages/platform/types/oauth-clients/inputs/create-oauth-client.input.ts b/packages/platform/types/oauth-clients/inputs/create-oauth-client.input.ts index 87819d9b4f..1ddf23e567 100644 --- a/packages/platform/types/oauth-clients/inputs/create-oauth-client.input.ts +++ b/packages/platform/types/oauth-clients/inputs/create-oauth-client.input.ts @@ -1,8 +1,11 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Transform } from "class-transformer"; import { IsArray, IsEnum, IsOptional, IsBoolean, IsString } from "class-validator"; import { PERMISSION_MAP } from "@calcom/platform-constants"; +export const ARE_DEFAULT_EVENT_TYPES_ENABLED_DOCS = + "If true, when creating a managed user the managed user will have 4 default event types: 30 and 60 minutes without Cal video, 30 and 60 minutes with Cal video. Set this as false if you want to create a managed user and then manually create event types for the user."; export class CreateOAuthClientInput { @IsOptional() @IsString() @@ -47,4 +50,14 @@ export class CreateOAuthClientInput { @IsBoolean() @ApiPropertyOptional() areEmailsEnabled?: boolean; + + @IsOptional() + @IsBoolean() + @Transform(({ value }) => value ?? false) + @ApiPropertyOptional({ + type: Boolean, + default: false, + description: ARE_DEFAULT_EVENT_TYPES_ENABLED_DOCS, + }) + areDefaultEventTypesEnabled? = false; } diff --git a/packages/platform/types/oauth-clients/inputs/update-oauth-client.input.ts b/packages/platform/types/oauth-clients/inputs/update-oauth-client.input.ts index 5282efbddf..58cc453de1 100644 --- a/packages/platform/types/oauth-clients/inputs/update-oauth-client.input.ts +++ b/packages/platform/types/oauth-clients/inputs/update-oauth-client.input.ts @@ -1,6 +1,8 @@ import { ApiPropertyOptional } from "@nestjs/swagger"; import { IsArray, IsBoolean, IsOptional, IsString } from "class-validator"; +import { ARE_DEFAULT_EVENT_TYPES_ENABLED_DOCS } from "./create-oauth-client.input"; + export class UpdateOAuthClientInput { @IsOptional() @IsString() @@ -37,4 +39,12 @@ export class UpdateOAuthClientInput { @IsBoolean() @ApiPropertyOptional() areEmailsEnabled?: boolean; + + @IsOptional() + @IsBoolean() + @ApiPropertyOptional({ + type: Boolean, + description: ARE_DEFAULT_EVENT_TYPES_ENABLED_DOCS, + }) + areDefaultEventTypesEnabled?: boolean; } diff --git a/packages/platform/types/oauth-clients/outputs/create-oauth-client.output.ts b/packages/platform/types/oauth-clients/outputs/create-oauth-client.output.ts new file mode 100644 index 0000000000..5a7b9c5e4a --- /dev/null +++ b/packages/platform/types/oauth-clients/outputs/create-oauth-client.output.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsString } from "class-validator"; + +export class CreateOAuthClientOutput { + @ApiProperty({ + example: "clsx38nbl0001vkhlwin9fmt0", + }) + @IsString() + clientId!: string; + + @ApiProperty({ + example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoib2F1dGgtY2xpZW50Iiwi", + }) + @IsString() + clientSecret!: string; +} diff --git a/packages/platform/types/oauth-clients/outputs/index.ts b/packages/platform/types/oauth-clients/outputs/index.ts index 146ddc4e13..7adecc910f 100644 --- a/packages/platform/types/oauth-clients/outputs/index.ts +++ b/packages/platform/types/oauth-clients/outputs/index.ts @@ -1 +1,2 @@ export * from "./oauth-client.output"; +export * from "./create-oauth-client.output"; diff --git a/packages/platform/types/oauth-clients/outputs/oauth-client.output.ts b/packages/platform/types/oauth-clients/outputs/oauth-client.output.ts index 070d513624..6f423aeaeb 100644 --- a/packages/platform/types/oauth-clients/outputs/oauth-client.output.ts +++ b/packages/platform/types/oauth-clients/outputs/oauth-client.output.ts @@ -44,6 +44,18 @@ export class PlatformOAuthClientDto { @IsDate() createdAt!: Date; + @IsBoolean() + @ApiProperty({ example: true }) + areEmailsEnabled!: boolean; + + @IsBoolean() + @ApiProperty({ + example: true, + description: + "If enabled, when creating a managed user the managed user will have 4 default event types: 30 and 60 minutes without Cal video, 30 and 60 minutes with Cal video. Leave this disabled if you want to create a managed user and then manually create event types for the user.", + }) + areDefaultEventTypesEnabled!: boolean; + @ApiPropertyOptional({ example: "https://example.com/booking-redirect" }) @IsOptional() @IsUrl() @@ -58,9 +70,4 @@ export class PlatformOAuthClientDto { @IsOptional() @IsUrl() bookingRescheduleRedirectUri?: string; - - @ApiPropertyOptional({ example: false }) - @IsOptional() - @IsBoolean() - areEmailsEnabled?: boolean; } diff --git a/packages/prisma/migrations/20250210114958_add_default_event_types_enabled_to_platform_oauth_client/migration.sql b/packages/prisma/migrations/20250210114958_add_default_event_types_enabled_to_platform_oauth_client/migration.sql new file mode 100644 index 0000000000..a53337f7a0 --- /dev/null +++ b/packages/prisma/migrations/20250210114958_add_default_event_types_enabled_to_platform_oauth_client/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "PlatformOAuthClient" ADD COLUMN "areDefaultEventTypesEnabled" BOOLEAN NOT NULL DEFAULT true; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 7d0eb8ff17..81c88d8759 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -1519,6 +1519,7 @@ model PlatformOAuthClient { bookingCancelRedirectUri String? bookingRescheduleRedirectUri String? areEmailsEnabled Boolean @default(false) + areDefaultEventTypesEnabled Boolean @default(true) createdAt DateTime @default(now()) }