* feat: optimize Prisma queries by replacing findFirst with findUnique where applicable - Replace findFirst/findFirstOrThrow with findUnique/findUniqueOrThrow for queries using unique constraints - Maintain existing functionality and error handling behavior - Focus on queries using primary keys and unique index fields from schema - Revert problematic changes that caused test failures to maintain stability Co-Authored-By: benny@cal.com <benny@cal.com> * revert: exclude API files from Prisma query optimizations per user request - Reverted all 55 API-related files to their original state - Kept all non-API Prisma query optimizations intact - API files include apps/api/v1, apps/api/v2, apps/web/app/api, and packages/app-store/*/api - Non-API optimizations remain for packages/lib, packages/features, apps/web (non-api), etc. Co-Authored-By: benny@cal.com <benny@cal.com> * feat: optimize membership query in attributeUtils to use findUnique with userId_teamId constraint Co-Authored-By: benny@cal.com <benny@cal.com> * revert: exclude test files from Prisma query optimizations per user request Co-Authored-By: benny@cal.com <benny@cal.com> * revert: revert attributeUtils.ts to use findFirst for test compatibility Co-Authored-By: benny@cal.com <benny@cal.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: benny@cal.com <benny@cal.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
233 lines
6.2 KiB
TypeScript
233 lines
6.2 KiB
TypeScript
import type { Prisma } from "@prisma/client";
|
|
|
|
import logger from "@calcom/lib/logger";
|
|
import { prisma } from "@calcom/prisma";
|
|
import { safeCredentialSelect } from "@calcom/prisma/selects/credential";
|
|
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
|
|
|
import { buildNonDelegationCredential } from "../../delegationCredential/server";
|
|
|
|
const log = logger.getSubLogger({ prefix: ["CredentialRepository"] });
|
|
|
|
type CredentialCreateInput = {
|
|
type: string;
|
|
key: any;
|
|
userId: number;
|
|
appId: string;
|
|
delegationCredentialId?: string | null;
|
|
};
|
|
|
|
type CredentialUpdateInput = {
|
|
type?: string;
|
|
key?: any;
|
|
userId?: number;
|
|
appId?: string;
|
|
delegationCredentialId?: string | null;
|
|
invalid?: boolean;
|
|
};
|
|
|
|
export class CredentialRepository {
|
|
static async create(data: CredentialCreateInput) {
|
|
const credential = await prisma.credential.create({ data: { ...data } });
|
|
return buildNonDelegationCredential(credential);
|
|
}
|
|
static async findByAppIdAndUserId({ appId, userId }: { appId: string; userId: number }) {
|
|
const credential = await prisma.credential.findFirst({
|
|
where: {
|
|
appId,
|
|
userId,
|
|
},
|
|
});
|
|
return buildNonDelegationCredential(credential);
|
|
}
|
|
|
|
/**
|
|
* Doesn't retrieve key field as that has credentials
|
|
*/
|
|
static async findFirstByIdWithUser({ id }: { id: number }) {
|
|
const credential = await prisma.credential.findUnique({ where: { id }, select: safeCredentialSelect });
|
|
return buildNonDelegationCredential(credential);
|
|
}
|
|
|
|
/**
|
|
* Includes 'key' field which is sensitive data.
|
|
*/
|
|
static async findFirstByIdWithKeyAndUser({ id }: { id: number }) {
|
|
const credential = await prisma.credential.findUnique({
|
|
where: { id },
|
|
select: { ...safeCredentialSelect, key: true },
|
|
});
|
|
return buildNonDelegationCredential(credential);
|
|
}
|
|
|
|
static async findFirstByAppIdAndUserId({ appId, userId }: { appId: string; userId: number }) {
|
|
return await prisma.credential.findFirst({
|
|
where: {
|
|
appId,
|
|
userId,
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findFirstByUserIdAndType({ userId, type }: { userId: number; type: string }) {
|
|
const credential = await prisma.credential.findFirst({ where: { userId, type } });
|
|
return buildNonDelegationCredential(credential);
|
|
}
|
|
|
|
static async deleteById({ id }: { id: number }) {
|
|
await prisma.credential.delete({ where: { id } });
|
|
}
|
|
|
|
static async updateCredentialById({ id, data }: { id: number; data: CredentialUpdateInput }) {
|
|
await prisma.credential.update({
|
|
where: { id },
|
|
data,
|
|
});
|
|
}
|
|
|
|
static async deleteAllByDelegationCredentialId({
|
|
delegationCredentialId,
|
|
}: {
|
|
delegationCredentialId: string;
|
|
}) {
|
|
return prisma.credential.deleteMany({ where: { delegationCredentialId } });
|
|
}
|
|
|
|
static async findCredentialForCalendarServiceById({ id }: { id: number }) {
|
|
const dbCredential = await prisma.credential.findUnique({
|
|
where: { id },
|
|
select: credentialForCalendarServiceSelect,
|
|
});
|
|
|
|
if (!dbCredential) {
|
|
return dbCredential;
|
|
}
|
|
|
|
return buildNonDelegationCredential(dbCredential);
|
|
}
|
|
|
|
static async findByIdIncludeDelegationCredential({ id }: { id: number }) {
|
|
const dbCredential = await prisma.credential.findUnique({
|
|
where: { id },
|
|
select: { ...credentialForCalendarServiceSelect, delegationCredential: true },
|
|
});
|
|
|
|
return dbCredential;
|
|
}
|
|
|
|
static async findAllDelegationByUserIdsListAndDelegationCredentialIdAndType({
|
|
userIds,
|
|
delegationCredentialId,
|
|
type,
|
|
}: {
|
|
userIds: number[];
|
|
delegationCredentialId: string;
|
|
type: string;
|
|
}) {
|
|
return prisma.credential.findMany({
|
|
where: {
|
|
userId: {
|
|
in: userIds,
|
|
},
|
|
delegationCredentialId,
|
|
type,
|
|
},
|
|
select: {
|
|
userId: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findAllDelegationByTypeIncludeUserAndTake({ type, take }: { type: string; take: number }) {
|
|
const delegationUserCredentials = await prisma.credential.findMany({
|
|
where: {
|
|
delegationCredentialId: { not: null },
|
|
type,
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
email: true,
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
take,
|
|
});
|
|
return delegationUserCredentials.map(({ delegationCredentialId, ...rest }) => {
|
|
return {
|
|
...rest,
|
|
// We queried only those where delegationCredentialId is not null
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
delegationCredentialId: delegationCredentialId!,
|
|
};
|
|
});
|
|
}
|
|
|
|
static async findUniqueByUserIdAndDelegationCredentialId({
|
|
userId,
|
|
delegationCredentialId,
|
|
}: {
|
|
userId: number;
|
|
delegationCredentialId: string;
|
|
}) {
|
|
const delegationUserCredentials = await prisma.credential.findMany({
|
|
where: {
|
|
userId,
|
|
delegationCredentialId,
|
|
},
|
|
});
|
|
|
|
if (delegationUserCredentials.length > 1) {
|
|
// Instead of crashing use the first one and log for observability
|
|
// TODO: Plan to add a unique constraint on userId and delegationCredentialId
|
|
log.error(`DelegationCredential: Multiple delegation user credentials found - this should not happen`, {
|
|
userId,
|
|
delegationCredentialId,
|
|
});
|
|
}
|
|
|
|
return delegationUserCredentials[0];
|
|
}
|
|
|
|
static async updateWhereUserIdAndDelegationCredentialId({
|
|
userId,
|
|
delegationCredentialId,
|
|
data,
|
|
}: {
|
|
userId: number;
|
|
delegationCredentialId: string;
|
|
data: {
|
|
key: Prisma.InputJsonValue;
|
|
};
|
|
}) {
|
|
return prisma.credential.updateMany({
|
|
where: {
|
|
userId,
|
|
delegationCredentialId,
|
|
},
|
|
data,
|
|
});
|
|
}
|
|
|
|
static async createDelegationCredential({
|
|
userId,
|
|
delegationCredentialId,
|
|
type,
|
|
key,
|
|
appId,
|
|
}: {
|
|
userId: number;
|
|
delegationCredentialId: string;
|
|
type: string;
|
|
key: Prisma.InputJsonValue;
|
|
appId: string;
|
|
}) {
|
|
return prisma.credential.create({ data: { userId, delegationCredentialId, type, key, appId } });
|
|
}
|
|
|
|
static async updateWhereId({ id, data }: { id: number; data: { key: Prisma.InputJsonValue } }) {
|
|
return prisma.credential.update({ where: { id }, data });
|
|
}
|
|
}
|