false, 'error' => 'no_body'], 400); } if (strlen($raw) > $max) { json_response(['ok' => false, 'error' => 'payload_too_large'], 413); } $data = json_decode($raw, true); if (!is_array($data)) { json_response(['ok' => false, 'error' => 'invalid_json'], 400); } // Minimal sanity enforcement $data['meta'] = $data['meta'] ?? []; $data['meta']['schema'] = 1; $data['meta']['updatedAt'] = gmdate('c'); // Only allow the keys we expect (keeps junk out) $allowed = ['meta','trips','shopping','preTripTodos','preTripChecklist','upgrades','fixes']; $clean = []; foreach ($allowed as $k) { $clean[$k] = $data[$k] ?? ($k === 'meta' ? $data['meta'] : []); } $json = json_encode($clean, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); if ($json === false) { json_response(['ok' => false, 'error' => 'encode_failed'], 500); } $ok = write_json_file_atomic($config['DATA_FILE'], $json . "\n"); if (!$ok) { json_response(['ok' => false, 'error' => 'write_failed'], 500); } json_response(['ok' => true]);