From 261eded3166e80289647c957aec6823a7da41c33 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 30 May 2026 07:45:35 +0200 Subject: [PATCH] fix(api): align webapp client with post-cascade lnbits + surface error detail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes that together make the kind-0 server-side publish path (this PR's whole reason for existing) actually work end-to-end against the deployed cascade: 1. **updateProfile() uses PATCH /api/v1/auth, not PUT /auth/update.** aiolabs/lnbits PR #26 gap-fill (869f67c3) wired _publish_nostr_metadata_event into the PATCH handler at auth_api.py:546. The legacy `/auth/update` route doesn't exist on the post-cascade server — a `PUT /auth/update` request gets routed into the `/auth/{provider}` SSO wildcard which only allows GET and returns 405. Caught while smoke-testing this PR against a local regtest pointed at the issue-18-phase-2.3 branch. 2. **request() parses FastAPI's `{"detail": "..."}` error shape.** The old error path threw `API request failed: 405 Method Not Allowed` for the regtest's 405 above — useful only if you also opened the network panel and read the response body manually. Now we parse the detail (string or pydantic-validation array), include the endpoint path, and throw `LNbits /auth 405: Method Not Allowed`. Falls back to raw text for non-JSON bodies. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/api/lnbits.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/lib/api/lnbits.ts b/src/lib/api/lnbits.ts index 1e1ecc9..bdd2e18 100644 --- a/src/lib/api/lnbits.ts +++ b/src/lib/api/lnbits.ts @@ -112,12 +112,29 @@ export class LnbitsAPI extends BaseService { if (!response.ok) { const errorText = await response.text() + // Try to surface FastAPI's `{"detail": "..."}` shape; fall back to raw + // body for non-JSON errors. Without this, every backend error renders + // as a generic "API request failed: " and you can't distinguish + // "wrong endpoint" from "expired token" from "validation failure". + let detail: string = errorText + try { + const parsed = JSON.parse(errorText) + if (parsed && typeof parsed.detail === 'string') { + detail = parsed.detail + } else if (parsed && Array.isArray(parsed.detail)) { + // pydantic ValidationError: take the first msg + detail = parsed.detail[0]?.msg ?? errorText + } + } catch { + // body wasn't JSON; keep the raw text in `detail` + } console.error('LNBits API Error:', { + endpoint, status: response.status, statusText: response.statusText, - errorText + detail, }) - throw new Error(`API request failed: ${response.status} ${response.statusText}`) + throw new Error(`LNbits ${endpoint} ${response.status}: ${detail || response.statusText}`) } const data = await response.json() @@ -186,8 +203,12 @@ export class LnbitsAPI extends BaseService { } async updateProfile(data: Partial): Promise { - return this.request('/auth/update', { - method: 'PUT', + // aiolabs/lnbits PR #26 (gap-fill 869f67c3) wired + // _publish_nostr_metadata_event into PATCH /api/v1/auth + // (auth_api.py:546). The legacy PUT /auth/update route does not + // exist on the post-cascade server. + return this.request('/auth', { + method: 'PATCH', body: JSON.stringify(data), }) }