From e8e6ee0bc47771d3157268c40a1711b1726861ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 4 Sep 2023 15:23:27 +0200 Subject: [PATCH 01/11] Add configurable settings for BTCPay --- .env.example | 2 ++ app/models/setting.rb | 16 ++++++++++++++++ app/services/btc_pay.rb | 6 +++--- config/credentials.yml.enc | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index aa77727..b7244d8 100644 --- a/.env.example +++ b/.env.example @@ -34,6 +34,8 @@ EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin' EJABBERD_API_URL='https://xmpp.kosmos.org/api' BTCPAY_API_URL='http://localhost:23001/api/v1' +BTCPAY_STORE_ID='' +BTCPAY_AUTH_TOKEN='' LNDHUB_API_URL='http://localhost:3023' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' diff --git a/app/models/setting.rb b/app/models/setting.rb index 0a13905..5114501 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -38,6 +38,22 @@ class Setting < RailsSettings::Base field :sentry_enabled, type: :boolean, readonly: true, default: (ENV["SENTRY_DSN"].present?.to_s || false) + # + # BTCPay Server + # + + field :btcpay_api_url, type: :string, + default: ENV["BTCPAY_API_URL"].presence + + field :btcpay_enabled, type: :boolean, + default: (ENV["BTCPAY_API_URL"].present?.to_s || false) + + field :btcpay_store_id, type: :string, + default: ENV["BTCPAY_STORE_ID"].presence + + field :btcpay_auth_token, type: :string, + default: ENV["BTCPAY_AUTH_TOKEN"].presence + # # Discourse # diff --git a/app/services/btc_pay.rb b/app/services/btc_pay.rb index becd506..e612d0a 100644 --- a/app/services/btc_pay.rb +++ b/app/services/btc_pay.rb @@ -3,9 +3,9 @@ # class BtcPay def initialize - @base_url = ENV["BTCPAY_API_URL"] - @store_id = Rails.application.credentials.btcpay[:store_id] - @auth_token = Rails.application.credentials.btcpay[:auth_token] + @base_url = Setting.btcpay_api_url + @store_id = Setting.btcpay_store_id + @auth_token = Setting.btcpay_auth_token end def onchain_wallet_balance diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index 978af61..de77871 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -yEs5CyuAbqphlDWgtw/YQvkPn+EN4ecen2dAjs7zvYErkRRWp99FinGlQIMe6NRkMLLLSIj2BwR/wlscn1kLpIfwGpxfSZ89srK3do6Mb5QogpxdUsnQB8qv5PTGRQFBcjM47s1Q5m0t+OKxGvOnLyKnQp+cVS2KFJMbSzQarW8wIZSz2gKArn9Ttk0kqUHMlJWNY7Yh6xIrrxlEalaTOVzPdtnF7u8Tobminu15eeWHMormMRz4dYSaDc6hUtfpdy1NzOHaeXIU9A9RY/iytxuIQNgcMAlcWbPe//rVk/unH2F8xqSOfed4h/nC08F/qq4z8va3kEXBSdW/G91aIDMu1mo0kX3YNibq8s25C/CfGpzw39ozJ9erTBH7hy6nfmxU6qZuWcTGDj3NOfKe/XIfDcpOjsqkT2IOFARrYodb67q23IuOufraK1/FD4LXu8l0S8/Oi0cqMjtPPs7tS0M1C3DrbmlEzGKETrHpmoKHqjA0rgOmK4ZZM9LeI+l8Z+fDpYcCak9fLGGxnjf+nKiYMSUtm9+1dwycG2lpBV6fbmIKHJWngO2jVGcycODkc525oUaAO4hdPMqrz1AdU3AzYmLJTxW3aZ4uL5NyEJ7TbUBC0HT7h2gEi/tUry4cfD2EsM9bCrCUNuMBrnPqd4r8AvORoqqYIw1IEsP0RgWa2+hfeG1QCjBRPFHQOcqo+W25CelivMe79qI08w0iC8S4hfOQO4QrmMgtd1BhcR+wVpVE3X9EJZi3Hl7z14hXcSic+gkswJMtVZcnJL4rmZ0iEW1mpqUuegsX5vB/4qPxiQyeB80pg8Q33shvUbixzSBkl6znmLSiIffsiDsGOsnuzfl/MUT+JBs3UswNt4tSp7nEwhUjKFHrZHrAJiGCdtIS6yDPGe3HfQv1JkQ+9A8zv88hRmzeIx2JyT/shtIqGo+4ZTJd5cma--Lij/n0+cpstyZD28--FOUhwW3y+0jdaYkKvG2xrg== \ No newline at end of file +tmI5vm7qZhaigr52jEBVWkRdj+EE+9OmPh3vWXC7kA/OHuuucpr7SodychuMkQDPLM0BLk88LFsqvRIR+mqnLWpRC+P9aeUFE6ohxSWzcAd7Y4sgxUD8zpCRPndrwTw0hxXXj1WZSYeWn4BoAB34aV+gYen2MajZF3a95hJGtS5yjgWxvLVkQQKqRDfykkfX6fCS0BPo5X7sT7m4xwCATD/D4219wajm5W3TIdkriHtwt28ZLspaRWA5e0UkzKf8+/Gaj2CrW7UWcvew8R93zQ5RA2/Sp3sDTVN+kLz9I9Q095lQC0ywCAEFYHeKmc2tjrzqRaAAWu06xmWLqGIg21G+A/UU9lUJOkIpxQACWoOfS2IoXR1nXhgXMopkz3aCBXDxKw554v4H2QyOceOsuRf2C685ibMqzQkKMmJ4tcbiOJL77DUc08JTjB8Dq4Ohr8sMzXbV/hATevjYoRP0XarLekqhLv90ZLuIVY16DwB0CzACeNBKeKbeLqJF51upRRWgi+gTbYpV04yUwnXdyssF8mydWocgihrTryBi8F6PsuhBGcaYdP+0yibnGxDCC4x2rupbBfMj2OIX7pYzgtIHB3Eo954Y+bCoggqbE/Qrb9VVXNMgtKgLt8EGWU2tg6wl9QicitIq87uLDAade93zTn6rmcKPywjMDo6jbVIs653ZdUhiKdHGdpnJccbgQ/iLSPB1umNnCeaEX5jM+K9zBvl7ZMCdSk1YIQ==--ekKumqLiSlVJNwMe--K/ecXmmMT1x+WnIXMbHBDw== \ No newline at end of file From 46d59e337194e97d397a237c1c9690cd1b914022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 4 Sep 2023 15:24:35 +0200 Subject: [PATCH 02/11] Improve icons in admin service settings sidenav --- app/components/sidenav_link_component.html.erb | 4 ++++ app/components/sidenav_link_component.rb | 4 +++- .../_admin_sidenav_settings_services.html.erb | 18 +++++++++--------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/components/sidenav_link_component.html.erb b/app/components/sidenav_link_component.html.erb index a49bbab..a229606 100644 --- a/app/components/sidenav_link_component.html.erb +++ b/app/components/sidenav_link_component.html.erb @@ -1,4 +1,8 @@ <%= link_to @path, class: @link_class, title: (@disabled ? "Coming soon" : nil) do %> + <% if @icon.present? %> <%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %> + <% elsif @text_icon.present? %> + <%= @text_icon %> + <% end %> <%= @name %> <% end %> diff --git a/app/components/sidenav_link_component.rb b/app/components/sidenav_link_component.rb index 60e58ed..9436a67 100644 --- a/app/components/sidenav_link_component.rb +++ b/app/components/sidenav_link_component.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true class SidenavLinkComponent < ViewComponent::Base - def initialize(name:, level: 1, path:, icon:, active: false, disabled: false) + def initialize(name:, level: 1, path:, icon: nil, text_icon: nil, + active: false, disabled: false) @name = name @level = level @path = path @icon = icon + @text_icon = text_icon @active = active @disabled = disabled @link_class = class_names_link(path) diff --git a/app/views/shared/_admin_sidenav_settings_services.html.erb b/app/views/shared/_admin_sidenav_settings_services.html.erb index 973777f..f82a61f 100644 --- a/app/views/shared/_admin_sidenav_settings_services.html.erb +++ b/app/views/shared/_admin_sidenav_settings_services.html.erb @@ -2,62 +2,62 @@ level: 2, name: "Discourse", path: admin_settings_services_path(params: { s: "discourse" }), - icon: Setting.discourse_enabled? ? "check" : "x", + text_icon: Setting.discourse_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "discourse" })), ) %> <%= render SidenavLinkComponent.new( level: 2, name: "Drone CI", path: admin_settings_services_path(params: { s: "droneci" }), - icon: Setting.droneci_enabled? ? "check" : "x", + text_icon: Setting.droneci_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "droneci" })), ) %> <%= render SidenavLinkComponent.new( level: 2, name: "ejabberd", path: admin_settings_services_path(params: { s: "ejabberd" }), - icon: Setting.ejabberd_enabled? ? "check" : "x", + text_icon: Setting.ejabberd_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "ejabberd" })), ) %> <%= render SidenavLinkComponent.new( level: 2, name: "Gitea", path: admin_settings_services_path(params: { s: "gitea" }), - icon: Setting.gitea_enabled? ? "check" : "x", + text_icon: Setting.gitea_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "gitea" })), ) %> <%= render SidenavLinkComponent.new( level: 2, name: "LNDHub", path: admin_settings_services_path(params: { s: "lndhub" }), - icon: Setting.lndhub_enabled? ? "check" : "x", + text_icon: Setting.lndhub_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "lndhub" })), ) %> <%= render SidenavLinkComponent.new( level: 2, name: "Mastodon", path: admin_settings_services_path(params: { s: "mastodon" }), - icon: Setting.mastodon_enabled? ? "check" : "x", + text_icon: Setting.mastodon_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "mastodon" })), ) %> <%= render SidenavLinkComponent.new( level: 2, name: "MediaWiki", path: admin_settings_services_path(params: { s: "mediawiki" }), - icon: Setting.mediawiki_enabled? ? "check" : "x", + text_icon: Setting.mediawiki_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "mediawiki" })), ) %> <%= render SidenavLinkComponent.new( level: 2, name: "Nostr", path: admin_settings_services_path(params: { s: "nostr" }), - icon: Setting.nostr_enabled? ? "check" : "x", + text_icon: Setting.nostr_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "nostr" })), ) %> <%= render SidenavLinkComponent.new( level: 2, name: "RemoteStorage", path: admin_settings_services_path(params: { s: "remotestorage" }), - icon: Setting.remotestorage_enabled? ? "check" : "x", + text_icon: Setting.remotestorage_enabled? ? "◉" : "○", active: current_page?(admin_settings_services_path(params: { s: "remotestorage" })), ) %> From 8349ca5e12ad58d9960b88b96ecda0b46deff5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 4 Sep 2023 15:25:20 +0200 Subject: [PATCH 03/11] Add admin settings page for BTCPay --- .../admin/settings/services_controller.rb | 2 +- .../admin/settings/services/_btcpay.html.erb | 25 +++++++++++++++++++ .../_admin_sidenav_settings_services.html.erb | 7 ++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 app/views/admin/settings/services/_btcpay.html.erb diff --git a/app/controllers/admin/settings/services_controller.rb b/app/controllers/admin/settings/services_controller.rb index 54384c6..a228f5e 100644 --- a/app/controllers/admin/settings/services_controller.rb +++ b/app/controllers/admin/settings/services_controller.rb @@ -3,7 +3,7 @@ class Admin::Settings::ServicesController < Admin::SettingsController @service = params[:s] if @service.blank? - redirect_to admin_settings_services_path(params: { s: "discourse" }) + redirect_to admin_settings_services_path(params: { s: "btcpay" }) end end diff --git a/app/views/admin/settings/services/_btcpay.html.erb b/app/views/admin/settings/services/_btcpay.html.erb new file mode 100644 index 0000000..01d11f1 --- /dev/null +++ b/app/views/admin/settings/services/_btcpay.html.erb @@ -0,0 +1,25 @@ +

