From 96bec9f7b9ca68eeb2204fdd02781c40dca65967 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 9 Feb 2026 20:41:04 +0900 Subject: [PATCH] refactor: Move repositories from @calcom/lib to @calcom/features domain folders (#27570) * refactor: move repositories from lib to features domain folders - Move HolidayRepository to features/holidays/repositories - Move PrismaTrackingRepository to features/bookings/repositories - Move PrismaBookingPaymentRepository to features/bookings/repositories - Move PrismaRoutingFormResponseRepository to features/routing-forms/repositories - Move PrismaAssignmentReasonRepository to features/assignment-reason/repositories - Move VerificationTokenRepository to features/auth/repositories - Move WorkspacePlatformRepository to features/workspace-platform/repositories - Move DTO files to their respective feature domains - Merge lib DestinationCalendarRepository into features version - Merge lib SelectedCalendarRepository into features version - Update all import paths across the codebase This follows the vertical slice architecture pattern by organizing repositories by domain rather than by technical layer. Co-Authored-By: benny@cal.com * fix: update VerificationTokenService import path to new location Co-Authored-By: benny@cal.com * fix: update test file imports to use new repository locations Co-Authored-By: benny@cal.com * mv * fix structure * fix * refactor: merge unit tests for SelectedCalendarRepository into single file Co-Authored-By: benny@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../api/selected-calendars/[id]/_delete.ts | 2 +- .../pages/api/selected-calendars/[id]/_get.ts | 2 +- .../api/selected-calendars/[id]/_patch.ts | 4 +- .../v1/pages/api/selected-calendars/_get.ts | 4 +- .../v1/pages/api/selected-calendars/_post.ts | 2 +- .../app/api/availability/calendar/route.ts | 2 +- .../app/api/cron/selected-calendars/route.ts | 2 +- .../__tests__/queued-response.test.ts | 4 +- .../routing-forms/queued-response/route.ts | 2 +- biome.json | 21 + .../app-store/btcpayserver/api/webhook.ts | 2 +- .../btcpayserver/lib/PaymentService.ts | 4 +- .../googlecalendar/lib/CalendarService.ts | 2 +- .../app-store/hubspot/lib/CrmService.test.ts | 2 +- packages/app-store/hubspot/lib/CrmService.ts | 2 +- .../RoutingFormResponseDataFactory.test.ts | 2 +- .../lib/RoutingFormResponseDataFactory.ts | 2 +- .../app-store/salesforce/lib/CrmService.ts | 4 +- .../PrismaAssignmentReasonRepository.ts | 4 +- .../api/__tests__/paymentCallback.test.ts | 4 +- .../stripepayment/api/paymentCallback.ts | 2 +- .../lib}/VerificationTokenService.test.ts | 6 +- .../lib}/VerificationTokenService.ts | 2 +- .../VerificationTokenRepository.test.ts} | 4 +- .../VerificationTokenRepository.ts} | 0 .../service/BookingHistoryViewerService.ts | 2 +- .../BookingReferenceRepository.ts | 2 +- .../IBookingReferenceRepository.ts | 0 .../di/BookingReferenceRepository.module.ts | 2 +- .../BookingPaymentRepository.interface.ts | 0 .../repositories/BookingRepository.ts | 12 +- .../repositories}/IBookingRepository.ts | 0 .../PrismaBookingPaymentRepository.ts | 0 .../repositories}/PrismaTrackingRepository.ts | 0 .../TrackingRepository.interface.ts | 0 .../lib/getConnectedDestinationCalendars.ts | 2 +- .../DestinationCalendarRepository.ts | 38 ++ .../di/containers/DestinationCalendar.ts | 2 +- .../di/modules/DestinationCalendar.ts | 2 +- .../di/modules/RoutingFormResponse.ts | 2 +- .../ee/teams/lib/getTeamMemberEmailFromCrm.ts | 2 +- .../repositories/HashedLinkSelect.ts} | 0 .../repositories}/HolidayRepository.test.ts | 0 .../repositories}/HolidayRepository.ts | 0 .../repositories}/IProfileRepository.ts | 0 .../profile/repositories/ProfileRepository.ts | 2 +- .../RoutingFormResponseRepository.module.ts | 2 +- .../routing-forms/lib/handleResponse.test.ts | 4 +- .../routing-forms/lib/handleResponse.ts | 2 +- .../PrismaRoutingFormResponseRepository.ts | 0 ...RoutingFormResponseRepository.interface.ts | 0 .../RoutingFormResponseRepository.ts} | 0 .../SelectedCalendarRepository.test.ts | 489 ++++++++++++++++- .../SelectedCalendarRepository.ts | 412 +++++++++++++++ .../tasker/tasks/sendAwaitingPaymentEmail.ts | 2 +- .../formSubmissionValidation.ts | 2 +- .../WorkspacePlatformRepository.ts} | 0 packages/lib/holidays/HolidayService.ts | 2 +- .../holidays/HolidayServiceCachingProxy.ts | 2 +- .../server/repository/destinationCalendar.ts | 36 -- ...selectedSlotDeleteManyExpiredSlotsInput.ts | 0 ...lectedSlotFindManyReservedByOthersInput.ts | 0 ...selectedSlotFindManyUnexpiredSlotsInput.ts | 0 .../selectedSlotFindReservedByOthersInput.ts | 0 .../repository/selectedCalendar.test.ts | 493 ------------------ .../lib/server/repository/selectedCalendar.ts | 438 ---------------- packages/platform/libraries/index.ts | 2 +- packages/platform/libraries/repositories.ts | 2 +- .../admin/workspacePlatform/add.handler.ts | 2 +- .../admin/workspacePlatform/list.handler.ts | 2 +- .../toggleEnabled.handler.ts | 2 +- .../admin/workspacePlatform/update.handler.ts | 2 +- .../updateServiceAccount.handler.ts | 2 +- .../delegationCredential/add.handler.ts | 2 +- .../listWorkspacePlatforms.handler.ts | 2 +- .../delegationCredential/update.handler.ts | 2 +- .../trpc/server/routers/viewer/slots/util.ts | 2 +- .../viewer/teams/resendInvitation.handler.ts | 2 +- 78 files changed, 1027 insertions(+), 1036 deletions(-) rename packages/{lib/server/repository => app-store/salesforce/lib/repositories}/PrismaAssignmentReasonRepository.ts (88%) rename packages/{lib/server/service/__tests__ => app-store/stripepayment/lib}/VerificationTokenService.test.ts (94%) rename packages/{lib/server/service => app-store/stripepayment/lib}/VerificationTokenService.ts (84%) rename packages/{lib/server/repository/__tests__/verificationToken.test.ts => app-store/stripepayment/lib/repositories/VerificationTokenRepository.test.ts} (98%) rename packages/{lib/server/repository/verificationToken.ts => app-store/stripepayment/lib/repositories/VerificationTokenRepository.ts} (100%) rename packages/{lib/server/repository/dto => features/bookingReference/repositories}/IBookingReferenceRepository.ts (100%) rename packages/{lib/server/repository => features/bookings/repositories}/BookingPaymentRepository.interface.ts (100%) rename packages/{lib/server/repository/dto => features/bookings/repositories}/IBookingRepository.ts (100%) rename packages/{lib/server/repository => features/bookings/repositories}/PrismaBookingPaymentRepository.ts (100%) rename packages/{lib/server/repository => features/bookings/repositories}/PrismaTrackingRepository.ts (100%) rename packages/{lib/server/repository => features/bookings/repositories}/TrackingRepository.interface.ts (100%) rename packages/{lib/server/repository/dto/hashedLinkSelect.ts => features/hashedLink/repositories/HashedLinkSelect.ts} (100%) rename packages/{lib/server/repository => features/holidays/repositories}/HolidayRepository.test.ts (100%) rename packages/{lib/server/repository => features/holidays/repositories}/HolidayRepository.ts (100%) rename packages/{lib/server/repository/dto => features/profile/repositories}/IProfileRepository.ts (100%) rename packages/{lib/server/repository => features/routing-forms/repositories}/PrismaRoutingFormResponseRepository.ts (100%) rename packages/{lib/server/repository => features/routing-forms/repositories}/RoutingFormResponseRepository.interface.ts (100%) rename packages/{lib/server/repository/formResponse.ts => features/routing-forms/repositories/RoutingFormResponseRepository.ts} (100%) rename packages/{lib/server/repository/workspacePlatform.ts => features/workspace-platform/repositories/WorkspacePlatformRepository.ts} (100%) delete mode 100644 packages/lib/server/repository/destinationCalendar.ts delete mode 100644 packages/lib/server/repository/dto/selectedSlotDeleteManyExpiredSlotsInput.ts delete mode 100644 packages/lib/server/repository/dto/selectedSlotFindManyReservedByOthersInput.ts delete mode 100644 packages/lib/server/repository/dto/selectedSlotFindManyUnexpiredSlotsInput.ts delete mode 100644 packages/lib/server/repository/dto/selectedSlotFindReservedByOthersInput.ts delete mode 100644 packages/lib/server/repository/selectedCalendar.test.ts delete mode 100644 packages/lib/server/repository/selectedCalendar.ts diff --git a/apps/api/v1/pages/api/selected-calendars/[id]/_delete.ts b/apps/api/v1/pages/api/selected-calendars/[id]/_delete.ts index 91db9ec722..1d6835c5f2 100644 --- a/apps/api/v1/pages/api/selected-calendars/[id]/_delete.ts +++ b/apps/api/v1/pages/api/selected-calendars/[id]/_delete.ts @@ -1,7 +1,7 @@ import type { NextApiRequest } from "next"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar"; diff --git a/apps/api/v1/pages/api/selected-calendars/[id]/_get.ts b/apps/api/v1/pages/api/selected-calendars/[id]/_get.ts index e7e3576ec6..86698c6e1c 100644 --- a/apps/api/v1/pages/api/selected-calendars/[id]/_get.ts +++ b/apps/api/v1/pages/api/selected-calendars/[id]/_get.ts @@ -1,7 +1,7 @@ import type { NextApiRequest } from "next"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import { schemaSelectedCalendarPublic, selectedCalendarIdSchema } from "~/lib/validations/selected-calendar"; diff --git a/apps/api/v1/pages/api/selected-calendars/[id]/_patch.ts b/apps/api/v1/pages/api/selected-calendars/[id]/_patch.ts index e469e283ac..1066c6cadd 100644 --- a/apps/api/v1/pages/api/selected-calendars/[id]/_patch.ts +++ b/apps/api/v1/pages/api/selected-calendars/[id]/_patch.ts @@ -2,8 +2,8 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; -import type { UpdateArguments } from "@calcom/lib/server/repository/selectedCalendar"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import type { UpdateArguments } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; diff --git a/apps/api/v1/pages/api/selected-calendars/_get.ts b/apps/api/v1/pages/api/selected-calendars/_get.ts index e92a03766f..6ce2e40db0 100644 --- a/apps/api/v1/pages/api/selected-calendars/_get.ts +++ b/apps/api/v1/pages/api/selected-calendars/_get.ts @@ -2,8 +2,8 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; -import type { FindManyArgs } from "@calcom/lib/server/repository/selectedCalendar"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import type { FindManyArgs } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import { schemaSelectedCalendarPublic } from "~/lib/validations/selected-calendar"; import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId"; diff --git a/apps/api/v1/pages/api/selected-calendars/_post.ts b/apps/api/v1/pages/api/selected-calendars/_post.ts index b1e98ae22b..b80c9a775e 100644 --- a/apps/api/v1/pages/api/selected-calendars/_post.ts +++ b/apps/api/v1/pages/api/selected-calendars/_post.ts @@ -2,7 +2,7 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; diff --git a/apps/web/app/api/availability/calendar/route.ts b/apps/web/app/api/availability/calendar/route.ts index a8bc833497..e50e0249ec 100644 --- a/apps/web/app/api/availability/calendar/route.ts +++ b/apps/web/app/api/availability/calendar/route.ts @@ -12,7 +12,7 @@ import { import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { HttpError } from "@calcom/lib/http-error"; import notEmpty from "@calcom/lib/notEmpty"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import prisma from "@calcom/prisma"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; diff --git a/apps/web/app/api/cron/selected-calendars/route.ts b/apps/web/app/api/cron/selected-calendars/route.ts index 227dc773ac..6edd7b9d38 100644 --- a/apps/web/app/api/cron/selected-calendars/route.ts +++ b/apps/web/app/api/cron/selected-calendars/route.ts @@ -16,7 +16,7 @@ import { CalendarAppDelegationCredentialInvalidGrantError } from "@calcom/lib/Ca import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential"; import type { Ensure } from "@calcom/types/utils"; diff --git a/apps/web/app/api/routing-forms/queued-response/__tests__/queued-response.test.ts b/apps/web/app/api/routing-forms/queued-response/__tests__/queued-response.test.ts index 8434e08478..33ad140de3 100644 --- a/apps/web/app/api/routing-forms/queued-response/__tests__/queued-response.test.ts +++ b/apps/web/app/api/routing-forms/queued-response/__tests__/queued-response.test.ts @@ -4,11 +4,11 @@ import { beforeEach, describe, it, expect, vi } from "vitest"; import { onSubmissionOfFormResponse } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils"; import { getResponseToStore } from "@calcom/app-store/routing-forms/lib/getResponseToStore"; import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm"; -import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +import { RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; import { queuedResponseHandler } from "../route"; -vi.mock("@calcom/lib/server/repository/formResponse"); +vi.mock("@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"); const mockRoutingFormResponseRepository = { getQueuedFormResponseFromId: vi.fn(), diff --git a/apps/web/app/api/routing-forms/queued-response/route.ts b/apps/web/app/api/routing-forms/queued-response/route.ts index 719c3b7cde..d16eaed8b3 100644 --- a/apps/web/app/api/routing-forms/queued-response/route.ts +++ b/apps/web/app/api/routing-forms/queued-response/route.ts @@ -8,7 +8,7 @@ import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSeri import { PrismaPendingRoutingTraceRepository } from "@calcom/features/routing-trace/repositories/PrismaPendingRoutingTraceRepository"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +import { RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; import prisma from "@calcom/prisma"; import { defaultResponderForAppDir } from "../../defaultResponderForAppDir"; diff --git a/biome.json b/biome.json index 179185e42c..e3b3ae1b53 100644 --- a/biome.json +++ b/biome.json @@ -250,6 +250,27 @@ } } }, + { + "includes": [ + "packages/lib/**/*repository*.{ts,tsx,js,jsx}", + "packages/lib/**/*Repository*.{ts,tsx,js,jsx}", + "packages/lib/**/repository/**/*.{ts,tsx,js,jsx}", + "packages/lib/**/repositories/**/*.{ts,tsx,js,jsx}" + ], + "linter": { + "rules": { + "style": { + "useFilenamingConvention": { + "level": "error", + "options": { + "match": "REPOSITORY-FILES-NOT-ALLOWED-IN-PACKAGES-LIB[.](.+)", + "filenameCases": ["camelCase"] + } + } + } + } + } + }, { "includes": ["packages/app-store/**/*.{ts,tsx,js,jsx,mts,mjs,cjs,cts}"], "linter": { diff --git a/packages/app-store/btcpayserver/api/webhook.ts b/packages/app-store/btcpayserver/api/webhook.ts index 9989c89392..9ce4d67ed8 100644 --- a/packages/app-store/btcpayserver/api/webhook.ts +++ b/packages/app-store/btcpayserver/api/webhook.ts @@ -8,7 +8,7 @@ import { distributedTracing } from "@calcom/lib/tracing/factory"; import { IS_PRODUCTION } from "@calcom/lib/constants"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; -import { PrismaBookingPaymentRepository as BookingPaymentRepository } from "@calcom/lib/server/repository/PrismaBookingPaymentRepository"; +import { PrismaBookingPaymentRepository as BookingPaymentRepository } from "@calcom/features/bookings/repositories/PrismaBookingPaymentRepository"; import appConfig from "../config.json"; import { btcpayCredentialKeysSchema } from "../lib/btcpayCredentialKeysSchema"; diff --git a/packages/app-store/btcpayserver/lib/PaymentService.ts b/packages/app-store/btcpayserver/lib/PaymentService.ts index dcd0e9545b..48b78bccc3 100644 --- a/packages/app-store/btcpayserver/lib/PaymentService.ts +++ b/packages/app-store/btcpayserver/lib/PaymentService.ts @@ -4,8 +4,8 @@ import type z from "zod"; import { ErrorCode } from "@calcom/lib/errorCodes"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import type { IBookingPaymentRepository } from "@calcom/lib/server/repository/BookingPaymentRepository.interface"; -import { PrismaBookingPaymentRepository } from "@calcom/lib/server/repository/PrismaBookingPaymentRepository"; +import type { IBookingPaymentRepository } from "@calcom/features/bookings/repositories/BookingPaymentRepository.interface"; +import { PrismaBookingPaymentRepository } from "@calcom/features/bookings/repositories/PrismaBookingPaymentRepository"; import type { Booking, Payment, PaymentOption } from "@calcom/prisma/client"; import type { Prisma } from "@calcom/prisma/client"; import type { CalendarEvent } from "@calcom/types/Calendar"; diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index c53f7aed5a..1aa622dfa5 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -2,11 +2,11 @@ import { MeetLocationType } from "@calcom/app-store/constants"; import { getDestinationCalendarRepository } from "@calcom/features/di/containers/DestinationCalendar"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser"; import { ORGANIZER_EMAIL_EXEMPT_DOMAINS } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import type { Prisma } from "@calcom/prisma/client"; import type { Calendar, diff --git a/packages/app-store/hubspot/lib/CrmService.test.ts b/packages/app-store/hubspot/lib/CrmService.test.ts index d5be7ecde6..efca847423 100644 --- a/packages/app-store/hubspot/lib/CrmService.test.ts +++ b/packages/app-store/hubspot/lib/CrmService.test.ts @@ -141,7 +141,7 @@ vi.mock("@calcom/prisma", () => ({ }, })); -vi.mock("@calcom/lib/server/repository/PrismaTrackingRepository", () => ({ +vi.mock("@calcom/features/bookings/repositories/PrismaTrackingRepository", () => ({ PrismaTrackingRepository: class { findByBookingUid = mockTrackingRepository.findByBookingUid; }, diff --git a/packages/app-store/hubspot/lib/CrmService.ts b/packages/app-store/hubspot/lib/CrmService.ts index 269346155b..daac101907 100644 --- a/packages/app-store/hubspot/lib/CrmService.ts +++ b/packages/app-store/hubspot/lib/CrmService.ts @@ -7,7 +7,7 @@ import { getLocation } from "@calcom/lib/CalEventParser"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; -import { PrismaTrackingRepository } from "@calcom/lib/server/repository/PrismaTrackingRepository"; +import { PrismaTrackingRepository } from "@calcom/features/bookings/repositories/PrismaTrackingRepository"; import prisma from "@calcom/prisma"; import type { CalEventResponses, CalendarEvent } from "@calcom/types/Calendar"; import type { CredentialPayload } from "@calcom/types/Credential"; diff --git a/packages/app-store/routing-forms/lib/RoutingFormResponseDataFactory.test.ts b/packages/app-store/routing-forms/lib/RoutingFormResponseDataFactory.test.ts index 60fa2c8c93..664914d7a3 100644 --- a/packages/app-store/routing-forms/lib/RoutingFormResponseDataFactory.test.ts +++ b/packages/app-store/routing-forms/lib/RoutingFormResponseDataFactory.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; -import type { RoutingFormResponseRepositoryInterface } from "@calcom/lib/server/repository/RoutingFormResponseRepository.interface"; +import type { RoutingFormResponseRepositoryInterface } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository.interface"; import { RoutingFormResponseDataFactory } from "./RoutingFormResponseDataFactory"; import { parseRoutingFormResponse } from "./responseData/parseRoutingFormResponse"; diff --git a/packages/app-store/routing-forms/lib/RoutingFormResponseDataFactory.ts b/packages/app-store/routing-forms/lib/RoutingFormResponseDataFactory.ts index 8c5d192280..9fe335d31b 100644 --- a/packages/app-store/routing-forms/lib/RoutingFormResponseDataFactory.ts +++ b/packages/app-store/routing-forms/lib/RoutingFormResponseDataFactory.ts @@ -1,5 +1,5 @@ import type logger from "@calcom/lib/logger"; -import type { RoutingFormResponseRepositoryInterface } from "@calcom/lib/server/repository/RoutingFormResponseRepository.interface"; +import type { RoutingFormResponseRepositoryInterface } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository.interface"; import { parseRoutingFormResponse } from "./responseData/parseRoutingFormResponse"; diff --git a/packages/app-store/salesforce/lib/CrmService.ts b/packages/app-store/salesforce/lib/CrmService.ts index 3c4e3adf65..eb80fdd7f7 100644 --- a/packages/app-store/salesforce/lib/CrmService.ts +++ b/packages/app-store/salesforce/lib/CrmService.ts @@ -10,8 +10,8 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { RetryableError } from "@calcom/lib/crmManager/errors"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import { PrismaAssignmentReasonRepository } from "@calcom/lib/server/repository/PrismaAssignmentReasonRepository"; -import { PrismaRoutingFormResponseRepository as RoutingFormResponseRepository } from "@calcom/lib/server/repository/PrismaRoutingFormResponseRepository"; +import { PrismaAssignmentReasonRepository } from "./repositories/PrismaAssignmentReasonRepository"; +import { PrismaRoutingFormResponseRepository as RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/PrismaRoutingFormResponseRepository"; import { prisma } from "@calcom/prisma"; import type { CalendarEvent, CalEventResponses } from "@calcom/types/Calendar"; import type { CredentialPayload } from "@calcom/types/Credential"; diff --git a/packages/lib/server/repository/PrismaAssignmentReasonRepository.ts b/packages/app-store/salesforce/lib/repositories/PrismaAssignmentReasonRepository.ts similarity index 88% rename from packages/lib/server/repository/PrismaAssignmentReasonRepository.ts rename to packages/app-store/salesforce/lib/repositories/PrismaAssignmentReasonRepository.ts index f4d6216b26..4648c34e55 100644 --- a/packages/lib/server/repository/PrismaAssignmentReasonRepository.ts +++ b/packages/app-store/salesforce/lib/repositories/PrismaAssignmentReasonRepository.ts @@ -1,4 +1,4 @@ -import prisma from "@calcom/prisma"; +import { prisma } from "@calcom/prisma"; export class PrismaAssignmentReasonRepository { static async findLatestReasonFromBookingUid(bookingUid: string) { @@ -13,4 +13,4 @@ export class PrismaAssignmentReasonRepository { }, }); } -} +} \ No newline at end of file diff --git a/packages/app-store/stripepayment/api/__tests__/paymentCallback.test.ts b/packages/app-store/stripepayment/api/__tests__/paymentCallback.test.ts index 27094f5331..043ff61da7 100644 --- a/packages/app-store/stripepayment/api/__tests__/paymentCallback.test.ts +++ b/packages/app-store/stripepayment/api/__tests__/paymentCallback.test.ts @@ -3,7 +3,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import sendVerificationRequest from "@calcom/features/auth/lib/sendVerificationRequest"; import { HttpError } from "@calcom/lib/http-error"; -import { VerificationTokenService } from "@calcom/lib/server/service/VerificationTokenService"; +import { VerificationTokenService } from "../../lib/VerificationTokenService"; import { prisma } from "@calcom/prisma"; import { getCustomerAndCheckoutSession } from "../../lib/getCustomerAndCheckoutSession"; @@ -20,7 +20,7 @@ vi.mock("@calcom/prisma", () => ({ vi.mock("../../lib/getCustomerAndCheckoutSession"); vi.mock("@calcom/features/auth/lib/sendVerificationRequest"); -vi.mock("@calcom/lib/server/service/VerificationTokenService", () => ({ +vi.mock("../../lib/VerificationTokenService", () => ({ VerificationTokenService: { create: vi.fn(), }, diff --git a/packages/app-store/stripepayment/api/paymentCallback.ts b/packages/app-store/stripepayment/api/paymentCallback.ts index 4c646f2a9f..dbf0f05e09 100644 --- a/packages/app-store/stripepayment/api/paymentCallback.ts +++ b/packages/app-store/stripepayment/api/paymentCallback.ts @@ -8,7 +8,7 @@ import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { defaultHandler } from "@calcom/lib/server/defaultHandler"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; -import { VerificationTokenService } from "@calcom/lib/server/service/VerificationTokenService"; +import { VerificationTokenService } from "../lib/VerificationTokenService"; import { prisma } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; diff --git a/packages/lib/server/service/__tests__/VerificationTokenService.test.ts b/packages/app-store/stripepayment/lib/VerificationTokenService.test.ts similarity index 94% rename from packages/lib/server/service/__tests__/VerificationTokenService.test.ts rename to packages/app-store/stripepayment/lib/VerificationTokenService.test.ts index 490bb2bbb2..1f35c76340 100644 --- a/packages/lib/server/service/__tests__/VerificationTokenService.test.ts +++ b/packages/app-store/stripepayment/lib/VerificationTokenService.test.ts @@ -1,10 +1,10 @@ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"; -import { VerificationTokenRepository } from "@calcom/lib/server/repository/verificationToken"; +import { VerificationTokenRepository } from "./repositories/VerificationTokenRepository"; -import { VerificationTokenService } from "../VerificationTokenService"; +import { VerificationTokenService } from "./VerificationTokenService"; -vi.mock("@calcom/lib/server/repository/verificationToken", () => ({ +vi.mock("./repositories/VerificationTokenRepository", () => ({ VerificationTokenRepository: { create: vi.fn(), }, diff --git a/packages/lib/server/service/VerificationTokenService.ts b/packages/app-store/stripepayment/lib/VerificationTokenService.ts similarity index 84% rename from packages/lib/server/service/VerificationTokenService.ts rename to packages/app-store/stripepayment/lib/VerificationTokenService.ts index 429851bbc0..dcaeab7ece 100644 --- a/packages/lib/server/service/VerificationTokenService.ts +++ b/packages/app-store/stripepayment/lib/VerificationTokenService.ts @@ -1,6 +1,6 @@ import { randomBytes, createHash } from "node:crypto"; -import { VerificationTokenRepository } from "../repository/verificationToken"; +import { VerificationTokenRepository } from "./repositories/VerificationTokenRepository"; export class VerificationTokenService { static async create({ identifier, expires }: { identifier: string; expires: Date }) { diff --git a/packages/lib/server/repository/__tests__/verificationToken.test.ts b/packages/app-store/stripepayment/lib/repositories/VerificationTokenRepository.test.ts similarity index 98% rename from packages/lib/server/repository/__tests__/verificationToken.test.ts rename to packages/app-store/stripepayment/lib/repositories/VerificationTokenRepository.test.ts index 9d03f9db61..ddeda7c6e9 100644 --- a/packages/lib/server/repository/__tests__/verificationToken.test.ts +++ b/packages/app-store/stripepayment/lib/repositories/VerificationTokenRepository.test.ts @@ -2,7 +2,7 @@ import { describe, test, expect, vi, beforeEach } from "vitest"; import type { VerificationToken } from "@calcom/prisma/client"; -import { VerificationTokenRepository } from "../verificationToken"; +import { VerificationTokenRepository } from "./VerificationTokenRepository"; vi.mock("@calcom/prisma", () => ({ default: { @@ -161,4 +161,4 @@ describe("VerificationTokenRepository", () => { }); }); }); -}); +}); \ No newline at end of file diff --git a/packages/lib/server/repository/verificationToken.ts b/packages/app-store/stripepayment/lib/repositories/VerificationTokenRepository.ts similarity index 100% rename from packages/lib/server/repository/verificationToken.ts rename to packages/app-store/stripepayment/lib/repositories/VerificationTokenRepository.ts diff --git a/packages/features/booking-audit/lib/service/BookingHistoryViewerService.ts b/packages/features/booking-audit/lib/service/BookingHistoryViewerService.ts index e6d66452cc..1c64100770 100644 --- a/packages/features/booking-audit/lib/service/BookingHistoryViewerService.ts +++ b/packages/features/booking-audit/lib/service/BookingHistoryViewerService.ts @@ -1,4 +1,4 @@ -import type { RoutingFormResponseRepositoryInterface } from "@calcom/lib/server/repository/RoutingFormResponseRepository.interface"; +import type { RoutingFormResponseRepositoryInterface } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository.interface"; import type { BookingAuditViewerService, DisplayBookingAuditLog } from "./BookingAuditViewerService"; import { getFieldResponseByIdentifier } from "@calcom/features/routing-forms/lib/getFieldResponseByIdentifier"; diff --git a/packages/features/bookingReference/repositories/BookingReferenceRepository.ts b/packages/features/bookingReference/repositories/BookingReferenceRepository.ts index 2cfd0cfaff..ec37395be1 100644 --- a/packages/features/bookingReference/repositories/BookingReferenceRepository.ts +++ b/packages/features/bookingReference/repositories/BookingReferenceRepository.ts @@ -2,7 +2,7 @@ import { prisma } from "@calcom/prisma"; import type { Prisma, PrismaClient } from "@calcom/prisma/client"; import type { PartialReference } from "@calcom/types/EventManager"; -import type { IBookingReferenceRepository } from "@calcom/lib/server/repository/dto/IBookingReferenceRepository"; +import type { IBookingReferenceRepository } from "./IBookingReferenceRepository"; const bookingReferenceSelect = { id: true, diff --git a/packages/lib/server/repository/dto/IBookingReferenceRepository.ts b/packages/features/bookingReference/repositories/IBookingReferenceRepository.ts similarity index 100% rename from packages/lib/server/repository/dto/IBookingReferenceRepository.ts rename to packages/features/bookingReference/repositories/IBookingReferenceRepository.ts diff --git a/packages/features/bookings/di/BookingReferenceRepository.module.ts b/packages/features/bookings/di/BookingReferenceRepository.module.ts index f9ba2a6e24..0c344bedaa 100644 --- a/packages/features/bookings/di/BookingReferenceRepository.module.ts +++ b/packages/features/bookings/di/BookingReferenceRepository.module.ts @@ -1,6 +1,6 @@ import { bindModuleToClassOnToken, createModule } from "@calcom/features/di/di"; import { DI_TOKENS } from "@calcom/features/di/tokens"; -import { BookingReferenceRepository } from "@calcom/lib/server/repository/bookingReference"; +import { BookingReferenceRepository } from "@calcom/features/bookingReference/repositories/BookingReferenceRepository"; import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module"; export const bookingReferenceRepositoryModule = createModule(); diff --git a/packages/lib/server/repository/BookingPaymentRepository.interface.ts b/packages/features/bookings/repositories/BookingPaymentRepository.interface.ts similarity index 100% rename from packages/lib/server/repository/BookingPaymentRepository.interface.ts rename to packages/features/bookings/repositories/BookingPaymentRepository.interface.ts diff --git a/packages/features/bookings/repositories/BookingRepository.ts b/packages/features/bookings/repositories/BookingRepository.ts index f3700dee2e..56efd2900b 100644 --- a/packages/features/bookings/repositories/BookingRepository.ts +++ b/packages/features/bookings/repositories/BookingRepository.ts @@ -1,10 +1,4 @@ import { withReporting } from "@calcom/lib/sentryWrapper"; -import type { - BookingUpdateData, - BookingWhereInput, - BookingWhereUniqueInput, - IBookingRepository, -} from "@calcom/lib/server/repository/dto/IBookingRepository"; import type { PrismaClient } from "@calcom/prisma"; import type { Booking, Prisma } from "@calcom/prisma/client"; import { BookingStatus, RRTimestampBasis } from "@calcom/prisma/enums"; @@ -15,6 +9,12 @@ import { } from "@calcom/prisma/selects/booking"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import type { + BookingWhereInput, + IBookingRepository, + BookingUpdateData, + BookingWhereUniqueInput, +} from "./IBookingRepository"; import { workflowSelect } from "../../ee/workflows/lib/getAllWorkflows"; const workflowReminderSelect = { diff --git a/packages/lib/server/repository/dto/IBookingRepository.ts b/packages/features/bookings/repositories/IBookingRepository.ts similarity index 100% rename from packages/lib/server/repository/dto/IBookingRepository.ts rename to packages/features/bookings/repositories/IBookingRepository.ts diff --git a/packages/lib/server/repository/PrismaBookingPaymentRepository.ts b/packages/features/bookings/repositories/PrismaBookingPaymentRepository.ts similarity index 100% rename from packages/lib/server/repository/PrismaBookingPaymentRepository.ts rename to packages/features/bookings/repositories/PrismaBookingPaymentRepository.ts diff --git a/packages/lib/server/repository/PrismaTrackingRepository.ts b/packages/features/bookings/repositories/PrismaTrackingRepository.ts similarity index 100% rename from packages/lib/server/repository/PrismaTrackingRepository.ts rename to packages/features/bookings/repositories/PrismaTrackingRepository.ts diff --git a/packages/lib/server/repository/TrackingRepository.interface.ts b/packages/features/bookings/repositories/TrackingRepository.interface.ts similarity index 100% rename from packages/lib/server/repository/TrackingRepository.interface.ts rename to packages/features/bookings/repositories/TrackingRepository.interface.ts diff --git a/packages/features/calendars/lib/getConnectedDestinationCalendars.ts b/packages/features/calendars/lib/getConnectedDestinationCalendars.ts index 39d6fd7231..72a0192992 100644 --- a/packages/features/calendars/lib/getConnectedDestinationCalendars.ts +++ b/packages/features/calendars/lib/getConnectedDestinationCalendars.ts @@ -8,7 +8,7 @@ import { import { DestinationCalendarRepository } from "@calcom/features/calendars/repositories/DestinationCalendarRepository"; import { isDelegationCredential } from "@calcom/lib/delegationCredential"; import logger from "@calcom/lib/logger"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import type { PrismaClient } from "@calcom/prisma"; import prisma from "@calcom/prisma"; import type { DestinationCalendar, SelectedCalendar, User } from "@calcom/prisma/client"; diff --git a/packages/features/calendars/repositories/DestinationCalendarRepository.ts b/packages/features/calendars/repositories/DestinationCalendarRepository.ts index 543c87af3b..9f89c0ee09 100644 --- a/packages/features/calendars/repositories/DestinationCalendarRepository.ts +++ b/packages/features/calendars/repositories/DestinationCalendarRepository.ts @@ -1,11 +1,49 @@ import logger from "@calcom/lib/logger"; import { buildCredentialPayloadForPrisma } from "@calcom/lib/server/buildCredentialPayloadForCalendar"; +import type { PrismaClient } from "@calcom/prisma"; import { prisma } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; const log = logger.getSubLogger({ prefix: ["DestinationCalendarRepository"] }); export class DestinationCalendarRepository { + private prismaClient: PrismaClient; + + constructor(prismaClient?: PrismaClient) { + this.prismaClient = prismaClient ?? prisma; + } + + async getCustomReminderByCredentialId(credentialId: number): Promise { + const destinationCalendar = await this.prismaClient.destinationCalendar.findFirst({ + where: { credentialId }, + select: { customCalendarReminder: true }, + }); + return destinationCalendar?.customCalendarReminder ?? null; + } + + async updateCustomReminder({ + userId, + credentialId, + integration, + customCalendarReminder, + }: { + userId: number; + credentialId: number; + integration: string; + customCalendarReminder: number | null; + }) { + return await this.prismaClient.destinationCalendar.updateMany({ + where: { + userId, + credentialId, + integration, + }, + data: { + customCalendarReminder, + }, + }); + } + static async create(data: Prisma.DestinationCalendarCreateInput) { return await prisma.destinationCalendar.create({ data, diff --git a/packages/features/di/containers/DestinationCalendar.ts b/packages/features/di/containers/DestinationCalendar.ts index b468182596..dfd3d91f19 100644 --- a/packages/features/di/containers/DestinationCalendar.ts +++ b/packages/features/di/containers/DestinationCalendar.ts @@ -1,4 +1,4 @@ -import type { DestinationCalendarRepository } from "@calcom/lib/server/repository/destinationCalendar"; +import type { DestinationCalendarRepository } from "@calcom/features/calendars/repositories/DestinationCalendarRepository"; import { createContainer } from "../di"; import { moduleLoader as destinationCalendarRepositoryModuleLoader } from "../modules/DestinationCalendar"; diff --git a/packages/features/di/modules/DestinationCalendar.ts b/packages/features/di/modules/DestinationCalendar.ts index 5a69df8684..c300d34903 100644 --- a/packages/features/di/modules/DestinationCalendar.ts +++ b/packages/features/di/modules/DestinationCalendar.ts @@ -1,4 +1,4 @@ -import { DestinationCalendarRepository } from "@calcom/lib/server/repository/destinationCalendar"; +import { DestinationCalendarRepository } from "@calcom/features/calendars/repositories/DestinationCalendarRepository"; import { DI_TOKENS } from "@calcom/features/di/tokens"; import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma"; diff --git a/packages/features/di/modules/RoutingFormResponse.ts b/packages/features/di/modules/RoutingFormResponse.ts index 8fadfe109b..5644dff01d 100644 --- a/packages/features/di/modules/RoutingFormResponse.ts +++ b/packages/features/di/modules/RoutingFormResponse.ts @@ -1,5 +1,5 @@ import { DI_TOKENS } from "@calcom/features/di/tokens"; -import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +import { RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; import { createModule } from "../di"; diff --git a/packages/features/ee/teams/lib/getTeamMemberEmailFromCrm.ts b/packages/features/ee/teams/lib/getTeamMemberEmailFromCrm.ts index cfcd9f06e3..5f9b57b2c6 100644 --- a/packages/features/ee/teams/lib/getTeamMemberEmailFromCrm.ts +++ b/packages/features/ee/teams/lib/getTeamMemberEmailFromCrm.ts @@ -13,7 +13,7 @@ import { zodRoutes as routesSchema } from "@calcom/app-store/routing-forms/zod"; /* eslint-enable */ import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +import { RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { SchedulingType } from "@calcom/prisma/enums"; diff --git a/packages/lib/server/repository/dto/hashedLinkSelect.ts b/packages/features/hashedLink/repositories/HashedLinkSelect.ts similarity index 100% rename from packages/lib/server/repository/dto/hashedLinkSelect.ts rename to packages/features/hashedLink/repositories/HashedLinkSelect.ts diff --git a/packages/lib/server/repository/HolidayRepository.test.ts b/packages/features/holidays/repositories/HolidayRepository.test.ts similarity index 100% rename from packages/lib/server/repository/HolidayRepository.test.ts rename to packages/features/holidays/repositories/HolidayRepository.test.ts diff --git a/packages/lib/server/repository/HolidayRepository.ts b/packages/features/holidays/repositories/HolidayRepository.ts similarity index 100% rename from packages/lib/server/repository/HolidayRepository.ts rename to packages/features/holidays/repositories/HolidayRepository.ts diff --git a/packages/lib/server/repository/dto/IProfileRepository.ts b/packages/features/profile/repositories/IProfileRepository.ts similarity index 100% rename from packages/lib/server/repository/dto/IProfileRepository.ts rename to packages/features/profile/repositories/IProfileRepository.ts diff --git a/packages/features/profile/repositories/ProfileRepository.ts b/packages/features/profile/repositories/ProfileRepository.ts index e842d1c493..0f43a10aeb 100644 --- a/packages/features/profile/repositories/ProfileRepository.ts +++ b/packages/features/profile/repositories/ProfileRepository.ts @@ -4,7 +4,7 @@ import { getParsedTeam } from "@calcom/features/ee/teams/lib/getParsedTeam"; import { DATABASE_CHUNK_SIZE } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import type { IProfileRepository } from "@calcom/lib/server/repository/dto/IProfileRepository"; +import type { IProfileRepository } from "./IProfileRepository"; import prisma from "@calcom/prisma"; import type { Prisma, PrismaClient, User as PrismaUser, Team } from "@calcom/prisma/client"; import { MembershipRole } from "@calcom/prisma/enums"; diff --git a/packages/features/routing-forms/di/RoutingFormResponseRepository.module.ts b/packages/features/routing-forms/di/RoutingFormResponseRepository.module.ts index fa1466a0b0..f1b03d1f52 100644 --- a/packages/features/routing-forms/di/RoutingFormResponseRepository.module.ts +++ b/packages/features/routing-forms/di/RoutingFormResponseRepository.module.ts @@ -1,4 +1,4 @@ -import { PrismaRoutingFormResponseRepository } from "@calcom/lib/server/repository/PrismaRoutingFormResponseRepository"; +import { PrismaRoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/PrismaRoutingFormResponseRepository"; import { ROUTING_FORM_DI_TOKENS } from "@calcom/features/routing-forms/di/tokens"; import { bindModuleToClassOnToken } from "@calcom/features/di/di"; import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma"; diff --git a/packages/features/routing-forms/lib/handleResponse.test.ts b/packages/features/routing-forms/lib/handleResponse.test.ts index f82bfa39b1..c9b63f2fea 100644 --- a/packages/features/routing-forms/lib/handleResponse.test.ts +++ b/packages/features/routing-forms/lib/handleResponse.test.ts @@ -7,7 +7,7 @@ import routerGetCrmContactOwnerEmail from "@calcom/app-store/routing-forms/lib/c import type { TargetRoutingFormForResponse } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils"; import { onSubmissionOfFormResponse } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils"; import isRouter from "@calcom/app-store/routing-forms/lib/isRouter"; -import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +import { RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; import { findTeamMembersMatchingAttributeLogic } from "./findTeamMembersMatchingAttributeLogic"; import { handleResponse } from "./handleResponse"; @@ -16,7 +16,7 @@ vi.mock("./findTeamMembersMatchingAttributeLogic", () => ({ findTeamMembersMatchingAttributeLogic: vi.fn(), })); -vi.mock("@calcom/lib/server/repository/formResponse"); +vi.mock("@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"); const mockRoutingFormResponseRepository = { recordQueuedFormResponse: vi.fn(), diff --git a/packages/features/routing-forms/lib/handleResponse.ts b/packages/features/routing-forms/lib/handleResponse.ts index dacbd1bee3..3f70c23e4d 100644 --- a/packages/features/routing-forms/lib/handleResponse.ts +++ b/packages/features/routing-forms/lib/handleResponse.ts @@ -11,7 +11,7 @@ import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { withReporting } from "@calcom/lib/sentryWrapper"; -import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +import { RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; import { prisma } from "@calcom/prisma"; import { Prisma } from "@calcom/prisma/client"; import { z } from "zod"; diff --git a/packages/lib/server/repository/PrismaRoutingFormResponseRepository.ts b/packages/features/routing-forms/repositories/PrismaRoutingFormResponseRepository.ts similarity index 100% rename from packages/lib/server/repository/PrismaRoutingFormResponseRepository.ts rename to packages/features/routing-forms/repositories/PrismaRoutingFormResponseRepository.ts diff --git a/packages/lib/server/repository/RoutingFormResponseRepository.interface.ts b/packages/features/routing-forms/repositories/RoutingFormResponseRepository.interface.ts similarity index 100% rename from packages/lib/server/repository/RoutingFormResponseRepository.interface.ts rename to packages/features/routing-forms/repositories/RoutingFormResponseRepository.interface.ts diff --git a/packages/lib/server/repository/formResponse.ts b/packages/features/routing-forms/repositories/RoutingFormResponseRepository.ts similarity index 100% rename from packages/lib/server/repository/formResponse.ts rename to packages/features/routing-forms/repositories/RoutingFormResponseRepository.ts diff --git a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.test.ts b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.test.ts index 67d6e5f790..8fe1c4b27d 100644 --- a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.test.ts +++ b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.test.ts @@ -1,7 +1,12 @@ +import prismock from "@calcom/testing/lib/__mocks__/prisma"; +import type { FeatureId } from "@calcom/features/flags/config"; +import { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; import type { PrismaClient } from "@calcom/prisma"; +import prisma from "@calcom/prisma"; import type { Prisma, SelectedCalendar } from "@calcom/prisma/client"; -import { beforeEach, describe, expect, test, vi } from "vitest"; +import { MembershipRole } from "@calcom/prisma/enums"; +import { beforeEach, describe, expect, test, vi, it } from "vitest"; const mockPrismaClient = { selectedCalendar: { @@ -367,4 +372,486 @@ describe("SelectedCalendarRepository", () => { expect(result).toEqual(updatedCalendar); }); }); + + describe("Static Methods", () => { + beforeEach(() => { + prismock.selectedCalendar.deleteMany(); + }); + + describe("getNextBatchToWatch", () => { + it("excludes calendars when calendar-cache feature is disabled on a team", async () => { + const user = await prisma.user.create({ + data: { + email: "calendar-cache-disabled@example.com", + username: "calendar-cache-disabled", + }, + }); + + const team = await prisma.team.create({ + data: { + name: "Calendar Cache Disabled Team", + slug: "calendar-cache-disabled-team", + }, + }); + + await prisma.membership.create({ + data: { + userId: user.id, + teamId: team.id, + role: MembershipRole.ADMIN, + accepted: true, + }, + }); + + const featuresRepository = new FeaturesRepository(prismock); + await featuresRepository.setTeamFeatureState({ + teamId: team.id, + featureId: "calendar-cache" as FeatureId, + state: "disabled", + assignedBy: "test", + }); + + await prisma.selectedCalendar.create({ + data: { + userId: user.id, + integration: "google_calendar", + externalId: "disabled@example.com", + credentialId: 1, + }, + }); + + const nextBatch = await SelectedCalendarRepository.getNextBatchToWatch(); + + expect(nextBatch).toEqual([]); + }); + }); + + describe("create", () => { + it("should create a selected calendar", async () => { + const data = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + }; + + const result = await SelectedCalendarRepository.create(data); + + expect(result).toEqual(expect.objectContaining(data)); + }); + + it("should throw error if we try to create a user-level calendar with same userId_integration_externalId as an existing user-level calendar", async () => { + const data = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId: null, + }; + + await SelectedCalendarRepository.create(data); + + await expect( + SelectedCalendarRepository.create({ + ...data, + credentialId: 2, + }) + ).rejects.toThrow("Selected calendar already exists"); + }); + + it("should allow creating a user-level calendar with same userId_integration_externalId as an existing event-type level calendar", async () => { + const data = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId: 1, + }; + + await SelectedCalendarRepository.create(data); + + const userLevelCalendarData = { + ...data, + eventTypeId: null, + }; + const created = await SelectedCalendarRepository.create(userLevelCalendarData); + + expect(created).toEqual(expect.objectContaining(userLevelCalendarData)); + }); + }); + + describe("update", () => { + it("should update a selected calendar and return it", async () => { + const calendarToUpdate = await SelectedCalendarRepository.create({ + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + }); + + const updatedCalendar = await SelectedCalendarRepository.update({ + where: { userId: 1, externalId: "test@gmail.com" }, + data: { integration: "office365_calendar" }, + }); + + expect(updatedCalendar.id).toBe(calendarToUpdate.id); + expect(updatedCalendar.integration).toBe("office365_calendar"); + }); + + it("should throw error when trying to update multiple calendars", async () => { + await SelectedCalendarRepository.create({ + userId: 1, + integration: "google_calendar", + externalId: "test1@gmail.com", + credentialId: 1, + }); + + await SelectedCalendarRepository.create({ + userId: 1, + integration: "google_calendar2", + externalId: "test1@gmail.com", + credentialId: 2, + }); + + await expect( + SelectedCalendarRepository.update({ + where: { userId: 1, externalId: "test1@gmail.com" }, + data: { integration: "office365_calendar" }, + }) + ).rejects.toThrow( + "Multiple SelectedCalendar records found to update. updateMany should be used instead" + ); + }); + }); + + describe("delete", () => { + it("should delete a selected calendar and return it", async () => { + const calendar = await SelectedCalendarRepository.create({ + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + }); + + const deleted = await SelectedCalendarRepository.delete({ + where: { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + }, + }); + + expect(deleted).toEqual(calendar); + const result = await SelectedCalendarRepository.findFirst({ + where: { id: calendar.id }, + }); + + expect(result).toBeNull(); + }); + + it("should throw error when trying to delete non-existent calendar", async () => { + await expect( + SelectedCalendarRepository.delete({ + where: { + userId: 999, + integration: "google_calendar", + externalId: "nonexistent@gmail.com", + credentialId: 999, + }, + }) + ).rejects.toThrow("SelectedCalendar not found"); + }); + }); + + describe("findUserLevelUniqueOrThrow", () => { + it("should find user level calendar", async () => { + const calendar = await SelectedCalendarRepository.create({ + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId: null, + }); + + const result = await SelectedCalendarRepository.findUserLevelUniqueOrThrow({ + where: { userId: 1, externalId: "test@gmail.com" }, + }); + + expect(result).toEqual( + expect.objectContaining({ + userId: calendar.userId, + externalId: calendar.externalId, + }) + ); + }); + + it("should not find event type level calendar", async () => { + await SelectedCalendarRepository.create({ + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId: 1, + }); + + await expect( + SelectedCalendarRepository.findUserLevelUniqueOrThrow({ + where: { userId: 1, externalId: "test@gmail.com" }, + }) + ).rejects.toThrow("SelectedCalendar not found"); + }); + }); + + describe("upsert", () => { + describe("User Level Calendar", () => { + const eventTypeId = null; + it("should update existing calendar as long as a record with same userId_integration_externalId is present for eventTypeId=null", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId, + }; + + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const updatedData = { + ...initialData, + credentialId: 2, + eventTypeId, + }; + + const result = await SelectedCalendarRepository.upsert(updatedData); + + expect(result.credentialId).toBe(2); + expect(existingCalendar.id).toBe(result.id); + }); + + it("should create new calendar if no record with same userId_integration_externalId is present when eventTypeId=null", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId, + }; + + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const updatedData = { + ...initialData, + externalId: "test2@gmail.com", + credentialId: 2, + eventTypeId, + }; + + const result = await SelectedCalendarRepository.upsert(updatedData); + expect(await prisma.selectedCalendar.count()).toBe(2); + expect(result).toEqual(expect.objectContaining(updatedData)); + expect(existingCalendar.id).not.toBe(result.id); + }); + }); + + describe("Event Type Level Calendar", () => { + const eventTypeId = 101; + it("should update existing calendar as long as record with same userId_integration_externalId_eventTypeId is present", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId, + }; + + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const updatedData = { + ...initialData, + credentialId: 2, + eventTypeId, + }; + + const result = await SelectedCalendarRepository.upsert(updatedData); + + expect(result.credentialId).toBe(2); + expect(existingCalendar.id).toBe(result.id); + }); + + it("should create new calendar if no record with same userId_integration_externalId_eventTypeId is present", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId, + }; + + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const updatedData = { + ...initialData, + credentialId: 2, + externalId: "test2@gmail.com", + eventTypeId, + }; + + const result = await SelectedCalendarRepository.upsert(updatedData); + expect(await prisma.selectedCalendar.count()).toBe(2); + expect(result).toEqual(expect.objectContaining(updatedData)); + expect(existingCalendar.id).not.toBe(result.id); + }); + }); + }); + + describe("Delegation Credential", () => { + it("should create a selected calendar with delegationCredentialId", async () => { + const data = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + delegationCredentialId: "delegationCredential-123", + }; + + const result = await SelectedCalendarRepository.create(data); + + expect(result).toEqual(expect.objectContaining(data)); + }); + + describe("upsert", () => { + describe("updation", () => { + it("should update existing record with delegationCredentialId if credentialId is -1", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId: null, + }; + + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const data = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: -1, + delegationCredentialId: "delegationCredential-123", + }; + + const result = await SelectedCalendarRepository.upsert(data); + expect(result.id).not.toBe(null); + expect(result.id).toBe(existingCalendar.id); + expect(result.credentialId).toBe(null); + expect(result.delegationCredentialId).toBe(data.delegationCredentialId); + }); + + it("should update existing record with credentialId if credentialId is valid(>0) even if delegationCredentialId is set", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId: null, + }; + + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const data = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 2, + delegationCredentialId: "delegationCredential-123", + }; + const beforeDelegationCredentialId = data.delegationCredentialId; + + const result = await SelectedCalendarRepository.upsert(data); + expect(result.id).not.toBe(null); + expect(result.id).toBe(existingCalendar.id); + expect(result.credentialId).toBe(data.credentialId); + expect(result.delegationCredentialId).toBe(beforeDelegationCredentialId); + }); + }); + + describe("creation", () => { + it("should create a new record with delegationCredentialId if credentialId is -1", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + eventTypeId: null, + }; + + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const data = { + userId: 1, + integration: "google_calendar", + externalId: "anotheremail@gmail.com", + credentialId: -1, + delegationCredentialId: "delegationCredential-123", + }; + + const result = await SelectedCalendarRepository.upsert(data); + expect(result.id).not.toBe(null); + expect(result.id).not.toBe(existingCalendar.id); + expect(result.credentialId).toBe(null); + expect(result.delegationCredentialId).toBe(data.delegationCredentialId); + }); + }); + + it("shouldnt update existing delegationCredentialId if upsert data doesn't have it", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + eventTypeId: null, + delegationCredentialId: "delegationCredential-123", + credentialId: 1, + }; + + const beforeDelegationCredentialId = initialData.delegationCredentialId; + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const data = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + credentialId: 1, + }; + + const result = await SelectedCalendarRepository.upsert(data); + expect(result.id).toBe(existingCalendar.id); + expect(result.credentialId).toBe(existingCalendar.credentialId); + expect(result.delegationCredentialId).toBe(beforeDelegationCredentialId); + }); + + it("shouldnt update delegationCredentialId if it is undefined", async () => { + const initialData = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + eventTypeId: null, + delegationCredentialId: "delegationCredential-123", + }; + + const existingCalendar = await SelectedCalendarRepository.create(initialData); + + const data = { + userId: 1, + integration: "google_calendar", + externalId: "test@gmail.com", + }; + + const result = await SelectedCalendarRepository.upsert(data); + expect(result.id).toBe(existingCalendar.id); + expect(result.credentialId).toBe(existingCalendar.credentialId); + expect(result.delegationCredentialId).toBe(existingCalendar.delegationCredentialId); + }); + }); + }); + }); }); diff --git a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts index c8a36a30c4..bae4aed7dc 100644 --- a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts +++ b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts @@ -1,12 +1,49 @@ import type { ISelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository.interface"; +import { buildCredentialPayloadForPrisma } from "@calcom/lib/server/buildCredentialPayloadForCalendar"; import type { PrismaClient } from "@calcom/prisma"; +import { prisma } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; +import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import type { SelectedCalendarEventTypeIds } from "@calcom/types/Calendar"; + +export type UpdateArguments = { + where: FindManyArgs["where"]; + data: Prisma.SelectedCalendarUpdateManyArgs["data"]; +}; +export type FindManyArgs = { + where?: { + userId?: + | number + | { + in: number[]; + }; + credentialId?: number | null; + integration?: string; + externalId?: string; + eventTypeId?: number | null; + googleChannelId?: + | string + | null + | { + not: null; + }; + }; + orderBy?: { + userId?: "asc" | "desc"; + }; + select?: Prisma.SelectedCalendarSelect; +}; + +const ensureUserLevelWhere = { + eventTypeId: null, +}; const MAX_SUBSCRIBE_ERRORS = 3; export class SelectedCalendarRepository implements ISelectedCalendarRepository { constructor(private prismaClient: PrismaClient) {} + // Instance method for DI (implements interface) async findById(id: string) { return this.prismaClient.selectedCalendar.findUnique({ where: { id }, @@ -104,4 +141,379 @@ export class SelectedCalendarRepository implements ISelectedCalendarRepository { data, }); } + + // Static methods for direct usage (from lib version) + private static async findConflicting(data: { + userId: number; + integration: string; + externalId: string; + eventTypeId?: number | null; + }) { + return await SelectedCalendarRepository.findFirst({ + where: { + userId: data.userId, + integration: data.integration, + externalId: data.externalId, + eventTypeId: data.eventTypeId || null, + }, + }); + } + + static async findById(id: string) { + return await prisma.selectedCalendar.findUnique({ where: { id } }); + } + + static async create(data: Prisma.SelectedCalendarUncheckedCreateInput) { + const conflictingCalendar = await SelectedCalendarRepository.findConflicting(data); + + if (conflictingCalendar) { + throw new Error( + `Selected calendar already exists for userId: ${data.userId}, integration: ${data.integration}, externalId: ${data.externalId}, eventTypeId: ${data.eventTypeId}` + ); + } + + return await prisma.selectedCalendar.create({ + data: { + ...data, + }, + }); + } + + static async upsert(data: Prisma.SelectedCalendarUncheckedCreateInput) { + const credentialPayload = buildCredentialPayloadForPrisma({ + credentialId: data.credentialId, + delegationCredentialId: data.delegationCredentialId, + }); + + const newData = { + ...data, + ...credentialPayload, + }; + + const conflictingCalendar = await SelectedCalendarRepository.findConflicting(newData); + if (conflictingCalendar) { + return await prisma.selectedCalendar.update({ + where: { + id: conflictingCalendar.id, + }, + data: newData, + }); + } + + return await prisma.selectedCalendar.create({ + data: newData, + }); + } + + static async delete({ where }: { where: Prisma.SelectedCalendarUncheckedCreateInput }) { + const calendarsToDelete = await SelectedCalendarRepository.findMany({ where }); + + if (calendarsToDelete.length === 0) { + throw new Error("SelectedCalendar not found"); + } + + if (calendarsToDelete.length > 1) { + throw new Error("Multiple SelectedCalendar records found to delete. deleteMany should be used instead"); + } + + return await prisma.selectedCalendar.delete({ + where: { + id: calendarsToDelete[0].id, + }, + }); + } + + static async deleteById({ id }: { id: string }) { + return await prisma.selectedCalendar.delete({ + where: { + id, + }, + }); + } + + static async createIfNotExists(data: Prisma.SelectedCalendarUncheckedCreateInput) { + const conflictingCalendar = await SelectedCalendarRepository.findConflicting(data); + + if (conflictingCalendar) return conflictingCalendar; + return await prisma.selectedCalendar.create({ + data, + }); + } + + static async getNextBatchToWatch(limit = 100) { + const oneDayInMS = 24 * 60 * 60 * 1000; + const tomorrowTimestamp = String(new Date().getTime() + oneDayInMS); + const nextBatch = await prisma.selectedCalendar.findMany({ + take: limit, + where: { + user: { + teams: { + some: { + team: { + features: { + some: { + featureId: "calendar-cache", + enabled: true, + }, + }, + }, + }, + }, + }, + integration: "google_calendar", + AND: [ + { + OR: [ + { error: null }, + { + error: { not: null }, + watchAttempts: { + lt: { + // @ts-expect-error - _ref is a Prisma extension field + _ref: "maxAttempts", + _container: "SelectedCalendar", + }, + }, + }, + ], + }, + { + OR: [ + { googleChannelExpiration: null }, + { googleChannelExpiration: { lt: tomorrowTimestamp } }, + ], + }, + ], + }, + }); + return nextBatch; + } + + static async getNextBatchToUnwatch(limit = 100) { + const where: Prisma.SelectedCalendarWhereInput = { + integration: "google_calendar", + googleChannelExpiration: { not: null }, + AND: [ + { + OR: [ + { error: null }, + { + error: { not: null }, + unwatchAttempts: { + lt: { + // @ts-expect-error - _ref is a Prisma extension field + _ref: "maxAttempts", + _container: "SelectedCalendar", + }, + }, + }, + ], + }, + { + user: { + teams: { + every: { + team: { + features: { + none: { + featureId: "calendar-cache", + enabled: true, + }, + }, + }, + }, + }, + }, + }, + ], + }; + const nextBatch = await prisma.selectedCalendar.findMany({ + take: limit, + where, + }); + return nextBatch; + } + + static async findMany({ where, select, orderBy }: FindManyArgs) { + const args = { + where, + select, + orderBy, + } satisfies Prisma.SelectedCalendarFindManyArgs; + return await prisma.selectedCalendar.findMany(args); + } + + static async findUniqueOrThrow({ where }: { where: Prisma.SelectedCalendarWhereInput }) { + const calendars = await prisma.selectedCalendar.findMany({ where }); + if (calendars.length === 0) { + throw new Error("SelectedCalendar not found"); + } + return calendars[0]; + } + + static async findFirstByGoogleChannelId(googleChannelId: string) { + return await prisma.selectedCalendar.findFirst({ + where: { + googleChannelId, + }, + select: { + credential: { + select: { + ...credentialForCalendarServiceSelect, + selectedCalendars: { + orderBy: { + externalId: "asc", + }, + }, + }, + }, + }, + }); + } + + static async findFirst({ where }: { where: Prisma.SelectedCalendarWhereInput }) { + return await prisma.selectedCalendar.findFirst({ + where, + }); + } + + static async findFirstOrThrow({ where }: { where: Prisma.SelectedCalendarWhereInput }) { + const calendar = await SelectedCalendarRepository.findFirst({ where }); + if (!calendar) { + throw new Error("SelectedCalendar not found"); + } + return calendar; + } + + static async update(args: UpdateArguments) { + const calendarsToUpdate = await SelectedCalendarRepository.findMany({ where: args.where }); + + if (calendarsToUpdate.length === 0) { + throw new Error("SelectedCalendar not found"); + } + + if (calendarsToUpdate.length > 1) { + throw new Error("Multiple SelectedCalendar records found to update. updateMany should be used instead"); + } + + const calendarToUpdate = calendarsToUpdate[0]; + + return await prisma.selectedCalendar.update({ + where: { + id: calendarToUpdate.id, + }, + data: args.data, + }); + } + + static async findUserLevelUniqueOrThrow({ where }: { where: Prisma.SelectedCalendarWhereInput }) { + const calendars = await SelectedCalendarRepository.findUniqueOrThrow({ + where: { + ...where, + ...ensureUserLevelWhere, + }, + }); + + if (!calendars) { + throw new Error("SelectedCalendar not found"); + } + return calendars; + } + + static async findManyUserLevel(args: FindManyArgs) { + return SelectedCalendarRepository.findMany({ + ...args, + where: { + ...args.where, + ...ensureUserLevelWhere, + }, + }); + } + + static async updateUserLevel(args: UpdateArguments) { + return SelectedCalendarRepository.update({ + where: { + ...args.where, + ...ensureUserLevelWhere, + }, + data: args.data, + }); + } + + static async deleteUserLevel({ where }: { where: Prisma.SelectedCalendarUncheckedCreateInput }) { + return await SelectedCalendarRepository.delete({ + where: { + ...where, + ...ensureUserLevelWhere, + }, + }); + } + + static async upsertManyForEventTypeIds({ + data, + eventTypeIds, + }: { + data: Prisma.SelectedCalendarUncheckedCreateInput; + eventTypeIds: SelectedCalendarEventTypeIds; + }) { + const userId = data.userId; + return await Promise.all( + eventTypeIds.map((eventTypeId) => + SelectedCalendarRepository.upsert({ + ...data, + eventTypeId, + userId, + integration: data.integration, + externalId: data.externalId, + credentialId: data.credentialId, + }) + ) + ); + } + + static async updateById(id: string, data: Prisma.SelectedCalendarUpdateInput) { + return await prisma.selectedCalendar.update({ + where: { id }, + data, + }); + } + + static async updateManyByCredentialId(credentialId: number, data: Prisma.SelectedCalendarUpdateInput) { + return await prisma.selectedCalendar.updateMany({ + where: { credentialId }, + data, + }); + } + + static async setErrorInWatching({ id, error }: { id: string; error: string }) { + await SelectedCalendarRepository.updateById(id, { + error, + lastErrorAt: new Date(), + watchAttempts: { increment: 1 }, + }); + } + + static async setErrorInUnwatching({ id, error }: { id: string; error: string }) { + await SelectedCalendarRepository.updateById(id, { + error, + lastErrorAt: new Date(), + unwatchAttempts: { increment: 1 }, + }); + } + + static async removeWatchingError({ id }: { id: string }) { + await SelectedCalendarRepository.updateById(id, { + error: null, + lastErrorAt: null, + watchAttempts: 0, + }); + } + + static async removeUnwatchingError({ id }: { id: string }) { + await SelectedCalendarRepository.updateById(id, { + error: null, + lastErrorAt: null, + unwatchAttempts: 0, + }); + } } diff --git a/packages/features/tasker/tasks/sendAwaitingPaymentEmail.ts b/packages/features/tasker/tasks/sendAwaitingPaymentEmail.ts index 6485fb7291..91cd1b3c98 100644 --- a/packages/features/tasker/tasks/sendAwaitingPaymentEmail.ts +++ b/packages/features/tasker/tasks/sendAwaitingPaymentEmail.ts @@ -7,7 +7,7 @@ import { AttendeeRepository } from "@calcom/features/bookings/repositories/Atten import stripe from "@calcom/features/ee/payments/server/stripe"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import { PrismaBookingPaymentRepository } from "@calcom/lib/server/repository/PrismaBookingPaymentRepository"; +import { PrismaBookingPaymentRepository } from "@calcom/features/bookings/repositories/PrismaBookingPaymentRepository"; import prisma from "@calcom/prisma"; const log = logger.getSubLogger({ prefix: ["sendAwaitingPaymentEmail"] }); diff --git a/packages/features/tasker/tasks/triggerFormSubmittedNoEvent/formSubmissionValidation.ts b/packages/features/tasker/tasks/triggerFormSubmittedNoEvent/formSubmissionValidation.ts index 9386d19c82..2fe71c34a9 100644 --- a/packages/features/tasker/tasks/triggerFormSubmittedNoEvent/formSubmissionValidation.ts +++ b/packages/features/tasker/tasks/triggerFormSubmittedNoEvent/formSubmissionValidation.ts @@ -1,5 +1,5 @@ import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; -import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +import { RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; import prisma from "@calcom/prisma"; import type { FORM_SUBMITTED_WEBHOOK_RESPONSES } from "@calcom/routing-forms/lib/formSubmissionUtils"; diff --git a/packages/lib/server/repository/workspacePlatform.ts b/packages/features/workspace-platform/repositories/WorkspacePlatformRepository.ts similarity index 100% rename from packages/lib/server/repository/workspacePlatform.ts rename to packages/features/workspace-platform/repositories/WorkspacePlatformRepository.ts diff --git a/packages/lib/holidays/HolidayService.ts b/packages/lib/holidays/HolidayService.ts index 455eeffc68..2b3c4914a2 100644 --- a/packages/lib/holidays/HolidayService.ts +++ b/packages/lib/holidays/HolidayService.ts @@ -1,5 +1,5 @@ import dayjs from "@calcom/dayjs"; -import { HolidayRepository } from "@calcom/lib/server/repository/HolidayRepository"; +import { HolidayRepository } from "@calcom/features/holidays/repositories/HolidayRepository"; import { getHolidayServiceCachingProxy, diff --git a/packages/lib/holidays/HolidayServiceCachingProxy.ts b/packages/lib/holidays/HolidayServiceCachingProxy.ts index 8cd1e883ec..87370b7ba4 100644 --- a/packages/lib/holidays/HolidayServiceCachingProxy.ts +++ b/packages/lib/holidays/HolidayServiceCachingProxy.ts @@ -1,5 +1,5 @@ import dayjs from "@calcom/dayjs"; -import { HolidayRepository } from "@calcom/lib/server/repository/HolidayRepository"; +import { HolidayRepository } from "@calcom/features/holidays/repositories/HolidayRepository"; import { GOOGLE_HOLIDAY_CALENDARS, HOLIDAY_CACHE_DAYS } from "./constants"; import { getGoogleCalendarClient, type GoogleCalendarClient, type GoogleCalendarHoliday } from "./GoogleCalendarClient"; diff --git a/packages/lib/server/repository/destinationCalendar.ts b/packages/lib/server/repository/destinationCalendar.ts deleted file mode 100644 index 6f64e2a89e..0000000000 --- a/packages/lib/server/repository/destinationCalendar.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { PrismaClient } from "@calcom/prisma"; - -export class DestinationCalendarRepository { - constructor(private prisma: PrismaClient) {} - - async getCustomReminderByCredentialId(credentialId: number): Promise { - const destinationCalendar = await this.prisma.destinationCalendar.findFirst({ - where: { credentialId }, - select: { customCalendarReminder: true }, - }); - return destinationCalendar?.customCalendarReminder ?? null; - } - - async updateCustomReminder({ - userId, - credentialId, - integration, - customCalendarReminder, - }: { - userId: number; - credentialId: number; - integration: string; - customCalendarReminder: number | null; - }) { - return await this.prisma.destinationCalendar.updateMany({ - where: { - userId, - credentialId, - integration, - }, - data: { - customCalendarReminder, - }, - }); - } -} diff --git a/packages/lib/server/repository/dto/selectedSlotDeleteManyExpiredSlotsInput.ts b/packages/lib/server/repository/dto/selectedSlotDeleteManyExpiredSlotsInput.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/lib/server/repository/dto/selectedSlotFindManyReservedByOthersInput.ts b/packages/lib/server/repository/dto/selectedSlotFindManyReservedByOthersInput.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/lib/server/repository/dto/selectedSlotFindManyUnexpiredSlotsInput.ts b/packages/lib/server/repository/dto/selectedSlotFindManyUnexpiredSlotsInput.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/lib/server/repository/dto/selectedSlotFindReservedByOthersInput.ts b/packages/lib/server/repository/dto/selectedSlotFindReservedByOthersInput.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/lib/server/repository/selectedCalendar.test.ts b/packages/lib/server/repository/selectedCalendar.test.ts deleted file mode 100644 index 8690d579dc..0000000000 --- a/packages/lib/server/repository/selectedCalendar.test.ts +++ /dev/null @@ -1,493 +0,0 @@ -import prismock from "@calcom/testing/lib/__mocks__/prisma"; - -import { describe, expect, it, beforeEach } from "vitest"; - -import type { FeatureId } from "@calcom/features/flags/config"; -import { FeaturesRepository } from "@calcom/features/flags/features.repository"; -import prisma from "@calcom/prisma"; -import { MembershipRole } from "@calcom/prisma/enums"; - -import { SelectedCalendarRepository } from "./selectedCalendar"; - -describe("SelectedCalendarRepository", () => { - beforeEach(() => { - prismock.selectedCalendar.deleteMany(); - }); - - describe("getNextBatchToWatch", () => { - it("excludes calendars when calendar-cache feature is disabled on a team", async () => { - const user = await prisma.user.create({ - data: { - email: "calendar-cache-disabled@example.com", - username: "calendar-cache-disabled", - }, - }); - - const team = await prisma.team.create({ - data: { - name: "Calendar Cache Disabled Team", - slug: "calendar-cache-disabled-team", - }, - }); - - await prisma.membership.create({ - data: { - userId: user.id, - teamId: team.id, - role: MembershipRole.ADMIN, - accepted: true, - }, - }); - - const featuresRepository = new FeaturesRepository(prismock); - await featuresRepository.setTeamFeatureState({ - teamId: team.id, - featureId: "calendar-cache" as FeatureId, - state: "disabled", - assignedBy: "test", - }); - - await prisma.selectedCalendar.create({ - data: { - userId: user.id, - integration: "google_calendar", - externalId: "disabled@example.com", - credentialId: 1, - }, - }); - - const nextBatch = await SelectedCalendarRepository.getNextBatchToWatch(); - - expect(nextBatch).toEqual([]); - }); - }); - - describe("create", () => { - it("should create a selected calendar", async () => { - const data = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - }; - - const result = await SelectedCalendarRepository.create(data); - - expect(result).toEqual(expect.objectContaining(data)); - }); - - it("should throw error if we try to create a user-level calendar with same userId_integration_externalId as an existing user-level calendar", async () => { - const data = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId: null, - }; - - await SelectedCalendarRepository.create(data); - - await expect( - SelectedCalendarRepository.create({ - ...data, - credentialId: 2, - }) - ).rejects.toThrow("Selected calendar already exists"); - }); - - it("should allow creating a user-level calendar with same userId_integration_externalId as an existing event-type level calendar", async () => { - const data = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId: 1, - }; - - await SelectedCalendarRepository.create(data); - - const userLevelCalendarData = { - ...data, - eventTypeId: null, - }; - const created = await SelectedCalendarRepository.create(userLevelCalendarData); - - expect(created).toEqual(expect.objectContaining(userLevelCalendarData)); - }); - }); - - describe("update", () => { - it("should update a selected calendar and return it", async () => { - const calendarToUpdate = await SelectedCalendarRepository.create({ - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - }); - - const updatedCalendar = await SelectedCalendarRepository.update({ - where: { userId: 1, externalId: "test@gmail.com" }, - data: { integration: "office365_calendar" }, - }); - - expect(updatedCalendar.id).toBe(calendarToUpdate.id); - expect(updatedCalendar.integration).toBe("office365_calendar"); - }); - - it("should throw error when trying to update multiple calendars", async () => { - await SelectedCalendarRepository.create({ - userId: 1, - integration: "google_calendar", - externalId: "test1@gmail.com", - credentialId: 1, - }); - - await SelectedCalendarRepository.create({ - userId: 1, - integration: "google_calendar2", - externalId: "test1@gmail.com", - credentialId: 2, - }); - - await expect( - SelectedCalendarRepository.update({ - where: { userId: 1, externalId: "test1@gmail.com" }, - data: { integration: "office365_calendar" }, - }) - ).rejects.toThrow( - "Multiple SelectedCalendar records found to update. updateMany should be used instead" - ); - }); - }); - - describe("delete", () => { - it("should delete a selected calendar and return it", async () => { - const calendar = await SelectedCalendarRepository.create({ - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - }); - - const deleted = await SelectedCalendarRepository.delete({ - where: { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - }, - }); - - expect(deleted).toEqual(calendar); - const result = await SelectedCalendarRepository.findFirst({ - where: { id: calendar.id }, - }); - - expect(result).toBeNull(); - }); - - it("should throw error when trying to delete non-existent calendar", async () => { - await expect( - SelectedCalendarRepository.delete({ - where: { - userId: 999, - integration: "google_calendar", - externalId: "nonexistent@gmail.com", - credentialId: 999, - }, - }) - ).rejects.toThrow("SelectedCalendar not found"); - }); - }); - - describe("findUserLevelUniqueOrThrow", () => { - it("should find user level calendar", async () => { - const calendar = await SelectedCalendarRepository.create({ - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId: null, // User level calendar - }); - - const result = await SelectedCalendarRepository.findUserLevelUniqueOrThrow({ - where: { userId: 1, externalId: "test@gmail.com" }, - }); - - expect(result).toEqual( - expect.objectContaining({ - userId: calendar.userId, - externalId: calendar.externalId, - }) - ); - }); - - it("should not find event type level calendar", async () => { - await SelectedCalendarRepository.create({ - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId: 1, // Event type level calendar - }); - - await expect( - SelectedCalendarRepository.findUserLevelUniqueOrThrow({ - where: { userId: 1, externalId: "test@gmail.com" }, - }) - ).rejects.toThrow("SelectedCalendar not found"); - }); - }); - - describe("upsert", () => { - describe("User Level Calendar", () => { - const eventTypeId = null; - it("should update existing calendar as long as a record with same userId_integration_externalId is present for eventTypeId=null", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId, - }; - - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const updatedData = { - ...initialData, - credentialId: 2, - eventTypeId, - }; - - const result = await SelectedCalendarRepository.upsert(updatedData); - - expect(result.credentialId).toBe(2); - expect(existingCalendar.id).toBe(result.id); - }); - - it("should create new calendar if no record with same userId_integration_externalId is present when eventTypeId=null", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId, - }; - - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const updatedData = { - ...initialData, - externalId: "test2@gmail.com", - credentialId: 2, - eventTypeId, - }; - - const result = await SelectedCalendarRepository.upsert(updatedData); - expect(await prisma.selectedCalendar.count()).toBe(2); - expect(result).toEqual(expect.objectContaining(updatedData)); - expect(existingCalendar.id).not.toBe(result.id); - }); - }); - - describe("Event Type Level Calendar", () => { - const eventTypeId = 101; - it("should update existing calendar as long as record with same userId_integration_externalId_eventTypeId is present", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId, - }; - - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const updatedData = { - ...initialData, - credentialId: 2, - eventTypeId, - }; - - const result = await SelectedCalendarRepository.upsert(updatedData); - - expect(result.credentialId).toBe(2); - expect(existingCalendar.id).toBe(result.id); - }); - - it("should create new calendar if no record with same userId_integration_externalId_eventTypeId is present", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId, - }; - - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const updatedData = { - ...initialData, - credentialId: 2, - externalId: "test2@gmail.com", - eventTypeId, - }; - - const result = await SelectedCalendarRepository.upsert(updatedData); - expect(await prisma.selectedCalendar.count()).toBe(2); - expect(result).toEqual(expect.objectContaining(updatedData)); - expect(existingCalendar.id).not.toBe(result.id); - }); - }); - }); - - describe("Delegation Credential", () => { - it("should create a selected calendar with delegationCredentialId", async () => { - const data = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - delegationCredentialId: "delegationCredential-123", - }; - - const result = await SelectedCalendarRepository.create(data); - - expect(result).toEqual(expect.objectContaining(data)); - }); - - describe("upsert", () => { - describe("updation", () => { - it("should update existing record with delegationCredentialId if credentialId is -1", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId: null, - }; - - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const data = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: -1, - delegationCredentialId: "delegationCredential-123", - }; - - const result = await SelectedCalendarRepository.upsert(data); - expect(result.id).not.toBe(null); - expect(result.id).toBe(existingCalendar.id); - expect(result.credentialId).toBe(null); - expect(result.delegationCredentialId).toBe(data.delegationCredentialId); - }); - - it("should update existing record with credentialId if credentialId is valid(>0) even if delegationCredentialId is set", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId: null, - }; - - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const data = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 2, - delegationCredentialId: "delegationCredential-123", - }; - const beforeDelegationCredentialId = data.delegationCredentialId; - - const result = await SelectedCalendarRepository.upsert(data); - expect(result.id).not.toBe(null); - expect(result.id).toBe(existingCalendar.id); - expect(result.credentialId).toBe(data.credentialId); - expect(result.delegationCredentialId).toBe(beforeDelegationCredentialId); - }); - }); - - describe("creation", () => { - it("should create a new record with delegationCredentialId if credentialId is -1", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - eventTypeId: null, - }; - - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const data = { - userId: 1, - integration: "google_calendar", - externalId: "anotheremail@gmail.com", - credentialId: -1, - delegationCredentialId: "delegationCredential-123", - }; - - // It will create a new record because of unique constraint violation - const result = await SelectedCalendarRepository.upsert(data); - expect(result.id).not.toBe(null); - expect(result.id).not.toBe(existingCalendar.id); - expect(result.credentialId).toBe(null); - expect(result.delegationCredentialId).toBe(data.delegationCredentialId); - }); - }); - - it("shouldnt update existing delegationCredentialId if upsert data doesn't have it", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - eventTypeId: null, - delegationCredentialId: "delegationCredential-123", - credentialId: 1, - }; - - const beforeDelegationCredentialId = initialData.delegationCredentialId; - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const data = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - credentialId: 1, - }; - - const result = await SelectedCalendarRepository.upsert(data); - expect(result.id).toBe(existingCalendar.id); - expect(result.credentialId).toBe(existingCalendar.credentialId); - expect(result.delegationCredentialId).toBe(beforeDelegationCredentialId); - }); - - it("shouldnt update delegationCredentialId if it is undefined", async () => { - const initialData = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - eventTypeId: null, - delegationCredentialId: "delegationCredential-123", - }; - - const existingCalendar = await SelectedCalendarRepository.create(initialData); - - const data = { - userId: 1, - integration: "google_calendar", - externalId: "test@gmail.com", - }; - - const result = await SelectedCalendarRepository.upsert(data); - expect(result.id).toBe(existingCalendar.id); - expect(result.credentialId).toBe(existingCalendar.credentialId); - expect(result.delegationCredentialId).toBe(existingCalendar.delegationCredentialId); - }); - }); - }); -}); diff --git a/packages/lib/server/repository/selectedCalendar.ts b/packages/lib/server/repository/selectedCalendar.ts deleted file mode 100644 index 1f438167c3..0000000000 --- a/packages/lib/server/repository/selectedCalendar.ts +++ /dev/null @@ -1,438 +0,0 @@ -import { prisma } from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; -import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; -import type { SelectedCalendarEventTypeIds } from "@calcom/types/Calendar"; - -import { buildCredentialPayloadForPrisma } from "../buildCredentialPayloadForCalendar"; - -export type UpdateArguments = { - where: FindManyArgs["where"]; - data: Prisma.SelectedCalendarUpdateManyArgs["data"]; -}; -export type FindManyArgs = { - // https://github.com/microsoft/TypeScript/issues/55217 It crashes atoms build with this if we become too generic here. Seems like a TS bug with complex prisma types. - where?: { - userId?: - | number - | { - in: number[]; - }; - credentialId?: number | null; - integration?: string; - externalId?: string; - eventTypeId?: number | null; - googleChannelId?: - | string - | null - | { - not: null; - }; - }; - orderBy?: { - userId?: "asc" | "desc"; - }; - select?: Prisma.SelectedCalendarSelect; -}; - -const ensureUserLevelWhere = { - eventTypeId: null, -}; - -export class SelectedCalendarRepository { - private static async findConflicting(data: { - userId: number; - integration: string; - externalId: string; - eventTypeId?: number | null; - }) { - // Because userId_integration_externalId_eventTypeId is a unique constraint but with eventTypeId being nullable, - // it allows for multiple entries with the same (userId, integration, externalId) with eventTypeId=null in DB. - // We restrict it at app level by ensuring that an entry doesn't exist already before creating a new one - return await SelectedCalendarRepository.findFirst({ - where: { - userId: data.userId, - integration: data.integration, - externalId: data.externalId, - eventTypeId: data.eventTypeId || null, - }, - }); - } - - static async findById(id: string) { - return await prisma.selectedCalendar.findUnique({ where: { id } }); - } - - static async create(data: Prisma.SelectedCalendarUncheckedCreateInput) { - const conflictingCalendar = await SelectedCalendarRepository.findConflicting(data); - - if (conflictingCalendar) { - throw new Error( - `Selected calendar already exists for userId: ${data.userId}, integration: ${data.integration}, externalId: ${data.externalId}, eventTypeId: ${data.eventTypeId}` - ); - } - - return await prisma.selectedCalendar.create({ - data: { - ...data, - }, - }); - } - - static async upsert(data: Prisma.SelectedCalendarUncheckedCreateInput) { - // userId_integration_externalId_eventTypeId is a unique constraint but with eventTypeId being nullable - // So, this unique constraint can't be used in upsert. Prisma doesn't allow that, So, we do create and update separately - const credentialPayload = buildCredentialPayloadForPrisma({ - credentialId: data.credentialId, - delegationCredentialId: data.delegationCredentialId, - }); - - const newData = { - ...data, - ...credentialPayload, - }; - - const conflictingCalendar = await SelectedCalendarRepository.findConflicting(newData); - if (conflictingCalendar) { - return await prisma.selectedCalendar.update({ - where: { - id: conflictingCalendar.id, - }, - data: newData, - }); - } - - return await prisma.selectedCalendar.create({ - data: newData, - }); - } - - static async delete({ where }: { where: Prisma.SelectedCalendarUncheckedCreateInput }) { - const calendarsToDelete = await SelectedCalendarRepository.findMany({ where }); - - if (calendarsToDelete.length === 0) { - // Same behaviour as Prisma.delete as it throws error if no record is found - throw new Error("SelectedCalendar not found"); - } - - if (calendarsToDelete.length > 1) { - throw new Error("Multiple SelectedCalendar records found to delete. deleteMany should be used instead"); - } - - // We can't use delete because unique constraint on userId_integration_externalId_eventTypeId has eventTypeId as nullable - return await prisma.selectedCalendar.delete({ - where: { - id: calendarsToDelete[0].id, - }, - }); - } - - static async deleteById({ id }: { id: string }) { - return await prisma.selectedCalendar.delete({ - where: { - id, - }, - }); - } - - static async createIfNotExists(data: Prisma.SelectedCalendarUncheckedCreateInput) { - const conflictingCalendar = await SelectedCalendarRepository.findConflicting(data); - - if (conflictingCalendar) return conflictingCalendar; - return await prisma.selectedCalendar.create({ - data, - }); - } - - /** Retrieve calendars that need to be watched */ - static async getNextBatchToWatch(limit = 100) { - // Get selected calendars from users that belong to a team that has calendar cache enabled - const oneDayInMS = 24 * 60 * 60 * 1000; - const tomorrowTimestamp = String(new Date().getTime() + oneDayInMS); - const nextBatch = await prisma.selectedCalendar.findMany({ - take: limit, - where: { - user: { - teams: { - some: { - team: { - features: { - some: { - featureId: "calendar-cache", - enabled: true, - }, - }, - }, - }, - }, - }, - // RN we only support google calendar subscriptions for now - integration: "google_calendar", - AND: [ - { - OR: [ - // Either is a calendar that has not errored - { error: null }, - // Or is a calendar that has errored but has not reached max attempts - { - error: { not: null }, - watchAttempts: { - lt: { - // @ts-expect-error - _ref is a Prisma extension field - _ref: "maxAttempts", - _container: "SelectedCalendar", - }, - }, - }, - ], - }, - { - OR: [ - // Either is a calendar pending to be watched - { googleChannelExpiration: null }, - // Or is a calendar that is about to expire - { googleChannelExpiration: { lt: tomorrowTimestamp } }, - ], - }, - ], - }, - }); - return nextBatch; - } - /** - * Retrieve calendars that are being watched but shouldn't be anymore - * e.g. when a user disables calendar cache for the organization - */ - static async getNextBatchToUnwatch(limit = 100) { - const where: Prisma.SelectedCalendarWhereInput = { - // RN we only support google calendar subscriptions for now - integration: "google_calendar", - googleChannelExpiration: { not: null }, - AND: [ - { - OR: [ - // Either is a calendar that has not errored during unwatch - { error: null }, - // Or is a calendar that has errored during unwatch but has not reached max attempts - { - error: { not: null }, - unwatchAttempts: { - lt: { - // @ts-expect-error - _ref is a Prisma extension field - _ref: "maxAttempts", - _container: "SelectedCalendar", - }, - }, - }, - ], - }, - { - user: { - teams: { - every: { - team: { - features: { - none: { - featureId: "calendar-cache", - enabled: true, - }, - }, - }, - }, - }, - }, - }, - ], - }; - // If calendar cache is disabled globally, we skip team features and unwatch all subscriptions - const nextBatch = await prisma.selectedCalendar.findMany({ - take: limit, - where, - }); - return nextBatch; - } - - static async findMany({ where, select, orderBy }: FindManyArgs) { - const args = { - where, - select, - orderBy, - } satisfies Prisma.SelectedCalendarFindManyArgs; - return await prisma.selectedCalendar.findMany(args); - } - - static async findUniqueOrThrow({ where }: { where: Prisma.SelectedCalendarWhereInput }) { - const calendars = await prisma.selectedCalendar.findMany({ where }); - if (calendars.length === 0) { - throw new Error("SelectedCalendar not found"); - } - return calendars[0]; - } - - static async findFirstByGoogleChannelId(googleChannelId: string) { - return await prisma.selectedCalendar.findFirst({ - where: { - googleChannelId, - }, - select: { - credential: { - select: { - ...credentialForCalendarServiceSelect, - selectedCalendars: { - orderBy: { - externalId: "asc", - }, - }, - }, - }, - }, - }); - } - - static async findFirst({ where }: { where: Prisma.SelectedCalendarWhereInput }) { - return await prisma.selectedCalendar.findFirst({ - where, - }); - } - - static async findFirstOrThrow({ where }: { where: Prisma.SelectedCalendarWhereInput }) { - const calendar = await SelectedCalendarRepository.findFirst({ where }); - if (!calendar) { - throw new Error("SelectedCalendar not found"); - } - return calendar; - } - - static async update(args: UpdateArguments) { - const calendarsToUpdate = await SelectedCalendarRepository.findMany({ where: args.where }); - - if (calendarsToUpdate.length === 0) { - throw new Error("SelectedCalendar not found"); - } - - if (calendarsToUpdate.length > 1) { - throw new Error("Multiple SelectedCalendar records found to update. updateMany should be used instead"); - } - - const calendarToUpdate = calendarsToUpdate[0]; - - return await prisma.selectedCalendar.update({ - where: { - id: calendarToUpdate.id, - }, - data: args.data, - }); - } - - /** - * Following methods ensure that they operate on user level calendars only - */ - static async findUserLevelUniqueOrThrow({ where }: { where: Prisma.SelectedCalendarWhereInput }) { - const calendars = await SelectedCalendarRepository.findUniqueOrThrow({ - where: { - ...where, - ...ensureUserLevelWhere, - }, - }); - - if (!calendars) { - throw new Error("SelectedCalendar not found"); - } - return calendars; - } - - static async findManyUserLevel(args: FindManyArgs) { - return SelectedCalendarRepository.findMany({ - ...args, - where: { - ...args.where, - ...ensureUserLevelWhere, - }, - }); - } - - static async updateUserLevel(args: UpdateArguments) { - return SelectedCalendarRepository.update({ - where: { - ...args.where, - ...ensureUserLevelWhere, - }, - data: args.data, - }); - } - - static async deleteUserLevel({ where }: { where: Prisma.SelectedCalendarUncheckedCreateInput }) { - return await SelectedCalendarRepository.delete({ - where: { - ...where, - ...ensureUserLevelWhere, - }, - }); - } - - static async upsertManyForEventTypeIds({ - data, - eventTypeIds, - }: { - data: Prisma.SelectedCalendarUncheckedCreateInput; - eventTypeIds: SelectedCalendarEventTypeIds; - }) { - const userId = data.userId; - return await Promise.all( - eventTypeIds.map((eventTypeId) => - SelectedCalendarRepository.upsert({ - ...data, - eventTypeId, - userId, - integration: data.integration, - externalId: data.externalId, - credentialId: data.credentialId, - }) - ) - ); - } - static async updateById(id: string, data: Prisma.SelectedCalendarUpdateInput) { - return await prisma.selectedCalendar.update({ - where: { id }, - data, - }); - } - - static async updateManyByCredentialId(credentialId: number, data: Prisma.SelectedCalendarUpdateInput) { - return await prisma.selectedCalendar.updateMany({ - where: { credentialId }, - data, - }); - } - - static async setErrorInWatching({ id, error }: { id: string; error: string }) { - await SelectedCalendarRepository.updateById(id, { - error, - lastErrorAt: new Date(), - watchAttempts: { increment: 1 }, - }); - } - - static async setErrorInUnwatching({ id, error }: { id: string; error: string }) { - await SelectedCalendarRepository.updateById(id, { - error, - lastErrorAt: new Date(), - unwatchAttempts: { increment: 1 }, - }); - } - - static async removeWatchingError({ id }: { id: string }) { - await SelectedCalendarRepository.updateById(id, { - error: null, - lastErrorAt: null, - watchAttempts: 0, - }); - } - - static async removeUnwatchingError({ id }: { id: string }) { - await SelectedCalendarRepository.updateById(id, { - error: null, - lastErrorAt: null, - unwatchAttempts: 0, - }); - } -} diff --git a/packages/platform/libraries/index.ts b/packages/platform/libraries/index.ts index 553ba99aea..9f9497b78c 100644 --- a/packages/platform/libraries/index.ts +++ b/packages/platform/libraries/index.ts @@ -108,7 +108,7 @@ export { getTeamMemberEmailForResponseOrContactUsingUrlQuery }; export { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner"; export { getCalendarLinks } from "@calcom/features/bookings/lib/getCalendarLinks"; export { findTeamMembersMatchingAttributeLogic } from "@calcom/features/routing-forms/lib/findTeamMembersMatchingAttributeLogic"; -export { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +export { SelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository"; export { encryptServiceAccountKey } from "@calcom/lib/server/serviceAccountKey"; export { createHandler as createApiKeyHandler } from "@calcom/trpc/server/routers/viewer/apiKeys/create.handler"; export type { TFindTeamMembersMatchingAttributeLogicInputSchema } from "@calcom/trpc/server/routers/viewer/attributes/findTeamMembersMatchingAttributeLogic.schema"; diff --git a/packages/platform/libraries/repositories.ts b/packages/platform/libraries/repositories.ts index 4083af7915..376590aa36 100644 --- a/packages/platform/libraries/repositories.ts +++ b/packages/platform/libraries/repositories.ts @@ -4,7 +4,7 @@ export { PrismaHolidayRepository } from "@calcom/features/holidays/repositories/ export { ScheduleRepository as PrismaScheduleRepository } from "@calcom/features/schedules/repositories/ScheduleRepository"; export { BookingRepository as PrismaBookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; export { EventTypeRepository as PrismaEventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository"; -export { RoutingFormResponseRepository as PrismaRoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +export { RoutingFormResponseRepository as PrismaRoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; export { PrismaSelectedSlotRepository } from "@calcom/features/selectedSlots/repositories/PrismaSelectedSlotRepository"; export { TeamRepository as PrismaTeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; export { UserRepository as PrismaUserRepository } from "@calcom/features/users/repositories/UserRepository"; diff --git a/packages/trpc/server/routers/viewer/admin/workspacePlatform/add.handler.ts b/packages/trpc/server/routers/viewer/admin/workspacePlatform/add.handler.ts index 07fe5ee263..cb463ae4f9 100644 --- a/packages/trpc/server/routers/viewer/admin/workspacePlatform/add.handler.ts +++ b/packages/trpc/server/routers/viewer/admin/workspacePlatform/add.handler.ts @@ -1,7 +1,7 @@ import type { z } from "zod"; import logger from "@calcom/lib/logger"; -import { WorkspacePlatformRepository } from "@calcom/lib/server/repository/workspacePlatform"; +import { WorkspacePlatformRepository } from "@calcom/features/workspace-platform/repositories/WorkspacePlatformRepository"; import { TRPCError } from "@trpc/server"; diff --git a/packages/trpc/server/routers/viewer/admin/workspacePlatform/list.handler.ts b/packages/trpc/server/routers/viewer/admin/workspacePlatform/list.handler.ts index 4071f6dddf..0040c5976c 100644 --- a/packages/trpc/server/routers/viewer/admin/workspacePlatform/list.handler.ts +++ b/packages/trpc/server/routers/viewer/admin/workspacePlatform/list.handler.ts @@ -1,4 +1,4 @@ -import { WorkspacePlatformRepository } from "@calcom/lib/server/repository/workspacePlatform"; +import { WorkspacePlatformRepository } from "@calcom/features/workspace-platform/repositories/WorkspacePlatformRepository"; import { TRPCError } from "@trpc/server"; diff --git a/packages/trpc/server/routers/viewer/admin/workspacePlatform/toggleEnabled.handler.ts b/packages/trpc/server/routers/viewer/admin/workspacePlatform/toggleEnabled.handler.ts index 23d7a89f18..ff1fcf5b7b 100644 --- a/packages/trpc/server/routers/viewer/admin/workspacePlatform/toggleEnabled.handler.ts +++ b/packages/trpc/server/routers/viewer/admin/workspacePlatform/toggleEnabled.handler.ts @@ -1,6 +1,6 @@ import type { z } from "zod"; -import { WorkspacePlatformRepository } from "@calcom/lib/server/repository/workspacePlatform"; +import { WorkspacePlatformRepository } from "@calcom/features/workspace-platform/repositories/WorkspacePlatformRepository"; import type { workspacePlatformToggleEnabledSchema } from "./schema"; import { ensureNoServiceAccountKey } from "./utils"; diff --git a/packages/trpc/server/routers/viewer/admin/workspacePlatform/update.handler.ts b/packages/trpc/server/routers/viewer/admin/workspacePlatform/update.handler.ts index daffe8b120..4380a2c9d0 100644 --- a/packages/trpc/server/routers/viewer/admin/workspacePlatform/update.handler.ts +++ b/packages/trpc/server/routers/viewer/admin/workspacePlatform/update.handler.ts @@ -1,6 +1,6 @@ import type { z } from "zod"; -import { WorkspacePlatformRepository } from "@calcom/lib/server/repository/workspacePlatform"; +import { WorkspacePlatformRepository } from "@calcom/features/workspace-platform/repositories/WorkspacePlatformRepository"; import { TRPCError } from "@trpc/server"; diff --git a/packages/trpc/server/routers/viewer/admin/workspacePlatform/updateServiceAccount.handler.ts b/packages/trpc/server/routers/viewer/admin/workspacePlatform/updateServiceAccount.handler.ts index bc6948b0d9..37d6f81c15 100644 --- a/packages/trpc/server/routers/viewer/admin/workspacePlatform/updateServiceAccount.handler.ts +++ b/packages/trpc/server/routers/viewer/admin/workspacePlatform/updateServiceAccount.handler.ts @@ -1,6 +1,6 @@ import type { z } from "zod"; -import { WorkspacePlatformRepository } from "@calcom/lib/server/repository/workspacePlatform"; +import { WorkspacePlatformRepository } from "@calcom/features/workspace-platform/repositories/WorkspacePlatformRepository"; import { TRPCError } from "@trpc/server"; diff --git a/packages/trpc/server/routers/viewer/delegationCredential/add.handler.ts b/packages/trpc/server/routers/viewer/delegationCredential/add.handler.ts index 6d0979db48..0bb4cbdee2 100644 --- a/packages/trpc/server/routers/viewer/delegationCredential/add.handler.ts +++ b/packages/trpc/server/routers/viewer/delegationCredential/add.handler.ts @@ -1,7 +1,7 @@ import type { z } from "zod"; import { DelegationCredentialRepository } from "@calcom/features/delegation-credentials/repositories/DelegationCredentialRepository"; -import { WorkspacePlatformRepository } from "@calcom/lib/server/repository/workspacePlatform"; +import { WorkspacePlatformRepository } from "@calcom/features/workspace-platform/repositories/WorkspacePlatformRepository"; import { TRPCError } from "@trpc/server"; diff --git a/packages/trpc/server/routers/viewer/delegationCredential/listWorkspacePlatforms.handler.ts b/packages/trpc/server/routers/viewer/delegationCredential/listWorkspacePlatforms.handler.ts index 712661dfee..627d4960af 100644 --- a/packages/trpc/server/routers/viewer/delegationCredential/listWorkspacePlatforms.handler.ts +++ b/packages/trpc/server/routers/viewer/delegationCredential/listWorkspacePlatforms.handler.ts @@ -1,4 +1,4 @@ -import { WorkspacePlatformRepository } from "@calcom/lib/server/repository/workspacePlatform"; +import { WorkspacePlatformRepository } from "@calcom/features/workspace-platform/repositories/WorkspacePlatformRepository"; export default async function handler() { const workspacePlatforms = await WorkspacePlatformRepository.findAll(); diff --git a/packages/trpc/server/routers/viewer/delegationCredential/update.handler.ts b/packages/trpc/server/routers/viewer/delegationCredential/update.handler.ts index fc79bf3798..53ef333346 100644 --- a/packages/trpc/server/routers/viewer/delegationCredential/update.handler.ts +++ b/packages/trpc/server/routers/viewer/delegationCredential/update.handler.ts @@ -1,7 +1,7 @@ import type { z } from "zod"; import { DelegationCredentialRepository } from "@calcom/features/delegation-credentials/repositories/DelegationCredentialRepository"; -import { WorkspacePlatformRepository } from "@calcom/lib/server/repository/workspacePlatform"; +import { WorkspacePlatformRepository } from "@calcom/features/workspace-platform/repositories/WorkspacePlatformRepository"; import { TRPCError } from "@trpc/server"; diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 775043eaa9..d212bfd898 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -50,7 +50,7 @@ import { import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { withReporting } from "@calcom/lib/sentryWrapper"; -import type { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; +import type { RoutingFormResponseRepository } from "@calcom/features/routing-forms/repositories/RoutingFormResponseRepository"; import { PeriodType, SchedulingType } from "@calcom/prisma/enums"; import type { CalendarFetchMode, EventBusyDate, EventBusyDetails } from "@calcom/types/Calendar"; import type { CredentialForCalendarService } from "@calcom/types/Credential"; diff --git a/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts b/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts index 4be85a3c58..562c78f5e7 100644 --- a/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts @@ -3,7 +3,7 @@ import { OnboardingPathService } from "@calcom/features/onboarding/lib/onboardin import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { getTranslation } from "@calcom/lib/server/i18n"; -import { VerificationTokenRepository } from "@calcom/lib/server/repository/verificationToken"; +import { VerificationTokenRepository } from "@calcom/app-store/stripepayment/lib/repositories/VerificationTokenRepository"; import { prisma } from "@calcom/prisma"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; import { ensureAtleastAdminPermissions, getTeamOrThrow } from "./inviteMember/utils";