perf: Refactor getEnabledApps to filter on credentials passed (#10353)

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
Joe Au-Yeung
2023-08-01 16:40:52 +05:30
committed by GitHub
co-authored by Omar López Hariom Balhara
parent 88f542cb0d
commit 6fa54aba46
20 changed files with 76 additions and 27 deletions
@@ -27,6 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
process.env.CALENDSO_ENCRYPTION_KEY || ""
),
userId: user.id,
teamId: null,
appId: "apple-calendar",
invalid: false,
};
@@ -27,6 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
process.env.CALENDSO_ENCRYPTION_KEY || ""
),
userId: user.id,
teamId: null,
appId: "caldav-calendar",
invalid: false,
};
@@ -62,7 +62,7 @@ export const FAKE_DAILY_CREDENTIAL: CredentialPayload & { invalid: boolean } = {
userId: 0,
appId: "daily-video",
invalid: false,
teamId: undefined,
teamId: null,
};
export const fetcher = async (endpoint: string, init?: RequestInit | undefined) => {
@@ -33,6 +33,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
type: "exchange2013_calendar",
key: symmetricEncrypt(JSON.stringify(body), process.env.CALENDSO_ENCRYPTION_KEY || ""),
userId: user.id,
teamId: null,
appId: "exchange2013-calendar",
invalid: false,
};
@@ -33,6 +33,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
type: "exchange2016_calendar",
key: symmetricEncrypt(JSON.stringify(body), process.env.CALENDSO_ENCRYPTION_KEY || ""),
userId: user.id,
teamId: null,
appId: "exchange2016-calendar",
invalid: false,
};
@@ -193,7 +193,6 @@ export default class ExchangeCalendarService implements Calendar {
}
async listCalendars(): Promise<IntegrationCalendar[]> {
console.log("This triggers");
try {
const allFolders: IntegrationCalendar[] = [];
return this.getExchangeService()
@@ -30,6 +30,7 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
type: "exchange_calendar",
key: encrypted,
userId: session.user?.id,
teamId: null,
appId: "exchange",
invalid: false,
};
+19 -11
View File
@@ -1,3 +1,4 @@
import type { Credential } from "@prisma/client";
import { Prisma } from "@prisma/client";
import type { TFunction } from "next-i18next";
@@ -33,7 +34,7 @@ const ALL_APPS_MAP = Object.keys(appStoreMetadata).reduce((store, key) => {
}, {} as Record<string, AppMeta>);
const credentialData = Prisma.validator<Prisma.CredentialArgs>()({
select: { id: true, type: true, key: true, userId: true, appId: true, invalid: true },
select: { id: true, type: true, key: true, userId: true, teamId: true, appId: true, invalid: true },
});
export type CredentialData = Prisma.CredentialGetPayload<typeof credentialData>;
@@ -108,26 +109,30 @@ export function getLocationGroupedOptions(integrations: ReturnType<typeof getApp
* This should get all available apps to the user based on his saved
* credentials, this should also get globally available apps.
*/
function getApps(userCredentials: CredentialData[]) {
const apps = ALL_APPS.map((appMeta) => {
const credentials = userCredentials.filter((credential) => credential.type === appMeta.type);
function getApps(credentials: CredentialData[], filterOnCredentials?: boolean) {
const apps = ALL_APPS.reduce((reducedArray, appMeta) => {
const appCredentials = credentials.filter((credential) => credential.type === appMeta.type);
if (filterOnCredentials && !appCredentials.length && !appMeta.isGlobal) return reducedArray;
let locationOption: LocationOption | null = null;
/** If the app is a globally installed one, let's inject it's key */
if (appMeta.isGlobal) {
credentials.push({
appCredentials.push({
id: 0,
type: appMeta.type,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
key: appMeta.key!,
userId: 0,
teamId: 0,
appId: appMeta.slug,
invalid: false,
});
}
/** Check if app has location option AND add it if user has credentials for it */
if (credentials.length > 0 && appMeta?.appData?.location) {
if (appCredentials.length > 0 && appMeta?.appData?.location) {
locationOption = {
value: appMeta.appData.location.type,
label: appMeta.appData.location.label || "No label set",
@@ -135,18 +140,21 @@ function getApps(userCredentials: CredentialData[]) {
};
}
const credential: (typeof credentials)[number] | null = credentials[0] || null;
return {
const credential: (typeof appCredentials)[number] | null = appCredentials[0] || null;
reducedArray.push({
...appMeta,
/**
* @deprecated use `credentials`
*/
credential,
credentials,
credentials: appCredentials,
/** Option to display in `location` field while editing event types */
locationOption,
};
});
});
return reducedArray;
}, [] as (App & { credential: Credential; credentials: Credential[]; locationOption: LocationOption | null })[]);
return apps;
}
+1 -1
View File
@@ -21,7 +21,7 @@ import getCalendarsEvents from "./getCalendarsEvents";
const log = logger.getChildLogger({ prefix: ["CalendarManager"] });
export const getCalendarCredentials = (credentials: Array<CredentialPayload>) => {
const calendarCredentials = getApps(credentials)
const calendarCredentials = getApps(credentials, true)
.filter((app) => app.type.endsWith("_calendar"))
.flatMap((app) => {
const credentials = app.credentials.flatMap((credential) => {
+1 -1
View File
@@ -75,7 +75,7 @@ export default class EventManager {
* @param user
*/
constructor(user: EventManagerUser) {
const appCredentials = getApps(user.credentials).flatMap((app) =>
const appCredentials = getApps(user.credentials, true).flatMap((app) =>
app.credentials.map((creds) => ({ ...creds, appName: app.name }))
);
// This includes all calendar-related apps, traditional calendars such as Google Calendar
+3
View File
@@ -176,6 +176,7 @@ const createMeetingWithCalVideo = async (calEvent: CalendarEvent) => {
appId: "daily-video",
type: "daily_video",
userId: null,
teamId: null,
key: dailyAppKeys,
invalid: false,
},
@@ -199,6 +200,7 @@ const getRecordingsOfCalVideoByRoomName = async (
appId: "daily-video",
type: "daily_video",
userId: null,
teamId: null,
key: dailyAppKeys,
invalid: false,
},
@@ -220,6 +222,7 @@ const getDownloadLinkOfCalVideoByRecordingId = async (recordingId: string) => {
appId: "daily-video",
type: "daily_video",
userId: null,
teamId: null,
key: dailyAppKeys,
invalid: false,
},
@@ -15,7 +15,7 @@ export function BookFormAsModal({ visible, onCancel }: { visible: boolean; onCan
const selectedDuration = useBookerStore((state) => state.selectedDuration);
const { data } = useEvent();
const parsedSelectedTimeslot = dayjs(selectedTimeslot);
const { timeFormat ,timezone} = useTimePreferences();
const { timeFormat, timezone } = useTimePreferences();
return (
<Dialog open={visible} onOpenChange={onCancel}>
+33 -3
View File
@@ -2,12 +2,42 @@ import getApps from "@calcom/app-store/utils";
import type { CredentialData } from "@calcom/app-store/utils";
import { prisma } from "@calcom/prisma";
const getEnabledApps = async (userCredentials: CredentialData[]) => {
import type { Prisma } from ".prisma/client";
/**
*
* @param credentials - Can be user or team credentials
* @param filterOnCredentials - Only include apps where credentials are present
* @returns A list of enabled app metadata & credentials tied to them
*/
const getEnabledApps = async (credentials: CredentialData[], filterOnCredentials?: boolean) => {
const filterOnIds = {
credentials: {
some: {
OR: [] as Prisma.CredentialWhereInput[],
},
},
} satisfies Prisma.AppWhereInput;
if (filterOnCredentials) {
const userIds: number[] = [],
teamIds: number[] = [];
for (const credential of credentials) {
if (credential.userId) userIds.push(credential.userId);
if (credential.teamId) teamIds.push(credential.teamId);
}
if (userIds.length) filterOnIds.credentials.some.OR.push({ userId: { in: userIds } });
if (teamIds.length) filterOnIds.credentials.some.OR.push({ teamId: { in: teamIds } });
}
const enabledApps = await prisma.app.findMany({
where: { enabled: true },
where: {
OR: [{ enabled: true, ...(filterOnIds.credentials.some.OR.length && filterOnIds) }],
},
select: { slug: true, enabled: true },
});
const apps = getApps(userCredentials);
const apps = getApps(credentials, filterOnCredentials);
const filteredApps = enabledApps.reduce((reducedArray, app) => {
const appMetadata = apps.find((metadata) => metadata.slug === app.slug);
+2 -1
View File
@@ -236,6 +236,7 @@ export default async function getEventTypeById({
type: true,
key: true,
userId: true,
teamId: true,
appId: true,
invalid: true,
},
@@ -326,7 +327,7 @@ export default async function getEventTypeById({
const currentUser = eventType.users.find((u) => u.id === userId);
const t = await getTranslation(currentUser?.locale ?? "en", "common");
const integrations = await getEnabledApps(credentials);
const integrations = await getEnabledApps(credentials, true);
const locationOptions = getLocationGroupedOptions(integrations, t);
if (eventType.schedulingType === SchedulingType.MANAGED) {
locationOptions.splice(0, 0, {
@@ -114,7 +114,7 @@ export const integrationsHandler = async ({ ctx, input }: IntegrationsOptions) =
}
}
const enabledApps = await getEnabledApps(credentials);
const enabledApps = await getEnabledApps(credentials, onlyInstalled);
//TODO: Refactor this to pick up only needed fields and prevent more leaking
let apps = enabledApps.map(
({ credentials: _, credential: _1, key: _2 /* don't leak to frontend */, ...app }) => {
@@ -29,12 +29,13 @@ export const locationOptionsHandler = async ({ ctx, input }: LocationOptionsOpti
type: true,
key: true,
userId: true,
teamId: true,
appId: true,
invalid: true,
},
});
const integrations = await getEnabledApps(credentials);
const integrations = await getEnabledApps(credentials, true);
const t = await getTranslation(ctx.user.locale ?? "en", "common");
@@ -22,7 +22,7 @@ export const updateUserDefaultConferencingAppHandler = async ({
}: UpdateUserDefaultConferencingAppOptions) => {
const currentMetadata = userMetadata.parse(ctx.user.metadata);
const credentials = ctx.user.credentials;
const foundApp = getApps(credentials).filter((app) => app.slug === input.appSlug)[0];
const foundApp = getApps(credentials, true).filter((app) => app.slug === input.appSlug)[0];
const appLocation = foundApp?.appData?.location;
if (!foundApp || !appLocation) throw new TRPCError({ code: "BAD_REQUEST", message: "App not installed" });
@@ -25,7 +25,7 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
const { schedulingType, teamId, metadata, ...rest } = input;
const userId = ctx.user.id;
const isManagedEventType = schedulingType === SchedulingType.MANAGED;
// Get Users default conferncing app
// Get Users default conferencing app
const defaultConferencingData = userMetadataSchema.parse(ctx.user.metadata)?.defaultConferencingApp;
const appKeys = await getAppKeysFromSlug("daily-video");
@@ -42,7 +42,9 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
if (defaultConferencingData && defaultConferencingData.appSlug !== "daily-video") {
const credentials = ctx.user.credentials;
const foundApp = getApps(credentials).filter((app) => app.slug === defaultConferencingData.appSlug)[0]; // There is only one possible install here so index [0] is the one we are looking for ;
const foundApp = getApps(credentials, true).filter(
(app) => app.slug === defaultConferencingData.appSlug
)[0]; // There is only one possible install here so index [0] is the one we are looking for ;
const locationType = foundApp?.locationOption?.value ?? DailyLocationType; // Default to Daily if no location type is found
locations = [{ type: locationType, link: defaultConferencingData.appLink }];
}
+1 -1
View File
@@ -111,7 +111,7 @@ export interface App {
/** Number of reviews, harcoded for now. Should be fetched later on. */
reviews?: number;
/**
* Wheter if the app is installed globally or needs user intervention.
* Whether if the app is installed globally or needs user intervention.
* Used to show Connect/Disconnect buttons in App Store
* */
isGlobal?: boolean;
+1 -1
View File
@@ -11,7 +11,7 @@ export type CredentialPayload = Prisma.CredentialGetPayload<{
appId: true;
type: true;
userId: true;
teamId?: true;
teamId: true;
key: true;
invalid: true;
};