Skip to content

Optimize getProjects action with pagination and reduced column selection - #116

Merged
fehranbit merged 5 commits into
mainfrom
perf/optimize-get-projects
Dec 5, 2025
Merged

fehranbit merged 5 commits into
mainfrom
perf/optimize-get-projects

Conversation

@akhun-web3

@akhun-web3 akhun-web3 commented Dec 5, 2025 •

Copy link
Copy Markdown
Collaborator

Overview: This PR optimizes the getProjects action by implementing pagination defaults and minimizing selected columns to improve performance and reduce database load.

Changes

  • Introduced default pagination parameters (take, skip) to the getProjects function in src/actions/projects/getProjects.ts.
  • Updated the Prisma query to explicitly select only necessary columns, avoiding full-table scans.
  • Ensured these changes maintain the existing API contract, making them a safe, incremental improvement.

Summary by CodeRabbit

  • New Features

    • Project listings now support pagination with configurable page and page-size and return total counts.
    • Exports fetch all pages so CSV/PDF include the full project set.
  • Improvements

    • Search and type filters preserved; results ordered by newest first.
    • CSV export now safely escapes values (dates, quotes, formula-like prefixes) for reliable downloads.
  • Compatibility

    • Data-fetching callers updated to the new paginated response shape.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 5, 2025 •

Copy link
Copy Markdown

Walkthrough

getProjects was rewritten to accept pagination (page, perPage), normalize and bound inputs, build a filtered where clause, run parallel findMany/count queries, and return { projects: ProjectListItem[]; total }. Call sites and the export helper were updated to call getProjects({ userId, ... }) and consume the new response shape.

Changes

Cohort / File(s) Change Summary
Projects action
src/actions/projects/getProjects.ts
Added exported ProjectListItem; extended params with page/perPage (rawPage/rawPerPage), normalized/enforced bounds, computed skip/take, built where clause (userId, optional case-insensitive title, optional type), executed parallel findMany (selecting list fields) and count, and changed return type to Result<{ projects: ProjectListItem[]; total: number }>
App route / SSR page
src/app/(general)/page.tsx
Updated getProjects call from getProjects(userId) to getProjects({ userId }); adapted response destructuring to { projects: initialProjects, total } and passed initialProjects to the UI
Home page container
src/components/pages/home/HomePageContainer.tsx
Updated fetch to getProjects({ userId }); destructured { projects: fetchedProjects, total } on success and set state from fetchedProjects (total parsed but unused)
Export helper
src/lib/export.ts
Imported ProjectListItem; changed exportProjects signature to exportProjects(userId, format); implemented looped pagination to fetch all pages via getProjects({ userId, page, perPage }), accumulated ProjectListItem[], added escapeCsvValue helper, and rebuilt CSV header/row generation using accumulated results

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant ServerAction as getProjects
    participant Database as Prisma

    Browser->>ServerAction: call getProjects({ userId, search?, type?, page?, perPage? })
    ServerAction->>ServerAction: normalize page/perPage, build where, compute skip/take
    par Queries
        ServerAction->>Database: findMany(select, where, skip, take)
        ServerAction->>Database: count(where)
    end
    Database-->>ServerAction: projects[], total
    ServerAction-->>Browser: Result<{ projects, total }>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • src/actions/projects/getProjects.ts — pagination math, perPage caps, where-clause correctness, and selected fields matching ProjectListItem.
    • src/lib/export.ts — pagination loop termination, successive page fetch error handling, and CSV-escaping correctness.
    • Call-site updates in src/app/(general)/page.tsx and src/components/pages/home/HomePageContainer.tsx for correct destructuring and types.

Possibly related PRs

Suggested reviewers

  • fehranbit

Poem

🐇 I hopped through pages, one by one,

I skipped and took until it was done.
Projects in rows, neat as a comb,
CSV carrots carried me home.
A tiny thump — the job is done! 🥕

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 pull request title accurately describes the main changes: pagination is added to getProjects and column selection is reduced to ProjectListItem fields.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perf/optimize-get-projects

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5e5233 and 8a41239.

📒 Files selected for processing (1)
  • src/actions/projects/getProjects.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/actions/projects/getProjects.ts (2)
src/lib/result.ts (2)
  • Result (18-18)
  • success (25-30)
src/lib/prisma.ts (1)
  • prisma (14-18)
🔇 Additional comments (2)
src/actions/projects/getProjects.ts (2)

15-24: LGTM on the where clause construction.

The conditional filter logic using spread operators is clean and correct. The case-insensitive search mode is appropriate for user-facing search functionality.


35-35: LGTM on the return statement.

Correctly uses the success helper and matches the updated return type.

Comment thread src/actions/projects/getProjects.ts
Comment thread src/actions/projects/getProjects.ts Outdated
orderBy: { createdAt: 'desc' }
});
return success(projects);
export async function getProjects({ userId, search, type, page = 1, perPage = 10 }: GetProjectsParams): Promise<Result<{ projects: Project[]; total: number }>> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all imports and calls to getProjects to verify breaking change handling

