[object Object]

← back to NationalPaperHangers

Coerce garbage numeric booking fields to null instead of 500ing

3fa1e627755eee28369cffc60219aec1d7c71e84 · 2026-05-18 17:38:33 -0700 · SteveStudio2

A booking POSTed directly with a non-numeric square_feet,
ceiling_height_ft, or roll_count_estimate ran parseFloat/parseInt → NaN
straight into an INTEGER/NUMERIC column, so the INSERT failed with
"invalid input syntax for type integer: NaN" and the customer got an
opaque 500. A numOrNull() helper now parses each field and yields null
when the value isn't a finite number; square_feet additionally falls
back to the measured-walls total. Verified: a booking with all three
fields set to junk now returns 201 with the columns stored as null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 3fa1e627755eee28369cffc60219aec1d7c71e84
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Mon May 18 17:38:33 2026 -0700

    Coerce garbage numeric booking fields to null instead of 500ing
    
    A booking POSTed directly with a non-numeric square_feet,
    ceiling_height_ft, or roll_count_estimate ran parseFloat/parseInt → NaN
    straight into an INTEGER/NUMERIC column, so the INSERT failed with
    "invalid input syntax for type integer: NaN" and the customer got an
    opaque 500. A numOrNull() helper now parses each field and yields null
    when the value isn't a finite number; square_feet additionally falls
    back to the measured-walls total. Verified: a booking with all three
    fields set to junk now returns 201 with the columns stored as null.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 routes/api.js | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/routes/api.js b/routes/api.js
index ce80bc5..d4cf608 100644
--- a/routes/api.js
+++ b/routes/api.js
@@ -14,6 +14,16 @@ const stripe = require('../lib/stripe');
 const { requireInstaller } = require('../lib/auth');
 const router = express.Router();
 
+// Parse a possibly-garbage form value to a finite number, or null. The
+// booking endpoint accepts a directly-posted body, so a non-numeric
+// square_feet / ceiling_height_ft / roll_count_estimate would otherwise
+// reach an INTEGER/NUMERIC column as NaN and 500 the INSERT.
+function numOrNull(v) {
+  if (v === undefined || v === null || v === '') return null;
+  const n = parseFloat(v);
+  return Number.isFinite(n) ? n : null;
+}
+
 // =======================================================================
 // PUBLIC API
 // =======================================================================
@@ -182,8 +192,10 @@ router.post('/installers/:slug/book', async (req, res, next) => {
     ]);
     const surfaceState = (f.surface_state && ALLOWED_SURFACE.has(f.surface_state)) ? f.surface_state : null;
     const accessCon    = (f.access_constraints && ALLOWED_ACCESS.has(f.access_constraints)) ? f.access_constraints : null;
-    const ceilingHeightFt = f.ceiling_height_ft ? Math.max(6, Math.min(40, parseFloat(f.ceiling_height_ft))) : null;
-    const rollCountEst    = f.roll_count_estimate ? Math.max(0, parseInt(f.roll_count_estimate, 10)) : null;
+    const ceilRaw = numOrNull(f.ceiling_height_ft);
+    const ceilingHeightFt = ceilRaw === null ? null : Math.max(6, Math.min(40, ceilRaw));
+    const rollRaw = numOrNull(f.roll_count_estimate);
+    const rollCountEst    = rollRaw === null ? null : Math.max(0, Math.round(rollRaw));
     const brandSku = f.brand_sku ? String(f.brand_sku).slice(0, 200) : null;
 
     // 5-question intake additions: who is ordering, has wallpaper been selected,
@@ -200,8 +212,9 @@ router.post('/installers/:slug/book', async (req, res, next) => {
     // shape. When the customer measured walls but left square_feet blank,
     // fall back to the captured total so the installer always has a number.
     roomCaptures = roomCapturesLib.sanitize(f.room_captures);
-    const squareFeet = f.square_feet
-      ? parseInt(f.square_feet, 10)
+    const sqftRaw = numOrNull(f.square_feet);
+    const squareFeet = (sqftRaw !== null && sqftRaw >= 0)
+      ? Math.round(sqftRaw)
       : roomCapturesLib.totalSqFt(roomCaptures);
 
     const ins = await client.query(

← 515caa4 Stop clobbering the customer's hand-entered square footage  ·  back to NationalPaperHangers  ·  Validate availability time windows instead of silently break da5acf0 →