Skip to content

Conversation

@mfts
Copy link
Owner

@mfts mfts commented Sep 24, 2025

Summary by CodeRabbit

  • New Features
    • Archived links are now included in analytics results and team document link lists.
    • Link counts and summaries reflect both active and archived links for a more complete view of activity.
  • Chores
    • Minor internal cleanup (no user-facing behavior changes).

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

Walkthrough

The PR removes an unused useSWR import from a links component, corrects an authOptions import path, and changes multiple API queries to include archived links by removing isArchived: false filters and expanding link selections/counts.

Changes

Cohort / File(s) Summary of Changes
Component import cleanup
components/links/links-table.tsx
Removed unused useSWR import while keeping mutate. No runtime logic changes.
Analytics API includes archived links
pages/api/analytics/index.ts
Removed top-level where: { isArchived: false } in the "links" query so archived links can be returned; other query structure unchanged.
Team document links API — import path & include archived links
pages/api/teams/[teamId]/documents/[id]/links.ts
Replaced relative authOptions import with project alias; removed isArchived: false filters from _count and nested links selections so all links are counted/returned (ordering retained).
Document overview API — include archived links in counts
pages/api/teams/[teamId]/documents/[id]/overview.ts
Changed _count.select.links from where: { isArchived: false } to links: true, so link counts include archived links.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant API as /api/analytics (links)
  participant DB as Database

  Client->>API: GET analytics?type=links
  API->>DB: Query links (no isArchived filter)
  DB-->>API: Links (archived + active) + nested view data
  API-->>Client: JSON response
  Note over API,DB: Archived links are now included
Loading
sequenceDiagram
  autonumber
  actor Client
  participant API as /api/teams/.../documents/.../links
  participant Auth as Auth (authOptions)
  participant DB as Database

  Client->>API: GET document links
  API->>Auth: Validate session (authOptions via alias)
  Auth-->>API: Session OK
  API->>DB: Fetch document with links (no isArchived filter), counts
  DB-->>API: Document + all links (ordered)
  API-->>Client: JSON response
  Note over API,DB: Counts and lists now include archived links
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 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: show archived links on documents" is a concise, single-sentence summary that directly describes the primary behavioral change in the diff—removing isArchived filters so archived links and their counts are included in document responses—making it accurate and clear for reviewers.
✨ 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/show-archived-links

📜 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 1bb44a0 and e131d12.

📒 Files selected for processing (1)
  • pages/api/teams/[teamId]/documents/[id]/overview.ts (1 hunks)
⏰ 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). (2)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
pages/api/teams/[teamId]/documents/[id]/overview.ts (1)

109-109: Semantics change: links count now includes archived; verify downstream usage (hasLinks/isEmpty/counts).

  • _count.links: true now treats archived links as present. This flips hasLinks and isEmpty when a doc only has archived links. Ensure the UI/empty‑state logic expects that.
  • counts.links now shows total links, while views still excludes archived. Confirm this intentional asymmetry.

If intended, consider a small inline note to avoid future regressions.

You could add a clarifying comment:

           _count: {
             select: {
-              links: true,
+              // Note: includes archived links by design
+              links: true,
               views: { where: { isArchived: false } },
             },
           },

Run to locate consumers that might rely on “active” link semantics:

Also applies to: 127-151, 167-169


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.

@vercel
Copy link

vercel bot commented Sep 24, 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 24, 2025 0:23am

