fix: validate all snowball order rows upfront, reject invalid ones with 400
PATCH /api/snowball/order silently skipped rows with bad ids or invalid snowball_order values via bare 'continue' — no feedback, partial updates. Now validates every item before touching the DB, returning 400 on the first bad entry. Also adds deleted_at IS NULL filter so soft-deleted bills are skipped instead of updated silently.
This commit is contained in:
parent
e41f413f61
commit
44320a7613
|
|
@ -214,22 +214,44 @@ router.patch('/order', (req, res) => {
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
return res.status(400).json(standardizeError('Request body must be an array', 'VALIDATION_ERROR'));
|
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 db = getDb();
|
||||||
const userId = req.user.id;
|
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) => {
|
db.transaction(() => {
|
||||||
for (const row of rows) {
|
for (const { id, order } of parsed) {
|
||||||
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;
|
|
||||||
update.run(order, id, userId);
|
update.run(order, id, userId);
|
||||||
}
|
}
|
||||||
})(items);
|
})();
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true, updated: parsed.length });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Snowball Plan helpers ─────────────────────────────────────────────────────
|
// ── Snowball Plan helpers ─────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue