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 <sldisek783@gmail.com> * fix: update VerificationTokenService import path to new location Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update test file imports to use new repository locations Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * mv * fix structure * fix * refactor: merge unit tests for SelectedCalendarRepository into single file Co-Authored-By: benny@cal.com <sldisek783@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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";
|
||||
|
||||
+21
@@ -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": {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
+3
-3
@@ -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(),
|
||||
},
|
||||
+1
-1
@@ -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 }) {
|
||||
+2
-2
@@ -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", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<number | null> {
|
||||
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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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";
|
||||
|
||||
+488
-1
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"] });
|
||||
|
||||
+1
-1
@@ -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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
export class DestinationCalendarRepository {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
async getCustomReminderByCredentialId(credentialId: number): Promise<number | null> {
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
+1
-1
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
+1
-1
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user