BTCPay Server

+
    + <%= render FormElements::FieldsetToggleComponent.new( + form: f, + attribute: :btcpay_enabled, + enabled: Setting.btcpay_enabled?, + title: "Enable BTCPay integration", + description: "BTCPay configuration present and features enabled" + ) %> +<% if Setting.btcpay_enabled? %> + <%= render FormElements::FieldsetResettableSettingComponent.new( + key: :btcpay_api_url, + title: "API URL" + ) %> + <%= render FormElements::FieldsetResettableSettingComponent.new( + key: :btcpay_store_id, + title: "Store ID" + ) %> + <%= render FormElements::FieldsetResettableSettingComponent.new( + key: :btcpay_auth_token, + type: :password, + title: "Auth Token" + ) %> +<% end %> +
diff --git a/app/views/shared/_admin_sidenav_settings_services.html.erb b/app/views/shared/_admin_sidenav_settings_services.html.erb index f82a61f..8650277 100644 --- a/app/views/shared/_admin_sidenav_settings_services.html.erb +++ b/app/views/shared/_admin_sidenav_settings_services.html.erb @@ -1,3 +1,10 @@ +<%= render SidenavLinkComponent.new( + level: 2, + name: "BTCPay", + path: admin_settings_services_path(params: { s: "btcpay" }), + text_icon: Setting.btcpay_enabled? ? "◉" : "○", + active: current_page?(admin_settings_services_path(params: { s: "btcpay" })), +) %> <%= render SidenavLinkComponent.new( level: 2, name: "Discourse", From 725fd2e5ea0d0b144a87c175276db212eb7aa9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 4 Sep 2023 15:36:22 +0200 Subject: [PATCH 04/11] Move lndhub admin token to env var/setting --- app/models/setting.rb | 3 ++ app/services/lndhub_v2.rb | 2 +- .../admin/settings/services/_lndhub.html.erb | 52 +++++++++++-------- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/app/models/setting.rb b/app/models/setting.rb index 5114501..e51ead6 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -113,6 +113,9 @@ class Setting < RailsSettings::Base field :lndhub_enabled, type: :boolean, default: (ENV["LNDHUB_API_URL"].present?.to_s || false) + field :lndhub_admin_token, type: :string, + default: ENV["LNDHUB_ADMIN_TOKEN"].presence + field :lndhub_admin_enabled, type: :boolean, default: (ENV["LNDHUB_ADMIN_UI"] || false) diff --git a/app/services/lndhub_v2.rb b/app/services/lndhub_v2.rb index e0a84dd..b816fcb 100644 --- a/app/services/lndhub_v2.rb +++ b/app/services/lndhub_v2.rb @@ -14,7 +14,7 @@ class LndhubV2 < Lndhub end def create_account(payload={}) - post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token] + post "v2/users", payload, admin_token: Setting.lndhub_admin_token end def create_invoice(payload) diff --git a/app/views/admin/settings/services/_lndhub.html.erb b/app/views/admin/settings/services/_lndhub.html.erb index a68ad37..ac679c4 100644 --- a/app/views/admin/settings/services/_lndhub.html.erb +++ b/app/views/admin/settings/services/_lndhub.html.erb @@ -9,29 +9,35 @@ ) %> <% if Setting.lndhub_enabled? %> <%= render FormElements::FieldsetResettableSettingComponent.new( - key: :lndhub_api_url, - title: "API URL" - ) %> - <% end %> - <%= render FormElements::FieldsetToggleComponent.new( - form: f, - attribute: :lndhub_admin_enabled, - enabled: Setting.lndhub_admin_enabled?, - title: "Enable LNDHub admin panel", - description: "LNDHub database configuration present and admin panel enabled" - ) %> - <%= render FormElements::FieldsetToggleComponent.new( - form: f, - attribute: :lndhub_keysend_enabled, - enabled: Setting.lndhub_keysend_enabled?, - title: "Enable keysend payments", - description: "Allow users to receive invoice-less payments to their Lightning Address" - ) %> - <% if Setting.lndhub_keysend_enabled? %> + key: :lndhub_api_url, + title: "API URL" + ) %> <%= render FormElements::FieldsetResettableSettingComponent.new( - key: :lndhub_public_key, - title: "Public key", - description: "The public key of the Lightning node used by LNDHub" - ) %> + key: :lndhub_admin_token, + type: :password, + title: "Admin token", + description: "Auth token for creating new lndhub accounts" + ) %> + <%= render FormElements::FieldsetToggleComponent.new( + form: f, + attribute: :lndhub_admin_enabled, + enabled: Setting.lndhub_admin_enabled?, + title: "Enable LNDHub admin panel", + description: "LNDHub database configuration present and admin panel enabled" + ) %> + <%= render FormElements::FieldsetToggleComponent.new( + form: f, + attribute: :lndhub_keysend_enabled, + enabled: Setting.lndhub_keysend_enabled?, + title: "Enable keysend payments", + description: "Allow users to receive invoice-less payments to their Lightning Address" + ) %> + <% if Setting.lndhub_keysend_enabled? %> + <%= render FormElements::FieldsetResettableSettingComponent.new( + key: :lndhub_public_key, + title: "Public key", + description: "The public key of the Lightning node used by LNDHub" + ) %> + <% end %> <% end %> From 074f9afcbbfdd340fe90c064309752890ef58b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 4 Sep 2023 15:37:02 +0200 Subject: [PATCH 05/11] Fix descriptions not being shown for resettable form fields --- .../form_elements/fieldset_resettable_setting_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/form_elements/fieldset_resettable_setting_component.rb b/app/components/form_elements/fieldset_resettable_setting_component.rb index 18a4609..bfaa1a0 100644 --- a/app/components/form_elements/fieldset_resettable_setting_component.rb +++ b/app/components/form_elements/fieldset_resettable_setting_component.rb @@ -6,7 +6,7 @@ module FormElements @tag = tag @positioning = :vertical @title = title - @descripton = description + @description = description @key = key.to_sym @type = type @resettable = is_resettable?(@key) From 5a5f62e98adba737ee623b37122e09440d2eda85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 4 Sep 2023 16:02:17 +0200 Subject: [PATCH 06/11] Refactor BTCPay service and API, add lightning balance --- .env.test | 5 +- app/controllers/api/btcpay_controller.rb | 21 +++++ app/controllers/api/kredits_controller.rb | 13 --- app/services/btc_pay.rb | 32 -------- .../fetch_lightning_wallet_balance.rb | 11 +++ .../fetch_onchain_wallet_balance.rb | 13 +++ app/services/btcpay_manager_service.rb | 22 ++++++ config/routes.rb | 3 +- spec/features/admin/settings_spec.rb | 2 +- spec/requests/api/btcpay_spec.rb | 79 +++++++++++++++++++ spec/requests/api/kredits_spec.rb | 43 ---------- 11 files changed, 152 insertions(+), 92 deletions(-) create mode 100644 app/controllers/api/btcpay_controller.rb delete mode 100644 app/controllers/api/kredits_controller.rb delete mode 100644 app/services/btc_pay.rb create mode 100644 app/services/btcpay_manager/fetch_lightning_wallet_balance.rb create mode 100644 app/services/btcpay_manager/fetch_onchain_wallet_balance.rb create mode 100644 app/services/btcpay_manager_service.rb create mode 100644 spec/requests/api/btcpay_spec.rb delete mode 100644 spec/requests/api/kredits_spec.rb diff --git a/.env.test b/.env.test index 33761c3..c032b0e 100644 --- a/.env.test +++ b/.env.test @@ -2,13 +2,14 @@ PRIMARY_DOMAIN=kosmos.org REDIS_URL='redis://localhost:6379/0' +BTCPAY_API_URL='http://btcpay.example.com/api/v1' +BTCPAY_STORE_ID='123456' + DISCOURSE_PUBLIC_URL='http://discourse.example.com' DISCOURSE_CONNECT_SECRET='discourse_connect_ftw' EJABBERD_API_URL='http://xmpp.example.com/api' -BTCPAY_API_URL='http://btcpay.example.com/api/v1' - LNDHUB_API_URL='http://localhost:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' diff --git a/app/controllers/api/btcpay_controller.rb b/app/controllers/api/btcpay_controller.rb new file mode 100644 index 0000000..630a028 --- /dev/null +++ b/app/controllers/api/btcpay_controller.rb @@ -0,0 +1,21 @@ +class Api::BtcpayController < Api::BaseController + + def onchain_btc_balance + balance = BtcpayManager::FetchOnchainWalletBalance.call + render json: balance + rescue => error + Rails.logger.warn "Failed to fetch BTC wallet balance: #{error.message}" + render json: { error: 'Failed to fetch wallet balance' }, + status: 500 + end + + def lightning_btc_balance + balance = BtcpayManager::FetchLightningWalletBalance.call + render json: balance + rescue => error + Rails.logger.warn "Failed to fetch BTC lightning balance: #{error.message}" + render json: { error: 'Failed to fetch wallet balance' }, + status: 500 + end + +end diff --git a/app/controllers/api/kredits_controller.rb b/app/controllers/api/kredits_controller.rb deleted file mode 100644 index e54993d..0000000 --- a/app/controllers/api/kredits_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Api::KreditsController < Api::BaseController - - def onchain_btc_balance - btcpay = BtcPay.new - balance = btcpay.onchain_wallet_balance - render json: balance - rescue => error - Rails.logger.warn "Failed to fetch kredits BTC wallet balance: #{error.message}" - render json: { error: 'Failed to fetch wallet balance' }, - status: 500 - end - -end diff --git a/app/services/btc_pay.rb b/app/services/btc_pay.rb deleted file mode 100644 index e612d0a..0000000 --- a/app/services/btc_pay.rb +++ /dev/null @@ -1,32 +0,0 @@ -# -# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/ -# -class BtcPay - def initialize - @base_url = Setting.btcpay_api_url - @store_id = Setting.btcpay_store_id - @auth_token = Setting.btcpay_auth_token - end - - def onchain_wallet_balance - res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet" - - { - balance: res["balance"].to_f, - unconfirmed_balance: res["unconfirmedBalance"].to_f, - confirmed_balance: res["confirmedBalance"].to_f - } - end - - private - - def get(endpoint) - res = Faraday.get("#{@base_url}/#{endpoint}", {}, { - "Content-Type" => "application/json", - "Accept" => "application/json", - "Authorization" => "token #{@auth_token}" - }) - - JSON.parse(res.body) - end -end diff --git a/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb b/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb new file mode 100644 index 0000000..156d6aa --- /dev/null +++ b/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb @@ -0,0 +1,11 @@ +module BtcpayManager + class FetchLightningWalletBalance < BtcpayManagerService + def call + res = get "stores/#{@store_id}/lightning/BTC/balance" + + { + balance: res["offchain"]["local"].to_i / 1000 # msats to sats + } + end + end +end diff --git a/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb b/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb new file mode 100644 index 0000000..2907d1b --- /dev/null +++ b/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb @@ -0,0 +1,13 @@ +module BtcpayManager + class FetchOnchainWalletBalance < BtcpayManagerService + def call + res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet" + + { + balance: (res["balance"].to_f * 100000000).to_i, # BTC to sats + unconfirmed_balance: (res["unconfirmedBalance"].to_f * 100000000).to_i, + confirmed_balance: (res["confirmedBalance"].to_f * 100000000).to_i + } + end + end +end diff --git a/app/services/btcpay_manager_service.rb b/app/services/btcpay_manager_service.rb new file mode 100644 index 0000000..1be3afc --- /dev/null +++ b/app/services/btcpay_manager_service.rb @@ -0,0 +1,22 @@ +# +# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/ +# +class BtcpayManagerService < LdapService + def initialize + @base_url = Setting.btcpay_api_url + @store_id = Setting.btcpay_store_id + @auth_token = Setting.btcpay_auth_token + end + + private + + def get(endpoint) + res = Faraday.get("#{@base_url}/#{endpoint}", {}, { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => "token #{@auth_token}" + }) + + JSON.parse(res.body) + end +end diff --git a/config/routes.rb b/config/routes.rb index 3dc922a..e7fef10 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -54,7 +54,8 @@ Rails.application.routes.draw do post 'webhooks/lndhub', to: 'webhooks#lndhub' namespace :api do - get 'kredits/onchain_btc_balance', to: 'kredits#onchain_btc_balance' + get 'btcpay/onchain_btc_balance', to: 'btcpay#onchain_btc_balance' + get 'btcpay/lightning_btc_balance', to: 'btcpay#lightning_btc_balance' end namespace :admin do diff --git a/spec/features/admin/settings_spec.rb b/spec/features/admin/settings_spec.rb index 757f991..01a43f4 100644 --- a/spec/features/admin/settings_spec.rb +++ b/spec/features/admin/settings_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Admin/global settings', type: :feature do scenario "Opening service settings shows page for first service" do visit admin_settings_services_path - expect(current_url).to eq(admin_settings_services_url(params: { s: "discourse" })) + expect(current_url).to eq(admin_settings_services_url(params: { s: "btcpay" })) end scenario "View service settings" do diff --git a/spec/requests/api/btcpay_spec.rb b/spec/requests/api/btcpay_spec.rb new file mode 100644 index 0000000..7203df7 --- /dev/null +++ b/spec/requests/api/btcpay_spec.rb @@ -0,0 +1,79 @@ +require 'rails_helper' +require 'webmock/rspec' + +RSpec.describe "/api/btcpay", type: :request do + + describe "GET /onchain_btc_balance" do + before do + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") + .to_return(status: 200, headers: {}, body: { + balance: 0.91108606, + unconfirmedBalance: 0, + confirmedBalance: 0.91108606 + }.to_json) + end + + it "returns a formatted result for the onchain wallet balance" do + get api_btcpay_onchain_btc_balance_path + + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["balance"]).to eq(91108606) + expect(res["unconfirmed_balance"]).to eq(0) + expect(res["confirmed_balance"]).to eq(91108606) + end + + context "upstream request error" do + before do + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") + .to_return(status: 500, headers: {}, body: "") + end + + it "returns a formatted error" do + get api_btcpay_onchain_btc_balance_path + + expect(response).to have_http_status(:server_error) + + res = JSON.parse(response.body) + expect(res["error"]).not_to be_nil + end + end + end + + describe "GET /lightning_btc_balance" do + before do + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/lightning/BTC/balance") + .to_return(status: 200, headers: {}, body: { + offchain: { + local: 4200000000 + }, + }.to_json) + end + + it "returns a formatted result for the onchain wallet balance" do + get api_btcpay_lightning_btc_balance_path + + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["balance"]).to eq(4200000) + end + + context "upstream request error" do + before do + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") + .to_return(status: 500, headers: {}, body: "") + end + + it "returns a formatted error" do + get api_btcpay_onchain_btc_balance_path + + expect(response).to have_http_status(:server_error) + + res = JSON.parse(response.body) + expect(res["error"]).not_to be_nil + end + end + end +end diff --git a/spec/requests/api/kredits_spec.rb b/spec/requests/api/kredits_spec.rb deleted file mode 100644 index 0e1e464..0000000 --- a/spec/requests/api/kredits_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'rails_helper' -require 'webmock/rspec' - -RSpec.describe "/api/kredits", type: :request do - - describe "GET /onchain_btc_balance" do - before do - stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") - .to_return(status: 200, headers: {}, body: { - balance: 0.91108606, - unconfirmedBalance: 0, - confirmedBalance: 0.91108606 - }.to_json) - end - - it "returns a formatted result for the onchain wallet balance" do - get api_kredits_onchain_btc_balance_path - - expect(response).to have_http_status(:ok) - - res = JSON.parse(response.body) - expect(res["balance"]).to eq(0.91108606) - expect(res["unconfirmed_balance"]).to eq(0) - expect(res["confirmed_balance"]).to eq(0.91108606) - end - - context "upstream request error" do - before do - stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") - .to_return(status: 500, headers: {}, body: "") - end - - it "returns a formatted error" do - get api_kredits_onchain_btc_balance_path - - expect(response).to have_http_status(:server_error) - - res = JSON.parse(response.body) - expect(res["error"]).not_to be_nil - end - end - end -end From 2c2ddabdffc0c016b0528d5e4567c47c54755dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 7 Sep 2023 19:14:21 +0200 Subject: [PATCH 07/11] Fix code being silly --- app/models/setting.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/setting.rb b/app/models/setting.rb index e51ead6..dccaffd 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -36,7 +36,7 @@ class Setting < RailsSettings::Base # field :sentry_enabled, type: :boolean, readonly: true, - default: (ENV["SENTRY_DSN"].present?.to_s || false) + default: ENV["SENTRY_DSN"].present? # # BTCPay Server @@ -46,7 +46,7 @@ class Setting < RailsSettings::Base default: ENV["BTCPAY_API_URL"].presence field :btcpay_enabled, type: :boolean, - default: (ENV["BTCPAY_API_URL"].present?.to_s || false) + default: ENV["BTCPAY_API_URL"].present? field :btcpay_store_id, type: :string, default: ENV["BTCPAY_STORE_ID"].presence @@ -62,7 +62,7 @@ class Setting < RailsSettings::Base default: ENV["DISCOURSE_PUBLIC_URL"].presence field :discourse_enabled, type: :boolean, - default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false) + default: ENV["DISCOURSE_PUBLIC_URL"].present? field :discourse_connect_secret, type: :string, default: ENV["DISCOURSE_CONNECT_SECRET"].presence @@ -75,14 +75,14 @@ class Setting < RailsSettings::Base default: ENV["DRONECI_PUBLIC_URL"].presence field :droneci_enabled, type: :boolean, - default: (ENV["DRONECI_PUBLIC_URL"].present?.to_s || false) + default: ENV["DRONECI_PUBLIC_URL"].present? # # ejabberd # field :ejabberd_enabled, type: :boolean, - default: (ENV["EJABBERD_API_URL"].present?.to_s || false) + default: ENV["EJABBERD_API_URL"].present? field :ejabberd_api_url, type: :string, default: ENV["EJABBERD_API_URL"].presence @@ -101,7 +101,7 @@ class Setting < RailsSettings::Base default: ENV["GITEA_PUBLIC_URL"].presence field :gitea_enabled, type: :boolean, - default: (ENV["GITEA_PUBLIC_URL"].present?.to_s || false) + default: ENV["GITEA_PUBLIC_URL"].present? # # Lightning Network @@ -111,19 +111,19 @@ class Setting < RailsSettings::Base default: ENV["LNDHUB_API_URL"].presence field :lndhub_enabled, type: :boolean, - default: (ENV["LNDHUB_API_URL"].present?.to_s || false) + default: ENV["LNDHUB_API_URL"].present? field :lndhub_admin_token, type: :string, default: ENV["LNDHUB_ADMIN_TOKEN"].presence field :lndhub_admin_enabled, type: :boolean, - default: (ENV["LNDHUB_ADMIN_UI"] || false) + default: ENV["LNDHUB_ADMIN_UI"] || false field :lndhub_public_key, type: :string, default: (ENV["LNDHUB_PUBLIC_KEY"] || "") field :lndhub_keysend_enabled, type: :boolean, - default: -> { self.lndhub_public_key.present?.to_s || false } + default: -> { self.lndhub_public_key.present? } # # Mastodon @@ -133,7 +133,7 @@ class Setting < RailsSettings::Base default: ENV["MASTODON_PUBLIC_URL"].presence field :mastodon_enabled, type: :boolean, - default: (ENV["MASTODON_PUBLIC_URL"].present?.to_s || false) + default: ENV["MASTODON_PUBLIC_URL"].present? field :mastodon_address_domain, type: :string, default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain @@ -146,7 +146,7 @@ class Setting < RailsSettings::Base default: ENV["MEDIAWIKI_PUBLIC_URL"].presence field :mediawiki_enabled, type: :boolean, - default: (ENV["MEDIAWIKI_PUBLIC_URL"].present?.to_s || false) + default: ENV["MEDIAWIKI_PUBLIC_URL"].present? # # Nostr @@ -159,7 +159,7 @@ class Setting < RailsSettings::Base # field :remotestorage_enabled, type: :boolean, - default: (ENV["RS_STORAGE_URL"].present?.to_s || false) + default: ENV["RS_STORAGE_URL"].present? field :rs_storage_url, type: :string, default: ENV["RS_STORAGE_URL"].presence From 2ba0116ca6f016e95169fa7feb0253c291426f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 7 Sep 2023 19:17:46 +0200 Subject: [PATCH 08/11] Fix wrong inheritance --- app/services/btcpay_manager_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/btcpay_manager_service.rb b/app/services/btcpay_manager_service.rb index 1be3afc..0affb73 100644 --- a/app/services/btcpay_manager_service.rb +++ b/app/services/btcpay_manager_service.rb @@ -1,7 +1,7 @@ # # API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/ # -class BtcpayManagerService < LdapService +class BtcpayManagerService < ApplicationService def initialize @base_url = Setting.btcpay_api_url @store_id = Setting.btcpay_store_id From 1949f1876fbbeda883b39b502f8b1990c24e496c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 7 Sep 2023 19:21:00 +0200 Subject: [PATCH 09/11] Use attr_reader instead of shared instance variables --- .../btcpay_manager/fetch_lightning_wallet_balance.rb | 2 +- app/services/btcpay_manager/fetch_onchain_wallet_balance.rb | 2 +- app/services/btcpay_manager_service.rb | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb b/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb index 156d6aa..533d2f1 100644 --- a/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb +++ b/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb @@ -1,7 +1,7 @@ module BtcpayManager class FetchLightningWalletBalance < BtcpayManagerService def call - res = get "stores/#{@store_id}/lightning/BTC/balance" + res = get "stores/#{store_id}/lightning/BTC/balance" { balance: res["offchain"]["local"].to_i / 1000 # msats to sats diff --git a/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb b/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb index 2907d1b..e28f197 100644 --- a/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb +++ b/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb @@ -1,7 +1,7 @@ module BtcpayManager class FetchOnchainWalletBalance < BtcpayManagerService def call - res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet" + res = get "stores/#{store_id}/payment-methods/onchain/BTC/wallet" { balance: (res["balance"].to_f * 100000000).to_i, # BTC to sats diff --git a/app/services/btcpay_manager_service.rb b/app/services/btcpay_manager_service.rb index 0affb73..d8fbf09 100644 --- a/app/services/btcpay_manager_service.rb +++ b/app/services/btcpay_manager_service.rb @@ -2,6 +2,8 @@ # API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/ # class BtcpayManagerService < ApplicationService + attr_reader :base_url, :store_id, :auth_token + def initialize @base_url = Setting.btcpay_api_url @store_id = Setting.btcpay_store_id @@ -11,10 +13,10 @@ class BtcpayManagerService < ApplicationService private def get(endpoint) - res = Faraday.get("#{@base_url}/#{endpoint}", {}, { + res = Faraday.get("#{base_url}/#{endpoint}", {}, { "Content-Type" => "application/json", "Accept" => "application/json", - "Authorization" => "token #{@auth_token}" + "Authorization" => "token #{auth_token}" }) JSON.parse(res.body) From 91d3b977e9df37f2ae86633f0a8343db8c2b232d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 20 Sep 2023 18:26:50 +0200 Subject: [PATCH 10/11] Fix spec --- spec/requests/api/btcpay_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/api/btcpay_spec.rb b/spec/requests/api/btcpay_spec.rb index 7203df7..6774652 100644 --- a/spec/requests/api/btcpay_spec.rb +++ b/spec/requests/api/btcpay_spec.rb @@ -62,12 +62,12 @@ RSpec.describe "/api/btcpay", type: :request do context "upstream request error" do before do - stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/lightning/BTC/balance") .to_return(status: 500, headers: {}, body: "") end it "returns a formatted error" do - get api_btcpay_onchain_btc_balance_path + get api_btcpay_lightning_btc_balance_path expect(response).to have_http_status(:server_error) From 69fffb29d8d68c25a4bdb8197a0321b157e21dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 20 Sep 2023 18:36:53 +0200 Subject: [PATCH 11/11] Make publishing of BTCPay wallet balances optional --- app/controllers/api/btcpay_controller.rb | 8 +++++++ app/models/setting.rb | 2 ++ .../admin/settings/services/_btcpay.html.erb | 12 ++++++++++ spec/requests/api/btcpay_spec.rb | 24 +++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/app/controllers/api/btcpay_controller.rb b/app/controllers/api/btcpay_controller.rb index 630a028..d973af2 100644 --- a/app/controllers/api/btcpay_controller.rb +++ b/app/controllers/api/btcpay_controller.rb @@ -1,4 +1,5 @@ class Api::BtcpayController < Api::BaseController + before_action :require_feature_enabled def onchain_btc_balance balance = BtcpayManager::FetchOnchainWalletBalance.call @@ -18,4 +19,11 @@ class Api::BtcpayController < Api::BaseController status: 500 end + private + + def require_feature_enabled + unless Setting.btcpay_publish_wallet_balances + http_status :not_found and return + end + end end diff --git a/app/models/setting.rb b/app/models/setting.rb index dccaffd..9cafa2e 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -54,6 +54,8 @@ class Setting < RailsSettings::Base field :btcpay_auth_token, type: :string, default: ENV["BTCPAY_AUTH_TOKEN"].presence + field :btcpay_publish_wallet_balances, type: :boolean, default: true + # # Discourse # diff --git a/app/views/admin/settings/services/_btcpay.html.erb b/app/views/admin/settings/services/_btcpay.html.erb index 01d11f1..9565826 100644 --- a/app/views/admin/settings/services/_btcpay.html.erb +++ b/app/views/admin/settings/services/_btcpay.html.erb @@ -21,5 +21,17 @@ type: :password, title: "Auth Token" ) %> + + +
+

