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:
co-authored by
Omar López
Hariom Balhara
parent
88f542cb0d
commit
6fa54aba46
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
+1
-1
@@ -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 }];
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -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;
|
||||
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ export type CredentialPayload = Prisma.CredentialGetPayload<{
|
||||
appId: true;
|
||||
type: true;
|
||||
userId: true;
|
||||
teamId?: true;
|
||||
teamId: true;
|
||||
key: true;
|
||||
invalid: true;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user