mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
Publish the nineteen routes the drift check could not see
`openapi:check` compares the registry against the committed YAML, so a route that was never registered produced no drift and no failure. These nineteen had shipped without a contract: the health probe, the dashboard analytics aggregate, the profile summary, personal records, the badge grid, the whole environmental-context module, both onboarding writes, the Apple Health import pair, the external medication ingest, the self-context adopt, the AI-provider update and the AI connection probe. Each entry is read off the route rather than inferred from a neighbour, so it carries what a client cannot guess: that `/api/health` answers a bare object and not the envelope, that `/api/analytics` records a score row and mints pattern ids on a GET, that `/api/ai/test` reports a provider failure as 200 with `ok: false` because a 5xx comes back as somebody's HTML error page, that the ingest route replays with 200 and creates with 201, and that a home location re-stamps its effective-from instant on every write. Three route-local request schemas move to `src/lib/validations/` so the published shape and the runtime parse are one object rather than two copies — a route module may only export handlers, which is why they could not be imported where they were. The AI-provider PATCH has no schema to move: it hand-rolls its parse field by field, so its entry says so and spells out that a wrongly-typed key is skipped in silence. The generated YAML is deliberately not in this commit.
This commit is contained in:
1 parent
1c2e6c5c80
commit
49da15feaa
15 files changed
+2097
-68
No files matched your search
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Request schema for `POST /api/ai/test` — the AI connection probe.
|
||||
*
|
||||
* Lives outside the route file so the OpenAPI registry can import it (a route
|
||||
* module may only export handlers plus the Next.js route config), which keeps
|
||||
* the published contract and the runtime parser one object.
|
||||
*
|
||||
* The body carries UNSAVED credentials on purpose: the settings surface tests
|
||||
* a selection before it is persisted. Nothing here is written to the user row
|
||||
* — `resolveProviderForTest` layers the override over the stored config for
|
||||
* the duration of the one probe and drops it.
|
||||
*/
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/** The five provider kinds a user may select. */
|
||||
export const AI_PROVIDER_KINDS = [
|
||||
"OPENAI",
|
||||
"ANTHROPIC",
|
||||
"LOCAL",
|
||||
"OPENAI_COMPATIBLE",
|
||||
"CHATGPT_OAUTH",
|
||||
] as const;
|
||||
|
||||
export const aiProviderKindSchema = z.enum(AI_PROVIDER_KINDS);
|
||||
|
||||
export const aiTestOverrideSchema = z
|
||||
.object({
|
||||
provider: aiProviderKindSchema.optional().nullable(),
|
||||
model: z.string().min(1).max(120).optional().nullable(),
|
||||
baseUrl: z.string().url().max(2048).optional().nullable(),
|
||||
anthropicKey: z.string().min(1).max(500).optional().nullable(),
|
||||
localKey: z.string().min(1).max(500).optional().nullable(),
|
||||
openaiKey: z.string().min(1).max(500).optional().nullable(),
|
||||
// The OpenAI-compatible gateway's own fields, kept separate from
|
||||
// `baseUrl` / `openaiKey` so testing an unsaved gateway config can never
|
||||
// reach the pinned OpenAI arm and vice versa.
|
||||
compatBaseUrl: z.string().url().max(2048).optional().nullable(),
|
||||
compatKey: z.string().min(1).max(500).optional().nullable(),
|
||||
compatModel: z.string().min(1).max(120).optional().nullable(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type AiTestOverrideInput = z.infer<typeof aiTestOverrideSchema>;
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Request schemas for the two onboarding write endpoints.
|
||||
*
|
||||
* They live outside the route files for the same reason
|
||||
* `src/lib/validations/about-me.ts` does: a route module may only export
|
||||
* handlers plus the Next.js route config, so the OpenAPI registry cannot
|
||||
* import a schema declared inside one. Keeping the shapes here lets the
|
||||
* published contract and the runtime parser be the same object rather than
|
||||
* two hand-kept copies.
|
||||
*
|
||||
* Prisma-free by construction — `@/lib/onboarding/goals` is pure data — so
|
||||
* the generator script can pull them in without dragging the server graph
|
||||
* along.
|
||||
*/
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { ONBOARDING_GOAL_SLUGS } from "@/lib/onboarding/goals";
|
||||
|
||||
/**
|
||||
* `POST /api/onboarding/step` — the step-by-step wizard checkpoint.
|
||||
*
|
||||
* `step` is the step being COMPLETED and must equal the stored step plus one;
|
||||
* the route refuses anything else with 409 rather than clamping. `goals`
|
||||
* rides the step-2 submit and is validated against the closed slug set, so an
|
||||
* unknown slug fails the whole request instead of being silently dropped.
|
||||
*/
|
||||
export const onboardingStepSchema = z.object({
|
||||
step: z.number().int().min(1).max(4),
|
||||
goals: z
|
||||
.array(z.enum(ONBOARDING_GOAL_SLUGS))
|
||||
.max(ONBOARDING_GOAL_SLUGS.length)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type OnboardingStepInput = z.infer<typeof onboardingStepSchema>;
|
||||
|
||||
/**
|
||||
* `POST /api/onboarding/complete` — the legacy single-shot completion path.
|
||||
*
|
||||
* Every field is optional: the endpoint's job is the completion stamp, and
|
||||
* the profile fields are whatever the wizard happened to collect. A field
|
||||
* that parses but is falsy (an empty `displayName`, `heightCm: 0`) is not
|
||||
* written — the route only assigns truthy values.
|
||||
*/
|
||||
export const onboardingCompleteSchema = z.object({
|
||||
displayName: z.string().trim().min(1).max(50).optional(),
|
||||
heightCm: z.number().min(50).max(300).optional(),
|
||||
dateOfBirth: z.string().optional(),
|
||||
// The same three values the profile schema stores. A narrower enum here
|
||||
// rejected the whole onboarding submission over a field the account is
|
||||
// allowed to hold, and left the value unrecordable until the person found
|
||||
// the setting again afterwards.
|
||||
gender: z.enum(["MALE", "FEMALE", "OTHER"]).optional(),
|
||||
});
|
||||
|
||||
export type OnboardingCompleteInput = z.infer<typeof onboardingCompleteSchema>;
|
||||
Reference in new issue
Block a user