echo "=== Searching for getProjects imports ==="
rg -n --type=ts --type=tsx "import.*getProjects" -A 2

echo ""
echo "=== Searching for getProjects function calls ==="
rg -n --type=ts --type=tsx "getProjects\s*\(" -B 2 -A 5

Repository: Authormaton/authormaton

Length of output: 231


🏁 Script executed:

#!/bin/bash
# Find all imports and calls to getProjects - adjusted for environment

echo "=== Searching for getProjects imports ==="
rg -n "import.*getProjects" --type=ts -A 2

echo ""
echo "=== Searching for getProjects function calls ==="
rg -n "getProjects\s*\(" --type=ts -B 2 -A 5

echo ""
echo "=== Broader search for getProjects ==="
rg -n "getProjects" --type=ts

Repository: Authormaton/authormaton

Length of output: 3260


🏁 Script executed:

#!/bin/bash
# Examine each caller in detail

echo "=== src/lib/export.ts context ==="
sed -n '1,15p' src/lib/export.ts

echo ""
echo "=== src/app/(general)/page.tsx context ==="
sed -n '1,30p' src/app/\(general\)/page.tsx

echo ""
echo "=== src/components/pages/home/HomePageContainer.tsx context ==="
sed -n '1,30p' src/components/pages/home/HomePageContainer.tsx

echo ""
echo "=== Check getProjects function signature and return handling ==="
sed -n '1,40p' src/actions/projects/getProjects.ts

Repository: Authormaton/authormaton

Length of output: 4004


Fix critical parameter passing and return type handling errors in all callers of getProjects.

The function signature requires an object parameter { userId, search?, type?, page?, perPage? }, but callers are passing arguments incorrectly:

  • src/lib/export.ts:4 calls getProjects() with no arguments (userId is required) and then accesses result.data as an array, but the new return type is { projects: Project[]; total: number }
  • src/app/(general)/page.tsx:19 calls getProjects(userId) but should pass { userId } object, and incorrectly accesses result.data as an array instead of destructuring { projects } from the object
  • src/components/pages/home/HomePageContainer.tsx:23 calls fetchProjects(userId) but should pass { userId } object, and incorrectly accesses result.data as an array

Update all three call sites to pass the correct object parameter and handle the new return structure properly.

🤖 Prompt for AI Agents
In src/actions/projects/getProjects.ts around line 13, the function expects a
single object parameter { userId, search?, type?, page?, perPage? } and returns
Result<{ projects: Project[]; total: number }>, so update all callers: in
src/lib/export.ts (line ~4) change the call from getProjects() to pass a valid
object including userId (e.g., getProjects({ userId })) and handle the return by
destructuring or accessing result.data.projects and result.data.total; in
src/app/(general)/page.tsx (line ~19) change getProjects(userId) to
getProjects({ userId }) and replace uses of result.data as an array with const {
projects, total } = result.data (or use result.data.projects); and in
src/components/pages/home/HomePageContainer.tsx (line ~23) change
fetchProjects(userId) to fetchProjects({ userId }) (or adapt the wrapper to
accept the object) and update code to read projects from result.data.projects
(and total from result.data.total) instead of treating result.data as an array.

Comment thread src/actions/projects/getProjects.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/components/pages/home/HomePageContainer.tsx (1)

21-25: Client usage of getProjects looks correct; consider dropping unused total.

The switch to fetchProjects({ userId }) and destructuring { projects: fetchedProjects } aligns with the new getProjects signature and payload shape.

If you don’t plan to use total here, you can omit it from the destructuring to avoid unused‑variable lint noise:

