← back to Nationalrealestate
fix(sublease): reject non-finite numerics + malformed dates in admin insert
289894d12922e98c332a8a5a0be54d5df0817d92 · 2026-07-24 08:20:59 -0700 · Steve Abrams
Bad numeric input reached Postgres verbatim: Number('abc')=NaN → term_months (integer) 500s
('invalid input syntax for type integer: NaN'), and sqft/asking_rate/lat/lng (numeric/double)
silently stored literal NaN, poisoning sorts + the map. available_date took any string → date
column 500 on a bad value. Now coerced to null (num/int/isoDate helpers) so bad input degrades
to unset instead of a 500 or a NaN row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 289894d12922e98c332a8a5a0be54d5df0817d92
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jul 24 08:20:59 2026 -0700
fix(sublease): reject non-finite numerics + malformed dates in admin insert
Bad numeric input reached Postgres verbatim: Number('abc')=NaN → term_months (integer) 500s
('invalid input syntax for type integer: NaN'), and sqft/asking_rate/lat/lng (numeric/double)
silently stored literal NaN, poisoning sorts + the map. available_date took any string → date
column 500 on a bad value. Now coerced to null (num/int/isoDate helpers) so bad input degrades
to unset instead of a 500 or a NaN row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
src/server/sublease.ts | 33 +++++++++++++++++++++++++++------
1 file changed, 27 insertions(+), 6 deletions(-)
diff --git a/src/server/sublease.ts b/src/server/sublease.ts
index 0a24b7f..c04faab 100644
--- a/src/server/sublease.ts
+++ b/src/server/sublease.ts
@@ -16,26 +16,47 @@
import type { Express } from 'express';
import { query } from '../../db/pool.ts';
+// Numeric coercers: a non-numeric value (e.g. "abc") must NOT reach Postgres.
+// Number("abc") === NaN → an integer column (term_months) 500s ("invalid input
+// syntax for type integer: NaN"), and a numeric/double column (sqft/asking_rate/
+// lat/lng) silently STORES literal NaN, poisoning sorts and the map. Coerce
+// non-finite input to null so bad input degrades to "unset", never a 500 or NaN row.
+const num = (v: unknown): number | null => {
+ if (v === '' || v == null) return null;
+ const n = Number(v);
+ return Number.isFinite(n) ? n : null;
+};
+const int = (v: unknown): number | null => {
+ const n = num(v);
+ return n == null ? null : Math.round(n);
+};
+// A `date` column rejects a malformed string with a 500; require an ISO-ish date, else null.
+const isoDate = (v: unknown): string | null => {
+ if (v === '' || v == null) return null;
+ const s = String(v).trim();
+ return /^\d{4}-\d{2}-\d{2}/.test(s) && !Number.isNaN(Date.parse(s)) ? s : null;
+};
+
// Columns the admin form may set, mapped to their coercers. Anything else is ignored.
const FIELDS: Record<string, (v: unknown) => unknown> = {
address: v => String(v).trim(),
city: v => (v == null ? null : String(v).trim()),
submarket: v => (v == null ? null : String(v).trim()),
floor_suite: v => (v == null ? null : String(v).trim()),
- sqft: v => (v === '' || v == null ? null : Number(v)),
- asking_rate: v => (v === '' || v == null ? null : Number(v)),
+ sqft: num,
+ asking_rate: num,
rate_period: v => (['yr', 'mo'].includes(String(v)) ? String(v) : 'yr'),
lease_type: v => (['sublease', 'direct'].includes(String(v)) ? String(v) : 'sublease'),
- term_months: v => (v === '' || v == null ? null : Math.round(Number(v))),
- available_date: v => (v === '' || v == null ? null : String(v)),
+ term_months: int,
+ available_date: isoDate,
sublandlord: v => (v == null ? null : String(v).trim()),
firm_name: v => (v == null ? null : String(v).trim()),
broker_name: v => (v == null ? null : String(v).trim()),
broker_phone: v => (v == null ? null : String(v).trim()),
broker_email: v => (v == null ? null : String(v).trim()),
listing_url: v => (v == null ? null : String(v).trim()),
- lat: v => (v === '' || v == null ? null : Number(v)),
- lng: v => (v === '' || v == null ? null : Number(v)),
+ lat: num,
+ lng: num,
status: v => (['active', 'leased', 'withdrawn'].includes(String(v)) ? String(v) : 'active'),
notes: v => (v == null ? null : String(v).trim()),
};
← d7819af auth: add Scott login
·
back to Nationalrealestate
·
Add hourly self-balancing ingest loop + Google Places discov 8b31083 →