Files
calendar/packages/features/credentials/repositories/CredentialRepository.ts
T
98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* refactor: apply biome formatting to packages/features (batch 1 - small subdirs)

Format small subdirectories in packages/features: di, flags, holidays, oauth,
settings, users, assignment-reason, selectedCalendar, hashedLink, host, form,
form-builder, availability, data-table, pbac, schedules, troubleshooter,
eventtypes, calendar-subscription, and root-level files.

Also includes straggler apps/web BookEventForm.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 2 - medium subdirs)

Format medium subdirectories in packages/features: auth, credentials,
calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone,
tasker, and webhooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 3 - bookings + insights)

Format bookings and insights subdirectories in packages/features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 4 - ee)

Format packages/features/ee subdirectory covering billing, workflows,
organizations, teams, managed-event-types, round-robin, dsync,
integration-attribute-sync, and payments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1)

Format booking-audit di, actions, common, dto, repository, and types
subdirectories in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2)

Format booking-audit service subdirectory in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:47:14 +01:00

421 lines
10 KiB
TypeScript

import { buildNonDelegationCredential } from "@calcom/lib/delegationCredential";
import logger from "@calcom/lib/logger";
import { prisma } from "@calcom/prisma";
import type { Prisma, PrismaClient } from "@calcom/prisma/client";
import { safeCredentialSelect } from "@calcom/prisma/selects/credential";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
const log = logger.getSubLogger({ prefix: ["CredentialRepository"] });
type CredentialCreateInput = {
type: string;
key: object;
userId: number;
appId: string;
delegationCredentialId?: string | null;
encryptedKey?: string | null;
};
type CredentialUpdateInput = {
type?: string;
key?: object;
userId?: number;
appId?: string;
delegationCredentialId?: string | null;
invalid?: boolean;
};
export class CredentialRepository {
constructor(private prismaClient: PrismaClient) {}
async findByCredentialId(id: number) {
return this.prismaClient.credential.findUnique({
where: { id },
select: safeCredentialSelect,
});
}
async findByIds({ ids }: { ids: number[] }): Promise<{ id: number; appId: string | null }[]> {
if (ids.length === 0) return [];
return this.prismaClient.credential.findMany({
where: { id: { in: ids } },
select: { id: true, appId: true },
});
}
async findByIdWithDelegationCredential(id: number) {
return this.prismaClient.credential.findUnique({
where: { id },
select: {
...credentialForCalendarServiceSelect,
delegationCredential: true,
},
});
}
static async create(data: CredentialCreateInput) {
const credential = await prisma.credential.create({
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, encryptedKey: 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
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,
encryptedKey,
}: {
userId: number;
delegationCredentialId: string;
type: string;
key: Prisma.InputJsonValue;
appId: string;
encryptedKey?: string | null;
}) {
return prisma.credential.create({
data: { userId, delegationCredentialId, type, key, appId, ...(encryptedKey && { encryptedKey }) },
});
}
static async updateWhereId({ id, data }: { id: number; data: { key: Prisma.InputJsonValue } }) {
return prisma.credential.update({ where: { id }, data });
}
static async findPaymentCredentialByAppIdAndTeamId({
appId,
teamId,
}: {
appId: string | null;
teamId: number;
}) {
return await prisma.credential.findFirst({
where: {
teamId,
appId,
},
include: {
app: true,
},
});
}
static async findPaymentCredentialByAppIdAndUserId({
appId,
userId,
}: {
appId: string | null;
userId: number;
}) {
return await prisma.credential.findFirst({
where: {
userId,
appId,
},
include: {
app: true,
},
});
}
static async findPaymentCredentialByAppIdAndUserIdOrTeamId({
appId,
userId,
teamId,
}: {
appId: string | null;
userId: number;
teamId?: number | null;
}) {
const idToSearchObject = teamId ? { teamId } : { userId };
return await prisma.credential.findFirst({
where: {
...idToSearchObject,
appId,
},
include: {
app: true,
},
});
}
findByTeamIdAndSlugs({ teamId, slugs }: { teamId: number; slugs: string[] }) {
return this.prismaClient.credential.findMany({
where: {
teamId,
appId: {
in: slugs,
},
},
select: { ...safeCredentialSelect, team: { select: { name: true } } },
});
}
findByIdAndTeamId({ id, teamId }: { id: number; teamId: number }) {
return this.prismaClient.credential.findFirst({
where: {
id,
teamId,
},
select: {
...safeCredentialSelect,
app: {
select: {
slug: true,
},
},
},
});
}
async findByAppIdAndKeyValue({
appId,
keyPath,
value,
keyFields,
}: {
appId: string;
keyPath: string[];
value: Prisma.InputJsonValue;
keyFields?: string[];
}) {
const credential = await this.prismaClient.credential.findFirst({
where: {
appId,
key: {
path: keyPath,
equals: value,
},
},
select: {
...safeCredentialSelect,
integrationAttributeSyncs: {
select: {
id: true,
attributeSyncRule: {
select: {
id: true,
rule: true,
},
},
syncFieldMappings: {
select: {
id: true,
integrationFieldName: true,
attributeId: true,
enabled: true,
},
},
},
},
key: keyFields ? true : false,
},
});
if (!credential || !keyFields) {
return credential;
}
const key = credential.key as Record<string, unknown>;
const filteredKey = keyFields.reduce(
(acc, field) => {
if (field in key) {
acc[field] = key[field];
}
return acc;
},
{} as Record<string, unknown>
);
return { ...credential, key: filteredKey };
}
}