REST API

+
    + <%= render FormElements::FieldsetToggleComponent.new( + form: f, + attribute: :btcpay_publish_wallet_balances, + enabled: Setting.btcpay_publish_wallet_balances?, + title: "Publish wallet balances", + description: "Publish the store's on-chain and Lightning wallet balances" + ) %> <% end %>
diff --git a/spec/requests/api/btcpay_spec.rb b/spec/requests/api/btcpay_spec.rb index 6774652..9dc5728 100644 --- a/spec/requests/api/btcpay_spec.rb +++ b/spec/requests/api/btcpay_spec.rb @@ -39,6 +39,18 @@ RSpec.describe "/api/btcpay", type: :request do expect(res["error"]).not_to be_nil end end + + context "feature disabled" do + before do + Setting.btcpay_publish_wallet_balances = false + end + + it "returns a 404 status" do + get api_btcpay_onchain_btc_balance_path + + expect(response).to have_http_status(:not_found) + end + end end describe "GET /lightning_btc_balance" do @@ -75,5 +87,17 @@ RSpec.describe "/api/btcpay", type: :request do expect(res["error"]).not_to be_nil end end + + context "feature disabled" do + before do + Setting.btcpay_publish_wallet_balances = false + end + + it "returns a 404 status" do + get api_btcpay_lightning_btc_balance_path + + expect(response).to have_http_status(:not_found) + end + end end end