refactor(routes): monthly-starting-amounts → throw pattern; doc exemplar
5 ad-hoc {error} validations → throw ValidationError(msg, field) (adds
proper code + field). CODE_STANDARDS references routes/categories.cts as
the canonical error-pattern example and notes converted/unconverted routes
emit identical output (terminal handler normalizes both). 236/236.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3b016d4876
commit
a75ff1a466
|
|
@ -38,7 +38,10 @@ rolled out. Update this doc when a standard changes.
|
|||
(`ValidationError`, `NotFoundError`, `ConflictError`, …) and let the single terminal error
|
||||
handler format them — do not hand-roll `res.status().json({ error })`. Express 5 forwards
|
||||
rejected async handlers automatically, so route bodies should not wrap everything in
|
||||
`try/catch`. Clients read `message`/`code` + status.
|
||||
`try/catch`. Clients read `message`/`code` + status. **Canonical example:**
|
||||
`routes/categories.cts`. (The terminal handler already normalizes any legacy
|
||||
`standardizeError`/`{ error }` body to the same shape, so converted and not-yet-converted
|
||||
routes emit identical responses — convert opportunistically.)
|
||||
- **Success envelope (target):** reads return the bare resource/list; mutations return
|
||||
`{ success: true, … }`.
|
||||
- **Validation (target):** validate inputs with the shared validators (throwing
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const { getDb } = require('../db/database.cts');
|
|||
const { getCycleRange } = require('../services/statusService.cts');
|
||||
const { accountingActiveSql } = require('../services/paymentAccountingService.cts');
|
||||
const { toCents, fromCents } = require('../utils/money.mts');
|
||||
const { ValidationError } = require('../utils/apiError.cts');
|
||||
|
||||
function parseYearMonth(source) {
|
||||
const now = new Date();
|
||||
|
|
@ -147,7 +148,7 @@ function buildStartingAmountsResponse(db, userId, year, month) {
|
|||
|
||||
router.get('/', (req: Req, res: Res) => {
|
||||
const parsed = parseYearMonth(req.query);
|
||||
if (parsed.error) return res.status(400).json({ error: parsed.error });
|
||||
if (parsed.error) throw ValidationError(parsed.error, 'month');
|
||||
|
||||
const db = getDb();
|
||||
res.json(buildStartingAmountsResponse(db, req.user.id, parsed.year, parsed.month));
|
||||
|
|
@ -155,27 +156,24 @@ router.get('/', (req: Req, res: Res) => {
|
|||
|
||||
router.put('/', (req: Req, res: Res) => {
|
||||
const parsed = parseYearMonth(req.body || {});
|
||||
if (parsed.error) return res.status(400).json({ error: parsed.error });
|
||||
if (parsed.error) throw ValidationError(parsed.error, 'month');
|
||||
|
||||
const firstAmount = Number(req.body?.first_amount);
|
||||
if (!Number.isFinite(firstAmount) || firstAmount < 0 || firstAmount > 1000000000) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'first_amount must be a number between 0 and 1000000000' });
|
||||
throw ValidationError('first_amount must be a number between 0 and 1000000000', 'first_amount');
|
||||
}
|
||||
|
||||
const fifteenthAmount = Number(req.body?.fifteenth_amount);
|
||||
if (!Number.isFinite(fifteenthAmount) || fifteenthAmount < 0 || fifteenthAmount > 1000000000) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'fifteenth_amount must be a number between 0 and 1000000000' });
|
||||
throw ValidationError(
|
||||
'fifteenth_amount must be a number between 0 and 1000000000',
|
||||
'fifteenth_amount',
|
||||
);
|
||||
}
|
||||
|
||||
const otherAmount = Number(req.body?.other_amount);
|
||||
if (!Number.isFinite(otherAmount) || otherAmount < 0 || otherAmount > 1000000000) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'other_amount must be a number between 0 and 1000000000' });
|
||||
throw ValidationError('other_amount must be a number between 0 and 1000000000', 'other_amount');
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
|
|
|
|||
Loading…
Reference in New Issue