Files
HealthLog/src/lib/nutrients/day-bounds.ts
T
Marc-André Bombeck 94157a50c7 fix(nutrients): bound the manual water writes and put nutrient totals in the backup
The water quick-add validated calendar realism but never the upper day bound the
batch route applies, so a far-future day was accepted and — every read filters
`day >= since` with no ceiling — became the permanent latest day on the settings
card and the dashboard tile. Hoist the bound into one shared helper both routes
use.

`mode: "add"` bounded a single request to the catalog's plausible daily max but
nothing across requests, so repeated quick-adds drove the stored total
arbitrarily high while the batch route enforced the same cap strictly. Clamp
after the increment so the write stays atomic.

Thiamin's cap sat at exactly 1000 mg while its PRI is exactly 1.0 mg, so a
microgram-read-as-milligram sample landed on precisely the cap and the `>`
comparison let it through. 500 still clears any real supplement dose.

NutrientIntakeDay was in no export path, which contradicted the schema's own
reason for denormalising the unit column. Carry it in the full backup with its
unit and source — source is part of the composite key, so without it a restore
cannot tell a manual entry from a synced day total.
2026-07-19 13:40:03 +02:00

21 lines
989 B
TypeScript

/**
* Shared upper day bound for the nutrient write paths.
*
* `NutrientIntakeDay.day` is a local `YYYY-MM-DD` key, so the server cannot
* simply compare it against the UTC date: a client in the most-ahead IANA zone
* (UTC+14) is legitimately already on "tomorrow" relative to UTC. Two days of
* slack covers every real zone and still rejects a key that cannot be a local
* day at all.
*
* The batch route carried this bound privately while `POST /api/nutrients/water`
* validated calendar realism only — so a manual write could land a `2999-01-01`
* row, and since every read filters `day: { gte: since }` with no upper bound,
* that row became the permanent `latestDay` on the settings card and the
* permanent `lastSeenAt` on the dashboard water tile. Both routes now share
* this one bound.
*/
export function maxAcceptableNutrientDay(now: Date): string {
const limit = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000);
return limit.toISOString().slice(0, 10);
}