fix(events): NaN from slideshow seed breaks event creation on PostgreSQL

The create route seeds show_interval_ms/show_transition_ms from
app_settings through an inline guard that pre-checked Number.isFinite(+v)
but then used parseInt(v). The two disagree for null/''/true — +null is 0
(finite) while parseInt(null) is NaN — so when the slideshow settings rows
are absent (getAppSetting returns its null default), NaN flowed through
Math.min/Math.max into the INSERT. PostgreSQL rejects NaN for integer
columns; SQLite silently stores NULL, which is why every SQLite-based
test passed while POST /api/admin/events 500'd on the PG dev stack and
broke the e2e smoke suite.

Fix: parse first, then check — clampIntOrUndefined in utils/numericHelpers
(unit-tested against every failure-mode input). Verified end-to-end: the
previously-failing minimal create now succeeds against the PG dev stack.
This commit is contained in:
Paul Nothaft
2026-07-03 08:57:48 +02:00
parent b04ef216f5
commit 8c86518aad
3 changed files with 73 additions and 2 deletions
+17 -1
View File
@@ -31,4 +31,20 @@ function ensureNumber(value, fallback = 0) {
return Number.isFinite(n) ? n : fallback;
}
module.exports = { ensureInt, ensureNumber };
/**
* Parse a value as an integer clamped to [min, max]; `undefined` on
* anything that doesn't parse (null, undefined, '', booleans, garbage).
*
* Exists because the inline guard `Number.isFinite(+v) ? parseInt(v)`
* disagrees with itself for null/''/true (`+null` is 0 but
* `parseInt(null)` is NaN), which let NaN through Math.min/Math.max
* and into an INSERT — PostgreSQL rejects NaN for integer columns
* while SQLite silently stores NULL, so it only failed on PG.
*/
function clampIntOrUndefined(value, min, max) {
const n = parseInt(value, 10);
if (!Number.isFinite(n)) return undefined;
return Math.min(max, Math.max(min, n));
}
module.exports = { ensureInt, ensureNumber, clampIntOrUndefined };