diff --git a/routes/snowball.js b/routes/snowball.js index ac9a67b..e93e635 100644 --- a/routes/snowball.js +++ b/routes/snowball.js @@ -214,22 +214,44 @@ router.patch('/order', (req, res) => { if (!Array.isArray(items)) { return res.status(400).json(standardizeError('Request body must be an array', 'VALIDATION_ERROR')); } + if (items.length === 0) { + return res.json({ success: true, updated: 0 }); + } + + // Validate every row before touching the DB — no silent skips + const parsed = []; + for (let i = 0; i < items.length; i++) { + const row = items[i]; + const id = parseInt(row?.id, 10); + const order = parseInt(row?.snowball_order, 10); + if (!Number.isInteger(id) || id <= 0) { + return res.status(400).json(standardizeError( + `Item at index ${i} has an invalid id: ${JSON.stringify(row?.id)}`, + 'VALIDATION_ERROR', + )); + } + if (!Number.isInteger(order) || order < 0) { + return res.status(400).json(standardizeError( + `Item at index ${i} has an invalid snowball_order: ${JSON.stringify(row?.snowball_order)}`, + 'VALIDATION_ERROR', + )); + } + parsed.push({ id, order }); + } const db = getDb(); const userId = req.user.id; - const update = db.prepare('UPDATE bills SET snowball_order = ? WHERE id = ? AND user_id = ?'); + const update = db.prepare( + 'UPDATE bills SET snowball_order = ? WHERE id = ? AND user_id = ? AND deleted_at IS NULL' + ); - db.transaction((rows) => { - for (const row of rows) { - const id = parseInt(row.id, 10); - const order = parseInt(row.snowball_order, 10); - if (!Number.isInteger(id) || id <= 0) continue; - if (!Number.isInteger(order) || order < 0) continue; + db.transaction(() => { + for (const { id, order } of parsed) { update.run(order, id, userId); } - })(items); + })(); - res.json({ success: true }); + res.json({ success: true, updated: parsed.length }); }); // ── Snowball Plan helpers ─────────────────────────────────────────────────────