_count: {
select: {
links: { where: { isArchived: false } },
links: true,
Copy link

@vercel vercel bot Sep 24, 2025

Choose a reason for hiding this comment

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

The count query and actual data query are now inconsistent - the count checks all links but the early return logic expects only non-archived links, potentially causing incorrect empty responses.

View Details
📝 Patch Details
diff --git a/pages/api/teams/[teamId]/documents/[id]/links.ts b/pages/api/teams/[teamId]/documents/[id]/links.ts
index c13d0689..e2f7e1e3 100644
--- a/pages/api/teams/[teamId]/documents/[id]/links.ts
+++ b/pages/api/teams/[teamId]/documents/[id]/links.ts
@@ -42,7 +42,7 @@ export default async function handle(
           ownerId: true,
           _count: {
             select: {
-              links: true,
+              links: { where: { isArchived: false } },
             },
           },
         },
@@ -64,6 +64,7 @@ export default async function handle(
           id: true,
           ownerId: true,
           links: {
+            where: { isArchived: false },
             orderBy: { createdAt: "desc" },
             include: {
               views: { orderBy: { viewedAt: "desc" } },

Analysis

Inconsistent link filtering returns archived links in /api/teams/[teamId]/documents/[id]/links

What fails: Document links API (pages/api/teams/[teamId]/documents/[id]/links.ts) returns archived links that should be filtered out, breaking consistency with other APIs

How to reproduce:

  1. Create a document with links
  2. Archive all links for that document
  3. Call GET /api/teams/{teamId}/documents/{docId}/links

Result: Returns archived links in response array instead of empty array

Expected: Should return empty array, consistent with other APIs like /api/revalidate.ts (line 81) and /api/teams/[teamId]/documents/[id]/overview.ts (line 109) which filter isArchived: false

Root cause: Count query counts all links but main query also fetches all links, creating inconsistent behavior where archived links are returned when they should be hidden

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: 0

Caution

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

⚠️ Outside diff range comments (1)
components/links/links-table.tsx (1)

121-124: Bug: links.sort mutates props; copy before sorting.

Sorting in place can cause subtle UI/state issues.

-    const sortedLinks = links.sort(
+    const sortedLinks = [...links].sort(
       (a, b) =>
         new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
     );
🧹 Nitpick comments (8)
components/links/links-table.tsx (6)

20-20: Prefer scoped mutate via useSWRConfig over global mutate.

Using the context-scoped mutate avoids surprises in multi-provider setups and aligns with SWR best practices.

-import { mutate } from "swr";
+import { useSWRConfig } from "swr";

Add inside the component (outside this range):

const { mutate } = useSWRConfig();

283-289: Use functional mutate to avoid stale updates.

Current updates rely on the props snapshot; can drop changes if cache diverges.

-    mutate(
+    mutate(
       `/api/teams/${currentTeamId}/${endpointTargetType}/${encodeURIComponent(
         link.documentId ?? link.dataroomId ?? "",
       )}/links`,
-      (links || []).concat(duplicatedLink),
-      false,
+      (current: LinkWithViews[] = []) => current.concat(duplicatedLink),
+      false,
     );
-      mutate(
+      mutate(
         `/api/teams/${currentTeamId}/${endpointTargetType}/${encodeURIComponent(
           duplicatedLink.documentId ?? duplicatedLink.dataroomId ?? "",
         )}/groups/${duplicatedLink.groupId}/links`,
-        groupLinks.concat(duplicatedLink),
-        false,
+        (current: LinkWithViews[] = []) =>
+          (current.length ? current : groupLinks).concat(duplicatedLink),
+        false,
       );

Also applies to: 295-301


521-527: Use functional mutate when archiving to ensure consistency.

Prevents race conditions with concurrent cache updates.

-      mutate(
+      mutate(
         `/api/teams/${currentTeamId}/${endpointTargetType}/${encodeURIComponent(
           targetId,
         )}/links`,
-        (links || []).map((link) => (link.id === linkId ? archivedLink : link)),
-        false,
+        (current: LinkWithViews[] = []) =>
+          current.map((l) => (l.id === linkId ? archivedLink : l)),
+        false,
       );
-        mutate(
+        mutate(
           `/api/teams/${currentTeamId}/${endpointTargetType}/${encodeURIComponent(
             archivedLink.documentId ?? archivedLink.dataroomId ?? "",
           )}/groups/${groupId}/links`,
-          groupLinks.map((link) => (link.id === linkId ? archivedLink : link)),
-          false,
+          (current: LinkWithViews[] = []) => {
+            const base = current.length ? current : groupLinks;
+            return base.map((l) => (l.id === linkId ? archivedLink : l));
+          },
+          false,
         );

Also applies to: 534-539


262-306: Prevent stuck loading state on duplicate errors.

Wrap in try/catch/finally; currently errors leave isLoading=true.

   const handleDuplicateLink = async (link: LinkWithViews) => {
     setIsLoading(true);
-
-    const response = await fetch(`/api/links/${link.id}/duplicate`, {
-      method: "POST",
-      headers: {
-        "Content-Type": "application/json",
-      },
-      body: JSON.stringify({
-        teamId: currentTeamId,
-      }),
-    });
-
-    if (!response.ok) {
-      throw new Error(`HTTP error! status: ${response.status}`);
-    }
-
-    const duplicatedLink = await response.json();
-    const endpointTargetType = `${targetType.toLowerCase()}s`; // "documents" or "datarooms"
-
-    // Update the duplicated link in the list of links
-    mutate(
-      `/api/teams/${currentTeamId}/${endpointTargetType}/${encodeURIComponent(
-        link.documentId ?? link.dataroomId ?? "",
-      )}/links`,
-      (links || []).concat(duplicatedLink),
-      false,
-    );
-
-    // Update the group-specific links cache if this is a group link
-    if (!!groupId) {
-      const groupLinks =
-        links?.filter((link) => link.groupId === groupId) || [];
-      mutate(
-        `/api/teams/${currentTeamId}/${endpointTargetType}/${encodeURIComponent(
-          duplicatedLink.documentId ?? duplicatedLink.dataroomId ?? "",
-        )}/groups/${duplicatedLink.groupId}/links`,
-        groupLinks.concat(duplicatedLink),
-        false,
-      );
-    }
-
-    toast.success("Link duplicated successfully");
-    setIsLoading(false);
+    try {
+      const response = await fetch(`/api/links/${link.id}/duplicate`, {
+        method: "POST",
+        headers: { "Content-Type": "application/json" },
+        body: JSON.stringify({ teamId: currentTeamId }),
+      });
+      if (!response.ok) {
+        throw new Error(`HTTP error! status: ${response.status}`);
+      }
+      const duplicatedLink = await response.json();
+      const endpointTargetType = `${targetType.toLowerCase()}s`;
+      mutate(
+        `/api/teams/${currentTeamId}/${endpointTargetType}/${encodeURIComponent(
+          link.documentId ?? link.dataroomId ?? "",
+        )}/links`,
+        (current: LinkWithViews[] = []) => current.concat(duplicatedLink),
+        false,
+      );
+      if (!!groupId) {
+        const groupLinks = links?.filter((l) => l.groupId === groupId) || [];
+        mutate(
+          `/api/teams/${currentTeamId}/${endpointTargetType}/${encodeURIComponent(
+            duplicatedLink.documentId ?? duplicatedLink.dataroomId ?? "",
+          )}/groups/${duplicatedLink.groupId}/links`,
+          (current: LinkWithViews[] = []) =>
+            (current.length ? current : groupLinks).concat(duplicatedLink),
+          false,
+        );
+      }
+      toast.success("Link duplicated successfully");
+    } catch (e) {
+      console.error(e);
+      toast.error("Failed to duplicate link");
+    } finally {
+      setIsLoading(false);
+    }
   };

833-840: Param naming mismatch confuses logic.

You pass checked (active) into a param named isArchived, then invert inside the API body. Rename for clarity to reduce future mistakes.

-  const handleArchiveLink = async (
-    linkId: string,
-    targetId: string,
-    isArchived: boolean,
-  ) => {
+  const handleArchiveLink = async (
+    linkId: string,
+    targetId: string,
+    nextActive: boolean,
+  ) => {
     ...
-        body: JSON.stringify({
-          isArchived: !isArchived,
-        }),
+        body: JSON.stringify({ isArchived: !nextActive }),
     ...
-      toast.success(
-        !isArchived ? "Link successfully archived" : "Link successfully reactivated",
-      );
+      toast.success(nextActive ? "Link successfully reactivated" : "Link successfully archived");
-                              onCheckedChange={(checked) =>
+                              onCheckedChange={(checked) =>
                                 handleArchiveLink(
                                   link.id,
                                   link.documentId ?? link.dataroomId ?? "",
-                                  checked,
+                                  checked,
                                 )
                               }

Also applies to: 495-500


106-107: Minor: avoid invalidating memo every render with now dependency.

Either compute now inside the memo or make it stable (e.g., useRef) since there’s no timer-driven refresh.

Also applies to: 117-143

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

91-121: N+1 queries for tags will scale poorly; batch-fetch tags.

For many links (now including archived), this will be slow. Consider a single query for all linkIds and grouping on the app side.

Example approach (sketch):

const linkIds = docWithLinks!.links.map(l => l.id);
const tags = await prisma.tag.findMany({
  where: { items: { some: { linkId: { in: linkIds }, itemType: "LINK_TAG" } } },
  select: { id: true, name: true, color: true, description: true, items: { select: { linkId: true } } },
});
const tagsByLinkId = new Map<string, typeof tags>();
for (const t of tags) {
  for (const it of t.items) {
    const arr = tagsByLinkId.get(it.linkId) ?? [];
    arr.push({ id: t.id, name: t.name, color: t.color, description: t.description });
    tagsByLinkId.set(it.linkId, arr);
  }
}
links = links.map(link => ({ ...link, tags: tagsByLinkId.get(link.id) ?? [] }));

95-97: Reassess returning decrypted passwords to clients.

Even with team membership checks, returning plaintext passwords increases risk. Prefer server-side-only comparisons or return a boolean that password is set.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da25b3 and 1bb44a0.

📒 Files selected for processing (3)
  • components/links/links-table.tsx (1 hunks)
  • pages/api/analytics/index.ts (0 hunks)
  • pages/api/teams/[teamId]/documents/[id]/links.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • pages/api/analytics/index.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). (2)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
components/links/links-table.tsx (1)

26-26: LGTM: utils import.

Looks correct; all imported symbols are used.

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

45-46: Inclusion of archived links: align consumers and expectations.

Count/select now include archived links. Confirm all consumers (UI, analytics, exports) expect archived records.

Also applies to: 66-85


3-3: Alias path verified — import resolves.
pages/api/auth/[...nextauth].ts exports authOptions (export const authOptions at line 38) and tsconfig.json maps "@/" → ["./"], so "@/pages/api/auth/[...nextauth]" resolves.

@mfts mfts merged commit 286ff88 into main Sep 24, 2025
10 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Sep 24, 2025
@mfts mfts deleted the feat/show-archived-links branch November 19, 2025 11:46
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