perf: Single query user enrichment calls by array of userIds instead (#17190)
* single query * getPublicEvent * perf: use Map for faster lookup for large objects --------- Co-authored-by: Udit Takkar <udit222001@gmail.com>
This commit is contained in:
co-authored by
Udit Takkar
parent
fcc2e0f2ae
commit
f70e8c5189
@@ -356,15 +356,16 @@ export const getPublicEvent = async (
|
||||
|
||||
const eventMetaData = EventTypeMetaDataSchema.parse(event.metadata || {});
|
||||
const teamMetadata = teamMetadataSchema.parse(event.team?.metadata || {});
|
||||
const hosts = [];
|
||||
for (const host of event.hosts) {
|
||||
hosts.push({
|
||||
...host,
|
||||
user: await UserRepository.enrichUserWithItsProfile({
|
||||
user: host.user,
|
||||
}),
|
||||
});
|
||||
}
|
||||
const usersAsHosts = event.hosts.map((host) => host.user);
|
||||
|
||||
// Enrich users in a single batch call
|
||||
const enrichedUsers = await UserRepository.enrichUsersWithTheirProfiles(usersAsHosts);
|
||||
|
||||
// Map enriched users back to the hosts
|
||||
const hosts = event.hosts.map((host, index) => ({
|
||||
...host,
|
||||
user: enrichedUsers[index],
|
||||
}));
|
||||
|
||||
const eventWithUserProfiles = {
|
||||
...event,
|
||||
@@ -558,18 +559,18 @@ async function getOwnerFromUsersArray(prisma: PrismaClient, eventTypeId: number)
|
||||
},
|
||||
});
|
||||
if (!users.length) return null;
|
||||
const usersWithUserProfile = [];
|
||||
for (const user of users) {
|
||||
const { profile } = await UserRepository.enrichUserWithItsProfile({
|
||||
user: user,
|
||||
});
|
||||
usersWithUserProfile.push({
|
||||
...user,
|
||||
organizationId: profile?.organization?.id ?? null,
|
||||
organization: profile?.organization,
|
||||
profile,
|
||||
});
|
||||
}
|
||||
|
||||
// Batch enrich users in a single call
|
||||
const enrichedUsers = await UserRepository.enrichUsersWithTheirProfiles(users);
|
||||
|
||||
// Map the enriched users back to include the organization info
|
||||
const usersWithUserProfile = enrichedUsers.map((user) => ({
|
||||
...user,
|
||||
organizationId: user.profile?.organization?.id ?? null,
|
||||
organization: user.profile?.organization,
|
||||
profile: user.profile,
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
...usersWithUserProfile[0],
|
||||
|
||||
@@ -437,6 +437,39 @@ export class ProfileRepository {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
static async findManyForUsers(userIds: number[]) {
|
||||
const profiles = await prisma.profile.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
in: userIds,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organization: {
|
||||
select: organizationSelect,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return profiles.map((profile) => {
|
||||
const parsedOrganization = getParsedTeam(profile.organization);
|
||||
|
||||
return normalizeProfile({
|
||||
username: profile.username,
|
||||
id: profile.id,
|
||||
userId: profile.userId,
|
||||
uid: profile.uid,
|
||||
name: parsedOrganization.name,
|
||||
organizationId: profile.organizationId,
|
||||
organization: {
|
||||
...parsedOrganization,
|
||||
requestedSlug: parsedOrganization.metadata?.requestedSlug ?? null,
|
||||
metadata: parsedOrganization.metadata,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static async findManyForUser(user: { id: number }) {
|
||||
const profiles = (
|
||||
await prisma.profile.findMany({
|
||||
|
||||
@@ -380,6 +380,65 @@ export class UserRepository {
|
||||
};
|
||||
}
|
||||
|
||||
static async enrichUsersWithTheirProfiles<T extends { id: number; username: string | null }>(
|
||||
users: T[]
|
||||
): Promise<
|
||||
Array<
|
||||
T & {
|
||||
nonProfileUsername: string | null;
|
||||
profile: UserProfile;
|
||||
}
|
||||
>
|
||||
> {
|
||||
if (users.length === 0) return [];
|
||||
|
||||
const userIds = users.map((user) => user.id);
|
||||
const profiles = await ProfileRepository.findManyForUsers(userIds);
|
||||
|
||||
// Create a Map for faster lookups, preserving arrays of profiles per user
|
||||
const profileMap = new Map<number, UserProfile[]>();
|
||||
profiles.forEach((profile) => {
|
||||
if (!profileMap.has(profile.userId)) {
|
||||
profileMap.set(profile.userId, []);
|
||||
}
|
||||
profileMap.get(profile.userId)!.push(profile);
|
||||
});
|
||||
|
||||
// Precompute personal profiles for all users
|
||||
const personalProfileMap = new Map<number, UserProfile>();
|
||||
users.forEach((user) => {
|
||||
personalProfileMap.set(user.id, ProfileRepository.buildPersonalProfileFromUser({ user }));
|
||||
});
|
||||
|
||||
return users.map((user) => {
|
||||
const userProfiles = profileMap.get(user.id) || [];
|
||||
if (userProfiles.length > 0) {
|
||||
const profile = userProfiles[0];
|
||||
if (profile?.organization?.isPlatform) {
|
||||
return {
|
||||
...user,
|
||||
nonProfileUsername: user.username,
|
||||
profile: personalProfileMap.get(user.id)!,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...user,
|
||||
username: profile.username,
|
||||
nonProfileUsername: user.username,
|
||||
profile,
|
||||
};
|
||||
}
|
||||
|
||||
// If no organization profile exists, use the precomputed personal profile
|
||||
return {
|
||||
...user,
|
||||
nonProfileUsername: user.username,
|
||||
profile: personalProfileMap.get(user.id)!,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static enrichUserWithItsProfileBuiltFromUser<T extends { id: number; username: string | null }>({
|
||||
user,
|
||||
}: {
|
||||
|
||||
Reference in New Issue
Block a user