Skip to content

Add DELETE and PATCH for weights and entries - #3

Open
eprouveze wants to merge 1 commit into
mainfrom
feat/weights-entries-crud
Open

Add DELETE and PATCH for weights and entries#3
eprouveze wants to merge 1 commit into
mainfrom
feat/weights-entries-crud

Conversation

@eprouveze

@eprouveze eprouveze commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • weights and entries API routes only supported GET/POST. Adds PATCH (partial update by id) and DELETE (by id) for both, matching the exact query-param ?id= pattern already used by food-entries and nutrition-sprints.
  • Returns 400 if id is missing, 404 if the row doesn't exist, 200 with the updated/deleted-confirmation JSON otherwise. Error handling/response shape matches existing GET/POST handlers in the same files.
  • No auth/user-ownership scoping was added because none exists anywhere else in this codebase — it's a single-user local app (SQLite, no auth middleware, no user_id columns on any table). Verified by grepping for auth/session/middleware before starting.

Verification

  • npx tsc --noEmit — clean, zero errors.
  • npx next build — compiled and type-checked successfully (webpack build succeeded for all routes including the two changed files); the build only failed afterward at page-data-collection due to a pre-existing, unrelated Node 26 / better-sqlite3 native-binding incompatibility (had to npm ci --ignore-scripts because the native module fails to compile against Node 26 on this machine). Not introduced by this change.
  • No lint config exists in the repo (next lint would prompt to scaffold one interactively) — skipped as out of scope.
  • No test harness exists for API routes in this repo.

Test plan

  • Manually exercise PATCH /api/weights?id=<id> and DELETE /api/weights?id=<id> against a running dev server with real data
  • Manually exercise PATCH /api/entries?id=<id> and DELETE /api/entries?id=<id>
  • Confirm 404 behavior for a nonexistent id on each

Note: better-sqlite3 doesn't currently build on this machine's Node 26 — worth checking whether that's already tracked, since it blocks any local dev-server run.

https://claude.ai/code/session_01TFnZVjgqB7CTE3MnZmFyeE

Summary by CodeRabbit

  • New Features

    • Added support for updating and deleting weight records.
    • Added support for updating and deleting entries, with clearer handling of missing or invalid record IDs.
  • Bug Fixes

    • Improved error responses for failed requests and validation issues.
    • Kept record updates consistent by returning the latest saved data after changes.

Weights and entries only supported GET/POST. Adds id-scoped PATCH
(partial update) and DELETE, matching the existing query-param
?id= pattern used by food-entries and nutrition-sprints. 404 when
the id doesn't exist, 400 when id is missing. No auth/ownership
scoping exists elsewhere in this single-user local app, so none
was added here (matches existing convention).
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Both entries and weights API routes are reformatted to use multi-line Drizzle query chains. New PATCH and DELETE handlers are added to both routes, validating the id query parameter, checking row existence (404), performing updates/deletes, and returning formatted 500 error responses on failure.

Changes

Entries and Weights CRUD Routes

Layer / File(s) Summary
Entries route: GET/POST reformatting and PATCH/DELETE handlers
src/app/api/entries/route.ts
GET and POST query chains reformatted to multi-line Drizzle syntax; PATCH and DELETE handlers added validating id, checking existence (404), and updating/deleting rows with formatted 500 error handling.
Weights route: imports, error formatting, and PATCH/DELETE handlers
src/app/api/weights/route.ts
Adds desc, eq, sql imports; reformats GET/POST error responses; adds PATCH (partial update with float parsing for weightKg) and DELETE handlers with id validation, 404 checks, and formatted 500 errors.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant APIRoute
  participant Database

  Client->>APIRoute: PATCH/DELETE ?id=X
  APIRoute->>Database: select existing row by id
  Database-->>APIRoute: row or none
  alt id missing
    APIRoute-->>Client: 400
  else not found
    APIRoute-->>Client: 404
  else found
    alt PATCH
      APIRoute->>Database: update row
      APIRoute->>Database: reselect updated row
      Database-->>APIRoute: updated row
      APIRoute-->>Client: updated row
    else DELETE
      APIRoute->>Database: delete row
      APIRoute-->>Client: success true
    end
  end
Loading

Related Issues: None specified

Related PRs: None specified

Suggested labels: api, enhancement

Suggested reviewers: eprouveze

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding DELETE and PATCH handlers for the weights and entries routes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/weights-entries-crud

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

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/app/api/weights/route.ts (1)

55-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared id-validation/existence-check boilerplate.

The id parsing → 400 check → existence lookup → 404 check → catch/500 sequence is duplicated verbatim across PATCH/DELETE in this file and in src/app/api/entries/route.ts, and per the PR description also mirrors food-entries/nutrition-sprints. A small shared helper (e.g. getRowOrNotFound(table, id)) would reduce this repetition across the growing set of CRUD routes.

