From e06e106d3cdfa981574960d69dbee7ae92cc1e60 Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Tue, 15 Jul 2025 17:08:47 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Query=20optimizations=20(#?= =?UTF-8?q?1829)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/auth/queries.ts | 18 ++- apps/web/src/features/spaces/queries.ts | 124 ++++++------------ .../migration.sql | 8 ++ packages/database/prisma/models/space.prisma | 3 +- 4 files changed, 61 insertions(+), 92 deletions(-) create mode 100644 packages/database/prisma/migrations/20250715153525_update_space_member_indexes/migration.sql diff --git a/apps/web/src/auth/queries.ts b/apps/web/src/auth/queries.ts index e5c7c2fd9..cb2c50115 100644 --- a/apps/web/src/auth/queries.ts +++ b/apps/web/src/auth/queries.ts @@ -1,7 +1,7 @@ import { notFound, redirect } from "next/navigation"; import { cache } from "react"; import { defineAbilityFor } from "@/features/ability-manager"; -import { getDefaultSpace, getSpace } from "@/features/spaces/queries"; +import { loadDefaultSpace, loadSpace } from "@/features/spaces/queries"; import { getUser } from "@/features/user/queries"; import { auth } from "@/next-auth"; @@ -44,18 +44,16 @@ export const getActiveSpace = cache(async () => { const { user } = await requireUserAbility(); if (user.activeSpaceId) { - const activeSpace = await getSpace({ id: user.activeSpaceId }); - - if (activeSpace) { - return activeSpace; + try { + return await loadSpace({ id: user.activeSpaceId }); + } catch { + console.warn( + `User ${user.id} has an active space ID ${user.activeSpaceId} that does not exist or is no longer accessible`, + ); } - - console.warn( - `User ${user.id} has an active space ID ${user.activeSpaceId} that does not exist or is no longer accessible`, - ); } - return await getDefaultSpace(); + return await loadDefaultSpace(); }); export const requireUserAbility = cache(async () => { diff --git a/apps/web/src/features/spaces/queries.ts b/apps/web/src/features/spaces/queries.ts index e8d3446db..ab00150d2 100644 --- a/apps/web/src/features/spaces/queries.ts +++ b/apps/web/src/features/spaces/queries.ts @@ -1,7 +1,6 @@ import { accessibleBy } from "@casl/prisma"; import type { SpaceMemberRole } from "@rallly/database"; import { prisma } from "@rallly/database"; -import { redirect } from "next/navigation"; import { cache } from "react"; import { requireUserAbility } from "@/auth/queries"; @@ -19,96 +18,59 @@ export const loadSpaces = cache(async () => { where: accessibleBy(ability).Space, include: { subscription: true, - members: true, - }, - }); - - const availableSpaces: SpaceDTO[] = []; - - for (const space of spaces) { - const role = space.members.find( - (member) => member.userId === user.id, - )?.role; - - if (!role) { - console.warn(`User ${user.id} does not have access to space ${space.id}`); - continue; - } - - availableSpaces.push({ - id: space.id, - name: space.name, - ownerId: space.ownerId, - isPro: Boolean(space.subscription?.active), - role, - }); - } - - return availableSpaces; -}); - -export const getDefaultSpace = cache(async () => { - const { user } = await requireUserAbility(); - const space = await prisma.space.findFirst({ - where: { - ownerId: user.id, - }, - orderBy: { - createdAt: "asc", - }, - include: { - subscription: true, - }, - }); - - if (!space) { - redirect("/setup"); - } - - return { - id: space.id, - name: space.name, - ownerId: space.ownerId, - isPro: Boolean(space.subscription?.active), - role: "OWNER", - } satisfies SpaceDTO; -}); - -export const getSpace = cache(async ({ id }: { id: string }) => { - const { user, ability } = await requireUserAbility(); - const space = await prisma.space.findFirst({ - where: { - AND: [accessibleBy(ability).Space, { id }], - }, - include: { - subscription: { - where: { - active: true, - }, - }, members: { where: { userId: user.id, }, + select: { + role: true, + }, }, }, }); + return spaces + .map((space) => { + const role = space.members[0]?.role; + + if (!role) { + console.warn( + `User ${user.id} does not have access to space ${space.id}`, + ); + return null; + } + + return { + id: space.id, + name: space.name, + ownerId: space.ownerId, + isPro: Boolean(space.subscription?.active), + role, + } satisfies SpaceDTO; + }) + .filter(Boolean) as SpaceDTO[]; +}); + +export const loadDefaultSpace = cache(async () => { + const { user } = await requireUserAbility(); + const spaces = await loadSpaces(); + const defaultSpace = spaces.find((space) => space.ownerId === user.id); + + if (!defaultSpace) { + throw new Error(`User ${user.id} does not have access to any spaces`); + } + + return defaultSpace; +}); + +export const loadSpace = cache(async ({ id }: { id: string }) => { + const spaces = await loadSpaces(); + const space = spaces.find((space) => space.id === id); + if (!space) { + const { user } = await requireUserAbility(); throw new Error(`User ${user.id} does not have access to space ${id}`); } - const role = space.members.find((member) => member.userId === user.id)?.role; - - if (!role) { - throw new Error(`User ${user.id} is not a member of space ${id}`); - } - - return { - id: space.id, - name: space.name, - ownerId: space.ownerId, - isPro: Boolean(space.subscription?.active), - role, - } satisfies SpaceDTO; + return space; }); diff --git a/packages/database/prisma/migrations/20250715153525_update_space_member_indexes/migration.sql b/packages/database/prisma/migrations/20250715153525_update_space_member_indexes/migration.sql new file mode 100644 index 000000000..fd23710eb --- /dev/null +++ b/packages/database/prisma/migrations/20250715153525_update_space_member_indexes/migration.sql @@ -0,0 +1,8 @@ +-- DropIndex +DROP INDEX "space_members_space_id_idx"; + +-- CreateIndex +CREATE INDEX "space_members_space_id_idx" ON "space_members" USING HASH ("space_id"); + +-- CreateIndex +CREATE INDEX "space_members_user_id_idx" ON "space_members" USING HASH ("user_id"); diff --git a/packages/database/prisma/models/space.prisma b/packages/database/prisma/models/space.prisma index 2a919413f..01ca430ed 100644 --- a/packages/database/prisma/models/space.prisma +++ b/packages/database/prisma/models/space.prisma @@ -35,6 +35,7 @@ model SpaceMember { user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([spaceId, userId]) - @@index([spaceId]) + @@index([spaceId], type: Hash, map: "space_members_space_id_idx") + @@index([userId], type: Hash, map: "space_members_user_id_idx") @@map("space_members") }