From 35022a0478218a3af8a0644cfbac294c4ead4b44 Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Mon, 14 Jul 2025 10:01:43 +0200 Subject: [PATCH] fix: get v2/calendars existing record conflict (#22286) * refactor: upsert destination calendar * test: v2 connected calendars * chore: bump libraries * refactor: have only createIfNotExistsForUser * refactor: exclude eventTypeId from user calendar search * chore: bump libraries --- apps/api/v2/package.json | 2 +- .../calendars.controller.e2e-spec.ts | 32 ++++++++++++++++++- .../lib/getConnectedDestinationCalendars.ts | 27 ++++++++-------- .../server/repository/destinationCalendar.ts | 27 ++++++++++++++++ yarn.lock | 10 +++--- 5 files changed, 77 insertions(+), 21 deletions(-) diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index 45910d225b..735536af3c 100644 --- a/apps/api/v2/package.json +++ b/apps/api/v2/package.json @@ -38,7 +38,7 @@ "@axiomhq/winston": "^1.2.0", "@calcom/platform-constants": "*", "@calcom/platform-enums": "*", - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.257", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.258", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", diff --git a/apps/api/v2/src/ee/calendars/controllers/calendars.controller.e2e-spec.ts b/apps/api/v2/src/ee/calendars/controllers/calendars.controller.e2e-spec.ts index 03dd039a02..ec77b4f98d 100644 --- a/apps/api/v2/src/ee/calendars/controllers/calendars.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/calendars/controllers/calendars.controller.e2e-spec.ts @@ -1,6 +1,7 @@ import { bootstrap } from "@/app"; import { AppModule } from "@/app.module"; import { CreateIcsFeedOutput, CreateIcsFeedOutputResponseDto } from "@/ee/calendars/input/create-ics.output"; +import { ConnectedCalendarsData } from "@/ee/calendars/outputs/connected-calendars.output"; 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"; @@ -30,7 +31,7 @@ import { GOOGLE_CALENDAR_ID, } from "@calcom/platform-constants"; import { OFFICE_365_CALENDAR_ID, OFFICE_365_CALENDAR_TYPE } from "@calcom/platform-constants"; -import { ICS_CALENDAR } from "@calcom/platform-constants/apps"; +import { ICS_CALENDAR, ICS_CALENDAR_TYPE } from "@calcom/platform-constants/apps"; import { IcsFeedCalendarService } from "@calcom/platform-libraries/app-store"; const CLIENT_REDIRECT_URI = "http://localhost:5555"; @@ -247,6 +248,35 @@ describe("Platform Calendars Endpoints", () => { .expect(200); }); + it(`/GET/v2/calendars with access token`, async () => { + const response = await request(app.getHttpServer()) + .get(`/v2/calendars`) + .set("Authorization", `Bearer ${accessTokenSecret}`) + .set("Origin", CLIENT_REDIRECT_URI) + .expect(200); + + const data: ConnectedCalendarsData = response.body.data; + expect(data.connectedCalendars).toBeDefined(); + expect(data.connectedCalendars).toHaveLength(3); + + const googleConnectedCalendar = data.connectedCalendars.find( + (calendar) => calendar.integration.type === GOOGLE_CALENDAR_TYPE + ); + const office365ConnectedCalendar = data.connectedCalendars.find( + (calendar) => calendar.integration.type === OFFICE_365_CALENDAR_TYPE + ); + const icsConnectedCalendar = data.connectedCalendars.find( + (calendar) => calendar.integration.type === ICS_CALENDAR_TYPE + ); + + expect(googleConnectedCalendar).toBeDefined(); + expect(office365ConnectedCalendar).toBeDefined(); + expect(icsConnectedCalendar).toBeDefined(); + + expect(data.destinationCalendar).toBeDefined(); + expect(data.destinationCalendar.integration).toEqual(GOOGLE_CALENDAR_TYPE); + }); + 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, diff --git a/packages/lib/getConnectedDestinationCalendars.ts b/packages/lib/getConnectedDestinationCalendars.ts index 28755b599a..0778553700 100644 --- a/packages/lib/getConnectedDestinationCalendars.ts +++ b/packages/lib/getConnectedDestinationCalendars.ts @@ -8,6 +8,7 @@ import type { DestinationCalendar, SelectedCalendar, User } from "@calcom/prisma import { AppCategories } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import { DestinationCalendarRepository } from "./server/repository/destinationCalendar"; import { EventTypeRepository } from "./server/repository/eventType"; import { SelectedCalendarRepository } from "./server/repository/selectedCalendar"; @@ -134,20 +135,18 @@ async function handleNoDestinationCalendar({ } } - user.destinationCalendar = await prisma.destinationCalendar.create({ - data: { - userId: user.id, - integration, - externalId, - primaryEmail, - ...(!isDelegationCredential({ credentialId }) - ? { - credentialId, - } - : { - delegationCredentialId, - }), - }, + user.destinationCalendar = await DestinationCalendarRepository.createIfNotExistsForUser({ + userId: user.id, + integration, + externalId, + primaryEmail, + ...(!isDelegationCredential({ credentialId }) + ? { + credentialId, + } + : { + delegationCredentialId, + }), }); return { diff --git a/packages/lib/server/repository/destinationCalendar.ts b/packages/lib/server/repository/destinationCalendar.ts index a4a1735053..682795f9b4 100644 --- a/packages/lib/server/repository/destinationCalendar.ts +++ b/packages/lib/server/repository/destinationCalendar.ts @@ -14,6 +14,18 @@ export class DestinationCalendarRepository { }); } + static async createIfNotExistsForUser( + data: { userId: number } & Prisma.DestinationCalendarUncheckedCreateInput + ) { + const conflictingCalendar = await DestinationCalendarRepository.findConflictingForUser(data); + if (conflictingCalendar) { + return conflictingCalendar; + } + return await prisma.destinationCalendar.create({ + data, + }); + } + static async getByUserId(userId: number) { return await prisma.destinationCalendar.findFirst({ where: { @@ -36,6 +48,21 @@ export class DestinationCalendarRepository { }); } + private static async findConflictingForUser(data: { + userId: number; + integration: string; + externalId: string; + }) { + return await DestinationCalendarRepository.find({ + where: { + userId: data.userId, + integration: data.integration, + externalId: data.externalId, + eventTypeId: null, + }, + }); + } + static async upsert({ where, update, diff --git a/yarn.lock b/yarn.lock index f85a816617..7489ef9d30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2519,7 +2519,7 @@ __metadata: "@axiomhq/winston": ^1.2.0 "@calcom/platform-constants": "*" "@calcom/platform-enums": "*" - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.257" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.258" "@calcom/platform-types": "*" "@calcom/platform-utils": "*" "@calcom/prisma": "*" @@ -3565,13 +3565,13 @@ __metadata: languageName: unknown linkType: soft -"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.257": - version: 0.0.257 - resolution: "@calcom/platform-libraries@npm:0.0.257" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.258": + version: 0.0.258 + resolution: "@calcom/platform-libraries@npm:0.0.258" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: d45647591a26b6a73b6ae7602d8b8e59d87339de0e49468737437ffdd716fbd953898cd59a1eff8938326eba9ca895c28990bc91355aa55006980a2e716ddfd0 + checksum: ca522eee904921d4a2f285ba6ed195118a6d68c76fb454d826574d1f5487e8d3d7e41915c968ad0eff6bb01730578ee414069684c7a395b0c19b910f136bffb7 languageName: node linkType: hard