🤖 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 `@src/app/api/weights/route.ts` around lines 55 - 129, The `PATCH` and `DELETE`
handlers in `weights/route.ts` duplicate the same id parsing, 400 response,
existence lookup, and 404 handling logic used in other CRUD routes like
`entries/route.ts` (and the similar food-entries/nutrition-sprints handlers).
Extract that boilerplate into a shared helper, such as a reusable
`getRowOrNotFound`-style function, and refactor `PATCH`/`DELETE` to call it so
the `db.select().from(weights)...` and id-validation flow is centralized.
🤖 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 `@src/app/api/entries/route.ts`:
- Around line 78-152: The PATCH and DELETE handlers in the entries route are
accepting malformed id values because parseInt(id) can produce NaN and fall
through to the existing/not-found path. Parse the id once in PATCH and DELETE,
validate that it is a real integer before any db lookup, return a 400 for
invalid ids, and then reuse the parsed numeric id in the existing db.select,
db.update, and db.delete calls.

In `@src/app/api/weights/route.ts`:
- Around line 55-100: The PATCH handler in route.ts needs stricter input
validation before querying or updating weights. In PATCH, replace the loose
parseInt(id) usage with a strict integer check so malformed ids like "1foo" are
rejected with a 400 instead of being coerced. Also validate weightKg before
db.update in PATCH: reject null, undefined, empty, or non-numeric values rather
than passing them through to parseFloat, and keep the existing record only when
the input is a finite number.

---

Nitpick comments:
In `@src/app/api/weights/route.ts`:
- Around line 55-129: The `PATCH` and `DELETE` handlers in `weights/route.ts`
duplicate the same id parsing, 400 response, existence lookup, and 404 handling
logic used in other CRUD routes like `entries/route.ts` (and the similar
food-entries/nutrition-sprints handlers). Extract that boilerplate into a shared
helper, such as a reusable `getRowOrNotFound`-style function, and refactor
`PATCH`/`DELETE` to call it so the `db.select().from(weights)...` and
id-validation flow is centralized.
🪄 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: db9b7ade-aea2-42b8-a415-150acd256b91

📥 Commits

Reviewing files that changed from the base of the PR and between 1734cc0 and 860080f.

📒 Files selected for processing (2)
  • src/app/api/entries/route.ts
  • src/app/api/weights/route.ts

Comment on lines +78 to +152
export async function PATCH(request: Request) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");

if (!id) {
return NextResponse.json({ error: "id required" }, { status: 400 });
}

const existing = await db
.select()
.from(entries)
.where(eq(entries.id, parseInt(id)))
.limit(1);

if (!existing[0]) {
return NextResponse.json({ error: "Entry not found" }, { status: 404 });
}

const body = await request.json();
const { date, notes, energyLevel, fastingHours } = body;

await db
.update(entries)
.set({
date: date ?? existing[0].date,
notes: notes ?? existing[0].notes,
energyLevel: energyLevel ?? existing[0].energyLevel,
fastingHours: fastingHours ?? existing[0].fastingHours,
})
.where(eq(entries.id, parseInt(id)));

const updated = await db
.select()
.from(entries)
.where(eq(entries.id, parseInt(id)))
.limit(1);
return NextResponse.json(updated[0]);
} catch (error) {
console.error("Failed to update entry:", error);
return NextResponse.json(
{ error: "Failed to update entry" },
{ status: 500 },
);
}
}

export async function DELETE(request: Request) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");

if (!id) {
return NextResponse.json({ error: "id required" }, { status: 400 });
}

const existing = await db
.select()
.from(entries)
.where(eq(entries.id, parseInt(id)))
.limit(1);

if (!existing[0]) {
return NextResponse.json({ error: "Entry not found" }, { status: 404 });
}

await db.delete(entries).where(eq(entries.id, parseInt(id)));

