From 4d0e715982c51a0aa697e611e7e9211741178b00 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Tue, 19 May 2026 23:24:16 -0400 Subject: [PATCH] fix(sequences): coerce NUMERIC frame_rate to float in all API responses node-postgres returns NUMERIC columns as strings by default. Add a mapSeq() helper that parses frame_rate to a JS float before any response is sent. Affected routes: GET /, POST /, PUT /:id, GET /:id. --- services/mam-api/src/routes/sequences.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/services/mam-api/src/routes/sequences.js b/services/mam-api/src/routes/sequences.js index 419d6f2..92f68ab 100644 --- a/services/mam-api/src/routes/sequences.js +++ b/services/mam-api/src/routes/sequences.js @@ -7,6 +7,15 @@ import { requireAuth } from '../middleware/auth.js'; const router = express.Router(); router.use(requireAuth); +// ── Row mapper ──────────────────────────────────────────────────────────────── +// node-postgres returns NUMERIC columns as strings. Coerce frame_rate to a +// JS float before sending any sequence object to clients. + +function mapSeq(row) { + if (!row) return row; + return { ...row, frame_rate: parseFloat(row.frame_rate) || 59.94 }; +} + // ── Timecode helpers ────────────────────────────────────────────────────────── // // generateEDL emits CMX3600 timecode using the sequence's frame_rate. @@ -19,7 +28,7 @@ router.use(requireAuth); function pad2(n) { return String(Math.floor(n)).padStart(2, '0'); } function framesToTC(totalFrames, fps) { - fps = fps || 59.94; + fps = parseFloat(fps) || 59.94; const fc = Math.max(0, Math.round(totalFrames)); // 29.97 DF ─ drop 2 frames per minute except every 10th @@ -77,7 +86,7 @@ function framesToTC(totalFrames, fps) { } function generateEDL(seqName, clips, fps) { - fps = fps || 59.94; + fps = parseFloat(fps) || 59.94; const lines = [`TITLE: ${seqName}`, '']; clips.forEach((c, i) => { const num = String(i + 1).padStart(3, '0'); @@ -105,7 +114,7 @@ router.get('/', async (req, res, next) => { `SELECT * FROM sequences WHERE project_id = $1 ORDER BY updated_at DESC`, [project_id] ); - res.json(r.rows); + res.json(r.rows.map(mapSeq)); } catch (e) { next(e); } }); @@ -125,7 +134,7 @@ router.post('/', async (req, res, next) => { VALUES ($1, $2, $3, $4, $5) RETURNING *`, [project_id, name, frame_rate, width, height] ); - res.status(201).json(r.rows[0]); + res.status(201).json(mapSeq(r.rows[0])); } catch (e) { next(e); } }); @@ -160,7 +169,7 @@ router.get('/:id', async (req, res, next) => { }) ); - res.json({ ...seqR.rows[0], clips }); + res.json({ ...mapSeq(seqR.rows[0]), clips }); } catch (e) { next(e); } }); @@ -183,7 +192,7 @@ router.put('/:id', async (req, res, next) => { params ); if (!r.rows.length) return res.status(404).json({ error: 'Sequence not found' }); - res.json(r.rows[0]); + res.json(mapSeq(r.rows[0])); } catch (e) { next(e); } }); @@ -254,7 +263,7 @@ router.post('/:id/export/edl', async (req, res, next) => { try { const seqR = await pool.query(`SELECT * FROM sequences WHERE id = $1`, [req.params.id]); if (!seqR.rows.length) return res.status(404).json({ error: 'Sequence not found' }); - const seq = seqR.rows[0]; + const seq = mapSeq(seqR.rows[0]); // Export V1 clips only (primary video track) sorted by position const clipsR = await pool.query(