-      const result = await fetchProjects({ userId });
-      if (result.success) {
-        const { projects: fetchedProjects, total } = result.data;
-        setProjects(fetchedProjects);
+      const result = await fetchProjects({ userId });
+      if (result.success) {
+        const { projects: fetchedProjects } = result.data;
+        setProjects(fetchedProjects);
       }
src/app/(general)/page.tsx (1)

19-31: New getProjects call/shape wiring is correct; unused total can be dropped.

The adaptation to getProjects({ userId }) and destructuring { projects: initialProjects } fits the updated Result payload and keeps the data flow into HomePageContainer intact.

Unless you plan to surface pagination metadata on this page, you can skip total in the destructuring to keep things cleaner:

-  const { projects: initialProjects, total } = projectsResult.data;
+  const { projects: initialProjects } = projectsResult.data;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a41239 and 07df49a.

📒 Files selected for processing (4)
  • src/actions/projects/getProjects.ts (1 hunks)
  • src/app/(general)/page.tsx (2 hunks)
  • src/components/pages/home/HomePageContainer.tsx (1 hunks)
  • src/lib/export.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/lib/export.ts (1)
src/actions/projects/getProjects.ts (1)
  • getProjects (13-48)
src/actions/projects/getProjects.ts (2)
src/lib/result.ts (2)
  • Result (18-18)
  • success (25-30)
src/lib/prisma.ts (1)
  • prisma (14-18)
src/app/(general)/page.tsx (2)
src/lib/async.ts (1)
  • safeAwait (3-10)
src/actions/projects/getProjects.ts (1)
  • getProjects (13-48)

Comment thread src/actions/projects/getProjects.ts
Comment thread src/actions/projects/getProjects.ts
Comment thread src/lib/export.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/lib/export.ts (1)

3-22: Avoid repeated concat in the pagination loop to reduce copying overhead

The pagination logic looks sound, but allProjects = allProjects.concat(result.data.projects); reallocates and copies the entire array on every page, which becomes O(n²) for large exports. You can keep the same behavior with linear cost by pushing into the existing array:

-    allProjects = allProjects.concat(result.data.projects);
+    allProjects.push(...result.data.projects);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07df49a and 1e62c30.

📒 Files selected for processing (2)
  • src/actions/projects/getProjects.ts (1 hunks)
  • src/lib/export.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/actions/projects/getProjects.ts (2)
src/lib/result.ts (2)
  • Result (18-18)
  • success (25-30)
src/lib/prisma.ts (1)
  • prisma (14-18)
src/lib/export.ts (1)
src/actions/projects/getProjects.ts (2)
  • ProjectListItem (5-12)
  • getProjects (22-57)

Comment thread src/actions/projects/getProjects.ts
Comment thread src/lib/export.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e62c30 and 7aad7ca.

📒 Files selected for processing (2)
  • src/actions/projects/getProjects.ts (1 hunks)
  • src/lib/export.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/lib/export.ts (1)
src/actions/projects/getProjects.ts (2)
  • ProjectListItem (5-12)
  • getProjects (22-57)
🪛 Biome (2.1.2)
src/lib/export.ts

[error] 13-13: unterminated string literal

The closing quote must be on the same line.

(parse)

🔇 Additional comments (3)
src/actions/projects/getProjects.ts (1)

1-57: LGTM! All previous concerns have been properly addressed.

The implementation successfully incorporates pagination with type-safe normalization, reduced column selection via ProjectListItem, and parallel queries. The code handles edge cases (NaN, undefined, invalid ranges) correctly and maintains backward compatibility through the Result wrapper.

src/lib/export.ts (2)

19-38: Pagination loop correctly fetches all projects for export.

The implementation successfully addresses the previous concern about only exporting the first page. The loop continues until all projects are fetched (fetchedCount < total), ensuring complete data export.


42-52: CSV generation properly uses escaping and handles empty projects.

The CSV generation correctly applies the escapeCsvValue helper to both headers and row values, and includes a sensible fallback for empty project lists. The type-safe access via keyof ProjectListItem is appropriate.

Comment thread src/lib/export.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/lib/export.ts (2)

19-38: Pagination loop correctly fetches all projects; consider a small efficiency tweak

The do/while with fetchedCount < total safely fetches all pages (including the 0‑projects case) and restores full‑export behavior. You can avoid repeated array reallocation by pushing in place:

-    allProjects = allProjects.concat(result.data.projects);
+    allProjects.push(...result.data.projects);

Optionally, you could also only assign total from the first page, since it should remain constant.


42-48: CSV header/row generation is correct; tighten typing and consider a typed header constant

Using Object.keys(allProjects[0]) and the escapeCsvValue helper yields well‑formed CSV and keeps headers/data aligned. To catch future schema drift at compile time (esp. the fallback branch), you could make the headers explicitly typed:

const DEFAULT_HEADERS: (keyof ProjectListItem)[] = ['id', 'title', 'createdAt', 'updatedAt', 'type', 'userId'];

const headers: (keyof ProjectListItem)[] =
  allProjects.length > 0 ? (Object.keys(allProjects[0]) as (keyof ProjectListItem)[]) : DEFAULT_HEADERS;

and then drop the casts at the usage sites.

Also applies to: 52-52

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7aad7ca and 0fc8f16.

📒 Files selected for processing (1)
  • src/lib/export.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/lib/export.ts (1)
src/actions/projects/getProjects.ts (2)
  • ProjectListItem (5-12)
  • getProjects (22-57)
🔇 Additional comments (1)
src/lib/export.ts (1)

3-17: CSV escaping helper correctly handles dates, quotes, and CSV/Excel injection vectors

This implementation covers the key edge cases (Date normalization, quote escaping, and guarding against = + - @ prefixes) and is a substantial improvement over the previous naive join approach.

@fehranbit
fehranbit merged commit d399acb into main Dec 5, 2025
1 of 2 checks passed
@fehranbit
fehranbit deleted the perf/optimize-get-projects branch December 5, 2025 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants