Skip to content

Feat/set row collapsing threshold#1127

Open
lissavxo wants to merge 2 commits into
masterfrom
feat/set-row-collapsing-threshold
Open

Feat/set row collapsing threshold#1127
lissavxo wants to merge 2 commits into
masterfrom
feat/set-row-collapsing-threshold

Conversation

@lissavxo

@lissavxo lissavxo commented Mar 19, 2026

Copy link
Copy Markdown
Collaborator

Related to #953

Description

Allow users to adjust the CSV collapse threshold directly from account settings.

  • Added a csvCollapseThreshold parameter to the UserProfile

  • Added an input field in account settings to configure csvCollapseThreshold

  • Updated CSV generation logic to use the csvCollapseThreshold value from UserProfile

Test plan

  1. Go to Payments and generate a CSV file
  2. Navigate to Account Settings and update the CSV Collapse Threshold value
  3. Return to Payments and generate a new CSV
  4. Verify that the collapsing behavior reflects the updated threshold value

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a CSV Collapse Threshold setting to the Account page, letting you update the threshold used when downloading transaction CSVs.
  • Improvements
    • Added input validation and inline feedback (success/error) when saving the new threshold.
  • UI Updates
    • Included dedicated styling for the new threshold control, with dark-theme support.
  • Maintenance
    • Added server support and data persistence for the new setting, plus updated related tests.

@coderabbitai

coderabbitai Bot commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

A new persisted CSV collapse threshold setting is added, exposed in the Account page, updated through a new API route, and threaded into CSV download calls.

Changes

CSV collapse threshold

Layer / File(s) Summary
Schema and fixture updates
prisma-local/schema.prisma, prisma-local/migrations/20260317024924_csv_collapse_threshold/migration.sql, tests/mockedObjects.ts
UserProfile gains csvCollapseThreshold with default 1, the migration adds the column, and mock user profiles include the new field.
Threshold update API
pages/api/user/csvCollapseThreshold/index.ts, services/userService.ts
The new PUT route validates csvCollapseThreshold, updates the stored value through userService, and returns method-not-allowed for other verbs.
Account page threshold editor
pages/account/index.tsx, components/Account/ChangeCsvCollapseThreshold.tsx, components/Account/account.module.css
The Account page renders a threshold editor component with validation, submit handling, and styling for the new row and input.
CSV download parameter wiring
pages/api/paybutton/download/transactions/[paybuttonId].ts, pages/api/payments/download/index.ts
CSV download handlers pass csvCollapseThreshold and the extra boolean argument into downloadTxsFile alongside the existing collapse option.

Sequence Diagram(s)

sequenceDiagram
  participant AccountPage
  participant CsvThresholdAPI
  participant userService
  participant PrismaDB

  AccountPage->>CsvThresholdAPI: PUT csvCollapseThreshold
  CsvThresholdAPI->>CsvThresholdAPI: setSession(req, res, true)
  CsvThresholdAPI->>CsvThresholdAPI: validate body value
  CsvThresholdAPI->>userService: updateCsvCollapseThreshold(userId, threshold)
  userService->>PrismaDB: prisma.userProfile.update(...)
  PrismaDB-->>userService: update result
  userService-->>CsvThresholdAPI: resolved
  CsvThresholdAPI-->>AccountPage: 200 success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Klakurka

Poem

