mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
refactor(api): read the remaining settings and admin route bodies through parseJsonBody
Admin, coach, consent, chart-overlay, device registration, insights feedback and provider chain, ECG ingest, onboarding, search and the AI provider settings move to parseJsonBody with the same message, error code, status and wide-event action they answered with. A refusal whose annotate meta is computed from the issues, or puts its keys in another order than the helper would, keeps that annotate verbatim in onInvalid. The routes that still call safeJson do something between the read and the schema: an optimistic-concurrency token split off the body, a count cap answered first, a refunded quota slot, a conditional read, a non-Zod check, or the body handed to a service.
This commit is contained in:
1 parent
af14d0aa7e
commit
ca04ef01b3
19 files changed
+203
-411
No files matched your search
@@ -24,13 +24,8 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import {
|
||||
apiError,
|
||||
apiSuccess,
|
||||
getClientIp,
|
||||
returnAllZodIssues,
|
||||
safeJson,
|
||||
} from "@/lib/api-response";
|
||||
import { apiError, apiSuccess, getClientIp } from "@/lib/api-response";
|
||||
import { parseJsonBody } from "@/lib/api/body";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { prisma, toJson } from "@/lib/db";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
@@ -68,21 +63,13 @@ export const PATCH = apiHandler(async (request: NextRequest) => {
|
||||
});
|
||||
}
|
||||
|
||||
const { data: body, error: jsonError } = await safeJson<unknown>(request, {
|
||||
const parsed = await parseJsonBody(request, onboardingAnswerSchema, {
|
||||
maxBytes: 16 * 1024,
|
||||
action: "onboarding.needs.save",
|
||||
meta: { outcome: "validation_failed" },
|
||||
errorCode: "onboarding.answers.invalid",
|
||||
});
|
||||
if (jsonError) return jsonError;
|
||||
|
||||
const parsed = onboardingAnswerSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
annotate({
|
||||
action: { name: "onboarding.needs.save" },
|
||||
meta: { outcome: "validation_failed" },
|
||||
});
|
||||
return returnAllZodIssues(parsed.error, 422, {
|
||||
errorCode: "onboarding.answers.invalid",
|
||||
});
|
||||
}
|
||||
if (!parsed.ok) return parsed.error;
|
||||
const answer = parsed.data;
|
||||
|
||||
const existing = await prisma.onboardingRecord.findUnique({
|
||||
|
||||
@@ -2,14 +2,8 @@ import { prisma, toJson } from "@/lib/db";
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import {
|
||||
apiError,
|
||||
apiSuccess,
|
||||
apiValidationError,
|
||||
getClientIp,
|
||||
safeJson,
|
||||
sanitiseZodIssues,
|
||||
} from "@/lib/api-response";
|
||||
import { apiError, apiSuccess, getClientIp } from "@/lib/api-response";
|
||||
import { parseJsonBody } from "@/lib/api/body";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { setOnboardingPendingCookie } from "@/lib/auth/session";
|
||||
import { normalisePrefs } from "@/lib/modules/gate";
|
||||
@@ -34,7 +28,6 @@ import {
|
||||
import { readManagedProfileForGuardian } from "@/lib/managed-profiles/lifecycle";
|
||||
import { writeRecordModulePreferences } from "@/lib/record-settings/modules";
|
||||
import { NextRequest } from "next/server";
|
||||
import { z } from "zod/v4";
|
||||
import { onboardingCompleteSchema } from "@/lib/validations/onboarding";
|
||||
|
||||
/**
|
||||
@@ -75,22 +68,12 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
const { user } = await requireAuth();
|
||||
annotate({ action: { name: "onboarding.complete" } });
|
||||
|
||||
const { data: body, error: jsonError } = await safeJson(request, {
|
||||
const result = await parseJsonBody(request, onboardingCompleteSchema, {
|
||||
maxBytes: 64 * 1024,
|
||||
message: "Invalid input",
|
||||
errorCode: "onboarding.complete.invalid",
|
||||
});
|
||||
|
||||
if (jsonError) return jsonError;
|
||||
const result = z.safeParse(onboardingCompleteSchema, body);
|
||||
if (!result.success) {
|
||||
return apiValidationError(
|
||||
"Invalid input",
|
||||
sanitiseZodIssues(result.error.issues),
|
||||
422,
|
||||
{
|
||||
errorCode: "onboarding.complete.invalid",
|
||||
},
|
||||
);
|
||||
}
|
||||
if (!result.ok) return result.error;
|
||||
|
||||
const data: Record<string, unknown> = {
|
||||
onboardingCompletedAt: new Date(),
|
||||
|
||||
@@ -3,12 +3,8 @@ import { z } from "zod/v4";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import {
|
||||
apiSuccess,
|
||||
apiValidationError,
|
||||
safeJson,
|
||||
sanitiseZodIssues,
|
||||
} from "@/lib/api-response";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import { parseJsonBody } from "@/lib/api/body";
|
||||
import { DISCLAIMER_VERSION } from "@/lib/onboarding/disclaimer";
|
||||
|
||||
/**
|
||||
@@ -36,22 +32,12 @@ const bodySchema = z
|
||||
export const POST = apiHandler(async (request) => {
|
||||
const { user } = await requireAuth();
|
||||
|
||||
const { data: rawBody, error: jsonError } = await safeJson(request, {
|
||||
const parsed = await parseJsonBody(request, bodySchema, {
|
||||
maxBytes: 4 * 1024,
|
||||
message: "Invalid input",
|
||||
errorCode: "onboarding.disclaimer.invalid",
|
||||
});
|
||||
if (jsonError) return jsonError;
|
||||
|
||||
const parsed = z.safeParse(bodySchema, rawBody);
|
||||
if (!parsed.success) {
|
||||
return apiValidationError(
|
||||
"Invalid input",
|
||||
sanitiseZodIssues(parsed.error.issues),
|
||||
422,
|
||||
{
|
||||
errorCode: "onboarding.disclaimer.invalid",
|
||||
},
|
||||
);
|
||||
}
|
||||
if (!parsed.ok) return parsed.error;
|
||||
|
||||
// The server pins the canonical version it stamps — the body version is
|
||||
// only a freshness signal, never the source of truth.
|
||||
|
||||
@@ -31,13 +31,8 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import {
|
||||
apiError,
|
||||
apiSuccess,
|
||||
getClientIp,
|
||||
returnAllZodIssues,
|
||||
safeJson,
|
||||
} from "@/lib/api-response";
|
||||
import { apiError, apiSuccess, getClientIp } from "@/lib/api-response";
|
||||
import { parseJsonBody } from "@/lib/api/body";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { prisma, toJson } from "@/lib/db";
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
@@ -74,21 +69,14 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
});
|
||||
}
|
||||
|
||||
const { data: body, error: jsonError } = await safeJson<unknown>(request, {
|
||||
const parsed = await parseJsonBody(request, onboardingRestartSchema, {
|
||||
maxBytes: 4 * 1024,
|
||||
input: (body) => body ?? {},
|
||||
action: "onboarding.needs.restart",
|
||||
meta: { outcome: "validation_failed" },
|
||||
errorCode: "onboarding.restart.invalid",
|
||||
});
|
||||
if (jsonError) return jsonError;
|
||||
|
||||
const parsed = onboardingRestartSchema.safeParse(body ?? {});
|
||||
if (!parsed.success) {
|
||||
annotate({
|
||||
action: { name: "onboarding.needs.restart" },
|
||||
meta: { outcome: "validation_failed" },
|
||||
});
|
||||
return returnAllZodIssues(parsed.error, 422, {
|
||||
errorCode: "onboarding.restart.invalid",
|
||||
});
|
||||
}
|
||||
if (!parsed.ok) return parsed.error;
|
||||
|
||||
const steps = defaultOnboardingSteps();
|
||||
const written = await prisma.onboardingRecord.upsert({
|
||||
|
||||
@@ -4,12 +4,8 @@ import { z } from "zod/v4";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import {
|
||||
apiSuccess,
|
||||
apiValidationError,
|
||||
safeJson,
|
||||
sanitiseZodIssues,
|
||||
} from "@/lib/api-response";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import { parseJsonBody } from "@/lib/api/body";
|
||||
import { tourProgressSchema } from "@/lib/onboarding/tour-progress";
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
|
||||
@@ -54,22 +50,12 @@ const tourBodySchema = z
|
||||
export const POST = apiHandler(async (request: NextRequest) => {
|
||||
const { user } = await requireAuth();
|
||||
|
||||
const { data: body, error: jsonError } = await safeJson(request, {
|
||||
const parsed = await parseJsonBody(request, tourBodySchema, {
|
||||
maxBytes: 64 * 1024,
|
||||
message: "Invalid input",
|
||||
errorCode: "onboarding.tour.invalid",
|
||||
});
|
||||
if (jsonError) return jsonError;
|
||||
|
||||
const parsed = z.safeParse(tourBodySchema, body);
|
||||
if (!parsed.success) {
|
||||
return apiValidationError(
|
||||
"Invalid input",
|
||||
sanitiseZodIssues(parsed.error.issues),
|
||||
422,
|
||||
{
|
||||
errorCode: "onboarding.tour.invalid",
|
||||
},
|
||||
);
|
||||
}
|
||||
if (!parsed.ok) return parsed.error;
|
||||
|
||||
// Build the update field-by-field (no mass assignment). `completed`
|
||||
// drives the coarse boolean; a replay (`completed:false`) wipes the
|
||||
|
||||
Reference in new issue
Block a user