From 5e313c02822aed35f74ff87e2249af39e00cbc46 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 15 Mar 2023 21:17:51 +0200 Subject: [PATCH 01/44] chore: code format --- static/components/relay-details/relay-details.js | 1 - templates/nostrrelay/public.html | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/static/components/relay-details/relay-details.js b/static/components/relay-details/relay-details.js index 0ab78b1..21a3775 100644 --- a/static/components/relay-details/relay-details.js +++ b/static/components/relay-details/relay-details.js @@ -131,7 +131,6 @@ async function relayDetails(path) { this.inkey ) this.relay = data - } catch (error) { LNbits.utils.notifyApiError(error) } diff --git a/templates/nostrrelay/public.html b/templates/nostrrelay/public.html index 7c19329..d300cb7 100644 --- a/templates/nostrrelay/public.html +++ b/templates/nostrrelay/public.html @@ -243,8 +243,7 @@ } } }, - created: function () { - } + created: function () {} }) {% endblock %} From 63be2b5b2df51bff3096bfab47083724770901ee Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 24 Feb 2023 15:10:58 +0200 Subject: [PATCH 02/44] feat: show `wss` URL --- .../relay-details/relay-details.html | 20 +++++++++++++++++++ .../components/relay-details/relay-details.js | 9 ++++++++- static/js/index.js | 6 +----- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/static/components/relay-details/relay-details.html b/static/components/relay-details/relay-details.html index 2deeeb5..8ea8394 100644 --- a/static/components/relay-details/relay-details.html +++ b/static/components/relay-details/relay-details.html @@ -68,6 +68,26 @@
+
+
Web Socket Link:
+
+ +
+
+ Copy +
+
diff --git a/static/components/relay-details/relay-details.js b/static/components/relay-details/relay-details.js index 21a3775..37616b9 100644 --- a/static/components/relay-details/relay-details.js +++ b/static/components/relay-details/relay-details.js @@ -92,6 +92,13 @@ async function relayDetails(path) { {value: 'block', label: 'Block New Events'}, {value: 'prune', label: 'Prune Old Events'} ] + }, + wssLink: function () { + this.relay.config.domain = + this.relay.config.domain || window.location.hostname + return ( + 'wss://' + this.relay.config.domain + '/nostrrelay/' + this.relay.id + ) } }, @@ -138,7 +145,7 @@ async function relayDetails(path) { updateRelay: async function () { try { const {data} = await LNbits.api.request( - 'PUT', + 'PATCH', '/nostrrelay/api/v1/relay/' + this.relayId, this.adminkey, this.relay diff --git a/static/js/index.js b/static/js/index.js index 0845fc1..1b6d317 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -140,11 +140,7 @@ const relays = async () => { 'PUT', '/nostrrelay/api/v1/relay/' + relay.id, this.g.user.wallets[0].adminkey, - { - active: relay.active, - id: relay.id, - name: relay.name - } + {} ) } catch (error) { LNbits.utils.notifyApiError(error) From 67dda89c81540eeaf2b8d43fd8b9ad928ac24820 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 24 Feb 2023 15:11:16 +0200 Subject: [PATCH 03/44] fix: separate relay update from activete/deactivate --- views_api.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/views_api.py b/views_api.py index 1620a3d..61f361f 100644 --- a/views_api.py +++ b/views_api.py @@ -76,7 +76,7 @@ async def api_create_relay( ) -@nostrrelay_ext.put("/api/v1/relay/{relay_id}") +@nostrrelay_ext.patch("/api/v1/relay/{relay_id}") async def api_update_relay( relay_id: str, data: NostrRelay, wallet: WalletTypeInfo = Depends(require_admin_key) ) -> NostrRelay: @@ -95,6 +95,8 @@ async def api_update_relay( ) updated_relay = NostrRelay.parse_obj({**dict(relay), **dict(data)}) updated_relay = await update_relay(wallet.wallet.user, updated_relay) + # activate & deactivate have their own endpoint + updated_relay.active = relay.active if updated_relay.active: await client_manager.enable_relay(relay_id, updated_relay.config) @@ -113,6 +115,38 @@ async def api_update_relay( ) +@nostrrelay_ext.put("/api/v1/relay/{relay_id}") +async def api_toggle_relay( + relay_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) +) -> NostrRelay: + + try: + relay = await get_relay(wallet.wallet.user,relay_id) + if not relay: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Relay not found", + ) + relay.active = not relay.active + updated_relay = await update_relay(wallet.wallet.user, relay) + + if relay.active: + await client_manager.enable_relay(relay_id, relay.config) + else: + await client_manager.disable_relay(relay_id) + + return updated_relay + + except HTTPException as ex: + raise ex + except Exception as ex: + logger.warning(ex) + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Cannot update relay", + ) + + @nostrrelay_ext.get("/api/v1/relay") async def api_get_relays( wallet: WalletTypeInfo = Depends(require_invoice_key), From 6e1b5dd0bb8ade926db266208cbdbd56ec471164 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 24 Feb 2023 15:15:06 +0200 Subject: [PATCH 04/44] fix: copy button --- static/components/relay-details/relay-details.html | 2 +- static/components/relay-details/relay-details.js | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/static/components/relay-details/relay-details.html b/static/components/relay-details/relay-details.html index 8ea8394..a28c110 100644 --- a/static/components/relay-details/relay-details.html +++ b/static/components/relay-details/relay-details.html @@ -83,7 +83,7 @@ Copy diff --git a/static/components/relay-details/relay-details.js b/static/components/relay-details/relay-details.js index 37616b9..41d1a94 100644 --- a/static/components/relay-details/relay-details.js +++ b/static/components/relay-details/relay-details.js @@ -245,7 +245,17 @@ async function relayDetails(path) { value = +eventKind this.relay.config.forcedAuthEvents = this.relay.config.forcedAuthEvents.filter(e => e !== value) - } + }, + // todo: bad. base.js not present in custom components + copyText: function (text, message, position) { + var notify = this.$q.notify + Quasar.utils.copyToClipboard(text).then(function () { + notify({ + message: message || 'Copied to clipboard!', + position: position || 'bottom' + }) + }) + }, }, created: async function () { From 218b3243471b446c39f4786e19e61d16398699dd Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 24 Feb 2023 16:36:53 +0200 Subject: [PATCH 05/44] chore: code format --- static/components/relay-details/relay-details.html | 7 +------ static/components/relay-details/relay-details.js | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/static/components/relay-details/relay-details.html b/static/components/relay-details/relay-details.html index a28c110..a4e58cc 100644 --- a/static/components/relay-details/relay-details.html +++ b/static/components/relay-details/relay-details.html @@ -80,12 +80,7 @@ >
- Copy + Copy
diff --git a/static/components/relay-details/relay-details.js b/static/components/relay-details/relay-details.js index 41d1a94..ad14b3f 100644 --- a/static/components/relay-details/relay-details.js +++ b/static/components/relay-details/relay-details.js @@ -255,7 +255,7 @@ async function relayDetails(path) { position: position || 'bottom' }) }) - }, + } }, created: async function () { From 30ffcf7f554a2cf8eae7a36f5f569ec34bb49d1d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 24 Feb 2023 16:37:21 +0200 Subject: [PATCH 06/44] fix: show `wss` & invoice generation --- templates/nostrrelay/public.html | 203 +++++++++++++++---------------- views_api.py | 15 ++- 2 files changed, 110 insertions(+), 108 deletions(-) diff --git a/templates/nostrrelay/public.html b/templates/nostrrelay/public.html index d300cb7..7334568 100644 --- a/templates/nostrrelay/public.html +++ b/templates/nostrrelay/public.html @@ -11,43 +11,90 @@

+
+ + +
- Public Key: - +
+
+ Relay Link: +
+
+ +
+
+ Copy +
+
-
+
+
+ Public Key: +
+
+ +
+
+
+ + +
Cost to join:
- - sats +
+ + sats +
- Pay to Join +
+ Pay to Join +
+
+ Free to join + +
- +
+
-
+
Storage cost:
-
+
sats per @@ -56,6 +103,7 @@
-
- - sats +
+
+ + sats +
- Buy storage space +
+ Buy storage space +
+
+ Free storage + +
- This is a free relay +
+ This is a free Nostr Relay +
@@ -118,77 +177,6 @@
-
- - - - - - - - - - GET - /lnurlp/api/v1/links -
Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<pay_link_object>, ...] -
Curl example
-
-
-
- - - - - - - - - - - - - - - -
-
-
-
@@ -211,11 +199,20 @@ storageCost: function () { if (!this.relay || !this.relay.config.storageCostValue) return 0 return this.unitsToBuy * this.relay.config.storageCostValue + }, + wssLink: function () { + this.relay.config.domain = + this.relay.config.domain || window.location.hostname + return ( + 'wss://' + this.relay.config.domain + '/nostrrelay/' + this.relay.id + ) } }, methods: { createInvoice: async function (action) { + console.log('### action', action) if (!action) return + this.invoice = '' if (!this.pubkey) { this.$q.notify({ timeout: 5000, diff --git a/views_api.py b/views_api.py index 61f361f..347b197 100644 --- a/views_api.py +++ b/views_api.py @@ -283,11 +283,13 @@ async def api_pay_to_join(data: BuyOrder): detail="Relay not found", ) - if data.action == "join" and relay.is_free_to_join: - raise ValueError("Relay is free to join") - + amount = 0 storage_to_buy = 0 - if data.action == "storage": + if data.action == "join": + if relay.is_free_to_join: + raise ValueError("Relay is free to join") + amount = int(relay.config.cost_to_join) + elif data.action == "storage": if relay.config.storage_cost_value == 0: raise ValueError("Relay storage cost is zero. Cannot buy!") if data.units_to_buy == 0: @@ -295,10 +297,13 @@ async def api_pay_to_join(data: BuyOrder): storage_to_buy = data.units_to_buy * relay.config.storage_cost_value * 1024 if relay.config.storage_cost_unit == "MB": storage_to_buy *= 1024 + amount = data.units_to_buy * relay.config.storage_cost_value + else: + raise ValueError(f"Unknown action: '{data.action}'") _, payment_request = await create_invoice( wallet_id=relay.config.wallet, - amount=int(relay.config.cost_to_join), + amount=amount, memo=f"Pubkey '{data.pubkey}' wants to join {relay.id}", extra={ "tag": "nostrrely", From 3aa4875558edb1987ea9f8c9c61f395575327a16 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 17 Mar 2023 15:06:09 +0200 Subject: [PATCH 07/44] chore: code format --- __init__.py | 11 +++++------ helpers.py | 2 +- views_api.py | 7 ++++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/__init__.py b/__init__.py index 97dc8bd..12154d0 100644 --- a/__init__.py +++ b/__init__.py @@ -21,17 +21,16 @@ nostrrelay_static_files = [ ] nostrrelay_redirect_paths = [ - { - "from_path": "/", - "redirect_to_path": "/api/v1/relay-info", - "header_filters": { - "accept": "application/nostr+json" + { + "from_path": "/", + "redirect_to_path": "/api/v1/relay-info", + "header_filters": {"accept": "application/nostr+json"}, } - } ] scheduled_tasks: List[asyncio.Task] = [] + def nostrrelay_renderer(): return template_renderer(["lnbits/extensions/nostrrelay/templates"]) diff --git a/helpers.py b/helpers.py index 169c65c..5466e70 100644 --- a/helpers.py +++ b/helpers.py @@ -34,4 +34,4 @@ def relay_info_response(relay_public_data: dict) -> JSONResponse: "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Methods": "GET", }, - ) \ No newline at end of file + ) diff --git a/views_api.py b/views_api.py index 347b197..9c6557f 100644 --- a/views_api.py +++ b/views_api.py @@ -121,7 +121,7 @@ async def api_toggle_relay( ) -> NostrRelay: try: - relay = await get_relay(wallet.wallet.user,relay_id) + relay = await get_relay(wallet.wallet.user, relay_id) if not relay: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, @@ -159,12 +159,13 @@ async def api_get_relays( status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Cannot fetch relays", ) + + @nostrrelay_ext.get("/api/v1/relay-info") async def api_get_relay_info() -> JSONResponse: return relay_info_response(NostrRelay.info()) - @nostrrelay_ext.get("/api/v1/relay/{relay_id}") async def api_get_relay( relay_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key) @@ -288,7 +289,7 @@ async def api_pay_to_join(data: BuyOrder): if data.action == "join": if relay.is_free_to_join: raise ValueError("Relay is free to join") - amount = int(relay.config.cost_to_join) + amount = int(relay.config.cost_to_join) elif data.action == "storage": if relay.config.storage_cost_value == 0: raise ValueError("Relay storage cost is zero. Cannot buy!") From 527afa0c8cfa73fea3c8d9073d83ba2a12b18b5f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 17 Mar 2023 15:06:59 +0200 Subject: [PATCH 08/44] feat: wait for paid invoce --- tasks.py | 25 +++++++++++++----- templates/nostrrelay/public.html | 44 ++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/tasks.py b/tasks.py index 87b8eed..8b37e1f 100644 --- a/tasks.py +++ b/tasks.py @@ -1,8 +1,10 @@ import asyncio +import json from loguru import logger from lnbits.core.models import Payment +from lnbits.core.services import websocketUpdater from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener @@ -25,27 +27,36 @@ async def on_invoice_paid(payment: Payment): relay_id = payment.extra.get("relay_id") pubkey = payment.extra.get("pubkey") + hash = payment.payment_hash if not relay_id or not pubkey: - logger.warning( - f"Invoice extra data missing for 'relay_id' and 'pubkey'. Payment hash: {payment.payment_hash}" - ) + message = f"Invoice extra data missing for 'relay_id' and 'pubkey'. Payment hash: {hash}" + logger.warning(message) + await websocketUpdater(hash, json.dumps({"success": False, "message": message})) return - if payment.extra.get("action") == "join": + action = payment.extra.get("action") + if action == "join": await invoice_paid_to_join(relay_id, pubkey, payment.amount) + await websocketUpdater(hash, json.dumps({"success": True})) return - if payment.extra.get("action") == "storage": + if action == "storage": storage_to_buy = payment.extra.get("storage_to_buy") if not storage_to_buy: - logger.warning( - f"Invoice extra data missing for 'storage_to_buy'. Payment hash: {payment.payment_hash}" + message = ( + f"Invoice extra data missing for 'storage_to_buy'. Payment hash: {hash}" ) + logger.warning(message) return await invoice_paid_for_storage(relay_id, pubkey, storage_to_buy, payment.amount) + await websocketUpdater(hash, json.dumps({"success": True})) return + await websocketUpdater( + hash, json.dumps({"success": False, "message": f"Bad action name: '{action}'"}) + ) + async def invoice_paid_to_join(relay_id: str, pubkey: str, amount: int): try: diff --git a/templates/nostrrelay/public.html b/templates/nostrrelay/public.html index 7334568..9d4a9a0 100644 --- a/templates/nostrrelay/public.html +++ b/templates/nostrrelay/public.html @@ -144,8 +144,9 @@ - +
+ +
+
+
+ + +
+
+
+
@@ -192,6 +207,7 @@ relay: JSON.parse('{{relay | tojson | safe}}'), pubkey: '', invoice: '', + invoiceResponse: null, unitsToBuy: 0 } }, @@ -210,7 +226,6 @@ }, methods: { createInvoice: async function (action) { - console.log('### action', action) if (!action) return this.invoice = '' if (!this.pubkey) { @@ -235,9 +250,34 @@ reqData ) this.invoice = data.invoice + const paymentHashTag = decode(data.invoice).data.tags.find( + t => t.description === 'payment_hash' + ) + if (paymentHashTag) { + await this.waitForPaidInvoice(paymentHashTag.value) + } } catch (error) { LNbits.utils.notifyApiError(error) } + }, + waitForPaidInvoice: function (paymentHash) { + try { + const scheme = location.protocol === 'http:' ? 'ws' : 'wss' + const wsUrl = `${scheme}://${document.domain}:${location.port}/api/v1/ws/${paymentHash}` + const wsConnection = new WebSocket(wsUrl) + wsConnection.onmessage = e => { + this.invoiceResponse = JSON.parse(e.data) + this.invoice = null + wsConnection.close() + } + } catch (error) { + this.$q.notify({ + timeout: 5000, + type: 'warning', + message: 'Failed to get invoice status', + caption: `${error}` + }) + } } }, created: function () {} From 28c0947afb78cc6a04a88588ff72a87806ef0b0d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 17 Mar 2023 15:24:17 +0200 Subject: [PATCH 09/44] fix: invoice can have `undefined` tag --- templates/nostrrelay/public.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/nostrrelay/public.html b/templates/nostrrelay/public.html index 9d4a9a0..53eed80 100644 --- a/templates/nostrrelay/public.html +++ b/templates/nostrrelay/public.html @@ -251,7 +251,7 @@ ) this.invoice = data.invoice const paymentHashTag = decode(data.invoice).data.tags.find( - t => t.description === 'payment_hash' + t => t && t.description === 'payment_hash' ) if (paymentHashTag) { await this.waitForPaidInvoice(paymentHashTag.value) From f7fb926c5223b23d26efb4674dedddeee0fea71a Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 17 Mar 2023 17:24:33 +0200 Subject: [PATCH 10/44] fix: port value --- templates/nostrrelay/public.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/nostrrelay/public.html b/templates/nostrrelay/public.html index 53eed80..98487c5 100644 --- a/templates/nostrrelay/public.html +++ b/templates/nostrrelay/public.html @@ -263,7 +263,8 @@ waitForPaidInvoice: function (paymentHash) { try { const scheme = location.protocol === 'http:' ? 'ws' : 'wss' - const wsUrl = `${scheme}://${document.domain}:${location.port}/api/v1/ws/${paymentHash}` + const port = location.port ? `:${location.port}` : '' + const wsUrl = `${scheme}://${document.domain}${port}/api/v1/ws/${paymentHash}` const wsConnection = new WebSocket(wsUrl) wsConnection.onmessage = e => { this.invoiceResponse = JSON.parse(e.data) From f19fb4a18e42eed48bb7f1ff608fedb5a898ebf4 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 6 Apr 2023 16:59:15 +0300 Subject: [PATCH 11/44] fix: delete account --- crud.py | 10 ++++++ .../relay-details/relay-details.html | 18 +++++++--- .../components/relay-details/relay-details.js | 33 +++++++++++++++++++ views_api.py | 28 ++++++++++++++++ 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/crud.py b/crud.py index 1f965b1..926cedf 100644 --- a/crud.py +++ b/crud.py @@ -363,6 +363,16 @@ async def update_account(relay_id: str, a: NostrAccount) -> NostrAccount: return a +async def delete_account(relay_id: str, pubkey: str): + await db.execute( + """ + DELETE FROM nostrrelay.accounts + WHERE relay_id = ? AND pubkey = ? + """, + (relay_id, pubkey), + ) + + async def get_account( relay_id: str, pubkey: str, diff --git a/static/components/relay-details/relay-details.html b/static/components/relay-details/relay-details.html index a4e58cc..8896a0d 100644 --- a/static/components/relay-details/relay-details.html +++ b/static/components/relay-details/relay-details.html @@ -545,7 +545,7 @@ >
Public Key:
-
+
Allow
-
+
Block @@ -613,6 +613,16 @@ >