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(