import { NextResponse } from 'next/server'; import { authenticateDevice } from '@/lib/device/session'; import { clientIp } from '@/lib/device/headers'; import { checkTransport } from '@/lib/device/transport'; import { prisma } from '@/lib/db'; import { rateLimit } from '@/lib/ratelimit'; export const dynamic = 'force-dynamic'; /** Never let a firmware in a retry loop fill the table in one request. */ const MAX_ENTRIES_PER_CALL = 50; type IncomingLog = { message?: unknown; level?: unknown; created_at?: unknown; }; /** * Firmware-side logs. Answered with 204 whatever happens to the contents: a * device that cannot file a log must not conclude the server is down and * start retrying, and these records are diagnostics, not data. */ export async function POST(request: Request) { const transport = checkTransport(request); if (!transport.ok) { return transport.response; } const limit = rateLimit(`log:${clientIp(request)}`, 30, 60_000); if (!limit.allowed) { return new NextResponse(null, { status: 429 }); } const device = await authenticateDevice(request); if (!device) { return NextResponse.json({ error: 'Jeton invalide' }, { status: 401 }); } let entries: IncomingLog[] = []; try { const body: unknown = await request.json(); const logs = (body as { logs?: unknown } | null)?.logs; if (Array.isArray(logs)) { entries = logs.slice(0, MAX_ENTRIES_PER_CALL) as IncomingLog[]; } } catch { // A malformed body is a diagnostic in itself; 204 keeps the device calm. return new NextResponse(null, { status: 204 }); } if (entries.length > 0) { await prisma.deviceLog.createMany({ data: entries.map((entry) => ({ deviceId: device.id, level: levelOf(entry.level), message: String(entry.message ?? '').slice(0, 2000) || '(vide)', payload: entry as object, })), }); } return new NextResponse(null, { status: 204 }); } function levelOf(value: unknown): 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' { const level = String(value ?? '').toUpperCase(); return level === 'DEBUG' || level === 'WARN' || level === 'ERROR' ? level : 'INFO'; }