🐰 I hopped through forms and fields today,
A threshold learned a number’s way.
Rows may fold or stand in line,
And downloads now know where to align.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed It concisely describes the main change: setting the row-collapsing threshold.
Description check ✅ Passed It includes the required Related, Description, and Test plan sections and matches the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/set-row-collapsing-threshold

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.23.0)
pages/api/user/csvCollapseThreshold/index.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.50][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

services/userService.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.25][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

tests/mockedObjects.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.30][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

🔧 SQLFluff (4.2.2)
prisma-local/migrations/20260317024924_csv_collapse_threshold/migration.sql
🔧 Stylelint (17.13.0)
components/Account/account.module.css

Error: ENOENT: no such file or directory, open '/.stylelintrc.json'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async #readConfiguration (/usr/local/lib/node_modules/stylelint/node_modules/cosmiconfig/dist/Explorer.js:83:26)
at async load (/usr/local/lib/node_modules/stylelint/node_modules/cosmiconfig/dist/Explorer.js:20:48)
at async Explorer.load (/usr/local/lib/node_modules/stylelint/node_modules/cosmiconfig/dist/Explorer.js:23:20)
at async getConfigForFile (file:///usr/local/lib/node_modules/stylelint/lib/getConfigForFile.mjs:72:5)
at async resolveOptionValue (file:///usr/local/lib/node_modules/stylelint/lib/utils/resolveOptionValue.mjs:27:24)
at async standalone (file:///usr/local/lib/node_modules/stylelint/lib/standalone.mjs:127:22)

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

components/Account/account.module.css

Parsing error: Unexpected token .


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (4)
services/userService.ts (1)

159-166: Add a small service-layer guard for invalid threshold values.

This method currently trusts callers completely. Adding an integer/non-negative check here prevents bad writes from any non-API caller path.

Proposed hardening
 export async function updateCsvCollapseThreshold (id: string, csvCollapseThreshold: number): Promise<void> {
+  if (!Number.isInteger(csvCollapseThreshold) || csvCollapseThreshold < 0) {
+    throw new Error(RESPONSE_MESSAGES.BAD_REQUEST_400.message)
+  }
   await prisma.userProfile.update({
     where: { id },
     data: {
       csvCollapseThreshold
     }
   })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/userService.ts` around lines 159 - 166, The
updateCsvCollapseThreshold service blindly writes any number; add a guard in
updateCsvCollapseThreshold to validate the input is an integer and non-negative
before calling prisma.userProfile.update: check
Number.isInteger(csvCollapseThreshold) && csvCollapseThreshold >= 0 (or
coerce/convert input and then validate), and if the check fails throw a
descriptive error (e.g., InvalidArgumentError / new Error('csvCollapseThreshold
must be a non-negative integer')) so bad values are rejected at the service
layer rather than persisted by prisma.userProfile.update.
tests/mockedObjects.ts (1)

531-533: Consider keeping base fixture defaults aligned with production defaults.

Using csvCollapseThreshold: 0 in shared base profiles can silently alter CSV behavior in unrelated tests. Prefer 1 in base fixtures and override to 0 only in edge-case-specific tests.

Fixture alignment tweak
-  csvCollapseThreshold: 0
+  csvCollapseThreshold: 1
...
-  csvCollapseThreshold: 0
+  csvCollapseThreshold: 1

Also applies to: 547-549

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mockedObjects.ts` around lines 531 - 533, Update the shared base
fixture in tests/mockedObjects.ts so the csvCollapseThreshold default is aligned
with production by setting csvCollapseThreshold to 1 (instead of 0) in the base
profile object (the block that contains proUntil and csvCollapseThreshold), and
only set csvCollapseThreshold: 0 in individual edge-case tests where collapsing
must be disabled; adjust any other base-profile occurrences (e.g., the similar
block around lines 547-549) to the same default to avoid silently changing CSV
behavior across tests.
components/Account/ChangeCsvCollapseThreshold.tsx (1)

39-41: Clear the success timer on unmount to avoid stale state updates.

The timeout callback can run after unmount. Add timer cleanup for reliability.

Proposed fix
-import React, { ReactElement, useState } from 'react'
+import React, { ReactElement, useEffect, useRef, useState } from 'react'
@@
 export default function ChangeCsvCollapseThreshold ({ csvCollapseThreshold }: IProps): ReactElement {
+  const successTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
@@
+  useEffect(() => {
+    return () => {
+      if (successTimerRef.current !== null) {
+        clearTimeout(successTimerRef.current)
+      }
+    }
+  }, [])
@@
-        setTimeout(() => {
+        if (successTimerRef.current !== null) clearTimeout(successTimerRef.current)
+        successTimerRef.current = setTimeout(() => {
           setSuccess('')
         }, 3000)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Account/ChangeCsvCollapseThreshold.tsx` around lines 39 - 41, The
success timeout in the ChangeCsvCollapseThreshold component currently calls
setTimeout to clear setSuccess('') but doesn't clear it on unmount; modify the
effect (or wherever setTimeout is called) to capture the timer id (const timer =
window.setTimeout(...)) and return a cleanup that calls clearTimeout(timer) so
the callback can't run after the component unmounts; ensure the timer id type
matches the environment (use number from window.setTimeout or NodeJS.Timeout in
server contexts) and set timer to null after clearing if using state/refs.
pages/api/user/csvCollapseThreshold/index.ts (1)

26-27: Add a defensive guard for missing session.userId before Prisma update.

If session is unavailable, this currently falls through to a DB error path instead of a clear auth response.

Proposed fix
   if (req.method === 'PUT') {
     const session = req.session
+    if (!session?.userId) {
+      res.status(401).json({ message: 'Unauthorized' })
+      return
+    }
     const { csvCollapseThreshold } = req.body
@@
-    await userService.updateCsvCollapseThreshold(session.userId, threshold)
+    await userService.updateCsvCollapseThreshold(session.userId, threshold)
     res.status(200).json({ success: true })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/api/user/csvCollapseThreshold/index.ts` around lines 26 - 27, Add a
defensive guard that checks for a valid session and session.userId before
calling userService.updateCsvCollapseThreshold; if session or session.userId is
missing, immediately respond with an auth error (e.g., res.status(401).json({
success: false, error: 'Unauthorized' })) and return so you never call
userService.updateCsvCollapseThreshold with an undefined id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/Account/ChangeCsvCollapseThreshold.tsx`:
- Around line 18-19: The input parsing currently uses parseInt (e.g., in
ChangeCsvCollapseThreshold’s handling of inputValue) which silently truncates
decimals; replace parseInt usage with Number() to parse the value and then
validate using Number.isInteger(newThreshold) and newThreshold >= 0, rejecting
NaN or non-integer (decimal) values explicitly (apply the same change for the
other occurrence referenced at the second spot around the lines corresponding to
the existing parseInt usage), and update any validation/error paths to reflect
"must be a non-negative integer" instead of relying on parseInt truncation.
- Around line 74-80: Change the input in the ChangeCsvCollapseThreshold
component to be an accessible numeric control: replace type="text" with
type="number", add step="1" and keep min="0" so HTML5 numeric validation works,
and provide an associated label element referencing id="csvCollapseThreshold"
(or add aria-label if a visible label isn't desired) to ensure accessibility;
update any related handlers like handleInputChange to handle numeric values
(parseInt/Number) from the inputValue before using it.

In `@pages/api/user/csvCollapseThreshold/index.ts`:
- Around line 15-23: The current validation for csvCollapseThreshold (variable
csvCollapseThreshold and computed threshold) is too permissive; change it to
first reject empty or whitespace-only string inputs (if typeof
csvCollapseThreshold === 'string' check trim().length === 0) then parse to a
number and validate using Number.isFinite(threshold) and
Number.isInteger(threshold) and threshold >= 0; return 400 with an appropriate
message if any of these checks fail so decimals, Infinity/NaN, and blank inputs
are rejected before persisting the Int.

---

Nitpick comments:
In `@components/Account/ChangeCsvCollapseThreshold.tsx`:
- Around line 39-41: The success timeout in the ChangeCsvCollapseThreshold
component currently calls setTimeout to clear setSuccess('') but doesn't clear
it on unmount; modify the effect (or wherever setTimeout is called) to capture
the timer id (const timer = window.setTimeout(...)) and return a cleanup that
calls clearTimeout(timer) so the callback can't run after the component
unmounts; ensure the timer id type matches the environment (use number from
window.setTimeout or NodeJS.Timeout in server contexts) and set timer to null
after clearing if using state/refs.

In `@pages/api/user/csvCollapseThreshold/index.ts`:
- Around line 26-27: Add a defensive guard that checks for a valid session and
session.userId before calling userService.updateCsvCollapseThreshold; if session
or session.userId is missing, immediately respond with an auth error (e.g.,
res.status(401).json({ success: false, error: 'Unauthorized' })) and return so
you never call userService.updateCsvCollapseThreshold with an undefined id.

In `@services/userService.ts`:
- Around line 159-166: The updateCsvCollapseThreshold service blindly writes any
number; add a guard in updateCsvCollapseThreshold to validate the input is an
integer and non-negative before calling prisma.userProfile.update: check
Number.isInteger(csvCollapseThreshold) && csvCollapseThreshold >= 0 (or
coerce/convert input and then validate), and if the check fails throw a
descriptive error (e.g., InvalidArgumentError / new Error('csvCollapseThreshold
must be a non-negative integer')) so bad values are rejected at the service
layer rather than persisted by prisma.userProfile.update.

In `@tests/mockedObjects.ts`:
- Around line 531-533: Update the shared base fixture in tests/mockedObjects.ts
so the csvCollapseThreshold default is aligned with production by setting
csvCollapseThreshold to 1 (instead of 0) in the base profile object (the block
that contains proUntil and csvCollapseThreshold), and only set
csvCollapseThreshold: 0 in individual edge-case tests where collapsing must be
disabled; adjust any other base-profile occurrences (e.g., the similar block
around lines 547-549) to the same default to avoid silently changing CSV
behavior across tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 95c08745-b5d8-43aa-988d-626badf50bae

📥 Commits

Reviewing files that changed from the base of the PR and between f1cc9f2 and 56ef258.

📒 Files selected for processing (10)
  • components/Account/ChangeCsvCollapseThreshold.tsx
  • components/Account/account.module.css
  • pages/account/index.tsx
  • pages/api/paybutton/download/transactions/[paybuttonId].ts
  • pages/api/payments/download/index.ts
  • pages/api/user/csvCollapseThreshold/index.ts
  • prisma-local/migrations/20260317024924_csv_collapse_threshold/migration.sql
  • prisma-local/schema.prisma
  • services/userService.ts
  • tests/mockedObjects.ts

Comment on lines +18 to +19
const newThreshold = parseInt(inputValue, 10)
if (isNaN(newThreshold) || newThreshold < 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -i "ChangeCsvCollapseThreshold"

Repository: PayButton/paybutton-server

Length of output: 119


🏁 Script executed:

fd -i "changecsvcollapsethreshold" --type f

Repository: PayButton/paybutton-server

Length of output: 119


🏁 Script executed:

cat -n components/Account/ChangeCsvCollapseThreshold.tsx

Repository: PayButton/paybutton-server

Length of output: 3610


🏁 Script executed:

node <<'EOF'
// Verify parseInt behavior
console.log("parseInt('1.5', 10):", parseInt('1.5', 10));
console.log("parseInt('12abc', 10):", parseInt('12abc', 10));
console.log("parseInt('abc', 10):", parseInt('abc', 10));
console.log("isNaN(parseInt('abc', 10)):", isNaN(parseInt('abc', 10)));
console.log("isNaN(parseInt('1.5', 10)):", isNaN(parseInt('1.5', 10)));
console.log("Number.isInteger(Number('1.5')):", Number.isInteger(Number('1.5')));
console.log("Number.isInteger(Number('12')):", Number.isInteger(Number('12')));
EOF

Repository: PayButton/paybutton-server

Length of output: 286


parseInt silently truncates decimal input; use strict integer validation instead.

Values like 1.5 are accepted by isNaN() and truncated to 1 without warning. Use Number() with Number.isInteger() to enforce strict integer validation and reject decimal input explicitly.

Proposed fix
-    const newThreshold = parseInt(inputValue, 10)
-    if (isNaN(newThreshold) || newThreshold < 0) {
+    const normalized = inputValue.trim()
+    const newThreshold = Number(normalized)
+    if (normalized === '' || !Number.isInteger(newThreshold) || newThreshold < 0) {
       setError('Please enter a valid non-negative number')
       return
     }
@@
-    const numValue = parseInt(value, 10)
-    if (!isNaN(numValue) && numValue >= 0 && numValue !== threshold) {
+    const normalized = value.trim()
+    const numValue = Number(normalized)
+    if (normalized !== '' && Number.isInteger(numValue) && numValue >= 0 && numValue !== threshold) {
       setDisabled(false)
       setError('')

Also applies to: lines 56-57

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Account/ChangeCsvCollapseThreshold.tsx` around lines 18 - 19, The
input parsing currently uses parseInt (e.g., in ChangeCsvCollapseThreshold’s
handling of inputValue) which silently truncates decimals; replace parseInt
usage with Number() to parse the value and then validate using
Number.isInteger(newThreshold) and newThreshold >= 0, rejecting NaN or
non-integer (decimal) values explicitly (apply the same change for the other
occurrence referenced at the second spot around the lines corresponding to the
existing parseInt usage), and update any validation/error paths to reflect "must
be a non-negative integer" instead of relying on parseInt truncation.

Comment on lines +74 to +80
<input
id="csvCollapseThreshold"
type="text"
min="0"
required
value={inputValue}
onChange={handleInputChange}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd "ChangeCsvCollapseThreshold.tsx" --type f

Repository: PayButton/paybutton-server

Length of output: 119


🏁 Script executed:

wc -l components/Account/ChangeCsvCollapseThreshold.tsx

Repository: PayButton/paybutton-server

Length of output: 122


🏁 Script executed:

cat -n components/Account/ChangeCsvCollapseThreshold.tsx

Repository: PayButton/paybutton-server

Length of output: 3610


Add an accessible label and use a numeric input type.

The input currently lacks an accessible label and type="text" makes the min="0" attribute ineffective. With manual validation expecting an integer, type="number" is more semantically appropriate and enables proper HTML5 validation.

Proposed fix
         <div className={style.threshold_row}>
+          <label htmlFor="csvCollapseThreshold">CSV collapse threshold</label>
           <input
             id="csvCollapseThreshold"
-            type="text"
+            type="number"
             min="0"
+            step="1"
+            inputMode="numeric"
             required
             value={inputValue}
             onChange={handleInputChange}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Account/ChangeCsvCollapseThreshold.tsx` around lines 74 - 80,
Change the input in the ChangeCsvCollapseThreshold component to be an accessible
numeric control: replace type="text" with type="number", add step="1" and keep
min="0" so HTML5 numeric validation works, and provide an associated label
element referencing id="csvCollapseThreshold" (or add aria-label if a visible
label isn't desired) to ensure accessibility; update any related handlers like
handleInputChange to handle numeric values (parseInt/Number) from the inputValue
before using it.

Comment on lines +15 to +23
if (csvCollapseThreshold === undefined || csvCollapseThreshold === null) {
res.status(400).json({ message: 'csvCollapseThreshold is required' })
return
}

const threshold = Number(csvCollapseThreshold)
if (isNaN(threshold) || threshold < 0) {
res.status(400).json({ message: 'csvCollapseThreshold must be a non-negative number' })
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify JS coercion edge-cases that affect current validation logic.
node - <<'NODE'
const inputs = ['', '   ', '1.5', 'Infinity', null, undefined, '12abc'];
for (const v of inputs) {
  console.log(JSON.stringify(v), '=> Number:', Number(v), 'isFinite:', Number.isFinite(Number(v)));
}
NODE

Repository: PayButton/paybutton-server

Length of output: 330


🏁 Script executed:

find . -type f -name "*.ts" -path "*/pages/api/user/csvCollapseThreshold*" | head -20

Repository: PayButton/paybutton-server

Length of output: 116


🏁 Script executed:

cat -n pages/api/user/csvCollapseThreshold/index.ts

Repository: PayButton/paybutton-server

Length of output: 1301


🏁 Script executed:

# Check the userService to understand what updateCsvCollapseThreshold expects
find . -type f -name "userService.ts" | head -5

Repository: PayButton/paybutton-server

Length of output: 95


🏁 Script executed:

cat -n services/userService.ts | head -100

Repository: PayButton/paybutton-server

Length of output: 4017


🏁 Script executed:

# Search for the updateCsvCollapseThreshold function specifically
rg -A 15 "updateCsvCollapseThreshold" services/userService.ts

Repository: PayButton/paybutton-server

Length of output: 520


🏁 Script executed:

# Check if there's a Prisma schema that defines csvCollapseThreshold type
find . -type f \( -name "schema.prisma" -o -name "*.prisma" \)

Repository: PayButton/paybutton-server

Length of output: 98


🏁 Script executed:

rg -A 3 -B 3 "csvCollapseThreshold" ./prisma-local/schema.prisma

Repository: PayButton/paybutton-server

Length of output: 275


🏁 Script executed:

# Also check the UserProfile model in the schema
rg -A 50 "model UserProfile" ./prisma-local/schema.prisma | head -80

Repository: PayButton/paybutton-server

Length of output: 1930


Harden threshold validation to reject blank, non-finite, and non-integer values.

Current coercion accepts invalid inputs like empty strings, whitespace, decimals, and Infinity. Since csvCollapseThreshold is defined as an Int in the database schema, these invalid inputs could cause unintended behavior or persist incorrect values.

Proposed fix
-    if (csvCollapseThreshold === undefined || csvCollapseThreshold === null) {
+    if (
+      csvCollapseThreshold === undefined ||
+      csvCollapseThreshold === null ||
+      (typeof csvCollapseThreshold === 'string' && csvCollapseThreshold.trim() === '')
+    ) {
       res.status(400).json({ message: 'csvCollapseThreshold is required' })
       return
     }

-    const threshold = Number(csvCollapseThreshold)
-    if (isNaN(threshold) || threshold < 0) {
-      res.status(400).json({ message: 'csvCollapseThreshold must be a non-negative number' })
+    const threshold = Number(csvCollapseThreshold)
+    if (!Number.isFinite(threshold) || !Number.isInteger(threshold) || threshold < 0) {
+      res.status(400).json({ message: 'csvCollapseThreshold must be a non-negative integer' })
       return
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (csvCollapseThreshold === undefined || csvCollapseThreshold === null) {
res.status(400).json({ message: 'csvCollapseThreshold is required' })
return
}
const threshold = Number(csvCollapseThreshold)
if (isNaN(threshold) || threshold < 0) {
res.status(400).json({ message: 'csvCollapseThreshold must be a non-negative number' })
return
if (
csvCollapseThreshold === undefined ||
csvCollapseThreshold === null ||
(typeof csvCollapseThreshold === 'string' && csvCollapseThreshold.trim() === '')
) {
res.status(400).json({ message: 'csvCollapseThreshold is required' })
return
}
const threshold = Number(csvCollapseThreshold)
if (!Number.isFinite(threshold) || !Number.isInteger(threshold) || threshold < 0) {
res.status(400).json({ message: 'csvCollapseThreshold must be a non-negative integer' })
return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/api/user/csvCollapseThreshold/index.ts` around lines 15 - 23, The
current validation for csvCollapseThreshold (variable csvCollapseThreshold and
computed threshold) is too permissive; change it to first reject empty or
whitespace-only string inputs (if typeof csvCollapseThreshold === 'string' check
trim().length === 0) then parse to a number and validate using
Number.isFinite(threshold) and Number.isInteger(threshold) and threshold >= 0;
return 400 with an appropriate message if any of these checks fail so decimals,
Infinity/NaN, and blank inputs are rejected before persisting the Int.

@Klakurka Klakurka self-requested a review June 24, 2026 05:26
@Klakurka Klakurka added the enhancement (UI/UX/feature) New feature or request label Jun 24, 2026
@Klakurka Klakurka added this to the Phase 3 milestone Jun 24, 2026
@lissavxo lissavxo force-pushed the feat/set-row-collapsing-threshold branch from 56ef258 to 5c9d2f4 Compare June 25, 2026 15:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pages/api/payments/download/index.ts`:
- Line 65: The calls to downloadTxsFile are passing one argument too many, so
user.csvRowCollapsing is being dropped and collapseTransactions stays hardcoded.
Update the downloadTxsFile call in both the payments download handler and the
paybutton transactions handler to match the function’s 8-argument signature, and
pass user.csvRowCollapsing in the correct position so the collapsing behavior is
driven by the user setting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aead8a7b-7f60-45b9-99c5-ce485c2d7566

📥 Commits

Reviewing files that changed from the base of the PR and between 56ef258 and 5c9d2f4.

📒 Files selected for processing (10)
  • components/Account/ChangeCsvCollapseThreshold.tsx
  • components/Account/account.module.css
  • pages/account/index.tsx
  • pages/api/paybutton/download/transactions/[paybuttonId].ts
  • pages/api/payments/download/index.ts
  • pages/api/user/csvCollapseThreshold/index.ts
  • prisma-local/migrations/20260317024924_csv_collapse_threshold/migration.sql
  • prisma-local/schema.prisma
  • services/userService.ts
  • tests/mockedObjects.ts
✅ Files skipped from review due to trivial changes (2)
  • prisma-local/migrations/20260317024924_csv_collapse_threshold/migration.sql
  • pages/api/user/csvCollapseThreshold/index.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • pages/account/index.tsx
  • pages/api/paybutton/download/transactions/[paybuttonId].ts
  • components/Account/account.module.css
  • services/userService.ts
  • components/Account/ChangeCsvCollapseThreshold.tsx
  • prisma-local/schema.prisma
  • tests/mockedObjects.ts

const transactions = await fetchAllPaymentsByUserId(userId, networkIdArray, buttonIds, years, startDate, endDate, timezone)

await downloadTxsFile(res, quoteSlug, timezone, transactions, userId, undefined, user.csvRowCollapsing)
await downloadTxsFile(res, quoteSlug, timezone, transactions, userId, undefined, true, user.csvCollapseThreshold, user.csvRowCollapsing)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep --pattern 'export const downloadTxsFile = async ($$$) => { $$$ }' utils/files.ts
rg -nP 'downloadTxsFile\s*\(' --type=ts -g '!utils/files.ts'

Repository: PayButton/paybutton-server

Length of output: 164


Call at line 65 passes an incorrect argument count, dropping user.csvRowCollapsing.

Function downloadTxsFile accepts 8 arguments. The current call passes 9, causing the 9th argument (user.csvRowCollapsing) to be silently ignored and collapseTransactions to remain hardcoded as true.

<details>
<summary>Proposed fix</summary>

-    await downloadTxsFile(res, quoteSlug, timezone, transactions, userId, undefined, true, user.csvCollapseThreshold, user.csvRowCollapsing)
+    await downloadTxsFile(res, quoteSlug, timezone, transactions, userId, undefined, user.csvRowCollapsing, user.csvCollapseThreshold)

</details>

The same mismatch exists in pages/api/paybutton/download/transactions/[paybuttonId].ts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await downloadTxsFile(res, quoteSlug, timezone, transactions, userId, undefined, true, user.csvCollapseThreshold, user.csvRowCollapsing)
await downloadTxsFile(res, quoteSlug, timezone, transactions, userId, undefined, user.csvRowCollapsing, user.csvCollapseThreshold)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/api/payments/download/index.ts` at line 65, The calls to
downloadTxsFile are passing one argument too many, so user.csvRowCollapsing is
being dropped and collapseTransactions stays hardcoded. Update the
downloadTxsFile call in both the payments download handler and the paybutton
transactions handler to match the function’s 8-argument signature, and pass
user.csvRowCollapsing in the correct position so the collapsing behavior is
driven by the user setting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement (UI/UX/feature) New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants