Skip to content

Conversation

@mfts
Copy link
Owner

@mfts mfts commented Sep 17, 2025

Summary by CodeRabbit

  • Refactor

    • Standardized authorization to user-team checks (unauthorized now returns 401) and consistent existence guards; many endpoints moved from a helper to explicit two-step data flows.
    • Separated data loading from count aggregation for clearer list responses.
  • Performance

    • Faster listings via group-by counting strategies and new DB indexes (viewer groups, document versions, links, views, dataroom documents).
  • Bug Fixes

    • Validation for annotation images (required fields, image MIME type), clarified status codes, and refined versioning/page-count behavior.
  • Chores

    • Removed unused imports.

@vercel
Copy link

vercel bot commented Sep 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
papermark Ready Ready Preview Comment Sep 18, 2025 9:54am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 17, 2025

Walkthrough

Replaces helper-based team access with direct prisma.userTeam.findUnique composite checks (401 on failure), adds explicit resource existence checks (404), rewrites several list endpoints to two-phase fetch + groupBy aggregation for counts, restructures dataroom viewer/permission-group loading, and adds Prisma indexes and a migration.

Changes

Cohort / File(s) Summary of changes
Authorization via userTeam composite key
pages/api/teams/[teamId]/documents/[id]/index.ts, pages/api/teams/[teamId]/documents/[id]/update-name.ts, pages/api/teams/[teamId]/documents/[id]/versions/index.ts, pages/api/teams/[teamId]/documents/[id]/views/[viewId]/stats.ts, pages/api/teams/[teamId]/documents/update.ts, pages/api/teams/[teamId]/documents/agreement.ts, pages/api/teams/[teamId]/documents/[id]/overview.ts, pages/api/teams/[teamId]/folders/index.ts, pages/api/teams/[teamId]/folders/move.ts, pages/api/teams/[teamId]/folders/parents/[...name].ts, pages/api/teams/[teamId]/folders/documents/[...name].ts, pages/api/teams/[teamId]/datarooms/[id]/groups/index.ts, pages/api/teams/[teamId]/datarooms/[id]/permission-groups/index.ts, pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId].ts, pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId]/images.ts, pages/api/teams/[teamId]/documents/[id]/annotations/index.ts
Replaced helper-based membership checks with prisma.userTeam.findUnique({ where: { userId_teamId: { userId, teamId } } }); standardized 401 on missing access and added early resource-existence checks (404).
Document listing: two-step counts via groupBy
pages/api/teams/[teamId]/documents/index.ts, pages/api/teams/[teamId]/documents/search.ts, pages/api/teams/[teamId]/folders/documents/[...name].ts
Removed expensive _count includes; fetch documents first, then compute counts via groupBy on Link, View, DocumentVersion (and DataroomDocument where applicable), merging counts into _count on each document.
Dataroom viewer / permission groups loading
pages/api/teams/[teamId]/datarooms/[id]/groups/index.ts, pages/api/teams/[teamId]/datarooms/[id]/permission-groups/index.ts
Switched nested includes to staged queries: fetch dataroom, fetch viewer/permission groups separately, compute membership/view counts via groupBy, conditionally load accessControls (filtered by itemType/itemId), assemble response objects with nested data and _count.
Annotations endpoints: auth & validation
pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId].ts, pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId]/images.ts, pages/api/teams/[teamId]/documents/[id]/annotations/index.ts
Added userTeam authorization and minimal document existence checks; images POST now validates filename, url, mimeType and enforces mimeType starts with image/. Core annotation behavior preserved.
Single-document flows adjustments
pages/api/teams/[teamId]/documents/[id]/index.ts, pages/api/teams/[teamId]/documents/[id]/versions/index.ts, pages/api/teams/[teamId]/documents/agreement.ts, pages/api/teams/[teamId]/documents/[id]/overview.ts, pages/api/teams/[teamId]/documents/[id]/views/[viewId]/stats.ts, pages/api/teams/[teamId]/documents/[id]/update-name.ts, pages/api/teams/[teamId]/documents/update.ts
Replaced helper-based access with direct Prisma queries, tightened selects (minimal fields), adjusted version creation (derive next version number; set numPages = 1 when advancedExcelEnabled), and small conditional tweaks (e.g., counting when team.plan includes "free").
Minor import cleanup
pages/api/teams/[teamId]/datarooms/[id]/links.ts, pages/api/teams/[teamId]/documents/[id]/links.ts
Removed unused imports; no behavioral changes.
Prisma performance indexes & migration
prisma/migrations/20250917000000_add_performance_index_dataroom/migration.sql, prisma/schema/dataroom.prisma, prisma/schema/document.prisma, prisma/schema/link.prisma, prisma/schema/schema.prisma
Added indexes: DataroomDocument(documentId), DocumentVersion(documentId, isPrimary, createdAt), Link(permissionGroupId), ViewerGroup(dataroomId, createdAt), View(groupId). Migration SQL added.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant API as Endpoint
  participant DB as Prisma/DB

  U->>API: Request (teamId, resourceId)
  API->>DB: userTeam.findUnique(userId, teamId)
  alt unauthorized
    DB-->>API: null
    API-->>U: 401 Unauthorized
  else authorized
    API->>DB: resource.findUnique({ id, teamId })
    alt not found
      DB-->>API: null
      API-->>U: 404 Not Found
    else found
      API->>DB: additional queries (findMany / groupBy)
      DB-->>API: data + aggregated counts
      API-->>U: 200/201 Response
    end
  end
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant API as Dataroom ViewerGroups GET
  participant DB as Prisma/DB

  U->>API: GET /datarooms/:id/groups
  API->>DB: userTeam.findUnique(userId, teamId)
  DB-->>API: teamAccess
  API->>DB: dataroom.findUnique(id, teamId, select: { id })
  DB-->>API: dataroom|null
  alt dataroom missing
    API-->>U: 404
  else
    API->>DB: viewerGroup.findMany(dataroomId, orderBy: createdAt desc)
    par counts
      API->>DB: viewerGroupMembership.groupBy(groupId, _count)
      API->>DB: view.groupBy(groupId, _count)
    and access controls (conditional)
      API->>DB: accessControl.findMany(filter by itemType/itemId)
    end
    API-->>U: 200 viewerGroupsWithCounts and accessControls
  end
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant API as Documents List/Search
  participant DB as Prisma/DB

  U->>API: GET /documents
  API->>DB: userTeam.findUnique(userId, teamId)
  DB-->>API: teamAccess
  API->>DB: document.findMany(filters, no counts)
  DB-->>API: documents[]
  par counts
    API->>DB: link.groupBy(documentId)
    API->>DB: view.groupBy(documentId)
    API->>DB: documentVersion.groupBy(documentId)
    API->>DB: dataroomDocument.groupBy(documentId)  %% where applicable
  end
  API->>API: Merge counts into documents as _count
  API-->>U: 200 documentsWithCounts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: speed improvements" accurately describes the primary intent of the changeset — numerous query refactors and new DB indexes aimed at improving performance across datarooms, documents, and permission/visitor group lookups — and is concise and readable for a reviewer scanning history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/index-improvements

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cec8b9 and 6ac61a3.

📒 Files selected for processing (1)
  • pages/api/teams/[teamId]/documents/[id]/versions/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pages/api/teams/[teamId]/documents/[id]/versions/index.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (17)
pages/api/teams/[teamId]/documents/[id]/overview.ts (2)

36-46: Bug: invalid use of findUnique with relation filter; switch to userTeam join and read plan from there.

prisma.team.findUnique cannot include a relation filter (users: { some: ... }) in where. This will not compile and breaks auth. Use userTeam composite lookup and pull plan via relation.

Apply:

-    const team = await prisma.team.findUnique({
-      where: {
-        id: teamId,
-        users: {
-          some: {
-            userId,
-          },
-        },
-      },
-      select: { plan: true },
-    });
+    const membership = await prisma.userTeam.findUnique({
+      where: { userId_teamId: { userId, teamId } },
+      select: { team: { select: { plan: true } } },
+    });
+
+    if (!membership) {
+      return res.status(401).end("Unauthorized");
+    }
+
+    const teamPlan = membership.team?.plan ?? "free";

And update downstream usages:

-    if (primaryVersion && team.plan.includes("free")) {
+    if (primaryVersion && teamPlan.includes("free")) {
-      team: {
-        plan: team?.plan || "free",
-        isTrial: team?.plan.includes("drtrial") || false,
-      },
+      team: {
+        plan: teamPlan,
+        isTrial: teamPlan.includes("drtrial"),
+      },

36-46: Replace prisma.team.findUnique calls that include relation filters with findFirst (or check membership separately).

Prisma's findUnique accepts only unique identifier fields in where; relation filters (e.g., users: { some: ... }) should be handled with findFirst/findMany or by querying the team by id and checking the users relation. (prisma.io)

  • Action: Replace patterns like
    prisma.team.findUnique({ where: { id: teamId, users: { some: { userId } } }, ... })
    with
    prisma.team.findFirst({ where: { id: teamId, users: { some: { userId } } }, ... })
  • Alternative: keep findUnique({ where: { id: teamId } }) and then assert membership via a select on users (or a separate query) before proceeding.
  • Scope: scan found this in multiple API routes (example: pages/api/teams/[teamId]/documents/[id]/overview.ts lines 36–46) — fix all occurrences under pages/api.
pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId].ts (3)

87-101: Use findFirst (or a composite unique) for annotation fetch with multiple filters

Same concern: findUnique won’t accept where: { id, documentId, teamId } unless a matching composite unique exists. Prefer findFirst here since you already guard membership and document existence.

Apply:

-      const fullAnnotation = await prisma.documentAnnotation.findUnique({
-        where: { id: annotationId, documentId: docId, teamId },
+      const fullAnnotation = await prisma.documentAnnotation.findFirst({
+        where: { id: annotationId, documentId: docId, teamId },
         include: {
           images: true,
           createdBy: {
             select: { id: true, name: true, email: true },
           },
         },
       });

108-121: Update selector must be unique — either rely on id after prior checks or add a composite unique

You previously verified the annotation belongs to the document/team. Use id for the update, or add a composite unique in schema and use that selector.

Apply:

-      const updatedAnnotation = await prisma.documentAnnotation.update({
-        where: { id: annotationId, documentId: docId, teamId },
+      const updatedAnnotation = await prisma.documentAnnotation.update({
+        where: { id: annotationId },
         data: validatedData,
         include: {
           images: true,
           createdBy: {
             select: { id: true, name: true, email: true },
           },
         },
       });

126-128: Delete selector must be unique — use id after the earlier scope checks

Same reasoning as the update path.

Apply:

-      await prisma.documentAnnotation.delete({
-        where: { id: annotationId, documentId: docId, teamId },
-      });
+      await prisma.documentAnnotation.delete({
+        where: { id: annotationId },
+      });
pages/api/teams/[teamId]/documents/[id]/index.ts (1)

60-96: Prisma findUnique used with non-unique/relational filters — switch to findFirst

findUnique must match a unique constraint. Filtering by { id, teamId } is not a unique selector unless you expose a composite unique input; the current shape is invalid. Use findFirst with the filter.

Apply:

-      const document = await prisma.document.findUnique({
-        where: {
-          id: docId,
-          teamId,
-        },
+      const document = await prisma.document.findFirst({
+        where: { id: docId, teamId },
         include: {
           versions: {
             where: { isPrimary: true },
             orderBy: { createdAt: "desc" },
             take: 1,
           },
           folder: { select: { name: true, path: true } },
           datarooms: {
             select: {
               dataroom: { select: { id: true, name: true } },
               folder:  { select: { id: true, name: true, path: true } },
             },
           },
         },
       });
pages/api/teams/[teamId]/folders/move.ts (2)

68-73: Don’t write the HTTP response inside a Prisma transaction

Returning res.status(...).json(...) inside $transaction can lead to “headers already sent” and leaves the outer handler flow inconsistent. Detect duplicates before opening the transaction or throw and handle in the outer catch.

Apply:

-          if (duplicateNames.length > 0) {
-            return res.status(409).json({
-              message: `Cannot move folders: Duplicate names found inside target folder - ${duplicateNames.join(", ")}`,
-            });
-          }
+          if (duplicateNames.length > 0) {
+            throw new Error(`DUPLICATE_FOLDER_NAMES:${duplicateNames.join(",")}`);
+          }

And in the catch block:

-    } catch (error) {
-      return res.status(500).end(error);
+    } catch (error: any) {
+      if (typeof error.message === "string" && error.message.startsWith("DUPLICATE_FOLDER_NAMES:")) {
+        const list = error.message.split(":")[1]?.replaceAll(",", ", ");
+        return res.status(409).json({
+          message: `Cannot move folders: Duplicate names found inside target folder - ${list}`,
+        });
+      }
+      return res.status(500).end("Internal Server Error");
     }

95-117: updates contains nulls — Promise.all will throw; filter out falsy entries

Map can return null when no parentFolder; filter before Promise.all.

Apply:

-        const updates = allSubfolders.map((subfolder) => {
+        const updates = allSubfolders.map((subfolder) => {
           // Find the parent folder it belongs to
           const parentFolder = foldersToMove.find((folder) =>
             subfolder.path.startsWith(folder.path),
           );
 
           if (!parentFolder) return null;
 
           // Get the new base path for the parent
           const newParentPath = folderPathUpdates.get(parentFolder.id);
 
           // Calculate the new subfolder path by replacing the old path with the new one
           const relativePath = subfolder.path
             .replace(parentFolder.path, "")
             .trim();
           const newSubfolderPath = `${newParentPath}${relativePath}`;
 
           return prisma.folder.update({
             where: { id: subfolder.id, teamId },
             data: { path: newSubfolderPath },
           });
-        });
+        }).filter(Boolean) as Promise<unknown>[];
pages/api/teams/[teamId]/documents/search.ts (1)

41-45: Bug: contains receives undefined when query is absent

Unconditional filter will throw at runtime in Prisma when query is undefined. Make the filter conditional (and optionally trim).

Apply:

-        where: {
-          teamId: teamId,
-          name: {
-            contains: query,
-            mode: "insensitive",
-          },
-        },
+        where: {
+          teamId: teamId,
+          ...(query?.trim()
+            ? {
+                name: {
+                  contains: query.trim(),
+                  mode: "insensitive",
+                },
+              }
+            : {}),
+        },
pages/api/teams/[teamId]/documents/[id]/versions/index.ts (4)

143-146: team is undefined in conversionQueue(team.plan)

These trigger options reference team.plan but no team is fetched. Fetch the plan after authorization.

       if (!teamAccess) {
         return res.status(401).end("Unauthorized");
       }
 
+      // Fetch team plan for queue selection
+      const team = await prisma.team.findUnique({
+        where: { id: teamId },
+        select: { plan: true },
+      });
+      if (!team) {
+        return res.status(404).json({ error: "Team not found" });
+      }

Also applies to: 169-171, 192-194, 64-67


89-92: Version number calc breaks when no prior versions

document.versions is always truthy (empty array), causing versions[0] to be undefined.

-      const currentVersionNumber = document?.versions
-        ? document.versions[0].versionNumber
-        : 1;
+      const currentVersionNumber = document.versions[0]?.versionNumber ?? 0;

118-127: Duplicate updateMany demotion

The second demotion block is redundant.

-      // turn off isPrimary flag for all other versions
-      await prisma.documentVersion.updateMany({
-        where: {
-          documentId: documentId,
-          id: { not: version.id },
-        },
-        data: {
-          isPrimary: false,
-        },
-      });

219-221: Wrong Allow header and message

Endpoint supports POST, not GET.

-    // We only allow GET requests
-    res.setHeader("Allow", ["GET"]);
+    // We only allow POST requests
+    res.setHeader("Allow", ["POST"]);
pages/api/teams/[teamId]/datarooms/[id]/permission-groups/index.ts (1)

143-156: findUnique with relational filter in POST auth

Use the same userTeam composite check as GET; current team.findUnique filter is invalid and inconsistent.

-      // Verify team membership
-      const team = await prisma.team.findUnique({
-        where: {
-          id: teamId,
-          users: {
-            some: { userId },
-          },
-        },
-      });
-
-      if (!team) {
+      // Verify team membership
+      const teamAccess = await prisma.userTeam.findUnique({
+        where: { userId_teamId: { userId, teamId } },
+      });
+
+      if (!teamAccess) {
         return res.status(401).end("Unauthorized");
       }
pages/api/teams/[teamId]/documents/index.ts (1)

193-221: Eliminate N+1 folder lookups when building folderList

Batch fetch unique folder paths once.

-        documentsWithFolderList = await Promise.all(
-          documentsWithCounts.map(async (doc) => {
-            const folderNames = [];
-            const pathSegments = doc.folder?.path?.split("/") || [];
-
-            if (pathSegments.length > 0) {
-              const folders = await prisma.folder.findMany({
-                where: {
-                  teamId,
-                  path: {
-                    in: pathSegments.map((_, index) =>
-                      pathSegments.slice(0, index + 1).join("/"),
-                    ),
-                  },
-                },
-                select: {
-                  path: true,
-                  name: true,
-                },
-                orderBy: {
-                  path: "asc",
-                },
-              });
-              folderNames.push(...folders.map((f) => f.name));
-            }
-            return { ...doc, folderList: folderNames };
-          }),
-        );
+        // Collect all unique folder paths across docs
+        const allPaths = new Set<string>();
+        for (const doc of documentsWithCounts) {
+          const segments = doc.folder?.path?.split("/") || [];
+          segments.forEach((_, i) =>
+            allPaths.add(segments.slice(0, i + 1).join("/")),
+          );
+        }
+        const folders = await prisma.folder.findMany({
+          where: { teamId, path: { in: Array.from(allPaths).filter(Boolean) } },
+          select: { path: true, name: true },
+        });
+        const nameByPath = new Map(folders.map((f) => [f.path, f.name]));
+        documentsWithFolderList = documentsWithCounts.map((doc) => {
+          const segments = doc.folder?.path?.split("/") || [];
+          const folderList = segments
+            .map((_, i) => segments.slice(0, i + 1).join("/"))
+            .map((p) => nameByPath.get(p))
+            .filter(Boolean) as string[];
+          return { ...doc, folderList };
+        });
pages/api/teams/[teamId]/datarooms/[id]/groups/index.ts (2)

45-55: Bug: prisma.dataroom.findUnique uses non-unique filter.
findUnique requires a unique field/composite. Passing both id and teamId at the top level is invalid unless you target a defined composite key. Use findFirst with a compound where, or target the composite unique name.

Apply one of these:

-      const dataroom = await prisma.dataroom.findUnique({
-        where: {
-          id: dataroomId,
-          teamId: teamId,
-        },
-        select: {
-          id: true,
-          teamId: true,
-          name: true,
-        },
-      });
+      const dataroom = await prisma.dataroom.findFirst({
+        where: { id: dataroomId, teamId },
+        select: { id: true, teamId: true, name: true },
+      });

Or, if a composite unique exists:

-      const dataroom = await prisma.dataroom.findUnique({
-        where: { id: dataroomId, teamId },
+      const dataroom = await prisma.dataroom.findUnique({
+        where: { teamId_id: { teamId, id: dataroomId } },
         select: { id: true, teamId: true, name: true },
       });

165-171: Validate dataroom existence/team match before create.
Prevents cross-team linkage and improves error clarity.

     const group = await prisma.viewerGroup.create({
-      data: {
-        name: name,
-        dataroomId,
-        teamId,
-      },
-    });
+      // ensure dataroom exists and belongs to team
+      });
+    const dr = await prisma.dataroom.findFirst({ where: { id: dataroomId, teamId } });
+    if (!dr) {
+      return res.status(404).end("Dataroom not found");
+    }
+    const group = await prisma.viewerGroup.create({
+      data: { name, dataroomId, teamId },
+    });
🧹 Nitpick comments (27)
pages/api/teams/[teamId]/documents/[id]/update-name.ts (3)

35-38: Use 403 for authenticated‑but‑not‑authorized and keep error shape consistent

If the user is logged in but lacks team access, prefer 403 and JSON to match other errors.

-      if (!teamAccess) {
-        return res.status(401).end("Unauthorized");
-      }
+      if (!teamAccess) {
+        return res.status(403).json({ error: "Forbidden" });
+      }

55-57: Validate and bound the input name

Guard against empty/oversized names before persisting. The diff in the previous comment adds a minimal validation; tighten further if you already enforce different limits elsewhere.


15-15: Fix the route comment (GET → POST)

The docstring says GET but the handler only allows POST.

-    // GET /api/teams/:teamId/documents/:id/update-name
+    // POST /api/teams/:teamId/documents/:id/update-name
prisma/schema/document.prisma (1)

64-69: Consolidate and direction-align the new composite index.

To fully support queries like where { documentId, isPrimary } orderBy { createdAt: 'desc' } and avoid redundant indexes, set an explicit DESC on createdAt here and consider dropping the overlapping two-index combo in a follow-up migration.

-  @@index([documentId, isPrimary, createdAt]) // Optimize primary version lookups with ordering
+  @@index([documentId, isPrimary, createdAt(sort: Desc)]) // Optimize primary version lookups with ordering

Optional follow-up (separate migration): drop either @@index([documentId, isPrimary]) and/or @@index([documentId, createdAt(sort: Desc)]) if this composite fully covers their use cases.

pages/api/teams/[teamId]/folders/[...name].ts (1)

89-91: Fix comment: method guard says POST but route allows GET.

Tiny doc nit.

-    // We only allow POST requests
+    // We only allow GET requests
pages/api/teams/[teamId]/documents/[id]/overview.ts (2)

78-82: Trim the selected fields on versions for lighter payloads.

You only need the latest primary version’s id (and maybe fileSize). Reduce selected fields to cut response size.

           versions: {
             where: { isPrimary: true },
             orderBy: { createdAt: "desc" },
             take: 1,
+            select: { id: true, fileSize: true, createdAt: true },
           },

119-125: 404 on “no primary version” conflates states.

Returning 404 when the document exists but has no primary version muddies error semantics. Consider 200 with primaryVersion: null and isEmpty: true, or a 409/422 to indicate incomplete state.

pages/api/teams/[teamId]/folders/parents/[...name].ts (1)

33-35: Batch parent path lookups and type the accumulator.

Avoid N queries in the loop and add a concrete type.

-    const validatedName = nameValidation.data;
-    let folderNames = [];
+    const validatedName = nameValidation.data;
+    const folderNames: Array<{ name: string; path: string }> = [];
@@
-      for (let i = 0; i < validatedName.length; i++) {
-        const path = "/" + validatedName.slice(0, i + 1).join("/"); // construct the materialized path
-
-        const folder = await prisma.folder.findUnique({
-          where: {
-            teamId_path: {
-              teamId: teamId,
-              path: path,
-            },
-          },
-          select: {
-            id: true,
-            parentId: true,
-            name: true,
-          },
-        });
+      const paths = validatedName.map((_, i) => "/" + validatedName.slice(0, i + 1).join("/"));
+      const folders = await prisma.folder.findMany({
+        where: { teamId, path: { in: paths } },
+        select: { name: true, path: true },
+        orderBy: { path: "asc" },
+      });
@@
-        folderNames.push({ name: folder.name, path: path });
+      folders.forEach((f) => folderNames.push({ name: f.name, path: f.path }));

Also applies to: 51-73

pages/api/teams/[teamId]/documents/[id]/annotations/index.ts (1)

110-116: Validate page numbers against document.numPages (defensive check).

Prevent out‑of‑range pages from being stored.

-      const document = await prisma.document.findUnique({
+      const document = await prisma.document.findUnique({
         where: {
           id: docId,
           teamId,
         },
-        select: { id: true },
+        select: { id: true, numPages: true },
       });
@@
-      const annotation = await prisma.documentAnnotation.create({
+      // Validate pages are within bounds
+      if (
+        document.numPages &&
+        (validatedData.pages.some((p) => p < 1) ||
+          Math.max(...validatedData.pages) > document.numPages)
+      ) {
+        return res.status(400).json({
+          error: "Page out of bounds",
+          details: `Allowed range: 1..${document.numPages}`,
+        });
+      }
+
+      const annotation = await prisma.documentAnnotation.create({

Also applies to: 122-130

pages/api/teams/[teamId]/documents/update.ts (1)

23-29: Add input validation and return 200 for updates (not 201).

Validate documentId and coercion/bounds for numPages; use 200/204 for update.

+import { z } from "zod";
@@
-    // Assuming data is an object with `name` and `description` properties
-    const { documentId, numPages } = req.body;
+    const updateSchema = z.object({
+      documentId: z.string().min(1),
+      numPages: z.coerce.number().int().positive().max(20000),
+    });
+    const { documentId, numPages } = updateSchema.parse(req.body);
@@
-      return res.status(201).json({ message: "Document updated successfully" });
+      return res.status(200).json({ message: "Document updated successfully" });

Also applies to: 56-71

pages/api/teams/[teamId]/documents/[id]/views/[viewId]/stats.ts (2)

66-71: Guard against empty duration data from Tinybird.

Avoid reduce on undefined/null.

-      const total_duration = duration.data.reduce(
+      const rows = Array.isArray(duration?.data) ? duration.data : [];
+      const total_duration = rows.reduce(
         (totalDuration, data) => totalDuration + data.sum_duration,
         0,
       );

78-80: Fix comment: only GET is allowed.

Header is Allow: ["GET"]; update the comment for consistency.

-    // We only allow GET and POST requests
+    // We only allow GET requests
pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId]/images.ts (1)

77-89: Strengthen file validations (size bounds, stricter MIME).

Bound size and consider a MIME whitelist. Client‑provided mimeType is untrusted; prefer metadata from your storage callback when available.

-  if (!mimeType.startsWith("image/")) {
+  const allowedMime = new Set([
+    "image/png",
+    "image/jpeg",
+    "image/webp",
+    "image/gif",
+    "image/svg+xml",
+  ]);
+  if (!allowedMime.has(mimeType)) {
     return res.status(400).json({ error: "Only image files are allowed" });
   }
+
+  if (
+    size != null &&
+    (!Number.isFinite(size) || size < 0 || size > 25 * 1024 * 1024)
+  ) {
+    return res.status(400).json({ error: "Image size must be between 0 and 25 MB" });
+  }

Also applies to: 90-101

prisma/migrations/20250917000000_add_performance_index_dataroom/migration.sql (1)

1-12: Plan index rollout to avoid locks on large tables

If you’re on Postgres with sizable tables, consider out-of-band CREATE INDEX CONCURRENTLY during a maintenance window (Prisma Migrate can’t use CONCURRENTLY inside transactions). Otherwise, expect lock contention.

pages/api/teams/[teamId]/documents/search.ts (2)

52-76: Skip count queries when there are no documents

Avoid three extra round-trips when documentIds.length === 0.

       const documentIds = documents.map((d) => d.id);
+      if (documentIds.length === 0) {
+        return res.status(200).json([]);
+      }

79-87: Type the maps to tighten TS and prevent any drift

Small cleanup; no behavior change.

-      const linkCountMap = new Map(
+      const linkCountMap = new Map<string, number>(
         linkCounts.map((lc) => [lc.documentId, lc._count.id]),
       );
-      const viewCountMap = new Map(
+      const viewCountMap = new Map<string, number>(
         viewCounts.map((vc) => [vc.documentId, vc._count.id]),
       );
-      const versionCountMap = new Map(
+      const versionCountMap = new Map<string, number>(
         versionCounts.map((vsc) => [vsc.documentId, vsc._count.id]),
       );
pages/api/teams/[teamId]/documents/[id]/versions/index.ts (2)

88-116: Make create+demote atomic to prevent two primaries

Wrap creation + demotion in a transaction to avoid races under concurrent uploads.

-      const version = await prisma.documentVersion.create({
-        data: {
-          documentId: documentId,
-          file: url,
-          originalFile: url,
-          type: type,
-          storageType,
-          numPages: document?.advancedExcelEnabled ? 1 : numPages,
-          isPrimary: true,
-          versionNumber: currentVersionNumber + 1,
-          contentType,
-          fileSize,
-        },
-      });
-
-      // turn off isPrimary flag for all other versions
-      await prisma.documentVersion.updateMany({
-        where: {
-          documentId: documentId,
-          id: { not: version.id },
-        },
-        data: {
-          isPrimary: false,
-        },
-      });
+      const [version] = await prisma.$transaction([
+        prisma.documentVersion.create({
+          data: {
+            documentId: documentId,
+            file: url,
+            originalFile: url,
+            type,
+            storageType,
+            numPages: document.advancedExcelEnabled ? 1 : numPages,
+            isPrimary: true,
+            versionNumber: currentVersionNumber + 1,
+            contentType,
+            fileSize,
+          },
+        }),
+        prisma.documentVersion.updateMany({
+          where: { documentId: documentId },
+          data: { isPrimary: false },
+        }),
+      ]);
+      // Re-promote the created version
+      await prisma.documentVersion.update({
+        where: { id: version.id },
+        data: { isPrimary: true },
+      });

158-161: Fragile docId parsing from URL

url.split("/")[1] assumes a strict path layout. Prefer deriving from a known key format or pass documentId directly when available.

pages/api/teams/[teamId]/datarooms/[id]/permission-groups/index.ts (2)

66-68: Short-circuit when no groups

Avoid extra queries when groupIds.length === 0.

       const groupIds = permissionGroups.map((g) => g.id);
+      if (groupIds.length === 0) {
+        return res.status(200).json([]);
+      }

88-96: Type the maps for clarity

Use generics to avoid any[].

-      const accessControlsMap = new Map<string, any[]>();
-      const linksMap = new Map<string, any[]>();
+      const accessControlsMap = new Map<string, typeof accessControls>();
+      const linksMap = new Map<string, { id: string; name: string }[]>();

Also applies to: 99-110

pages/api/teams/[teamId]/folders/documents/[...name].ts (2)

80-82: Skip count queries when no documents

Tiny latency win on empty folders.

       const documentIds = documents.map((d) => d.id);
+      if (documentIds.length === 0) {
+        return res.status(200).json([]);
+      }

115-127: Type the maps

No behavior change; improves readability.

-      const linkCountMap = new Map(
+      const linkCountMap = new Map<string, number>(
         linkCounts.map((lc) => [lc.documentId, lc._count.id]),
       );
-      const viewCountMap = new Map(
+      const viewCountMap = new Map<string, number>(
         viewCounts.map((vc) => [vc.documentId, vc._count.id]),
       );
-      const versionCountMap = new Map(
+      const versionCountMap = new Map<string, number>(
         versionCounts.map((vsc) => [vsc.documentId, vsc._count.id]),
       );
-      const dataroomCountMap = new Map(
+      const dataroomCountMap = new Map<string, number>(
         dataroomCounts.map((dc) => [dc.documentId, dc._count.id]),
       );
pages/api/teams/[teamId]/documents/index.ts (2)

130-164: Skip count queries when no documents

Save 4 queries on empty result sets.

       const documentIds = documents.map((d) => d.id);
+      if (documentIds.length === 0) {
+        const documentsWithCounts = documents.map((document) => ({
+          ...document,
+          _count: { links: 0, views: 0, versions: 0, datarooms: 0 },
+        }));
+        return res.status(200).json({
+          documents: documentsWithCounts,
+          ...(usePagination && {
+            pagination: {
+              total: totalDocuments,
+              pages: Math.ceil(totalDocuments! / limit!),
+              currentPage: page,
+              pageSize: limit,
+            },
+          }),
+        });
+      }

166-177: Type the maps

Small TS improvement; consistent with other files.

-      const linkCountMap = new Map(
+      const linkCountMap = new Map<string, number>(
         linkCounts.map((lc) => [lc.documentId, lc._count.id]),
       );
-      const viewCountMap = new Map(
+      const viewCountMap = new Map<string, number>(
         viewCounts.map((vc) => [vc.documentId, vc._count.id]),
       );
-      const versionCountMap = new Map(
+      const versionCountMap = new Map<string, number>(
         versionCounts.map((vsc) => [vsc.documentId, vsc._count.id]),
       );
-      const dataroomCountMap = new Map(
+      const dataroomCountMap = new Map<string, number>(
         dataroomCounts.map((dc) => [dc.documentId, dc._count.id]),
       );
pages/api/teams/[teamId]/datarooms/[id]/groups/index.ts (3)

27-29: Normalize documentId from req.query (string|string[]).
Next.js query params can be arrays; casting to string risks runtime errors in the include filter.

-    const documentId = req.query?.documentId as string;
-    const userId = (session.user as CustomUser).id;
+    const docRaw = req.query?.documentId;
+    const documentId = Array.isArray(docRaw) ? docRaw[0] : docRaw;
+    const userId = (session.user as CustomUser).id;

89-92: Guard empty IN lists to skip unnecessary groupBy calls.
Minor save and avoids edge-case SQL on some drivers.

       const groupIds = viewerGroups.map((g) => g.id);
+      if (groupIds.length === 0) {
+        return res.status(200).json([]);
+      }

175-177: Unify error handling and fix message.
Use shared errorhandler/log; correct resource name.

-      console.error("Request error", error);
-      res.status(500).json({ error: "Error creating folder" });
+      log({ message: `Failed to create viewer group for dataroom ${dataroomId}. ${error}`, type: "error" });
+      errorhandler(error, res);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d08c09 and 67d5b8b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (25)
  • pages/api/teams/[teamId]/datarooms/[id]/groups/index.ts (5 hunks)
  • pages/api/teams/[teamId]/datarooms/[id]/links.ts (0 hunks)
  • pages/api/teams/[teamId]/datarooms/[id]/permission-groups/index.ts (3 hunks)
  • pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId].ts (1 hunks)
  • pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId]/images.ts (1 hunks)
  • pages/api/teams/[teamId]/documents/[id]/annotations/index.ts (2 hunks)
  • pages/api/teams/[teamId]/documents/[id]/index.ts (1 hunks)
  • pages/api/teams/[teamId]/documents/[id]/links.ts (0 hunks)
  • pages/api/teams/[teamId]/documents/[id]/overview.ts (2 hunks)
  • pages/api/teams/[teamId]/documents/[id]/update-name.ts (1 hunks)
  • pages/api/teams/[teamId]/documents/[id]/versions/index.ts (1 hunks)
  • pages/api/teams/[teamId]/documents/[id]/views/[viewId]/stats.ts (2 hunks)
  • pages/api/teams/[teamId]/documents/agreement.ts (1 hunks)
  • pages/api/teams/[teamId]/documents/index.ts (5 hunks)
  • pages/api/teams/[teamId]/documents/search.ts (2 hunks)
  • pages/api/teams/[teamId]/documents/update.ts (2 hunks)
  • pages/api/teams/[teamId]/folders/[...name].ts (1 hunks)
  • pages/api/teams/[teamId]/folders/documents/[...name].ts (3 hunks)
  • pages/api/teams/[teamId]/folders/index.ts (2 hunks)
  • pages/api/teams/[teamId]/folders/move.ts (1 hunks)
  • pages/api/teams/[teamId]/folders/parents/[...name].ts (1 hunks)
  • prisma/migrations/20250917000000_add_performance_index_dataroom/migration.sql (1 hunks)
  • prisma/schema/dataroom.prisma (2 hunks)
  • prisma/schema/document.prisma (1 hunks)
  • prisma/schema/link.prisma (1 hunks)
💤 Files with no reviewable changes (2)
  • pages/api/teams/[teamId]/datarooms/[id]/links.ts
  • pages/api/teams/[teamId]/documents/[id]/links.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Socket Security: Pull Request Alerts
🔇 Additional comments (25)
pages/api/teams/[teamId]/documents/[id]/update-name.ts (1)

26-33: Membership check via composite key looks good; confirm role gating as needed

Using userId_teamId for O(1) auth is solid. Please confirm this endpoint should allow any member to rename documents. If edit is role‑gated, add a role check in the same query/select.

pages/api/teams/[teamId]/folders/parents/[...name].ts (1)

38-49: LGTM: standardized auth via userTeam composite key.

Consistent with other endpoints; early 401 is good.

pages/api/teams/[teamId]/documents/[id]/annotations/index.ts (1)

43-66: LGTM: explicit membership + document existence checks improve clarity and performance.

Early 401/404 returns are solid; good move away from the heavy helper.

Also applies to: 97-121

pages/api/teams/[teamId]/documents/update.ts (1)

31-43: LGTM: standardized auth flow.

Two-phase membership + existence checks are consistent with the PR.

pages/api/teams/[teamId]/documents/[id]/views/[viewId]/stats.ts (1)

35-59: LGTM: explicit membership + doc check; early returns are correct.

Consistent with the refactor across endpoints; good.

pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId]/images.ts (1)

38-63: LGTM: explicit authorization + existence checks; solid validation.

Good 401/404 handling and basic input checks.

pages/api/teams/[teamId]/folders/[...name].ts (1)

38-47: LGTM — composite key verified

prisma/schema/team.prisma defines model UserTeam with @@id([userId, teamId]); prisma.userTeam.findUnique({ where: { userId_teamId: { userId, teamId } } }) is valid and more efficient.

pages/api/teams/[teamId]/documents/[id]/annotations/[annotationId].ts (1)

48-59: Good: lightweight, indexed team membership check via userTeam composite key

This aligns with the PR’s pattern and reduces over-fetching.

pages/api/teams/[teamId]/documents/[id]/index.ts (1)

44-57: Good: cheap team membership gate before heavier document load

This should cut unnecessary document queries for unauthorized users.

prisma/schema/dataroom.prisma (2)

70-71: Index on DataroomDocument.documentId looks right for document-scoped fetches

Good addition to speed up lookups by document.


128-131: Composite index on ViewerGroup(dataroomId, createdAt) is appropriate for sorted pagination

Solid choice for time-ordered group listings per dataroom.

prisma/schema/link.prisma (1)

90-91: Index on permissionGroupId is appropriate for permission-group joins

Matches the new query patterns; low risk.

prisma/migrations/20250917000000_add_performance_index_dataroom/migration.sql (1)

1-12: Indexes match schema changes and target the new hot paths

Looks consistent with the Prisma schema updates.

pages/api/teams/[teamId]/folders/index.ts (2)

26-36: Good: switched to composite userTeam lookup for membership

Lean check, avoids loading Team graph.


110-121: Same here: consistent, cheap membership gate

Matches the new pattern; minimal data fetched.

pages/api/teams/[teamId]/datarooms/[id]/permission-groups/index.ts (1)

55-65: Two-phase fetch LGTM

Good split of base entities and nested data; aligns with PR goals.

pages/api/teams/[teamId]/folders/documents/[...name].ts (1)

141-143: Fix error message to match endpoint

The route fetches documents; message says “folders”.
[raise_nitpick_refactor]

-      return res.status(500).json({ error: "Error fetching folders" });
+      return res.status(500).json({ error: "Error fetching documents" });
pages/api/teams/[teamId]/documents/[id]/versions/index.ts (1)

323-336: ```shell
#!/bin/bash
set -euo pipefail
echo "Searching repo for improper prisma findUnique usages with relational filters..."

1) Show all findUnique occurrences with context

echo -e "\n== All findUnique occurrences (context 3 lines) =="
rg -n --hidden --no-ignore -S -C3 "findUnique\s*(" || true

2) Multiline search: findUnique(...) with where:{ ... some|every|none ... }

echo -e "\n== findUnique with 'some|every|none' inside where (multiline) =="
rg -nP --hidden --no-ignore -U "findUnique(\s*{(?:.|\n)?where\s:\s*{(?:.|\n)*?\b(?:some|every|none)\b" -C6 || true

3) findUnique where clause referencing relation keys with an object (e.g. users: { some: ... } )

echo -e "\n== findUnique with relation keys (users|members|teams|participants|owners|memberships) as objects inside where =="
rg -nP --hidden --no-ignore -U "findUnique(\s*{(?:.|\n)?where\s:\s*{(?:.|\n)?\b(?:users|members|teams|participants|owners|memberships)\s:\s*{" -C6 || true

4) As a fallback, list files containing 'findUnique' for manual inspection

echo -e "\n== Files containing 'findUnique' =="
rg -n --hidden --no-ignore -S "findUnique" --files-with-matches | sort -u || true

echo -e "\nDone."


</blockquote></details>
<details>
<summary>pages/api/teams/[teamId]/datarooms/[id]/groups/index.ts (7)</summary><blockquote>

`4-4`: **Good add: ItemType import used for filtered include.**
Keeps the include predicate explicit.

---

`61-87`: **AccessControls exposure—confirm intended audience.**
Including canView/canDownload for all team members may be sensitive; confirm this aligns with your RBAC.

---

`120-123`: **_count field naming—ensure consistency with Prisma relation names.**
Keys ‘members’ and ‘views’ should match client expectations; if other endpoints return `_count.memberships`, this will diverge.

---

`152-160`: **POST auth LGTM—same composite check as GET.**
Consistent with the new pattern.

---

`40-42`: **401 vs 403 semantics—confirm consistency.**
For authenticated-but-not-authorized, many APIs prefer 403. If 401 is a deliberate convention, ignore.



Also applies to: 161-163

---

`126-126`: **Response shape changed — verify/update all callers**

- Change: endpoint now returns an array of groups (viewerGroupsWithCounts) instead of a dataroom object with nested groups — callers reading dataroom.groups will break.
- Evidence: sandbox ripgrep returned "No files were searched" so callers weren't discoverable here.
- Action: run a local repo search and update all consumers, types and tests. Suggested commands:
  rg -n --hidden -uu -S '\bdataroom\.groups\b' -g '!**/dist/**' -g '!**/build/**' -C2
  rg -n --hidden -uu -S 'viewerGroupsWithCounts' -C2

---

`31-38`: **Auth check OK — composite primary key exists.**
model UserTeam declares @@id([userId, teamId]) in prisma/schema/team.prisma, so prisma.userTeam.findUnique({ where: { userId_teamId: { userId, teamId } } }) is valid.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
pages/api/teams/[teamId]/documents/[id]/update-name.ts (1)

58-79: Fix Prisma misuse, remove side‑effects inside transactions, and collapse to a single atomic update.

Problems:

  • findUnique({ where: { id: docId, teamId }}) is invalid unless you use the generated compound key name; same for update({ where: { id, teamId }}). This likely won’t compile.
  • Calling res.status(...).json(...) inside $transaction is unsafe (side‑effects may run multiple times on transaction retries) and leads to double‑send (you later send 200). This will throw “Cannot set headers after they are sent.”
  • Two queries (exists then update) create a TOCTOU window and extra round trip.

Solution: validate input once, then perform a single updateMany scoped by { id: docId, teamId }, check count, and respond outside of any transaction. This is atomic, retry‑safe, and index‑friendly.

Apply this diff:

-      // Atomic transaction to update document name
-      const result = await prisma.$transaction(async (tx) => {
-        // Perform atomic update with both teamId and id in the filter
-        const document = await tx.document.findUnique({
-          where: {
-            id: docId,
-            teamId: teamId, // Ensure document belongs to the team
-          },
-          select: { id: true },
-        });
-
-        if (!document) {
-          return res.status(404).json({ error: "Document not found" });
-        }
-
-        const updateResult = await tx.document.update({
-          where: { id: docId, teamId },
-          data: { name: name },
-        });
-
-        return updateResult;
-      });
-
-      // Check if any rows were affected
-      if (!result) {
-        return res.status(404).json({ error: "Document not found" });
-      }
+      // Single atomic update scoped by teamId + id
+      const update = await prisma.document.updateMany({
+        where: { id: docId, teamId },
+        data: { name },
+      });
+      if (update.count === 0) {
+        return res.status(404).json({ error: "Document not found" });
+      }

If you do have a composite unique on (teamId, id) and want the updated record back, you can alternatively use:

await prisma.document.update({
  where: { teamId_id: { teamId, id: docId } }, // adjust compound key name
  data: { name },
});

Follow‑up: ensure an index/unique on (teamId, id) exists for this hot path.

#!/bin/bash
# Check for composite index/unique on Document(teamId, id)
fd -a schema.prisma | while read -r f; do
  echo ">>> $f"
  rg -nC2 -e 'model\s+Document\b' -e '@@unique\s*\(\s*\[\s*teamId\s*,\s*id\s*\]' -e '@@index\s*\(\s*teamId\s*,\s*id\s*\)' "$f"
done

Also applies to: 81-85

🧹 Nitpick comments (3)
prisma/schema/schema.prisma (1)

147-159: Optionally tighten indexes to match query shapes

If your hot paths filter per dataroom or fetch top-N by time within a group, consider these additions.

Option A — per‑dataroom grouping:

@@ model View {
   @@index([viewerId])
   @@index([groupId]) // Performance optimization for groupBy queries on groupId
+  @@index([dataroomId, groupId]) // If queries commonly filter by dataroomId + groupId
   @@index([teamId])

Option B — recent views per group:

@@ model View {
-  @@index([groupId]) // Performance optimization for groupBy queries on groupId
+  @@index([groupId]) // Performance optimization for groupBy queries on groupId
+  @@index([groupId, viewedAt(sort: Desc)]) // If you paginate/sort by viewedAt within a group

If most analytics exclude archived rows, a partial index can help:

  • Do in SQL migration (Prisma schema doesn’t express partial indexes); e.g., CREATE INDEX CONCURRENTLY IF NOT EXISTS "View_groupId_active_idx" ON "View" ("groupId") WHERE "isArchived" = false;

Would you like a tailored proposal after you share the exact WHERE/ORDER BY patterns from the impacted queries?

pages/api/teams/[teamId]/documents/[id]/update-name.ts (2)

11-16: Harden validation: trim and reject whitespace‑only names.

Currently, " " passes .min(1). Add .trim() to normalize and enforce non‑empty after trimming.

Apply this diff:

-const updateNameSchema = z.object({
-  name: z
-    .string()
-    .min(1, "Document name is required")
-    .max(255, "Document name too long"),
-});
+const updateNameSchema = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Document name is required")
+    .max(255, "Document name too long"),
+});

34-41: Consider a stable, UI‑friendly error shape.

Returning raw issues is verbose and shape‑unstable across Zod versions. Prefer a flattened list or first error string to keep the API contract tight.

Example:

- return res.status(400).json({
-   error: "Invalid input",
-   details: validationResult.error.issues,
- });
+ const errors = validationResult.error.flatten().fieldErrors;
+ return res.status(400).json({ error: "Invalid input", errors });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67d5b8b and 6cec8b9.

📒 Files selected for processing (3)
  • pages/api/teams/[teamId]/documents/[id]/update-name.ts (2 hunks)
  • prisma/migrations/20250917000000_add_performance_index_dataroom/migration.sql (1 hunks)
  • prisma/schema/schema.prisma (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • prisma/migrations/20250917000000_add_performance_index_dataroom/migration.sql
🔇 Additional comments (4)
prisma/schema/schema.prisma (1)

152-152: LGTM — targeted FK index on View.groupId confirmed

Migration(s) contain CREATE INDEX "View_groupId_idx"; no duplicate index on View.groupId found; package.json shows prisma and @prisma/client at ^6.5.0.

pages/api/teams/[teamId]/documents/[id]/update-name.ts (3)

5-5: LGTM on adding Zod.

Good choice for concise validation with minimal overhead.


43-44: LGTM on using parsed data.

Accessing validationResult.data avoids trusting req.body. This is correct.


54-57: LGTM on 401 for missing membership.

Short‑circuiting early is correct; keeps the hot path lean.

@mfts mfts merged commit 02f303c into main Sep 18, 2025
9 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Sep 18, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants