import { prisma } from "@/lib/db"; import { apiHandler, requireAdmin } from "@/lib/api-handler"; import { auditLog } from "@/lib/auth/audit"; import { apiSuccess, apiError, getClientIp } from "@/lib/api-response"; import { annotate } from "@/lib/logging/context"; import { checkRateLimit, rateLimitHeaders } from "@/lib/rate-limit"; import { NextRequest, NextResponse } from "next/server"; export const dynamic = "force-dynamic"; /** * Admin-only global wipe of all health/integration data. * Keeps users/passkeys so access to the app remains possible. */ export const DELETE = apiHandler(async (request: NextRequest) => { const ip = getClientIp(request); const rl = await checkRateLimit(`admin-data-delete:${ip}`, 5, 60 * 1000); if (!rl.allowed) { return NextResponse.json( { data: null, error: "Rate limit exceeded" }, { status: 429, headers: rateLimitHeaders(rl) }, ); } const { user } = await requireAdmin(); annotate({ action: { name: "admin.data.delete" } }); let confirm = ""; try { const body = await request.json(); confirm = typeof body?.confirm === "string" ? body.confirm : ""; } catch { return apiError("Invalid request", 422); } if (confirm !== "DELETE ALL") { return apiError("Confirmation missing", 422); } // Audit-log the *intent* before the transaction begins so the trail // describing "an admin is about to wipe data" survives even if the // operation crashes midway. AuditLog rows are intentionally NOT // deleted as part of the wipe — the entire point of an audit trail // is to outlive the data it documents. await auditLog("admin.data.clear.start", { userId: user.id, ipAddress: getClientIp(request), details: { confirm: "DELETE ALL" }, }); const result = await prisma.$transaction(async (tx) => { const measurements = await tx.measurement.deleteMany({}); const intakeEvents = await tx.medicationIntakeEvent.deleteMany({}); const medications = await tx.medication.deleteMany({}); const apiTokens = await tx.apiToken.deleteMany({}); const withingsConnections = await tx.withingsConnection.deleteMany({}); const authChallenges = await tx.authChallenge.deleteMany({}); await tx.user.updateMany({ data: { heightCm: null, dateOfBirth: null, gender: null, insightsPrivacyMode: "aggregated", insightsCachedAt: null, insightsCachedText: null, telegramBotToken: null, telegramChatId: null, telegramEnabled: false, withingsClientIdEncrypted: null, withingsClientSecretEncrypted: null, onboardingCompletedAt: null, }, }); return { measurements: measurements.count, intakeEvents: intakeEvents.count, medications: medications.count, apiTokens: apiTokens.count, withingsConnections: withingsConnections.count, authChallenges: authChallenges.count, }; }); await auditLog("admin.data.clear", { userId: user.id, ipAddress: getClientIp(request), details: result, }); return apiSuccess({ cleared: true, ...result }); });