feat: api endpoints for calendar settings atom (#15761)

* calendars repository

* add disconnect endpoint

* update platform libraries to include getCalendar fn

* cleanup

* fix typings

* update calendars service to include delete credentials handler

* update typings

* add missing return

* refactor delete calendar credentials handler

* update disconnect endpoint

* fix typing

* fixup

* add handlers to insert and remove selected calendar

* init selected calendars controller

* fix naming

* output response dto for deleting calendar credentials

* fix typing

* fix merge conflicts

* resolve merge conflicts

* fixup

* cleanup

* capitalize controller name

* include selected calendars controller in selected calendars module

* fix type error

* cleanup

* fixup

* add calendars repository

* custom hook for calendar credentials

* fix typing

* cleanup

* take input from query params instead of body in delete request

* custom hook to add selected calendar

* custom hook to remove selected calendar

* better naming

* fix typo

* fixup

* update input for delete calendar query params

* address PR feedback

* add method to check calendar credentials

* abstract logic to check calendar credentials in calendars service

* resolve module errors

* export custom hooks for calendar settings

* e2e for deleting calendar credentials endpoint

* fix typo

* set authorization header to calendars post

* e2e tests for selected calendars controller

* fix output typing

* better error messages

* restructuring

* fix imports

* remove unused not found exception

* fixup! remove unused not found exception

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
Rajiv Sahal
2024-07-22 17:26:39 +00:00
committed by GitHub
co-authored by Morgan Morgan Vernay
parent de2f51e104
commit b46a902a3d
17 changed files with 681 additions and 2 deletions
@@ -1,3 +1,4 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsController } from "@/ee/calendars/controllers/calendars.controller";
import { AppleCalendarService } from "@/ee/calendars/services/apple-calendar.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
@@ -21,6 +22,7 @@ import { Module } from "@nestjs/common";
AppleCalendarService,
SelectedCalendarsRepository,
AppsRepository,
CalendarsRepository,
],
controllers: [CalendarsController],
exports: [CalendarsService],
@@ -0,0 +1,51 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client";
const credentialForCalendarRepositorySelect = Prisma.validator<Prisma.CredentialSelect>()({
id: true,
appId: true,
type: true,
userId: true,
user: {
select: {
email: true,
},
},
teamId: true,
key: true,
invalid: true,
});
@Injectable()
export class CalendarsRepository {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async getCalendarCredentials(credentialId: number, userId: number) {
return await this.dbRead.prisma.credential.findFirst({
where: {
id: credentialId,
userId,
},
select: {
...credentialForCalendarRepositorySelect,
app: {
select: {
slug: true,
categories: true,
dirName: true,
},
},
},
});
}
async deleteCredentials(credentialId: number) {
return await this.dbWrite.prisma.credential.delete({
where: {
id: credentialId,
},
});
}
}
@@ -1,5 +1,6 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { DeletedCalendarCredentialsOutputResponseDto } from "@/ee/calendars/outputs/delete-calendar-credentials.output";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
@@ -18,6 +19,7 @@ import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.reposit
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import {
GOOGLE_CALENDAR,
OFFICE_365_CALENDAR,
@@ -189,6 +191,26 @@ describe("Platform Calendars Endpoints", () => {
.expect(200);
});
it.skip(`/POST/v2/calendars/${OFFICE_365_CALENDAR}/disconnect: it should respond with a 201 returning back the user deleted calendar credentials`, async () => {
const body = {
id: 10,
};
return request(app.getHttpServer())
.post(`/v2/calendars/${OFFICE_365_CALENDAR}/disconnect`)
.set("Authorization", `Bearer ${accessTokenSecret}`)
.send(body)
.expect(201)
.then(async (response) => {
const responseBody: Promise<DeletedCalendarCredentialsOutputResponseDto> = response.body;
expect((await responseBody).status).toEqual(SUCCESS_STATUS);
expect((await responseBody).data).toBeDefined();
expect((await responseBody).data.id).toEqual(body.id);
expect((await responseBody).data.userId).toEqual(user.id);
});
});
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
@@ -1,5 +1,10 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { GetBusyTimesOutput } from "@/ee/calendars/outputs/busy-times.output";
import { ConnectedCalendarsOutput } from "@/ee/calendars/outputs/connected-calendars.output";
import {
DeletedCalendarCredentialsOutputResponseDto,
DeletedCalendarCredentialsOutputDto,
} from "@/ee/calendars/outputs/delete-calendar-credentials.output";
import { AppleCalendarService } from "@/ee/calendars/services/apple-calendar.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GoogleCalendarService } from "@/ee/calendars/services/gcal.service";
@@ -27,6 +32,7 @@ import {
} from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { User } from "@prisma/client";
import { plainToClass } from "class-transformer";
import { Request } from "express";
import { z } from "zod";
@@ -50,7 +56,8 @@ export class CalendarsController {
private readonly calendarsService: CalendarsService,
private readonly outlookService: OutlookService,
private readonly googleCalendarService: GoogleCalendarService,
private readonly appleCalendarService: AppleCalendarService
private readonly appleCalendarService: AppleCalendarService,
private readonly calendarsRepository: CalendarsRepository
) {}
@UseGuards(ApiAuthGuard)
@@ -183,4 +190,29 @@ export class CalendarsController {
);
}
}
@UseGuards(ApiAuthGuard)
@Post("/:calendar/disconnect")
@HttpCode(HttpStatus.OK)
async deleteCalendarCredentials(
@Param("calendar") calendar: string,
@Body() body: { id: number },
@GetUser() user: UserWithProfile
): Promise<DeletedCalendarCredentialsOutputResponseDto> {
const { id: credentialId } = body;
await this.calendarsService.checkCalendarCredentials(credentialId, user.id);
const { id, type, userId, teamId, appId, invalid } = await this.calendarsRepository.deleteCredentials(
credentialId
);
return {
status: SUCCESS_STATUS,
data: plainToClass(
DeletedCalendarCredentialsOutputDto,
{ id, type, userId, teamId, appId, invalid },
{ strategy: "excludeAll" }
),
};
}
}
@@ -0,0 +1,42 @@
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import { IsString, ValidateNested, IsEnum, IsInt, IsBoolean } from "class-validator";
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
export class DeletedCalendarCredentialsOutputDto {
@IsInt()
@Expose()
readonly id!: number;
@IsString()
@Expose()
readonly type!: string;
@IsInt()
@Expose()
readonly userId!: number | null;
@IsInt()
@Expose()
readonly teamId!: number | null;
@IsString()
@Expose()
readonly appId!: string | null;
@IsBoolean()
@Expose()
readonly invalid!: boolean | null;
}
export class DeletedCalendarCredentialsOutputResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Expose()
@ValidateNested()
@Type(() => DeletedCalendarCredentialsOutputDto)
data!: DeletedCalendarCredentialsOutputDto;
}
@@ -1,3 +1,4 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { AppsRepository } from "@/modules/apps/apps.repository";
import {
CredentialsRepository,
@@ -29,6 +30,7 @@ export class CalendarsService {
private readonly usersRepository: UsersRepository,
private readonly credentialsRepository: CredentialsRepository,
private readonly appsRepository: AppsRepository,
private readonly calendarsRepository: CalendarsRepository,
private readonly dbRead: PrismaReadService,
private readonly dbWrite: PrismaWriteService,
private readonly config: ConfigService
@@ -136,4 +138,11 @@ export class CalendarsService {
return { client_id, client_secret };
}
async checkCalendarCredentials(credentialId: number, userId: number) {
const credential = await this.calendarsRepository.getCalendarCredentials(credentialId, userId);
if (!credential) {
throw new NotFoundException("Calendar credentials not found");
}
}
}
+2
View File
@@ -1,3 +1,4 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GcalController } from "@/ee/gcal/gcal.controller";
import { AppsRepository } from "@/modules/apps/apps.repository";
@@ -21,6 +22,7 @@ import { ConfigService } from "@nestjs/config";
GCalService,
CalendarsService,
UsersRepository,
CalendarsRepository,
],
controllers: [GcalController],
})
@@ -0,0 +1,156 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { SelectedCalendarOutputResponseDto } from "@/modules/selected-calendars/outputs/selected-calendars.output";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { PlatformOAuthClient, Team, User, Credential } from "@prisma/client";
import * as request from "supertest";
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
import { APPLE_CALENDAR_TYPE, APPLE_CALENDAR_ID } from "@calcom/platform-constants";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
const CLIENT_REDIRECT_URI = "http://localhost:5555";
describe("Platform Selected Calendars Endpoints", () => {
let app: INestApplication;
let oAuthClient: PlatformOAuthClient;
let organization: Team;
let userRepositoryFixture: UserRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let teamRepositoryFixture: TeamRepositoryFixture;
let tokensRepositoryFixture: TokensRepositoryFixture;
let credentialsRepositoryFixture: CredentialsRepositoryFixture;
let appleCalendarCredentials: Credential;
let user: User;
let accessTokenSecret: string;
let refreshTokenSecret: string;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [PrismaExceptionFilter, HttpExceptionFilter],
imports: [AppModule, UsersModule, TokensModule],
})
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
tokensRepositoryFixture = new TokensRepositoryFixture(moduleRef);
credentialsRepositoryFixture = new CredentialsRepositoryFixture(moduleRef);
organization = await teamRepositoryFixture.create({ name: "organization" });
oAuthClient = await createOAuthClient(organization.id);
user = await userRepositoryFixture.createOAuthManagedUser("office365-connect@gmail.com", oAuthClient.id);
const tokens = await tokensRepositoryFixture.createTokens(user.id, oAuthClient.id);
accessTokenSecret = tokens.accessToken;
refreshTokenSecret = tokens.refreshToken;
appleCalendarCredentials = await credentialsRepositoryFixture.create(
APPLE_CALENDAR_TYPE,
{},
user.id,
APPLE_CALENDAR_ID
);
await app.init();
jest
.spyOn(CalendarsService.prototype, "getCalendars")
.mockImplementation(CalendarsServiceMock.prototype.getCalendars);
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
name: "name",
redirectUris: [CLIENT_REDIRECT_URI],
permissions: 32,
};
const secret = "secret";
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
return client;
}
it("should be defined", () => {
expect(oauthClientRepositoryFixture).toBeDefined();
expect(userRepositoryFixture).toBeDefined();
expect(oAuthClient).toBeDefined();
expect(accessTokenSecret).toBeDefined();
expect(refreshTokenSecret).toBeDefined();
expect(user).toBeDefined();
});
it(`POST /v2/selected-calendars: it should respond with a 201 returning back the user added selected calendar`, async () => {
const body = {
integration: appleCalendarCredentials.type,
externalId: "https://caldav.icloud.com/20961146906/calendars/83C4F9A1-F1D0-41C7-8FC3-0B$9AE22E813/",
credentialId: appleCalendarCredentials.id,
};
await request(app.getHttpServer())
.post("/v2/selected-calendars")
.set("Authorization", `Bearer ${accessTokenSecret}`)
.send(body)
.expect(201)
.then(async (response) => {
const responseBody: SelectedCalendarOutputResponseDto = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.credentialId).toEqual(body.credentialId);
expect(responseBody.data.integration).toEqual(body.integration);
expect(responseBody.data.externalId).toEqual(body.externalId);
expect(responseBody.data.userId).toEqual(user.id);
});
});
it(`DELETE /v2/selected-calendars: it should respond with a 200 returning back the user deleted selected calendar`, async () => {
const integration = appleCalendarCredentials.type;
const externalId =
"https://caldav.icloud.com/20961146906/calendars/83C4F9A1-F1D0-41C7-8FC3-0B$9AE22E813/";
const credentialId = appleCalendarCredentials.id;
await request(app.getHttpServer())
.delete(
`/v2/selected-calendars?credentialId=${credentialId}&integration=${integration}&externalId=${externalId}`
)
.set("Authorization", `Bearer ${accessTokenSecret}`)
.expect(200)
.then(async (response) => {
const responseBody: SelectedCalendarOutputResponseDto = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.credentialId).toEqual(credentialId);
expect(responseBody.data.externalId).toEqual(externalId);
expect(responseBody.data.integration).toEqual(integration);
expect(responseBody.data.userId).toEqual(user.id);
});
});
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
await userRepositoryFixture.deleteByEmail(user.email);
await app.close();
});
});
@@ -0,0 +1,76 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import {
SelectedCalendarsInputDto,
SelectedCalendarsQueryParamsInputDto,
} from "@/modules/selected-calendars/inputs/selected-calendars.input";
import {
SelectedCalendarOutputResponseDto,
SelectedCalendarOutputDto,
} from "@/modules/selected-calendars/outputs/selected-calendars.output";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Body, Controller, Post, UseGuards, Delete, Query } from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { plainToClass } from "class-transformer";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
@Controller({
path: "/v2/selected-calendars",
version: API_VERSIONS_VALUES,
})
@DocsTags("Selected-Calendars")
export class SelectedCalendarsController {
constructor(
private readonly calendarsRepository: CalendarsRepository,
private readonly selectedCalendarsRepository: SelectedCalendarsRepository,
private readonly calendarsService: CalendarsService
) {}
@Post("/")
@UseGuards(ApiAuthGuard)
async addSelectedCalendar(
@Body() input: SelectedCalendarsInputDto,
@GetUser() user: UserWithProfile
): Promise<SelectedCalendarOutputResponseDto> {
const { integration, externalId, credentialId } = input;
await this.calendarsService.checkCalendarCredentials(Number(credentialId), user.id);
const newlyAddedCalendarEntry = await this.selectedCalendarsRepository.addUserSelectedCalendar(
user.id,
integration,
externalId,
credentialId
);
return {
status: SUCCESS_STATUS,
data: plainToClass(SelectedCalendarOutputDto, newlyAddedCalendarEntry, { strategy: "excludeAll" }),
};
}
@Delete("/")
@UseGuards(ApiAuthGuard)
async removeSelectedCalendar(
@Query() queryParams: SelectedCalendarsQueryParamsInputDto,
@GetUser() user: UserWithProfile
): Promise<SelectedCalendarOutputResponseDto> {
const { integration, externalId, credentialId } = queryParams;
await this.calendarsService.checkCalendarCredentials(Number(credentialId), user.id);
const removedCalendarEntry = await this.selectedCalendarsRepository.removeUserSelectedCalendar(
user.id,
integration,
externalId
);
return {
status: SUCCESS_STATUS,
data: plainToClass(SelectedCalendarOutputDto, removedCalendarEntry, { strategy: "excludeAll" }),
};
}
}
@@ -0,0 +1,23 @@
import { IsInt, IsString } from "class-validator";
export class SelectedCalendarsInputDto {
@IsString()
readonly integration!: string;
@IsString()
readonly externalId!: string;
@IsInt()
readonly credentialId!: number;
}
export class SelectedCalendarsQueryParamsInputDto {
@IsString()
readonly integration!: string;
@IsString()
readonly externalId!: string;
@IsString()
readonly credentialId!: string;
}
@@ -0,0 +1,34 @@
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import { IsInt, IsString, ValidateNested, IsEnum } from "class-validator";
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
export class SelectedCalendarOutputDto {
@IsInt()
@Expose()
readonly userId!: number;
@IsString()
@Expose()
readonly integration!: string;
@IsString()
@Expose()
readonly externalId!: string;
@IsInt()
@Expose()
readonly credentialId!: number | null;
}
export class SelectedCalendarOutputResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Expose()
@ValidateNested()
@Type(() => SelectedCalendarOutputDto)
data!: SelectedCalendarOutputDto;
}
@@ -1,10 +1,24 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { AppsRepository } from "@/modules/apps/apps.repository";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { SelectedCalendarsController } from "@/modules/selected-calendars/controllers/selected-calendars.controller";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { UsersRepository } from "@/modules/users/users.repository";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule],
providers: [SelectedCalendarsRepository],
providers: [
SelectedCalendarsRepository,
CalendarsRepository,
CalendarsService,
UsersRepository,
CredentialsRepository,
AppsRepository,
],
controllers: [SelectedCalendarsController],
exports: [SelectedCalendarsRepository],
})
export class SelectedCalendarsModule {}
@@ -37,4 +37,41 @@ export class SelectedCalendarsRepository {
},
});
}
async addUserSelectedCalendar(
userId: number,
integration: string,
externalId: string,
credentialId: number
) {
return await this.dbWrite.prisma.selectedCalendar.upsert({
where: {
userId_integration_externalId: {
userId,
integration,
externalId,
},
},
create: {
userId,
integration,
externalId,
credentialId,
},
// already exists
update: {},
});
}
async removeUserSelectedCalendar(userId: number, integration: string, externalId: string) {
return await this.dbWrite.prisma.selectedCalendar.delete({
where: {
userId_integration_externalId: {
userId,
externalId,
integration,
},
},
});
}
}
@@ -0,0 +1,54 @@
import { useMutation } from "@tanstack/react-query";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ApiErrorResponse, ApiResponse } from "@calcom/platform-types";
import http from "../../lib/http";
interface IUseAddSelectedCalendar {
onSuccess?: (res: ApiResponse) => void;
onError?: (err: ApiErrorResponse | Error) => void;
}
export const useAddSelectedCalendar = (
{ onSuccess, onError }: IUseAddSelectedCalendar = {
onSuccess: () => {
return;
},
onError: () => {
return;
},
}
) => {
const newlyAddedCalendarEntry = useMutation<
ApiResponse<{
status: string;
data: {
userId: number;
integration: string;
externalId: string;
credentialId: number | null;
};
}>,
unknown,
{ credentialId: number; integration: string; externalId: string }
>({
mutationFn: (data) => {
return http.post(`/selected-calendars`, data).then((res) => {
return res.data;
});
},
onSuccess: (data) => {
if (data.status === SUCCESS_STATUS) {
onSuccess?.(data);
} else {
onError?.(data);
}
},
onError: (err) => {
onError?.(err as ApiErrorResponse);
},
});
return newlyAddedCalendarEntry;
};
@@ -0,0 +1,62 @@
import { useMutation } from "@tanstack/react-query";
import type { CALENDARS } from "@calcom/platform-constants";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ApiErrorResponse, ApiResponse } from "@calcom/platform-types";
import http from "../../lib/http";
interface IUseDeleteCalendarCredentials {
onSuccess?: (res: ApiResponse) => void;
onError?: (err: ApiErrorResponse | Error) => void;
}
export const useDeleteCalendarCredentials = (
{ onSuccess, onError }: IUseDeleteCalendarCredentials = {
onSuccess: () => {
return;
},
onError: () => {
return;
},
}
) => {
const deleteCalendarCredentials = useMutation<
ApiResponse<{
status: string;
data: {
id: number;
type: string;
userId: number | null;
teamId: number | null;
appId: string | null;
invalid: boolean | null;
};
}>,
unknown,
{ id: number; calendar: (typeof CALENDARS)[number] }
>({
mutationFn: (data) => {
const { id, calendar } = data;
const body = {
id,
};
return http.post(`/calendars/${calendar}/disconnect`, body).then((res) => {
return res.data;
});
},
onSuccess: (data) => {
if (data.status === SUCCESS_STATUS) {
onSuccess?.(data);
} else {
onError?.(data);
}
},
onError: (err) => {
onError?.(err as ApiErrorResponse);
},
});
return deleteCalendarCredentials;
};
@@ -0,0 +1,60 @@
import { useMutation } from "@tanstack/react-query";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ApiErrorResponse, ApiResponse } from "@calcom/platform-types";
import http from "../../lib/http";
interface IUseRemoveSelectedCalendar {
onSuccess?: (res: ApiResponse) => void;
onError?: (err: ApiErrorResponse | Error) => void;
}
export const useRemoveSelectedCalendar = (
{ onSuccess, onError }: IUseRemoveSelectedCalendar = {
onSuccess: () => {
return;
},
onError: () => {
return;
},
}
) => {
const deletedCalendarEntry = useMutation<
ApiResponse<{
status: string;
data: {
userId: number;
integration: string;
externalId: string;
credentialId: number | null;
};
}>,
unknown,
{ credentialId: number; integration: string; externalId: string }
>({
mutationFn: (data) => {
const { credentialId, externalId, integration } = data;
return http
.delete(
`/selected-calendars?credentialId=${credentialId}&integration=${integration}&externalId=${externalId}`
)
.then((res) => {
return res.data;
});
},
onSuccess: (data) => {
if (data.status === SUCCESS_STATUS) {
onSuccess?.(data);
} else {
onError?.(data);
}
},
onError: (err) => {
onError?.(err as ApiErrorResponse);
},
});
return deletedCalendarEntry;
};
+3
View File
@@ -15,3 +15,6 @@ export { useMe } from "./hooks/useMe";
export { OutlookConnect } from "./connect/outlook/OutlookConnect";
export * as Connect from "./connect";
export { BookerEmbed } from "./booker-embed";
export { useDeleteCalendarCredentials } from "./hooks/calendars/useDeleteCalendarCredentials";
export { useAddSelectedCalendar } from "./hooks/calendars/useAddSelectedCalendar";
export { useRemoveSelectedCalendar } from "./hooks/calendars/useRemoveSelectedCalendar";