refactor(api): read the health-record route bodies through parseJsonBody

Labs, biomarkers, custom metrics, cycle, anamnesis, illness, measurement
reminders, the measurement edit, medication sub-routes, mood and mental
health assessments still spelled out safeJson, safeParse and the 422 by
hand. Each becomes one parseJsonBody call with the size cap, action,
issue count, error code and status it had. The audit rows and the
unstable-externalId breadcrumbs move into onInvalid, in the order they
ran; where a breadcrumb reads the body, the wide-event annotate moves with
it. A `body ?? {}` or a payload unwrapped from the body becomes `input`.

The medication ingest route reads through the helper too; its Bearer
resolution happens before the read and is unchanged.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 11:29:22 +02:00
1 parent f06df5fd71
commit af14d0aa7e
28 files changed
+495 -698

No files matched your search

+16 -20
View File
@@ -24,8 +24,8 @@ import {
apiError,
getClientIp,
returnAllZodIssues,
safeJson,
} from "@/lib/api-response";
import { parseJsonBody } from "@/lib/api/body";
import { withIdempotency } from "@/lib/idempotency";
import { requireCycleEnabled } from "@/lib/cycle/gate";
import {
@@ -99,27 +99,23 @@ async function postDayLog(request: NextRequest): Promise<Response> {
const gate = await requireCycleEnabled(user.id, user.gender);
if (!gate.enabled) return gate.response;
const { data: rawBody, error: jsonError } = await safeJson(request, {
const parsed = await parseJsonBody(request, cycleDayLogCreateSchema, {
maxBytes: 64 * 1024,
errorCode: "cycle.day-log.invalid",
onInvalid: (error, rawBody) => {
const shape = unstableExternalIdShape(rawBody);
annotate({
action: { name: "cycle.day-log.validation-failed" },
meta: {
issue_count: error.issues.length,
...(shape
? unstableExternalIdMeta("cycle.day_log.create", [shape])
: {}),
},
});
},
});
if (jsonError) return jsonError;
const parsed = cycleDayLogCreateSchema.safeParse(rawBody);
if (!parsed.success) {
const shape = unstableExternalIdShape(rawBody);
annotate({
action: { name: "cycle.day-log.validation-failed" },
meta: {
issue_count: parsed.error.issues.length,
...(shape
? unstableExternalIdMeta("cycle.day_log.create", [shape])
: {}),
},
});
return returnAllZodIssues(parsed.error, 422, {
errorCode: "cycle.day-log.invalid",
});
}
if (!parsed.ok) return parsed.error;
const entry = parsed.data;
const tz = user.timezone ?? DEFAULT_TIMEZONE;
+15 -23
View File
@@ -18,12 +18,8 @@ import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
import { annotate } from "@/lib/logging/context";
import { auditLog } from "@/lib/auth/audit";
import { overwriteDetails } from "@/lib/sharing/audit-details";
import {
apiSuccess,
getClientIp,
returnAllZodIssues,
safeJson,
} from "@/lib/api-response";
import { apiSuccess, getClientIp } from "@/lib/api-response";
import { parseJsonBody } from "@/lib/api/body";
import { requireCycleEnabled } from "@/lib/cycle/gate";
import { withIdempotency } from "@/lib/idempotency";
import { cyclePeriodSchema } from "@/lib/validations/cycle";
@@ -63,25 +59,21 @@ async function postPeriod(request: NextRequest): Promise<Response> {
const gate = await requireCycleEnabled(user.id, user.gender);
if (!gate.enabled) return gate.response;
const { data: rawBody, error: jsonError } = await safeJson(request, {
const parsed = await parseJsonBody(request, cyclePeriodSchema, {
maxBytes: 64 * 1024,
errorCode: "cycle.period.invalid",
onInvalid: (error, rawBody) => {
const shape = unstableExternalIdShape(rawBody);
annotate({
action: { name: "cycle.period.validation-failed" },
meta: {
issue_count: error.issues.length,
...(shape ? unstableExternalIdMeta("cycle.period", [shape]) : {}),
},
});
},
});
if (jsonError) return jsonError;
const parsed = cyclePeriodSchema.safeParse(rawBody);
if (!parsed.success) {
const shape = unstableExternalIdShape(rawBody);
annotate({
action: { name: "cycle.period.validation-failed" },
meta: {
issue_count: parsed.error.issues.length,
...(shape ? unstableExternalIdMeta("cycle.period", [shape]) : {}),
},
});
return returnAllZodIssues(parsed.error, 422, {
errorCode: "cycle.period.invalid",
});
}
if (!parsed.ok) return parsed.error;
const { action, date, externalId, loggedAt } = parsed.data;
const tz = user.timezone ?? DEFAULT_TIMEZONE;