diff --git a/apps/api/v1/pages/api/credential-sync/_post.ts b/apps/api/v1/pages/api/credential-sync/_post.ts index 32d74da2c10a62..f858bc833781e6 100644 --- a/apps/api/v1/pages/api/credential-sync/_post.ts +++ b/apps/api/v1/pages/api/credential-sync/_post.ts @@ -6,7 +6,9 @@ import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; import { symmetricDecrypt } from "@calcom/lib/crypto"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; +import { Prisma } from "@calcom/prisma/client"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; import { schemaCredentialPostBody, schemaCredentialPostParams } from "~/lib/validations/credential-sync"; @@ -88,14 +90,11 @@ async function handler(req: NextApiRequest) { const appMetadata = appStoreMetadata[app.dirName as keyof typeof appStoreMetadata]; - const createdcredential = await prisma.credential.create({ - data: { - userId, - appId: appSlug, - key, - type: appMetadata.type, - }, - select: credentialForCalendarServiceSelect, + const createdcredential = await CredentialRepository.create({ + userId, + appId: appSlug, + key: key ? (key as Prisma.InputJsonValue) : Prisma.JsonNull, + type: appMetadata.type, }); // createdcredential.user.email; // TODO: ^ Investigate why this select doesn't work. diff --git a/apps/api/v1/pages/api/selected-calendars/_post.ts b/apps/api/v1/pages/api/selected-calendars/_post.ts index 5d0a419883eb8c..8ba07cfbccba9f 100644 --- a/apps/api/v1/pages/api/selected-calendars/_post.ts +++ b/apps/api/v1/pages/api/selected-calendars/_post.ts @@ -3,6 +3,7 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; +import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import prisma from "@calcom/prisma"; import { @@ -63,7 +64,7 @@ async function postHandler(req: NextApiRequest) { args.data.userId = bodyUserId; } - const data = await prisma.selectedCalendar.create(args); + const data = await SelectedCalendarRepository.create(args.data); return { selected_calendar: schemaSelectedCalendarPublic.parse(data), diff --git a/apps/web/pages/api/teams/googleworkspace/callback.ts b/apps/web/pages/api/teams/googleworkspace/callback.ts index 8b44c5b827687b..3b24743d42af32 100644 --- a/apps/web/pages/api/teams/googleworkspace/callback.ts +++ b/apps/web/pages/api/teams/googleworkspace/callback.ts @@ -7,7 +7,7 @@ import { throwIfNotHaveAdminAccessToTeam } from "@calcom/app-store/_utils/throwI import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; -import prisma from "@calcom/prisma"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; const stateSchema = z.object({ teamId: z.string(), @@ -45,12 +45,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const credentials = await oAuth2Client.getToken(code); - await prisma.credential.create({ - data: { - type: "google_workspace_directory", - key: credentials.res?.data, - userId: session.user.id, - }, + await CredentialRepository.create({ + type: "google_workspace_directory", + key: credentials.res?.data, + userId: session.user.id, }); if (!teamId) { diff --git a/apps/web/pages/api/webhook/app-credential.ts b/apps/web/pages/api/webhook/app-credential.ts index 527b9232c3933c..b13c053cad80da 100644 --- a/apps/web/pages/api/webhook/app-credential.ts +++ b/apps/web/pages/api/webhook/app-credential.ts @@ -5,6 +5,7 @@ import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; import { CREDENTIAL_SYNC_SECRET, CREDENTIAL_SYNC_SECRET_HEADER_NAME } from "@calcom/lib/constants"; import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants"; import { symmetricDecrypt } from "@calcom/lib/crypto"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; const appCredentialWebhookRequestBodySchema = z.object({ @@ -78,13 +79,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }); return res.status(200).json({ message: `Credentials updated for userId: ${reqBody.userId}` }); } else { - await prisma.credential.create({ - data: { - key: keys, - userId: reqBody.userId, - appId: appMetadata.slug, - type: appMetadata.type, - }, + await CredentialRepository.create({ + key: keys, + userId: reqBody.userId, + appId: appMetadata.slug, + type: appMetadata.type, }); return res.status(200).json({ message: `Credentials created for userId: ${reqBody.userId}` }); } diff --git a/packages/app-store/_utils/installation.ts b/packages/app-store/_utils/installation.ts index 16d45bed7ff02b..4fb527a8717292 100644 --- a/packages/app-store/_utils/installation.ts +++ b/packages/app-store/_utils/installation.ts @@ -1,6 +1,7 @@ import type { Prisma } from "@prisma/client"; import { HttpError } from "@calcom/lib/http-error"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import type { UserProfile } from "@calcom/types/UserProfile"; @@ -40,16 +41,14 @@ export async function createDefaultInstallation({ paymentStatus, subscriptionId, }: InstallationArgs) { - const installation = await prisma.credential.create({ - data: { - type: appType, - key, - ...(teamId ? { teamId } : { userId: user.id }), - appId: slug, - subscriptionId, - paymentStatus, - billingCycleStart, - }, + const installation = await CredentialRepository.create({ + type: appType, + key, + ...(teamId ? { teamId } : { userId: user.id }), + appId: slug, + subscriptionId, + paymentStatus, + billingCycleStart, }); if (!installation) { throw new Error(`Unable to create user credential for type ${appType}`); diff --git a/packages/app-store/_utils/oauth/createOAuthAppCredential.ts b/packages/app-store/_utils/oauth/createOAuthAppCredential.ts index 5e820c00b59aff..30df3997f91632 100644 --- a/packages/app-store/_utils/oauth/createOAuthAppCredential.ts +++ b/packages/app-store/_utils/oauth/createOAuthAppCredential.ts @@ -1,7 +1,7 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; -import prisma from "@calcom/prisma"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import { decodeOAuthState } from "../oauth/decodeOAuthState"; import { throwIfNotHaveAdminAccessToTeam } from "../throwIfNotHaveAdminAccessToTeam"; @@ -31,23 +31,19 @@ const createOAuthAppCredential = async ( // Check that the user belongs to the team await throwIfNotHaveAdminAccessToTeam({ teamId: state?.teamId ?? null, userId }); - return await prisma.credential.create({ - data: { - type: appData.type, - key: key || {}, - teamId: state.teamId, - appId: appData.appId, - }, - }); - } - - return await prisma.credential.create({ - data: { + return await CredentialRepository.create({ type: appData.type, key: key || {}, - userId, + teamId: state.teamId, appId: appData.appId, - }, + }); + } + + return await CredentialRepository.create({ + type: appData.type, + key: key || {}, + userId, + appId: appData.appId, }); }; diff --git a/packages/app-store/alby/api/add.ts b/packages/app-store/alby/api/add.ts index f0f47ff83bc590..098db2f8391211 100644 --- a/packages/app-store/alby/api/add.ts +++ b/packages/app-store/alby/api/add.ts @@ -1,5 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import config from "../config.json"; @@ -19,13 +20,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (alreadyInstalled) { throw new Error("Already installed"); } - const installation = await prisma.credential.create({ - data: { - type: appType, - key: {}, - userId: req.session.user.id, - appId: "alby", - }, + const installation = await CredentialRepository.create({ + type: appType, + key: {}, + userId: req.session.user.id, + appId: "alby", }); if (!installation) { diff --git a/packages/app-store/caldavcalendar/api/add.ts b/packages/app-store/caldavcalendar/api/add.ts index f0eca3987853b5..ca0b7d1bc39221 100644 --- a/packages/app-store/caldavcalendar/api/add.ts +++ b/packages/app-store/caldavcalendar/api/add.ts @@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { symmetricEncrypt } from "@calcom/lib/crypto"; import logger from "@calcom/lib/logger"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -40,9 +41,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) user: { email: user.email }, }); await dav?.listCalendars(); - await prisma.credential.create({ - data, - }); + await CredentialRepository.create(data); } catch (e) { logger.error("Could not add this caldav account", e); if (e instanceof Error) { diff --git a/packages/app-store/closecom/api/_postAdd.ts b/packages/app-store/closecom/api/_postAdd.ts index 8d8c2f746e187b..ed4858f1d0aab7 100644 --- a/packages/app-store/closecom/api/_postAdd.ts +++ b/packages/app-store/closecom/api/_postAdd.ts @@ -4,7 +4,7 @@ import { symmetricEncrypt } from "@calcom/lib/crypto"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { defaultResponder } from "@calcom/lib/server"; -import prisma from "@calcom/prisma"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import checkSession from "../../_utils/auth"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -26,12 +26,7 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) { }; try { - await prisma.credential.create({ - data, - select: { - id: true, - }, - }); + await CredentialRepository.create(data); } catch (reason) { logger.error("Could not add Close.com app", reason); return res.status(500).json({ message: "Could not add Close.com app" }); diff --git a/packages/app-store/exchange2013calendar/api/add.ts b/packages/app-store/exchange2013calendar/api/add.ts index f41a3f8a922359..f5fceb829417ae 100644 --- a/packages/app-store/exchange2013calendar/api/add.ts +++ b/packages/app-store/exchange2013calendar/api/add.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { symmetricEncrypt } from "@calcom/lib/crypto"; import logger from "@calcom/lib/logger"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -46,9 +47,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { user: { email: user.email }, }); await dav?.listCalendars(); - await prisma.credential.create({ - data, - }); + await CredentialRepository.create(data); } catch (reason) { logger.error("Could not add this exchange account", reason); return res.status(500).json({ message: "Could not add this exchange account" }); diff --git a/packages/app-store/exchange2016calendar/api/add.ts b/packages/app-store/exchange2016calendar/api/add.ts index c8509a1ee7636d..158bf7aa7155a4 100644 --- a/packages/app-store/exchange2016calendar/api/add.ts +++ b/packages/app-store/exchange2016calendar/api/add.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { symmetricEncrypt } from "@calcom/lib/crypto"; import logger from "@calcom/lib/logger"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -46,9 +47,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { ...data, }); await dav?.listCalendars(); - await prisma.credential.create({ - data, - }); + await CredentialRepository.create(data); } catch (reason) { logger.error("Could not add this exchange account", reason); return res.status(500).json({ message: "Could not add this exchange account" }); diff --git a/packages/app-store/exchangecalendar/api/_postAdd.ts b/packages/app-store/exchangecalendar/api/_postAdd.ts index 0a7709f4e2a44a..c2e1d33ed43d66 100644 --- a/packages/app-store/exchangecalendar/api/_postAdd.ts +++ b/packages/app-store/exchangecalendar/api/_postAdd.ts @@ -6,7 +6,7 @@ import { symmetricEncrypt } from "@calcom/lib/crypto"; import { emailSchema } from "@calcom/lib/emailSchema"; import logger from "@calcom/lib/logger"; import { defaultResponder } from "@calcom/lib/server"; -import prisma from "@calcom/prisma"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import checkSession from "../../_utils/auth"; import { ExchangeAuthentication, ExchangeVersion } from "../enums"; @@ -39,7 +39,7 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) { try { const service = new CalendarService({ id: 0, user: { email: session.user.email || "" }, ...data }); await service?.listCalendars(); - await prisma.credential.create({ data }); + await CredentialRepository.create(data); } catch (reason) { logger.info(reason); if (reason instanceof SoapFaultDetails && reason.message != "") { diff --git a/packages/app-store/feishucalendar/api/callback.ts b/packages/app-store/feishucalendar/api/callback.ts index f17acbc8c29f99..26a82a10814a36 100644 --- a/packages/app-store/feishucalendar/api/callback.ts +++ b/packages/app-store/feishucalendar/api/callback.ts @@ -4,6 +4,8 @@ import { z } from "zod"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import logger from "@calcom/lib/logger"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; +import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -67,13 +69,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { }); if (!currentCredential) { - await prisma.credential.create({ - data: { - type: "feishu_calendar", - key, - userId: req.session?.user.id, - appId: "feishu-calendar", - }, + await CredentialRepository.create({ + type: "feishu_calendar", + key, + userId: req.session?.user.id, + appId: "feishu-calendar", }); } else { await prisma.credential.update({ @@ -104,13 +104,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { const primaryCalendar = await primaryCalendarResponse.json(); if (primaryCalendar.data.calendars.calendar.calendar_id && req.session?.user?.id) { - await prisma.selectedCalendar.create({ - data: { - userId: req.session?.user.id, - integration: "feishu_calendar", - externalId: primaryCalendar.data.calendars.calendar.calendar_id as string, - credentialId: currentCredential?.id, - }, + await SelectedCalendarRepository.create({ + userId: req.session?.user.id, + integration: "feishu_calendar", + externalId: primaryCalendar.data.calendars.calendar.calendar_id as string, + credentialId: currentCredential?.id, }); } } diff --git a/packages/app-store/giphy/api/add.ts b/packages/app-store/giphy/api/add.ts index ae3e40ee6677ba..9968c5654f835c 100644 --- a/packages/app-store/giphy/api/add.ts +++ b/packages/app-store/giphy/api/add.ts @@ -1,5 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -32,13 +33,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (alreadyInstalled) { throw new Error("Already installed"); } - const installation = await prisma.credential.create({ - data: { - type: appType, - key: {}, - ...credentialOwner, - appId: "giphy", - }, + const installation = await CredentialRepository.create({ + type: appType, + key: {}, + ...credentialOwner, + appId: "giphy", }); if (!installation) { throw new Error("Unable to create user credential for giphy"); diff --git a/packages/app-store/googlecalendar/api/callback.ts b/packages/app-store/googlecalendar/api/callback.ts index 1983d910959da9..a35b6b87c7a521 100644 --- a/packages/app-store/googlecalendar/api/callback.ts +++ b/packages/app-store/googlecalendar/api/callback.ts @@ -8,6 +8,8 @@ import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; +import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import prisma from "@calcom/prisma"; import { Prisma } from "@calcom/prisma/client"; @@ -89,13 +91,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { await updateProfilePhoto(oAuth2Client, req.session.user.id); } - const credential = await prisma.credential.create({ - data: { - type: "google_calendar", - key, - userId: req.session.user.id, - appId: "google-calendar", - }, + const credential = await CredentialRepository.create({ + type: "google_calendar", + key: key ? (key as Prisma.InputJsonValue) : Prisma.JsonNull, + userId: req.session.user.id, + appId: "google-calendar", }); // If we still don't have a primary calendar skip creating the selected calendar. @@ -117,11 +117,9 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { // Wrapping in a try/catch to reduce chance of race conditions- // also this improves performance for most of the happy-paths. try { - await prisma.selectedCalendar.create({ - data: { - credentialId: credential.id, - ...selectedCalendarWhereUnique, - }, + await SelectedCalendarRepository.create({ + credentialId: credential.id, + ...selectedCalendarWhereUnique, }); } catch (error) { let errorMessage = "something_went_wrong"; @@ -175,13 +173,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { } // Create a new google meet credential - await prisma.credential.create({ - data: { - type: "google_video", - key: {}, - userId: req.session.user.id, - appId: "google-meet", - }, + await CredentialRepository.create({ + type: "google_video", + key: {}, + userId: req.session.user.id, + appId: "google-meet", }); res.redirect( diff --git a/packages/app-store/googlevideo/api/_getAdd.ts b/packages/app-store/googlevideo/api/_getAdd.ts index f310528d024da1..c52fe8713c9688 100644 --- a/packages/app-store/googlevideo/api/_getAdd.ts +++ b/packages/app-store/googlevideo/api/_getAdd.ts @@ -1,5 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -20,13 +21,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (alreadyInstalled) { throw new Error("Already installed"); } - const installation = await prisma.credential.create({ - data: { - type: appType, - key: {}, - userId: req.session.user.id, - appId: "google-meet", - }, + const installation = await CredentialRepository.create({ + type: appType, + key: {}, + userId: req.session.user.id, + appId: "google-meet", }); if (!installation) { throw new Error("Unable to create user credential for google_video"); diff --git a/packages/app-store/ics-feedcalendar/api/add.ts b/packages/app-store/ics-feedcalendar/api/add.ts index 626a838206aa76..edab647dbb2d20 100644 --- a/packages/app-store/ics-feedcalendar/api/add.ts +++ b/packages/app-store/ics-feedcalendar/api/add.ts @@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { symmetricEncrypt } from "@calcom/lib/crypto"; import logger from "@calcom/lib/logger"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -43,9 +44,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) throw new Error(`Listed cals and URLs mismatch: ${listedCals.length} vs. ${urls.length}`); } - await prisma.credential.create({ - data, - }); + await CredentialRepository.create(data); } catch (e) { logger.error("Could not add ICS feeds", e); return res.status(500).json({ message: "Could not add ICS feeds" }); diff --git a/packages/app-store/jitsivideo/api/add.ts b/packages/app-store/jitsivideo/api/add.ts index 45df37e61da344..ec58a8235404e2 100644 --- a/packages/app-store/jitsivideo/api/add.ts +++ b/packages/app-store/jitsivideo/api/add.ts @@ -1,6 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { throwIfNotHaveAdminAccessToTeam } from "@calcom/app-store/_utils/throwIfNotHaveAdminAccessToTeam"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -30,13 +31,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (alreadyInstalled) { throw new Error("Already installed"); } - const installation = await prisma.credential.create({ - data: { - type: appType, - key: {}, - ...installForObject, - appId: "jitsi", - }, + const installation = await CredentialRepository.create({ + type: appType, + key: {}, + ...installForObject, + appId: "jitsi", }); if (!installation) { throw new Error("Unable to create user credential for jitsivideo"); diff --git a/packages/app-store/larkcalendar/api/callback.ts b/packages/app-store/larkcalendar/api/callback.ts index 16df208ac9b12b..68ce12e74cb35b 100644 --- a/packages/app-store/larkcalendar/api/callback.ts +++ b/packages/app-store/larkcalendar/api/callback.ts @@ -4,6 +4,8 @@ import { z } from "zod"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import logger from "@calcom/lib/logger"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; +import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -67,13 +69,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { }); if (!currentCredential) { - await prisma.credential.create({ - data: { - type: "lark_calendar", - key, - userId: req.session?.user.id, - appId: "lark-calendar", - }, + await CredentialRepository.create({ + type: "lark_calendar", + key, + userId: req.session?.user.id, + appId: "lark-calendar", }); } else { await prisma.credential.update({ @@ -104,13 +104,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { const primaryCalendar = await primaryCalendarResponse.json(); if (primaryCalendar.data.calendars.calendar.calendar_id && req.session?.user?.id) { - await prisma.selectedCalendar.create({ - data: { - userId: req.session?.user.id, - integration: "lark_calendar", - externalId: primaryCalendar.data.calendars.calendar.calendar_id as string, - credentialId: currentCredential?.id, - }, + await SelectedCalendarRepository.create({ + userId: req.session?.user.id, + integration: "lark_calendar", + externalId: primaryCalendar.data.calendars.calendar.calendar_id as string, + credentialId: currentCredential?.id, }); } } diff --git a/packages/app-store/office365calendar/api/callback.ts b/packages/app-store/office365calendar/api/callback.ts index de34f6be397670..30a66297b3e515 100644 --- a/packages/app-store/office365calendar/api/callback.ts +++ b/packages/app-store/office365calendar/api/callback.ts @@ -5,6 +5,8 @@ import { renewSelectedCalendarCredentialId } from "@calcom/lib/connectedCalendar import { WEBAPP_URL, WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants"; import { handleErrorsJson } from "@calcom/lib/errors"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; +import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import prisma from "@calcom/prisma"; import { Prisma } from "@calcom/prisma/client"; @@ -112,13 +114,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } if (defaultCalendar?.id && req.session?.user?.id) { - const credential = await prisma.credential.create({ - data: { - type: "office365_calendar", - key: responseBody, - userId: req.session?.user.id, - appId: "office365-calendar", - }, + const credential = await CredentialRepository.create({ + type: "office365_calendar", + key: responseBody, + userId: req.session?.user.id, + appId: "office365-calendar", }); const selectedCalendarWhereUnique = { userId: req.session?.user.id, @@ -128,11 +128,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) // Wrapping in a try/catch to reduce chance of race conditions- // also this improves performance for most of the happy-paths. try { - await prisma.selectedCalendar.create({ - data: { - ...selectedCalendarWhereUnique, - credentialId: credential.id, - }, + await SelectedCalendarRepository.create({ + ...selectedCalendarWhereUnique, + credentialId: credential.id, }); } catch (error) { let errorMessage = "something_went_wrong"; diff --git a/packages/app-store/paypal/api/add.ts b/packages/app-store/paypal/api/add.ts index e2762565d0ffee..55f547a9cd0100 100644 --- a/packages/app-store/paypal/api/add.ts +++ b/packages/app-store/paypal/api/add.ts @@ -1,6 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { throwIfNotHaveAdminAccessToTeam } from "@calcom/app-store/_utils/throwIfNotHaveAdminAccessToTeam"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import config from "../config.json"; @@ -26,13 +27,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (alreadyInstalled) { throw new Error("Already installed"); } - const installation = await prisma.credential.create({ - data: { - type: appType, - key: {}, - userId: req.session.user.id, - appId: "paypal", - }, + const installation = await CredentialRepository.create({ + type: appType, + key: {}, + userId: req.session.user.id, + appId: "paypal", }); if (!installation) { diff --git a/packages/app-store/routing-forms/api/add.ts b/packages/app-store/routing-forms/api/add.ts index 5037d705716135..8ba15cb5ccc013 100644 --- a/packages/app-store/routing-forms/api/add.ts +++ b/packages/app-store/routing-forms/api/add.ts @@ -1,4 +1,4 @@ -import prisma from "@calcom/prisma"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import type { AppDeclarativeHandler } from "@calcom/types/AppHandler"; import appConfig from "../config.json"; @@ -10,13 +10,11 @@ const handler: AppDeclarativeHandler = { supportsMultipleInstalls: false, handlerType: "add", createCredential: async ({ user, appType, slug, teamId }) => { - return await prisma.credential.create({ - data: { - type: appType, - key: {}, - ...(teamId ? { teamId } : { userId: user.id }), - appId: slug, - }, + return await CredentialRepository.create({ + type: appType, + key: {}, + ...(teamId ? { teamId } : { userId: user.id }), + appId: slug, }); }, redirect: { diff --git a/packages/app-store/sendgrid/api/_postAdd.ts b/packages/app-store/sendgrid/api/_postAdd.ts index b34826d5d91cf8..000c11729aa6ab 100644 --- a/packages/app-store/sendgrid/api/_postAdd.ts +++ b/packages/app-store/sendgrid/api/_postAdd.ts @@ -4,7 +4,7 @@ import { symmetricEncrypt } from "@calcom/lib/crypto"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { defaultResponder } from "@calcom/lib/server"; -import prisma from "@calcom/prisma"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import checkSession from "../../_utils/auth"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -31,9 +31,7 @@ export async function getHandler(req: NextApiRequest) { }; try { - await prisma.credential.create({ - data, - }); + await CredentialRepository.create(data); } catch (reason) { logger.error("Could not add Sendgrid app", reason); throw new HttpError({ statusCode: 500, message: "Could not add Sendgrid app" }); diff --git a/packages/app-store/sylapsvideo/api/add.ts b/packages/app-store/sylapsvideo/api/add.ts index edda5533b775d1..65e8d2d59a59b8 100644 --- a/packages/app-store/sylapsvideo/api/add.ts +++ b/packages/app-store/sylapsvideo/api/add.ts @@ -1,5 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -26,13 +27,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (alreadyInstalled) { throw new Error("Already installed"); } - const installation = await prisma.credential.create({ - data: { - type: appType, - key: {}, - userId: req.session.user.id, - appId: config.slug, - }, + const installation = await CredentialRepository.create({ + type: appType, + key: {}, + userId: req.session.user.id, + appId: config.slug, }); if (!installation) { throw new Error("Unable to create user credential for sylaps"); diff --git a/packages/app-store/vital/api/token.ts b/packages/app-store/vital/api/token.ts index 3e3c21fa83d898..397792fcdd7c24 100644 --- a/packages/app-store/vital/api/token.ts +++ b/packages/app-store/vital/api/token.ts @@ -2,7 +2,7 @@ import type { Prisma } from "@prisma/client"; import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; -import prisma from "@calcom/prisma"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import { initVitalClient, vitalEnv } from "../lib/client"; @@ -33,13 +33,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) try { if (userVital?.user_id) { - await prisma.credential.create({ - data: { - type: "vital_other", - key: { userVitalId: userVital.user_id } as unknown as Prisma.InputJsonObject, - userId: calcomUserId, - appId: "vital-automation", - }, + await CredentialRepository.create({ + type: "vital_other", + key: { userVitalId: userVital.user_id } as unknown as Prisma.InputJsonObject, + userId: calcomUserId, + appId: "vital-automation", }); } const token = await vitalClient.Link.create( diff --git a/packages/app-store/wipemycalother/api/add.ts b/packages/app-store/wipemycalother/api/add.ts index fed23464b40c1c..c9b1cfbe9fcf2d 100644 --- a/packages/app-store/wipemycalother/api/add.ts +++ b/packages/app-store/wipemycalother/api/add.ts @@ -1,5 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -24,13 +25,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (alreadyInstalled) { throw new Error("Already installed"); } - const installation = await prisma.credential.create({ - data: { - type: appType, - key: {}, - userId: req.session.user.id, - appId: "wipe-my-cal", - }, + const installation = await CredentialRepository.create({ + type: appType, + key: {}, + userId: req.session.user.id, + appId: "wipe-my-cal", }); if (!installation) { throw new Error("Unable to create user credential for wipe-my-cal"); diff --git a/packages/app-store/zapier/api/add.ts b/packages/app-store/zapier/api/add.ts index b10a72f9f7776e..2a60aef3c54daf 100644 --- a/packages/app-store/zapier/api/add.ts +++ b/packages/app-store/zapier/api/add.ts @@ -1,5 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -17,13 +18,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (alreadyInstalled) { throw new Error("Already installed"); } - const installation = await prisma.credential.create({ - data: { - type: appType, - key: {}, - userId: req.session.user.id, - appId: "zapier", - }, + const installation = await CredentialRepository.create({ + type: appType, + key: {}, + userId: req.session.user.id, + appId: "zapier", }); if (!installation) { throw new Error("Unable to create user credential for zapier"); diff --git a/packages/app-store/zohocalendar/api/callback.ts b/packages/app-store/zohocalendar/api/callback.ts index 7c9449d1bc3cf6..c4b3f9696a40dc 100644 --- a/packages/app-store/zohocalendar/api/callback.ts +++ b/packages/app-store/zohocalendar/api/callback.ts @@ -6,6 +6,8 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import logger from "@calcom/lib/logger"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; +import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import prisma from "@calcom/prisma"; import { Prisma } from "@calcom/prisma/client"; @@ -102,13 +104,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { const primaryCalendar = data.calendars.find((calendar: any) => calendar.isdefault); if (primaryCalendar.uid) { - const credential = await prisma.credential.create({ - data: { - type: config.type, - key, - userId: req.session.user.id, - appId: config.slug, - }, + const credential = await CredentialRepository.create({ + type: config.type, + key, + userId: req.session.user.id, + appId: config.slug, }); const selectedCalendarWhereUnique = { userId: req.session?.user.id, @@ -118,11 +118,9 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { // Wrapping in a try/catch to reduce chance of race conditions- // also this improves performance for most of the happy-paths. try { - await prisma.selectedCalendar.create({ - data: { - ...selectedCalendarWhereUnique, - credentialId: credential.id, - }, + await SelectedCalendarRepository.create({ + ...selectedCalendarWhereUnique, + credentialId: credential.id, }); } catch (error) { let errorMessage = "something_went_wrong"; diff --git a/packages/lib/server/repository/bookingReference.test.ts b/packages/lib/server/repository/bookingReference.test.ts new file mode 100644 index 00000000000000..c2afed01086887 --- /dev/null +++ b/packages/lib/server/repository/bookingReference.test.ts @@ -0,0 +1,321 @@ +import prismock from "../../../../tests/libs/__mocks__/prisma"; + +import { + getGoogleCalendarCredential, + createBookingScenario, + TestData, + createCredentials, + BookingLocations, + getGoogleMeetCredential, +} from "@calcom/web/test/utils/bookingScenario/bookingScenario"; + +import { v4 as uuidv4 } from "uuid"; +import { vi, beforeEach, afterEach, describe, it, expect } from "vitest"; + +import { appStoreMetadata } from "@calcom/app-store/apps.metadata.generated"; +import { BookingStatus } from "@calcom/prisma/enums"; + +import { BookingReferenceRepository } from "./bookingReference"; + +const cleanup = async () => { + await prismock.eventType.deleteMany(); + await prismock.user.deleteMany(); + await prismock.schedule.deleteMany(); + await prismock.selectedCalendar.deleteMany(); + await prismock.credential.deleteMany(); + await prismock.booking.deleteMany(); + await prismock.bookingReference.deleteMany(); + await prismock.app.deleteMany(); + vi.useRealTimers(); +}; + +export function setupAndTeardown() { + beforeEach(async () => { + await cleanup(); + }); + + afterEach(async () => { + await cleanup(); + }); +} + +describe("bookingReferences", () => { + setupAndTeardown(); + + it("should reconnect bookingReference when a calendar app is reconnected", async () => { + vi.setSystemTime("2024-10-24T00:00:10Z"); + + const plus2DateString = "2024-10-26"; + const mockGoogleCredential = getGoogleCalendarCredential(); + const meetingId = uuidv4(); + const userId = 101; + const currentCredentialId = 1; + const bookingId = 1; + const bookingReferenceId = 1; + const newCredentialId = 2; + + const bookingScenarioData = { + eventTypes: [ + { + id: 1, + slotInterval: 30, + length: 30, + users: [ + { + id: userId, + }, + ], + }, + ], + users: [ + { + ...TestData.users.example, + id: userId, + schedules: [TestData.schedules.IstWorkHours], + credentials: [{ ...mockGoogleCredential, id: currentCredentialId }], + selectedCalendars: [ + { + ...TestData.selectedCalendars.google, + credential: { + connect: { + id: currentCredentialId, + }, + }, + }, + ], + }, + ], + apps: [TestData.apps["google-calendar"]], + bookings: [ + { + id: bookingId, + eventTypeId: 1, + userId, + status: BookingStatus.ACCEPTED, + startTime: `${plus2DateString}T04:30:00.000Z`, + endTime: `${plus2DateString}T05:00:00.000Z`, + references: [ + { + id: bookingReferenceId, + type: appStoreMetadata.googlecalendar.type, + uid: meetingId, + meetingId, + meetingUrl: "https://meet.google.com/mock", + externalCalendarId: TestData.selectedCalendars.google.externalId, + credential: { + connect: { + id: currentCredentialId, + }, + }, + }, + ], + }, + ], + }; + + await createBookingScenario(bookingScenarioData); + + //mock disconnet google-calendar app, + //credential and selectedCalendar gets deleted in db. + await prismock.credential.delete({ + where: { id: currentCredentialId }, + }); + + //mock reconnect or reinstall google-calendar app, + //new credential and selectedCalendar is created in db. + await createCredentials([ + { + type: mockGoogleCredential.type, + key: mockGoogleCredential.key, + id: newCredentialId, + userId, + }, + ]); + await prismock.selectedCalendar.create({ + data: { + ...TestData.selectedCalendars.google, + user: { + connect: { + id: userId, + }, + }, + credential: { + connect: { + id: newCredentialId, + }, + }, + }, + }); + + await BookingReferenceRepository.reconnectWithNewCredential(newCredentialId); + + //verify booking reference is reconnected to new cred + const bookingReference = await prismock.bookingReference.findUnique({ + where: { + id: bookingReferenceId, + }, + select: { + credentialId: true, + }, + }); + expect(bookingReference?.credentialId).toStrictEqual(newCredentialId); + }); + + it("should reconnect bookingReference when 'google-calendar' and 'google-meet' apps are reconnected", async () => { + vi.setSystemTime("2024-10-24T00:00:10Z"); + + const plus2DateString = "2024-10-26"; + const mockGoogleCalCredential = getGoogleCalendarCredential(); + const mockGoogleMeetCredential = getGoogleMeetCredential(); + const meetingId = uuidv4(); + const userId = 101; + const currentGoogleCalCredentialId = 1; + const currentGoogleMeetCredentialId = 2; + const bookingId = 1; + const bookingReferenceId_Cal = 1; + const bookingReferenceId_Video = 2; + const newGoogleCalCredentialId = 3; + const newGoogleMeetCredentialId = 4; + + const bookingScenarioData = { + eventTypes: [ + { + id: 1, + slotInterval: 30, + length: 30, + users: [ + { + id: userId, + }, + ], + locations: [ + { + type: BookingLocations.GoogleMeet, + }, + ], + destinationCalendar: { + integration: "google_calendar", + externalId: "event-type-1@example.com", + }, + }, + ], + users: [ + { + ...TestData.users.example, + id: userId, + schedules: [TestData.schedules.IstWorkHours], + credentials: [ + { ...mockGoogleCalCredential, id: currentGoogleCalCredentialId }, + { ...mockGoogleMeetCredential, id: currentGoogleMeetCredentialId }, + ], + selectedCalendars: [ + { + ...TestData.selectedCalendars.google, + credential: { + connect: { + id: currentGoogleCalCredentialId, + }, + }, + }, + ], + }, + ], + apps: [TestData.apps["google-calendar"], TestData.apps["google-meet"]], + bookings: [ + { + id: bookingId, + eventTypeId: 1, + userId, + status: BookingStatus.ACCEPTED, + location: BookingLocations.GoogleMeet, + startTime: `${plus2DateString}T04:30:00.000Z`, + endTime: `${plus2DateString}T05:00:00.000Z`, + references: [ + { + id: bookingReferenceId_Cal, + type: appStoreMetadata.googlecalendar.type, + uid: meetingId, + meetingId, + meetingUrl: "https://meet.google.com/mock-link", + externalCalendarId: TestData.selectedCalendars.google.externalId, + credential: { + connect: { + id: currentGoogleCalCredentialId, + }, + }, + }, + { + id: bookingReferenceId_Video, + type: "google_meet_video", + uid: meetingId, + meetingId, + meetingUrl: "https://meet.google.com/mock-link", + externalCalendarId: null, + credential: { + connect: { + id: currentGoogleCalCredentialId, + }, + }, + }, + ], + }, + ], + }; + + await createBookingScenario(bookingScenarioData); + + //mock disconnet google-calendar app and google-video, + //credential and selectedCalendar gets deleted in db. + await prismock.credential.deleteMany({ + where: { id: { in: [currentGoogleCalCredentialId, currentGoogleMeetCredentialId] } }, + }); + + //mock reconnect or reinstall google-calendar app and google-video, + //new credential and selectedCalendar is created in db. + await createCredentials([ + { + id: newGoogleCalCredentialId, + type: mockGoogleCalCredential.type, + key: mockGoogleCalCredential.key, + userId, + }, + { + id: newGoogleMeetCredentialId, + type: mockGoogleMeetCredential.type, + key: mockGoogleMeetCredential.key, + userId, + }, + ]); + await prismock.selectedCalendar.create({ + data: { + ...TestData.selectedCalendars.google, + user: { + connect: { + id: userId, + }, + }, + credential: { + connect: { + id: newGoogleCalCredentialId, + }, + }, + }, + }); + + await BookingReferenceRepository.reconnectWithNewCredential(newGoogleCalCredentialId); + + //verify both bookingReferences are reconnected to new google_calendar credential + const bookingReferences = await prismock.bookingReference.findMany({ + where: { + id: { + in: [bookingReferenceId_Cal, bookingReferenceId_Video], + }, + }, + select: { + credentialId: true, + }, + }); + expect(bookingReferences[0].credentialId).toStrictEqual(newGoogleCalCredentialId); + expect(bookingReferences[1].credentialId).toStrictEqual(newGoogleCalCredentialId); + }); +}); diff --git a/packages/lib/server/repository/bookingReference.ts b/packages/lib/server/repository/bookingReference.ts index 905bdec75fe28a..b6c8c331acd770 100644 --- a/packages/lib/server/repository/bookingReference.ts +++ b/packages/lib/server/repository/bookingReference.ts @@ -1,8 +1,12 @@ import { Prisma } from "@prisma/client"; +import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; import { prisma } from "@calcom/prisma"; import type { PartialReference } from "@calcom/types/EventManager"; +import { CredentialRepository } from "./credential"; + const bookingReferenceSelect = Prisma.validator()({ id: true, type: true, @@ -14,6 +18,10 @@ const bookingReferenceSelect = Prisma.validator() bookingId: true, }); +const log = logger.getSubLogger({ + prefix: ["BookingReferenceRepository"], +}); + export class BookingReferenceRepository { static async findDailyVideoReferenceByRoomName({ roomName }: { roomName: string }) { return prisma.bookingReference.findFirst({ @@ -49,4 +57,106 @@ export class BookingReferenceRepository { }), }); } + + /** + * Whenever a new Booking is created, a BookingReference is also created and is connected with Credential record by 'CredentialId'. + * If for some reason a App is uninstalled and installed, the Credential record is deleted and BookingReference is orphaned by 'CredentialId' field being set to null. + * This function detects the orphaned BookingReference whenever a new Credential is created and reconnects it. + * So, this function has to be called whenever a new Credential record is created. + * The combination of 3 fields - 'userId or teamId' , 'credentialType' , 'credentialId==null' is used to detect orphaned bookingReference. + * For Calendar Apps, additional field - 'externalCalendarId' is used to detect orphaned bookingReferences. + * Hence, this function has to be called also whenever a new SelectedCalendar (linked with credential) record is added. + */ + static async reconnectWithNewCredential(newCredentialId: number) { + try { + const newCredential = await CredentialRepository.findByIdWithSelectedCalendar({ + id: newCredentialId, + }); + + if (!newCredential) { + throw new Error("Credential not found."); + } + + //In case of credential created for a team, get member userIds + let teamMembersUserIds: number[] = []; + if (newCredential.teamId && !newCredential.userId) { + const members = await prisma.membership.findMany({ + where: { + teamId: newCredential.teamId, + }, + select: { + userId: true, + }, + }); + teamMembersUserIds = members.map((member) => member.userId); + } + + //Detect bookingReferences to connect with new Credential. + const bookingReferences = await prisma.bookingReference.findMany({ + where: { + type: newCredential.type, + booking: { + ...(newCredential.userId + ? { userId: newCredential.userId } + : { userId: { in: teamMembersUserIds } }), + }, + credentialId: null, + ...(!newCredential.selectedCalendars || newCredential.selectedCalendars.length === 0 + ? { externalCalendarId: null } // for non-calendar apps + : { + externalCalendarId: { + in: newCredential.selectedCalendars + .filter((selectedCalendar) => !!selectedCalendar.externalId) + .map((selectedCalendar) => selectedCalendar.externalId as string), + }, + }), + }, + select: { + id: true, + bookingId: true, + }, + }); + + if (bookingReferences.length > 0) { + //Detect additional bookingReferences in case of 'google_calendar'. + if (newCredential.type === "google_calendar") { + // get 'google_meet_video' booking references for the same bookings + bookingReferences.push( + ...(await prisma.bookingReference.findMany({ + where: { + type: "google_meet_video", + bookingId: { + in: bookingReferences + .filter((bookingReference) => !!bookingReference.bookingId) + .map((bookingReference) => bookingReference.bookingId as number), + }, + credentialId: null, + }, + select: { + id: true, + bookingId: true, + }, + })) + ); + } + + //Connect detected bookingReferences with new Credential. + await prisma.bookingReference.updateMany({ + where: { + id: { + in: bookingReferences.map((bookingReference) => bookingReference.id), + }, + }, + data: { + credentialId: newCredential.id, + }, + }); + } + } catch (error) { + log.error( + `Error in reconnectWithNewCredential() for credential id:${newCredentialId}`, + safeStringify(error) + ); + } + } } diff --git a/packages/lib/server/repository/credential.ts b/packages/lib/server/repository/credential.ts index 8f2d50a2874006..8ccc215bfb971f 100644 --- a/packages/lib/server/repository/credential.ts +++ b/packages/lib/server/repository/credential.ts @@ -1,11 +1,41 @@ import type { Prisma } from "@prisma/client"; +import type { Credential as PrismaCredential } from "@prisma/client"; import { prisma } from "@calcom/prisma"; import { safeCredentialSelect } from "@calcom/prisma/selects/credential"; +import { BookingReferenceRepository } from "./bookingReference"; + +type ICredential = Prisma.CredentialCreateInput & { + userId?: PrismaCredential["userId"]; + appId?: PrismaCredential["appId"]; + teamId?: PrismaCredential["teamId"]; +}; + export class CredentialRepository { - static async create(data: Prisma.CredentialCreateInput) { - return await prisma.credential.create({ data }); + private static generateCreateCredentialData = (credentialCreateData: ICredential) => { + const { userId, appId, teamId, ...rest } = credentialCreateData; + return { + ...rest, + ...(userId ? { user: { connect: { id: userId } } } : null), + ...(appId ? { app: { connect: { slug: appId } } } : null), + ...(teamId ? { team: { connect: { id: teamId } } } : null), + }; + }; + + static async create(data: ICredential) { + const newCredential = await prisma.credential.create({ + data: this.generateCreateCredentialData(data), + include: { + selectedCalendars: { + select: { + externalId: true, + }, + }, + }, + }); + await BookingReferenceRepository.reconnectWithNewCredential(newCredential.id); + return newCredential; } /** @@ -24,4 +54,18 @@ export class CredentialRepository { select: { ...safeCredentialSelect, key: true }, }); } + + static async findByIdWithSelectedCalendar({ id }: { id: number }) { + return await prisma.credential.findFirst({ + where: { id }, + select: { + ...safeCredentialSelect, + selectedCalendars: { + select: { + externalId: true, + }, + }, + }, + }); + } } diff --git a/packages/lib/server/repository/selectedCalendar.ts b/packages/lib/server/repository/selectedCalendar.ts new file mode 100644 index 00000000000000..1f9dd54b9fa5d3 --- /dev/null +++ b/packages/lib/server/repository/selectedCalendar.ts @@ -0,0 +1,39 @@ +import type { Prisma } from "@prisma/client"; +import type { SelectedCalendar as PrismaSelectedCalendar } from "@prisma/client"; + +import { prisma } from "@calcom/prisma"; + +import { BookingReferenceRepository } from "./bookingReference"; + +type ISelectedCalendar = Omit & { + credentialId?: PrismaSelectedCalendar["credentialId"]; + userId?: PrismaSelectedCalendar["userId"]; +}; + +export class SelectedCalendarRepository { + private static generateSelectedCalendarData = (selectedCalendarCreateData: ISelectedCalendar) => { + const { credentialId, userId, ...rest } = selectedCalendarCreateData; + return { + ...rest, + ...{ user: { connect: { id: userId } } }, + ...(credentialId ? { credential: { connect: { id: credentialId } } } : null), + }; + }; + + static async create(data: ISelectedCalendar) { + let selectedCalendar; + try { + selectedCalendar = await prisma.selectedCalendar.create({ + data: this.generateSelectedCalendarData(data), + }); + } catch (error) { + throw error; + } + + if (!!selectedCalendar.credentialId) { + await BookingReferenceRepository.reconnectWithNewCredential(selectedCalendar.credentialId); + } + + return selectedCalendar; + } +}