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:
Benny Joo
2026-02-09 08:41:04 -03:00
committed by GitHub
co-authored by Devin AI
parent d4a059033e
commit 96bec9f7b9
78 changed files with 1027 additions and 1036 deletions
@@ -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
View File
@@ -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;
},
+1 -1
View File
@@ -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";
@@ -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";
@@ -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,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,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";
@@ -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,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 -1
View File
@@ -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,
});
}
}
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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,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,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,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";