mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
chore(format): prettier sweep across develop
Mostly table column normalisation under docs/audit/ + planning, object- literal expansion in src/lib/ai/coach/snapshot.ts after the W4 F6 Apple Health metric block landed, and small wrap/unwrap line adjustments in the device + APNs routes / tests touched across W3/W4/W5 sessions. No behavioural change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5dcd3954a0
commit
f1e6630958
70 files changed
+899
-660
No files matched your search
@@ -60,22 +60,30 @@ function req(): NextRequest {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(prisma.refreshToken.findMany).mockResolvedValue([]);
|
||||
vi.mocked(prisma.refreshToken.updateMany).mockResolvedValue({ count: 0 } as never);
|
||||
vi.mocked(prisma.apiToken.updateMany).mockResolvedValue({ count: 0 } as never);
|
||||
vi.mocked(prisma.refreshToken.updateMany).mockResolvedValue({
|
||||
count: 0,
|
||||
} as never);
|
||||
vi.mocked(prisma.apiToken.updateMany).mockResolvedValue({
|
||||
count: 0,
|
||||
} as never);
|
||||
vi.mocked(prisma.$transaction).mockResolvedValue([] as never);
|
||||
});
|
||||
|
||||
describe("DELETE /api/auth/me/devices/[id]", () => {
|
||||
it("returns 401 when unauthenticated", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(null);
|
||||
const res = await DELETE(req(), { params: Promise.resolve({ id: "dev-1" }) });
|
||||
const res = await DELETE(req(), {
|
||||
params: Promise.resolve({ id: "dev-1" }),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it("returns 404 when the device doesn't exist", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(prisma.device.findUnique).mockResolvedValue(null);
|
||||
const res = await DELETE(req(), { params: Promise.resolve({ id: "dev-x" }) });
|
||||
const res = await DELETE(req(), {
|
||||
params: Promise.resolve({ id: "dev-x" }),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
@@ -87,7 +95,9 @@ describe("DELETE /api/auth/me/devices/[id]", () => {
|
||||
model: "iPhone",
|
||||
bundleId: "io.healthlog.app",
|
||||
} as never);
|
||||
const res = await DELETE(req(), { params: Promise.resolve({ id: "dev-x" }) });
|
||||
const res = await DELETE(req(), {
|
||||
params: Promise.resolve({ id: "dev-x" }),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
expect(vi.mocked(prisma.device.delete)).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -105,11 +115,17 @@ describe("DELETE /api/auth/me/devices/[id]", () => {
|
||||
{ accessTokenHash: "hash:a2" },
|
||||
{ accessTokenHash: null },
|
||||
] as never);
|
||||
vi.mocked(prisma.refreshToken.updateMany).mockResolvedValue({ count: 3 } as never);
|
||||
vi.mocked(prisma.apiToken.updateMany).mockResolvedValue({ count: 2 } as never);
|
||||
vi.mocked(prisma.refreshToken.updateMany).mockResolvedValue({
|
||||
count: 3,
|
||||
} as never);
|
||||
vi.mocked(prisma.apiToken.updateMany).mockResolvedValue({
|
||||
count: 2,
|
||||
} as never);
|
||||
vi.mocked(prisma.device.delete).mockResolvedValue({ id: "dev-1" } as never);
|
||||
|
||||
const res = await DELETE(req(), { params: Promise.resolve({ id: "dev-1" }) });
|
||||
const res = await DELETE(req(), {
|
||||
params: Promise.resolve({ id: "dev-1" }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.data.revoked).toBe(true);
|
||||
@@ -120,7 +136,8 @@ describe("DELETE /api/auth/me/devices/[id]", () => {
|
||||
});
|
||||
// The refresh-token revoke filter MUST scope to the deviceId, not
|
||||
// every refresh row the user has.
|
||||
const updateCall = vi.mocked(prisma.refreshToken.updateMany).mock.calls[0]?.[0];
|
||||
const updateCall = vi.mocked(prisma.refreshToken.updateMany).mock
|
||||
.calls[0]?.[0];
|
||||
expect(updateCall?.where).toMatchObject({
|
||||
userId: "user-1",
|
||||
deviceId: "dev-1",
|
||||
|
||||
@@ -55,7 +55,11 @@ export const DELETE = apiHandler(
|
||||
},
|
||||
});
|
||||
annotate({
|
||||
action: { name: "auth.me.devices.revoke", entity_type: "device", entity_id: result.id },
|
||||
action: {
|
||||
name: "auth.me.devices.revoke",
|
||||
entity_type: "device",
|
||||
entity_id: result.id,
|
||||
},
|
||||
meta: {
|
||||
refresh_tokens_revoked: result.refreshTokensRevoked,
|
||||
access_tokens_revoked: result.accessTokensRevoked,
|
||||
|
||||
@@ -60,15 +60,21 @@ function req(): NextRequest {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(prisma.refreshToken.findMany).mockResolvedValue([]);
|
||||
vi.mocked(prisma.refreshToken.updateMany).mockResolvedValue({ count: 0 } as never);
|
||||
vi.mocked(prisma.apiToken.updateMany).mockResolvedValue({ count: 0 } as never);
|
||||
vi.mocked(prisma.refreshToken.updateMany).mockResolvedValue({
|
||||
count: 0,
|
||||
} as never);
|
||||
vi.mocked(prisma.apiToken.updateMany).mockResolvedValue({
|
||||
count: 0,
|
||||
} as never);
|
||||
vi.mocked(prisma.$transaction).mockResolvedValue([] as never);
|
||||
});
|
||||
|
||||
describe("DELETE /api/devices/[id] (iOS token-rotation endpoint)", () => {
|
||||
it("returns 401 when unauthenticated", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(null);
|
||||
const res = await DELETE(req(), { params: Promise.resolve({ id: "dev-1" }) });
|
||||
const res = await DELETE(req(), {
|
||||
params: Promise.resolve({ id: "dev-1" }),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
@@ -80,7 +86,9 @@ describe("DELETE /api/devices/[id] (iOS token-rotation endpoint)", () => {
|
||||
model: "iPhone",
|
||||
bundleId: "io.healthlog.app",
|
||||
} as never);
|
||||
const res = await DELETE(req(), { params: Promise.resolve({ id: "dev-x" }) });
|
||||
const res = await DELETE(req(), {
|
||||
params: Promise.resolve({ id: "dev-x" }),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
@@ -97,7 +105,9 @@ describe("DELETE /api/devices/[id] (iOS token-rotation endpoint)", () => {
|
||||
] as never);
|
||||
vi.mocked(prisma.device.delete).mockResolvedValue({ id: "dev-1" } as never);
|
||||
|
||||
const res = await DELETE(req(), { params: Promise.resolve({ id: "dev-1" }) });
|
||||
const res = await DELETE(req(), {
|
||||
params: Promise.resolve({ id: "dev-1" }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(vi.mocked(prisma.device.delete)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -46,7 +46,11 @@ export const DELETE = apiHandler(
|
||||
},
|
||||
});
|
||||
annotate({
|
||||
action: { name: "devices.revoke", entity_type: "device", entity_id: result.id },
|
||||
action: {
|
||||
name: "devices.revoke",
|
||||
entity_type: "device",
|
||||
entity_id: result.id,
|
||||
},
|
||||
meta: {
|
||||
refresh_tokens_revoked: result.refreshTokensRevoked,
|
||||
access_tokens_revoked: result.accessTokensRevoked,
|
||||
|
||||
@@ -114,10 +114,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
deviceId: existingApns.id,
|
||||
},
|
||||
});
|
||||
return apiError(
|
||||
"APNs token already registered to another account",
|
||||
409,
|
||||
);
|
||||
return apiError("APNs token already registered to another account", 409);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,12 @@ import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { apiError, apiSuccess, getClientIp, safeJson } from "@/lib/api-response";
|
||||
import {
|
||||
apiError,
|
||||
apiSuccess,
|
||||
getClientIp,
|
||||
safeJson,
|
||||
} from "@/lib/api-response";
|
||||
import { withIdempotency } from "@/lib/idempotency";
|
||||
import { mapAppleHealthEntry } from "@/lib/measurements/apple-health-mapping";
|
||||
import { validateMeasurementRange } from "@/lib/validations/measurement";
|
||||
@@ -84,11 +89,9 @@ async function postBatch(request: NextRequest): Promise<Response> {
|
||||
Array.isArray((rawBody as { entries: unknown }).entries) &&
|
||||
(rawBody as { entries: unknown[] }).entries.length > MAX_BATCH_ENTRIES
|
||||
) {
|
||||
return apiError(
|
||||
`Batch exceeds the ${MAX_BATCH_ENTRIES}-entry limit`,
|
||||
422,
|
||||
{ errorCode: "coach.batch.too_large" },
|
||||
);
|
||||
return apiError(`Batch exceeds the ${MAX_BATCH_ENTRIES}-entry limit`, 422, {
|
||||
errorCode: "coach.batch.too_large",
|
||||
});
|
||||
}
|
||||
|
||||
const parsed = batchPayloadSchema.safeParse(rawBody);
|
||||
|
||||
@@ -97,7 +97,10 @@ describe("<AdminShell>", () => {
|
||||
// missing from the nav even though the slug + page + i18n shipped.
|
||||
const navSlugs = new Set(ADMIN_SECTIONS.map((s) => s.slug));
|
||||
for (const slug of ADMIN_SECTION_SLUGS) {
|
||||
expect(navSlugs.has(slug), `ADMIN_SECTIONS missing nav entry for ${slug}`).toBe(true);
|
||||
expect(
|
||||
navSlugs.has(slug),
|
||||
`ADMIN_SECTIONS missing nav entry for ${slug}`,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -124,7 +127,9 @@ describe("<AdminShell>", () => {
|
||||
// filesystem read isn't accidentally noop'd by a future tree-
|
||||
// shaker pass.
|
||||
expect(
|
||||
statSync(join(process.cwd(), "src/app/admin/[section]/page.tsx")).isFile(),
|
||||
statSync(
|
||||
join(process.cwd(), "src/app/admin/[section]/page.tsx"),
|
||||
).isFile(),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -109,7 +109,9 @@ export function nextResettableValue<T>(
|
||||
previous: T,
|
||||
incoming: T,
|
||||
): { reset: true; value: T } | { reset: false } {
|
||||
return Object.is(previous, incoming) ? { reset: false } : { reset: true, value: incoming };
|
||||
return Object.is(previous, incoming)
|
||||
? { reset: false }
|
||||
: { reset: true, value: incoming };
|
||||
}
|
||||
|
||||
export function CoachDrawer({
|
||||
|
||||
@@ -130,7 +130,7 @@ export function CoachSettingsSheet({
|
||||
<SheetContent
|
||||
side="right"
|
||||
data-slot="coach-settings-sheet"
|
||||
className="w-full p-0 sm:max-w-[420px] flex h-[100dvh] flex-col gap-0"
|
||||
className="flex h-[100dvh] w-full flex-col gap-0 p-0 sm:max-w-[420px]"
|
||||
>
|
||||
<SheetHeader className="border-border/70 border-b p-4 pr-12">
|
||||
<SheetTitle className="text-sm font-semibold">
|
||||
@@ -171,10 +171,7 @@ export function CoachSettingsSheet({
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto p-4">
|
||||
{/* Tone */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label
|
||||
htmlFor="coach-prefs-tone"
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
<Label htmlFor="coach-prefs-tone" className="text-xs font-medium">
|
||||
{t("insights.coach.settingsToneLabel")}
|
||||
</Label>
|
||||
<Select
|
||||
@@ -267,9 +264,7 @@ export function CoachSettingsSheet({
|
||||
id={id}
|
||||
data-slot={`coach-prefs-exclude-${metric}`}
|
||||
checked={checked}
|
||||
onCheckedChange={(next) =>
|
||||
toggleExclude(metric, next)
|
||||
}
|
||||
onCheckedChange={(next) => toggleExclude(metric, next)}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
@@ -323,10 +318,7 @@ export function CoachSettingsSheet({
|
||||
data-slot="coach-prefs-save"
|
||||
>
|
||||
{save.isPending ? (
|
||||
<Loader2
|
||||
className="size-3.5 animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<Loader2 className="size-3.5 animate-spin" aria-hidden="true" />
|
||||
) : null}
|
||||
{t("insights.coach.settingsSave")}
|
||||
</Button>
|
||||
|
||||
@@ -444,10 +444,7 @@ function CoachMessageFeedback({ messageId }: CoachMessageFeedbackProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="coach-message-feedback"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<div data-slot="coach-message-feedback" className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
data-slot="coach-message-feedback-helpful"
|
||||
|
||||
@@ -94,9 +94,7 @@ describe("MeasurementList — APPLE_HEALTH badge", () => {
|
||||
// duplicate hand-entries on top of synced rows. Both branches must
|
||||
// carry the badge so the surface is honest at every viewport.
|
||||
const html = render("en");
|
||||
const matches = html.match(
|
||||
/data-testid="measurement-source-badge"/g,
|
||||
);
|
||||
const matches = html.match(/data-testid="measurement-source-badge"/g);
|
||||
expect(matches?.length ?? 0).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -52,7 +52,9 @@ describe("getStrictInsightsSystemPrompt — EN", () => {
|
||||
});
|
||||
|
||||
it("documents the additive sourceMetric tokens in the OUTPUT FORMAT", () => {
|
||||
expect(prompt).toMatch(/hrv \| sleep \| resting_hr \| steps \| active_energy/);
|
||||
expect(prompt).toMatch(
|
||||
/hrv \| sleep \| resting_hr \| steps \| active_energy/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,6 +72,8 @@ describe("getStrictInsightsSystemPrompt — DE", () => {
|
||||
});
|
||||
|
||||
it("documents the additive sourceMetric tokens in the German OUTPUT FORMAT", () => {
|
||||
expect(prompt).toMatch(/hrv \| sleep \| resting_hr \| steps \| active_energy/);
|
||||
expect(prompt).toMatch(
|
||||
/hrv \| sleep \| resting_hr \| steps \| active_energy/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -523,15 +523,60 @@ export async function buildCoachSnapshot(
|
||||
// Apple Health source maps to exactly one MeasurementType (BP is
|
||||
// the only fan-out and lives in its own legacy block above).
|
||||
const appleHealthBlocks: AppleHealthBlock[] = [
|
||||
{ metric: "hrv", snapshotKey: "heartRateVariability", type: "HEART_RATE_VARIABILITY", enabled: sources.has("hrv") },
|
||||
{ metric: "sleep", snapshotKey: "sleep", type: "SLEEP_DURATION", enabled: sources.has("sleep") },
|
||||
{ metric: "resting_hr", snapshotKey: "restingHeartRate", type: "RESTING_HEART_RATE", enabled: sources.has("resting_hr") },
|
||||
{ metric: "steps", snapshotKey: "steps", type: "ACTIVITY_STEPS", enabled: sources.has("steps") },
|
||||
{ metric: "active_energy", snapshotKey: "activeEnergy", type: "ACTIVE_ENERGY_BURNED", enabled: sources.has("active_energy") },
|
||||
{ metric: "flights", snapshotKey: "flightsClimbed", type: "FLIGHTS_CLIMBED", enabled: sources.has("flights") },
|
||||
{ metric: "distance", snapshotKey: "walkingRunningDistance", type: "WALKING_RUNNING_DISTANCE", enabled: sources.has("distance") },
|
||||
{ metric: "vo2_max", snapshotKey: "vo2Max", type: "VO2_MAX", enabled: sources.has("vo2_max") },
|
||||
{ metric: "body_temp", snapshotKey: "bodyTemperature", type: "BODY_TEMPERATURE", enabled: sources.has("body_temp") },
|
||||
{
|
||||
metric: "hrv",
|
||||
snapshotKey: "heartRateVariability",
|
||||
type: "HEART_RATE_VARIABILITY",
|
||||
enabled: sources.has("hrv"),
|
||||
},
|
||||
{
|
||||
metric: "sleep",
|
||||
snapshotKey: "sleep",
|
||||
type: "SLEEP_DURATION",
|
||||
enabled: sources.has("sleep"),
|
||||
},
|
||||
{
|
||||
metric: "resting_hr",
|
||||
snapshotKey: "restingHeartRate",
|
||||
type: "RESTING_HEART_RATE",
|
||||
enabled: sources.has("resting_hr"),
|
||||
},
|
||||
{
|
||||
metric: "steps",
|
||||
snapshotKey: "steps",
|
||||
type: "ACTIVITY_STEPS",
|
||||
enabled: sources.has("steps"),
|
||||
},
|
||||
{
|
||||
metric: "active_energy",
|
||||
snapshotKey: "activeEnergy",
|
||||
type: "ACTIVE_ENERGY_BURNED",
|
||||
enabled: sources.has("active_energy"),
|
||||
},
|
||||
{
|
||||
metric: "flights",
|
||||
snapshotKey: "flightsClimbed",
|
||||
type: "FLIGHTS_CLIMBED",
|
||||
enabled: sources.has("flights"),
|
||||
},
|
||||
{
|
||||
metric: "distance",
|
||||
snapshotKey: "walkingRunningDistance",
|
||||
type: "WALKING_RUNNING_DISTANCE",
|
||||
enabled: sources.has("distance"),
|
||||
},
|
||||
{
|
||||
metric: "vo2_max",
|
||||
snapshotKey: "vo2Max",
|
||||
type: "VO2_MAX",
|
||||
enabled: sources.has("vo2_max"),
|
||||
},
|
||||
{
|
||||
metric: "body_temp",
|
||||
snapshotKey: "bodyTemperature",
|
||||
type: "BODY_TEMPERATURE",
|
||||
enabled: sources.has("body_temp"),
|
||||
},
|
||||
];
|
||||
for (const block of appleHealthBlocks) {
|
||||
if (!block.enabled) continue;
|
||||
|
||||
@@ -142,8 +142,8 @@ describe("revokeDeviceCascade", () => {
|
||||
new Error("postgres-blip"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
revokeDeviceCascade("user-1", "dev-3"),
|
||||
).rejects.toThrow("postgres-blip");
|
||||
await expect(revokeDeviceCascade("user-1", "dev-3")).rejects.toThrow(
|
||||
"postgres-blip",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,8 @@ describe("APPLE_HEALTH_TYPE_MAP", () => {
|
||||
});
|
||||
|
||||
it("converts oxygen saturation from 0..1 fraction to 0..100 percent", () => {
|
||||
const mapping = APPLE_HEALTH_TYPE_MAP.HKQuantityTypeIdentifierOxygenSaturation;
|
||||
const mapping =
|
||||
APPLE_HEALTH_TYPE_MAP.HKQuantityTypeIdentifierOxygenSaturation;
|
||||
expect(mapping.convertToDbUnit(0.97)).toBeCloseTo(97);
|
||||
});
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
* DB unit, and the aggregation hint all live alongside the identifier
|
||||
* so future analytics work has a single discoverable surface.
|
||||
*/
|
||||
import type {
|
||||
MeasurementType,
|
||||
SleepStage,
|
||||
} from "@/generated/prisma/client";
|
||||
import type { MeasurementType, SleepStage } from "@/generated/prisma/client";
|
||||
|
||||
/**
|
||||
* How a batch of HealthKit samples for the same identifier should be
|
||||
|
||||
@@ -74,8 +74,7 @@ const VALID_ENV = {
|
||||
APNS_KEY_ID: "ABCDE12345",
|
||||
APNS_TEAM_ID: "TEAM123456",
|
||||
APNS_BUNDLE_ID: "test.healthlog.ios",
|
||||
APNS_KEY:
|
||||
"-----BEGIN PRIVATE KEY-----\\nMOCKKEY\\n-----END PRIVATE KEY-----",
|
||||
APNS_KEY: "-----BEGIN PRIVATE KEY-----\\nMOCKKEY\\n-----END PRIVATE KEY-----",
|
||||
};
|
||||
|
||||
function setEnv(over: Record<string, string | undefined> = {}): void {
|
||||
@@ -269,7 +268,6 @@ describe("sendApnsPush — failure classification", () => {
|
||||
expect(r.reason).toBe("apns_network_error");
|
||||
expect(r.shouldDisable).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("sendApnsPush — config disabled paths", () => {
|
||||
@@ -325,7 +323,11 @@ describe("sendViaApns — dispatcher fan-out", () => {
|
||||
.mockResolvedValueOnce({
|
||||
sent: [],
|
||||
failed: [
|
||||
{ device: "tok-b", status: 410, response: { reason: "Unregistered" } },
|
||||
{
|
||||
device: "tok-b",
|
||||
status: 410,
|
||||
response: { reason: "Unregistered" },
|
||||
},
|
||||
],
|
||||
});
|
||||
const r = await sendViaApns("u-1", {
|
||||
@@ -349,13 +351,21 @@ describe("sendViaApns — dispatcher fan-out", () => {
|
||||
.mockResolvedValueOnce({
|
||||
sent: [],
|
||||
failed: [
|
||||
{ device: "tok-a", status: 410, response: { reason: "Unregistered" } },
|
||||
{
|
||||
device: "tok-a",
|
||||
status: 410,
|
||||
response: { reason: "Unregistered" },
|
||||
},
|
||||
],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
sent: [],
|
||||
failed: [
|
||||
{ device: "tok-b", status: 400, response: { reason: "BadDeviceToken" } },
|
||||
{
|
||||
device: "tok-b",
|
||||
status: 400,
|
||||
response: { reason: "BadDeviceToken" },
|
||||
},
|
||||
],
|
||||
});
|
||||
const r = await sendViaApns("u-1", {
|
||||
|
||||
@@ -126,9 +126,7 @@ export function loadApnsConfig(): ApnsConfig | null {
|
||||
// configuring and stopped — surface that as a warning so the
|
||||
// half-finished state is visible in logs.
|
||||
const anySet = Boolean(keyId || teamId || bundleId || inlineKey || keyFile);
|
||||
const allSet = Boolean(
|
||||
keyId && teamId && bundleId && (inlineKey || keyFile),
|
||||
);
|
||||
const allSet = Boolean(keyId && teamId && bundleId && (inlineKey || keyFile));
|
||||
|
||||
if (!anySet) {
|
||||
cachedConfig = null;
|
||||
|
||||
@@ -58,9 +58,7 @@ function dataEnvelope<T extends z.ZodType>(payload: T, id: string) {
|
||||
.object({
|
||||
data: payload,
|
||||
error: z.null(),
|
||||
meta: z
|
||||
.object({ requestId: z.string().optional() })
|
||||
.optional(),
|
||||
meta: z.object({ requestId: z.string().optional() }).optional(),
|
||||
})
|
||||
.meta({ id });
|
||||
}
|
||||
@@ -118,7 +116,10 @@ const coachMessageFeedbackBody = z
|
||||
|
||||
const passkeyLoginVerifyRequest = z
|
||||
.object({
|
||||
challengeId: z.string().min(1).describe("Server-issued WebAuthn challenge id."),
|
||||
challengeId: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("Server-issued WebAuthn challenge id."),
|
||||
credential: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
@@ -157,9 +158,7 @@ const refreshRequest = z
|
||||
|
||||
const accessRefreshBundle = z
|
||||
.object({
|
||||
user: z
|
||||
.object({ id: z.string(), username: z.string() })
|
||||
.optional(),
|
||||
user: z.object({ id: z.string(), username: z.string() }).optional(),
|
||||
token: z.string().describe("Access token (`hlk_<64hex>`)."),
|
||||
tokenExpiresAt: z.iso.datetime({ offset: true }),
|
||||
refreshToken: z
|
||||
@@ -216,7 +215,9 @@ const batchEntrySchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(120)
|
||||
.describe("HealthKit identifier (e.g. `HKQuantityTypeIdentifierBodyMass`)."),
|
||||
.describe(
|
||||
"HealthKit identifier (e.g. `HKQuantityTypeIdentifierBodyMass`).",
|
||||
),
|
||||
value: z.number().finite(),
|
||||
unit: z.string().min(1).max(60),
|
||||
startDate: z.iso.datetime({ offset: true }),
|
||||
|
||||
Reference in new issue
Block a user