Files
HealthLog/src/app/api/insights/settings/route.ts
T
Marc-André Bombeck 65677c2112 fix(api): bound every JSON body read
Raising the middleware body cap to 512 MB for archive imports removed
the implicit ceiling that protected JSON routes, so any bulk endpoint
would buffer an arbitrarily large body before validation ran. Every
JSON body read under /api now carries an explicit byte cap sized to its
legitimate payload (64 KB for single-record routes up to 16 MB for the
measurement import), webhooks cap before signature work, and oversized
bodies return 413.
2026-06-10 11:36:51 +02:00

75 lines
2.3 KiB
TypeScript

import { prisma } from "@/lib/db";
import { apiSuccess, apiError, safeJson } from "@/lib/api-response";
import { NextRequest } from "next/server";
import { apiHandler, requireAuth } from "@/lib/api-handler";
import { annotate } from "@/lib/logging/context";
import { isCodexOAuthConfigured } from "@/lib/ai/codex-oauth";
export const GET = apiHandler(async () => {
const { user } = await requireAuth();
const dbUser = await prisma.user.findUnique({
where: { id: user.id },
select: {
codexConnectionStatus: true,
codexConnectedAt: true,
insightsPrivacyMode: true,
insightsCachedAt: true,
},
});
const settings = await prisma.appSettings.findUnique({
where: { id: "singleton" },
select: { adminAiKeyEncrypted: true },
});
annotate({ action: { name: "insights.settings.get" } });
return apiSuccess({
codexStatus: dbUser?.codexConnectionStatus ?? "disconnected",
codexConnectedAt: dbUser?.codexConnectedAt ?? null,
hasAdminKey: !!settings?.adminAiKeyEncrypted,
// v1.4.3: surface whether the operator has a `CODEX_OAUTH_CLIENT_ID`
// configured. Without it the Connect-with-ChatGPT flow is dead, so
// the UI hides the button instead of redirecting to a chatgpt.com
// login the user can never complete.
codexOauthConfigured: isCodexOAuthConfigured(),
privacyMode: dbUser?.insightsPrivacyMode ?? "aggregated",
lastInsightAt: dbUser?.insightsCachedAt ?? null,
});
});
export const PUT = apiHandler(async (request: NextRequest) => {
const { user } = await requireAuth();
const { data: body, error: jsonError } = await safeJson<
Record<string, unknown>
>(request, { maxBytes: 64 * 1024 });
if (jsonError) return jsonError;
const data: Record<string, unknown> = {};
if (typeof body.privacyMode === "string") {
const mode = body.privacyMode as string;
if (!["aggregated", "raw"].includes(mode)) {
return apiError("Invalid privacy mode", 422);
}
data.insightsPrivacyMode = mode;
data.insightsCachedAt = null;
data.insightsCachedText = null;
}
if (Object.keys(data).length === 0) {
return apiError("No changes", 422);
}
await prisma.user.update({
where: { id: user.id },
data,
});
annotate({ action: { name: "insights.settings.update" } });
return apiSuccess({ updated: true });
});