mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
Merge branch 'feat/v146-server-parse' into feat/v146-server
This commit is contained in:
commit
7ea247e193
47 files changed
+760
-1113
No files matched your search
@@ -1,21 +1,15 @@
|
||||
{
|
||||
"$comment": "safeJson calls per route file under src/app; new body-reading routes use parseJsonBody. Maintained by src/__tests__/route-body-parse-ratchet.test.ts.",
|
||||
"count": 106,
|
||||
"total": 106,
|
||||
"count": 61,
|
||||
"total": 61,
|
||||
"entries": {
|
||||
"app/api/admin/ai-settings/route.ts": 1,
|
||||
"app/api/admin/drain-per-sample-cumulative/route.ts": 1,
|
||||
"app/api/admin/encryption/key-backup/confirm/route.ts": 1,
|
||||
"app/api/admin/encryption/key-backup/verify/route.ts": 1,
|
||||
"app/api/admin/invites/route.ts": 1,
|
||||
"app/api/admin/notifications/reminder-check/route.ts": 1,
|
||||
"app/api/admin/rollups/recompute/route.ts": 1,
|
||||
"app/api/admin/settings/web-push-vapid/generate/route.ts": 1,
|
||||
"app/api/admin/users/[id]/reset-password/route.ts": 1,
|
||||
"app/api/ai/test/route.ts": 1,
|
||||
"app/api/anamnesis/emergency/route.ts": 1,
|
||||
"app/api/anamnesis/facts/[id]/route.ts": 1,
|
||||
"app/api/anamnesis/facts/route.ts": 1,
|
||||
"app/api/auth/login/route.ts": 1,
|
||||
"app/api/auth/me/locale/route.ts": 1,
|
||||
"app/api/auth/me/mfa/disable/route.ts": 1,
|
||||
@@ -26,85 +20,46 @@
|
||||
"app/api/auth/passkey/login-options/route.ts": 1,
|
||||
"app/api/auth/profile/route.ts": 1,
|
||||
"app/api/auth/refresh/route.ts": 1,
|
||||
"app/api/biomarkers/[id]/route.ts": 1,
|
||||
"app/api/biomarkers/route.ts": 1,
|
||||
"app/api/coach/about-me/questions/route.ts": 1,
|
||||
"app/api/coach/about-me/route.ts": 1,
|
||||
"app/api/consent/ai/route.ts": 1,
|
||||
"app/api/custom-metrics/[id]/entries/[entryId]/route.ts": 1,
|
||||
"app/api/custom-metrics/[id]/entries/route.ts": 1,
|
||||
"app/api/custom-metrics/[id]/route.ts": 1,
|
||||
"app/api/custom-metrics/route.ts": 1,
|
||||
"app/api/cycle/day-logs/bulk/route.ts": 1,
|
||||
"app/api/cycle/day-logs/route.ts": 1,
|
||||
"app/api/cycle/period/route.ts": 1,
|
||||
"app/api/dashboard/chart-overlay-prefs/route.ts": 1,
|
||||
"app/api/dashboard/widgets/route.ts": 1,
|
||||
"app/api/dawarich/connect/route.ts": 1,
|
||||
"app/api/dawarich/disconnect/route.ts": 1,
|
||||
"app/api/dawarich/settings/route.ts": 1,
|
||||
"app/api/dawarich/sync/route.ts": 1,
|
||||
"app/api/devices/route.ts": 1,
|
||||
"app/api/documents/inbound/[id]/extract/route.ts": 1,
|
||||
"app/api/documents/inbound/[id]/index/route.ts": 1,
|
||||
"app/api/documents/inbound/[id]/suggest/route.ts": 1,
|
||||
"app/api/documents/inbound/[id]/summary/route.ts": 1,
|
||||
"app/api/fitbit/credentials/route.ts": 1,
|
||||
"app/api/google-health/credentials/route.ts": 1,
|
||||
"app/api/illness/episodes/[id]/resolve/route.ts": 1,
|
||||
"app/api/import/route.ts": 1,
|
||||
"app/api/ingest/medication/route.ts": 1,
|
||||
"app/api/insights/chat/messages/[id]/feedback/route.ts": 1,
|
||||
"app/api/insights/ecg/route.ts": 1,
|
||||
"app/api/insights/feedback/route.ts": 1,
|
||||
"app/api/insights/generate/route.ts": 1,
|
||||
"app/api/insights/layout/route.ts": 1,
|
||||
"app/api/insights/provider-chain/route.ts": 1,
|
||||
"app/api/integrations/healthkit/route.ts": 1,
|
||||
"app/api/internal/deploy-webhook/route.ts": 1,
|
||||
"app/api/labs/[id]/route.ts": 1,
|
||||
"app/api/labs/ocr/commit/route.ts": 1,
|
||||
"app/api/labs/ocr/extract/route.ts": 1,
|
||||
"app/api/labs/route.ts": 1,
|
||||
"app/api/measurement-reminders/[id]/route.ts": 1,
|
||||
"app/api/measurement-reminders/[id]/snooze/route.ts": 1,
|
||||
"app/api/measurement-reminders/route.ts": 1,
|
||||
"app/api/measurements/[id]/route.ts": 1,
|
||||
"app/api/measurements/batch/route.ts": 1,
|
||||
"app/api/measurements/by-external-ids/route.ts": 1,
|
||||
"app/api/measurements/route.ts": 1,
|
||||
"app/api/medications/[id]/intake/[eventId]/route.ts": 1,
|
||||
"app/api/medications/[id]/intake/import/route.ts": 1,
|
||||
"app/api/medications/[id]/intake/route.ts": 1,
|
||||
"app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts": 1,
|
||||
"app/api/medications/[id]/schedule-revisions/route.ts": 1,
|
||||
"app/api/medications/extract/route.ts": 1,
|
||||
"app/api/medications/intake/bulk/route.ts": 1,
|
||||
"app/api/medications/intake/route.ts": 1,
|
||||
"app/api/medications/layout/route.ts": 1,
|
||||
"app/api/medications/route.ts": 1,
|
||||
"app/api/mental-health/assessments/route.ts": 1,
|
||||
"app/api/mood-entries/[id]/route.ts": 1,
|
||||
"app/api/mood-entries/bulk/route.ts": 1,
|
||||
"app/api/mood-entries/route.ts": 1,
|
||||
"app/api/mood/tags/layout/route.ts": 1,
|
||||
"app/api/nightscout/connect/route.ts": 1,
|
||||
"app/api/nutrients/batch/route.ts": 1,
|
||||
"app/api/onboarding/answers/route.ts": 1,
|
||||
"app/api/onboarding/complete/route.ts": 1,
|
||||
"app/api/onboarding/disclaimer/route.ts": 1,
|
||||
"app/api/onboarding/restart/route.ts": 1,
|
||||
"app/api/onboarding/tour/route.ts": 1,
|
||||
"app/api/oura/credentials/route.ts": 1,
|
||||
"app/api/polar/credentials/route.ts": 1,
|
||||
"app/api/record-settings/[family]/route.ts": 1,
|
||||
"app/api/search/route.ts": 1,
|
||||
"app/api/settings/email/route.ts": 1,
|
||||
"app/api/settings/ntfy/route.ts": 1,
|
||||
"app/api/settings/telegram/route.ts": 1,
|
||||
"app/api/settings/webhook/route.ts": 1,
|
||||
"app/api/strava/credentials/route.ts": 1,
|
||||
"app/api/user/ai-provider/route.ts": 1,
|
||||
"app/api/user/profile/route.ts": 1,
|
||||
"app/api/whoop/credentials/route.ts": 1,
|
||||
"app/api/withings/credentials/route.ts": 1,
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
* every `if (!<result>.success)` block whose identifier came from a `safeParse`
|
||||
* in that file is read on its own, and only the blocks that answer 422 are
|
||||
* judged — a 400, a 401 or a 403 raised off a parse result is a different
|
||||
* refusal and is left alone.
|
||||
* refusal and is left alone. A route that reads its body through
|
||||
* `parseJsonBody` has no such block: each call is a site of its own, carrying
|
||||
* the issue list by construction, and its `message` option is read as the
|
||||
* refused string by the token sweep below.
|
||||
*
|
||||
* The second half of the same audit item is the dotted token in `error`, and
|
||||
* it is checked by its own sweep at the bottom of this file rather than by the
|
||||
@@ -94,11 +97,58 @@ interface RefusalSite {
|
||||
carriesIssues: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every `parseJsonBody(…)` call in a file, as its paren-balanced source text
|
||||
* with the line it starts on. The helper reads, validates and refuses in one
|
||||
* call (`src/lib/api/body.ts`), so a route that uses it has no
|
||||
* `if (!x.success)` block for the matcher above to find; its refusal is the
|
||||
* helper's envelope, which always carries `details.issues`.
|
||||
*/
|
||||
function parseJsonBodyCalls(src: string): { text: string; line: number }[] {
|
||||
const calls: { text: string; line: number }[] = [];
|
||||
for (const m of src.matchAll(/\bparseJsonBody\s*\(/g)) {
|
||||
const start = m.index ?? 0;
|
||||
let depth = 0;
|
||||
let end = src.length;
|
||||
for (let j = start + m[0].length - 1; j < src.length; j++) {
|
||||
if (src[j] === "(") depth++;
|
||||
else if (src[j] === ")") {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
end = j + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
calls.push({
|
||||
text: src.slice(start, end),
|
||||
line: src.slice(0, start).split("\n").length,
|
||||
});
|
||||
}
|
||||
return calls;
|
||||
}
|
||||
|
||||
/** The status a `parseJsonBody` call refuses with: its `status` option, else 422. */
|
||||
function parseJsonBodyStatus(call: string): number {
|
||||
const m = /\bstatus:\s*(\d{3})\b/.exec(call);
|
||||
return m ? Number(m[1]) : 422;
|
||||
}
|
||||
|
||||
/** Every 422 refusal decided by a `safeParse` result, across the route tree. */
|
||||
function scanRefusalSites(): RefusalSite[] {
|
||||
const sites: RefusalSite[] = [];
|
||||
for (const file of routeFiles(API_ROOT)) {
|
||||
const src = readFileSync(file, "utf8");
|
||||
// A `parseJsonBody` refusal is a 422 with the issue list unless the call
|
||||
// names another status, which is then a different refusal, as below.
|
||||
for (const call of parseJsonBodyCalls(src)) {
|
||||
if (parseJsonBodyStatus(call.text) !== 422) continue;
|
||||
sites.push({
|
||||
route: relative(process.cwd(), file).split(sep).join("/"),
|
||||
line: call.line,
|
||||
carriesIssues: true,
|
||||
});
|
||||
}
|
||||
// Identifiers bound to a parse result. Both call styles are in the tree:
|
||||
// `schema.safeParse(body)` and the standalone `z.safeParse(schema, body)`.
|
||||
const parsed = new Set<string>();
|
||||
@@ -228,12 +278,20 @@ function tokenRefusals(): string[] {
|
||||
/apiValidationError\(\s*"([^"]+)"/g,
|
||||
/new HttpError\(\s*422\s*,\s*"([^"]+)"/g,
|
||||
];
|
||||
const refused: string[] = [];
|
||||
for (const shape of shapes) {
|
||||
for (const m of src.matchAll(shape)) {
|
||||
if (!DOTTED_TOKEN.test(m[1])) continue;
|
||||
const key = `${route} ${m[1]}`;
|
||||
if (!found.includes(key)) found.push(key);
|
||||
}
|
||||
for (const m of src.matchAll(shape)) refused.push(m[1]);
|
||||
}
|
||||
// `parseJsonBody`'s `message` option is the 422's `error` string.
|
||||
for (const call of parseJsonBodyCalls(src)) {
|
||||
if (parseJsonBodyStatus(call.text) !== 422) continue;
|
||||
const m = /\bmessage:\s*"([^"]+)"/.exec(call.text);
|
||||
if (m) refused.push(m[1]);
|
||||
}
|
||||
for (const text of refused) {
|
||||
if (!DOTTED_TOKEN.test(text)) continue;
|
||||
const key = `${route} ${text}`;
|
||||
if (!found.includes(key)) found.push(key);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
|
||||
Reference in new issue
Block a user