Stop a scalar NPM response killing the request, and add certificate deletion

NPM answers a successful DELETE with the bare literal true, which vv_npm_raw() is typed to
return an array from — so deleting or toggling a proxy host has always thrown a TypeError
rather than doing the thing.
This commit is contained in:
Gmer4Lfe
2026-08-15 15:51:29 -04:00
parent 8157673291
commit 13de1dab82
+35 -1
View File
@@ -234,7 +234,17 @@ function vv_npm_raw(string $method, string $path, array $data, string $token, ar
'code' => (int) curl_getinfo($ch, CURLINFO_HTTP_CODE), 'code' => (int) curl_getinfo($ch, CURLINFO_HTTP_CODE),
]); ]);
curl_close($ch); curl_close($ch);
return json_decode($body ?: '{}', true) ?: [];
// NPM answers a successful DELETE with the bare JSON literal `true`, not an object. This
// function is declared `: array`, so decoding that and returning it threw a TypeError and
// killed the request — which is what deleting a certificate did, and what deleting or toggling
// a proxy host has always done, since those three are the only callers whose endpoint answers
// with a scalar. Wrapped rather than returned raw, so every caller still gets an array and the
// outcome is readable as ['result' => true].
$decoded = json_decode($body ?: '{}', true);
if (is_array($decoded)) return $decoded;
if ($decoded === null) return [];
return ['result' => $decoded];
} }
function vv_npm_req(string $method, string $path, array $data = []): array { function vv_npm_req(string $method, string $path, array $data = []): array {
@@ -261,6 +271,30 @@ function vv_npm_list_certs(): array {
return array_values($list); return array_values($list);
} }
// Deleting a certificate is not undoable — the private key goes with it and a replacement means a
// fresh issuance against Let's Encrypt's rate limits. The caller is expected to have checked that
// no proxy host still points at it; NPM will happily remove one that is in use and leave the host
// serving nothing.
function vv_npm_delete_cert(int $id): array {
$r = vv_npm_req('DELETE', "/api/nginx/certificates/$id");
if (is_array($r) && isset($r['_err'])) return ['ok' => false, 'error' => $r['_err']];
// NPM answers `true` for a successful delete and an error object otherwise.
if (is_array($r) && isset($r['error']))
return ['ok' => false, 'error' => $r['error']['message'] ?? 'Delete failed'];
return ['ok' => true];
}
// Which proxy hosts reference a certificate. The guard that belongs with the delete above, so a
// caller cannot forget to ask the question.
function vv_npm_cert_users(int $id): array {
$p = vv_npm_list_proxies();
if (!($p['ok'] ?? false)) return [];
$out = [];
foreach ($p['proxies'] as $h)
if ((int) ($h['certificate_id'] ?? 0) === $id) $out[] = $h;
return $out;
}
function vv_npm_create_proxy(array $data): array { function vv_npm_create_proxy(array $data): array {
$r = vv_npm_req('POST', '/api/nginx/proxy-hosts', $data); $r = vv_npm_req('POST', '/api/nginx/proxy-hosts', $data);
return isset($r['id']) ? ['ok' => true, 'proxy' => $r] : ['ok' => false, 'error' => $r['error'] ?? ($r['_err'] ?? 'Create failed')]; return isset($r['id']) ? ['ok' => true, 'proxy' => $r] : ['ok' => false, 'error' => $r['error'] ?? ($r['_err'] ?? 'Create failed')];