return NextResponse.json({ success: true });
} catch (error) {
console.error("Failed to delete entry:", error);
return NextResponse.json(
{ error: "Failed to delete entry" },
{ status: 500 },
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== package files ==\n'
git ls-files 'package.json' 'pnpm-lock.yaml' 'package-lock.json' 'yarn.lock' 'src/**/schema.*' 'src/**/db.*' 'src/**/route.ts' | sed -n '1,200p'

printf '\n== route file outline ==\n'
ast-grep outline src/app/api/entries/route.ts --view expanded || true

printf '\n== route file excerpt ==\n'
cat -n src/app/api/entries/route.ts | sed -n '1,220p'

printf '\n== schema candidates ==\n'
rg -n "entries|sqliteTable|integer\\(|id:" src --glob '!**/node_modules/**' || true

Repository: eprouveze/HealthPulse

Length of output: 22892


Reject malformed id values in PATCH/DELETE. parseInt(id) accepts non-numeric input as NaN, so both handlers return 404 for bad IDs instead of a clear 400. Parse once, reject non-integer values up front, and reuse the parsed value in the queries.

🤖 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 `@src/app/api/entries/route.ts` around lines 78 - 152, The PATCH and DELETE
handlers in the entries route are accepting malformed id values because
parseInt(id) can produce NaN and fall through to the existing/not-found path.
Parse the id once in PATCH and DELETE, validate that it is a real integer before
any db lookup, return a 400 for invalid ids, and then reuse the parsed numeric
id in the existing db.select, db.update, and db.delete calls.

Comment on lines +55 to +100
export async function PATCH(request: Request) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");

if (!id) {
return NextResponse.json({ error: "id required" }, { status: 400 });
}

const existing = await db
.select()
.from(weights)
.where(eq(weights.id, parseInt(id)))
.limit(1);

if (!existing[0]) {
return NextResponse.json({ error: "Weight not found" }, { status: 404 });
}

const body = await request.json();
const { date, weightKg, source } = body;

await db
.update(weights)
.set({
date: date ?? existing[0].date,
weightKg:
weightKg !== undefined ? parseFloat(weightKg) : existing[0].weightKg,
source: source ?? existing[0].source,
})
.where(eq(weights.id, parseInt(id)));

const updated = await db
.select()
.from(weights)
.where(eq(weights.id, parseInt(id)))
.limit(1);
return NextResponse.json(updated[0]);
} catch (error) {
console.error("Failed to update weight:", error);
return NextResponse.json(
{ error: "Failed to update weight" },
{ status: 500 },
);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== weights route ==\n'
sed -n '1,220p' src/app/api/weights/route.ts

printf '\n== schema ==\n'
sed -n '1,120p' src/lib/schema.ts

printf '\n== entries route references ==\n'
rg -n "parseInt\\(id\\)|Weight not found|id required" src/app/api/entries/route.ts src/app/api/weights/route.ts

Repository: eprouveze/HealthPulse

Length of output: 10453


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== package.json ==\n'
sed -n '1,220p' package.json

printf '\n== parseFloat / NaN probe ==\n'
node - <<'JS'
for (const value of [null, undefined, "", "12.3", "abc"]) {
  const parsed = parseFloat(value);
  console.log(JSON.stringify({ value, parsed, isNaN: Number.isNaN(parsed) }));
}
JS

Repository: eprouveze/HealthPulse

Length of output: 2067


🌐 Web query:

better-sqlite3 NaN binds NULL SQLite not null constraint parseFloat(null)

💡 Result:

In better-sqlite3, and within SQLite itself, JavaScript NaN (Not-a-Number) values cannot be directly stored as floating-point numbers. SQLite treats NaN as NULL [1][2]. Consequently, if you attempt to bind a NaN value to a column defined with a NOT NULL constraint, the database operation will throw a SQLITE_CONSTRAINT error [3][4][5]. The behavior is summarized as follows: 1. SQLite's Data Handling: SQLite does not support NaN as a native floating-point value. When a NaN is passed to the SQLite binding layer, it is interpreted as NULL [1][2]. 2. Constraint Violations: Because the value is converted to NULL, any column with a NOT NULL constraint will reject the insert or update, resulting in an error [3][5]. 3. better-sqlite3 Limitations: The library is designed to be a thin, predictable wrapper around SQLite [6][7][8]. It explicitly supports binding only specific types: numbers (converted to REAL or INTEGER), strings (TEXT), BigInts (INTEGER), Buffers (BLOB), and null [3][6][9]. Since NaN is a special case of a JavaScript number, and SQLite maps floating-point numbers to REAL [3][8], the binding process delegates to the underlying SQLite C API, which triggers the NULL conversion [1]. To handle this, you must sanitize or validate your data before passing it to better-sqlite3. You should check for NaN values in your JavaScript code (e.g., using Number.isNaN) and either replace them with a valid number (like 0) or handle them according to your application logic before attempting to execute the SQL statement [1]. Attempting to use parseFloat(null) on these values will return NaN, which does not bypass this constraint and will still result in the same error if bound to a NOT NULL column.

Citations:


Validate PATCH inputs in src/app/api/weights/route.ts. parseInt(id) should be a strict integer check; otherwise malformed ids can be coerced into a different row (1foo1) or fall through to 404. weightKg also needs a null/NaN guard: null becomes NaN, and SQLite/better-sqlite3 binds that as NULL, tripping weights.weightKg's notNull() constraint and returning a generic 500.

🤖 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 `@src/app/api/weights/route.ts` around lines 55 - 100, The PATCH handler in
route.ts needs stricter input validation before querying or updating weights. In
PATCH, replace the loose parseInt(id) usage with a strict integer check so
malformed ids like "1foo" are rejected with a 400 instead of being coerced. Also
validate weightKg before db.update in PATCH: reject null, undefined, empty, or
non-numeric values rather than passing them through to parseFloat, and keep the
existing record only when the input is a finite number.

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.

1 participant