diff --git a/Dockerfile b/Dockerfile
index f2692e7..ff3eef7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,9 +9,9 @@ RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && apt-get install -y nodejs
WORKDIR /akkounts
-COPY Gemfile /akkounts/Gemfile
-COPY Gemfile.lock /akkounts/Gemfile.lock
-COPY package.json /akkounts/package.json
+
+COPY ["Gemfile", "Gemfile.lock", "package.json", "./"]
+
RUN bundle install
RUN gem install foreman
RUN npm install -g yarn
diff --git a/Gemfile b/Gemfile
index 81aceee..1f0a7b3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -57,6 +57,7 @@ gem "sentry-rails"
# Services
gem 'discourse_api'
+gem "lnurl"
gem 'nostr', git: 'https://gitea.kosmos.org/kosmos/nostr-gem.git', branch: 'feature/ruby_2.7_compat'
group :development, :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 89b4814..439cd1e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -206,6 +206,8 @@ GEM
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
+ lnurl (1.0.1)
+ bech32 (~> 1.1)
lockbox (1.2.0)
loofah (2.21.3)
crass (~> 1.0.2)
@@ -434,6 +436,7 @@ DEPENDENCIES
letter_opener
letter_opener_web
listen (~> 3.2)
+ lnurl
lockbox
net-ldap
nostr!
diff --git a/README.md b/README.md
index f14c3de..5a177e9 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,6 @@ so:
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
Docker Desktop)
-2. Uncomment the `redis`, `web`, and `sidekiq` sections in `docker-compose.yml`
3. Run `docker compose up` and wait until 389ds announces its successful start
in the log output
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
@@ -53,12 +52,14 @@ Running all specs:
### Docker (Compose)
-There is a working Docker Compose config file, which allows you to spin up both
+There is a working Docker Compose config file, which define a number of services including
an app server for Rails as well as a local 389ds (LDAP) server.
-By default, `docker-compose up` will only start the LDAP server, listening on
-port 389 on your machine. Uncomment other services in `docker-compose.yml` if
-you want to use them.
+For Rails developers, you probably just want to start the LDAP server: `docker-compose up ldap`,
+listening on port 389 on your machine.
+
+You can pick and choose your services adding them by name (listed in `docker-compose.yml`) at
+the end of the docker compose command. eg. `docker compose up ldap redis`
#### LDAP server
diff --git a/app/components/form_elements/fieldset_component.html.erb b/app/components/form_elements/fieldset_component.html.erb
index 2bde8ce..143da70 100644
--- a/app/components/form_elements/fieldset_component.html.erb
+++ b/app/components/form_elements/fieldset_component.html.erb
@@ -1,4 +1,6 @@
-<%= tag.public_send(@tag, class: "mb-6 last:mb-0") do %>
+<%= tag.public_send(@tag, class: "mb-6 last:mb-0", data: {
+ :'field-name' => @field_name
+ }) do %>
<% if @positioning == :vertical %>
">
@@ -9,7 +11,21 @@
<%= @descripton %>
<% end %>
- <%= content %>
+
+ <%= tag.p class: "flex gap-x-1", data: {
+ controller: @resettable ? "settings--resettable-field" : nil,
+ } do %>
+ <%= content %>
+ <% if @resettable %>
+
+ Reset
+
+ <% end %>
+ <% end %>
<% elsif @positioning == :horizontal %>
diff --git a/app/components/form_elements/fieldset_component.rb b/app/components/form_elements/fieldset_component.rb
index 23fad5b..cde51fc 100644
--- a/app/components/form_elements/fieldset_component.rb
+++ b/app/components/form_elements/fieldset_component.rb
@@ -2,11 +2,15 @@
module FormElements
class FieldsetComponent < ViewComponent::Base
- def initialize(tag: "li", positioning: :vertical, title:, description: nil)
+ def initialize(tag: "li", positioning: :vertical,
+ title:, description: nil,
+ field_name: nil, resettable: false)
@tag = tag
@positioning = positioning
@title = title
@descripton = description
+ @field_name = field_name
+ @resettable = resettable
end
end
end
diff --git a/app/components/form_elements/fieldset_resettable_setting_component.html.erb b/app/components/form_elements/fieldset_resettable_setting_component.html.erb
new file mode 100644
index 0000000..a429715
--- /dev/null
+++ b/app/components/form_elements/fieldset_resettable_setting_component.html.erb
@@ -0,0 +1,13 @@
+<%= render FormElements::FieldsetComponent.new(
+ title: @title,
+ description: @description,
+ field_name: "setting_#{@key.to_s}",
+ resettable: @resettable
+ ) do %>
+ <%= method("#{@type}_field").call :setting, @key,
+ value: Setting.public_send(@key),
+ data: {
+ :'default-value' => Setting.get_field(@key)[:default]
+ },
+ class: "w-full" %>
+<% end %>
diff --git a/app/components/form_elements/fieldset_resettable_setting_component.rb b/app/components/form_elements/fieldset_resettable_setting_component.rb
new file mode 100644
index 0000000..18a4609
--- /dev/null
+++ b/app/components/form_elements/fieldset_resettable_setting_component.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module FormElements
+ class FieldsetResettableSettingComponent < ViewComponent::Base
+ def initialize(tag: "li", key:, type: :text, title:, description: nil)
+ @tag = tag
+ @positioning = :vertical
+ @title = title
+ @descripton = description
+ @key = key.to_sym
+ @type = type
+ @resettable = is_resettable?(@key)
+ end
+
+ def is_resettable?(key)
+ default_value = Setting.get_field(key)[:default]
+ default_value.present? && (default_value != Setting.send(key))
+ end
+ end
+end
diff --git a/app/controllers/services/lightning_controller.rb b/app/controllers/services/lightning_controller.rb
index 44a4589..94aed24 100644
--- a/app/controllers/services/lightning_controller.rb
+++ b/app/controllers/services/lightning_controller.rb
@@ -1,4 +1,5 @@
require "rqrcode"
+require "lnurl"
class Services::LightningController < ApplicationController
before_action :authenticate_user!
@@ -8,9 +9,56 @@ class Services::LightningController < ApplicationController
def index
@wallet_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
+ initialize_lndhub_qr_code
+ end
- qrcode = RQRCode::QRCode.new(@wallet_url)
- @svg = qrcode.as_svg(
+ def transactions
+ @transactions = fetch_transactions
+ end
+
+ def qr_lnurlp
+ lnurlp_url = "https://kosmos.org/.well-known/lnurlp/#{current_user.cn}"
+ lnurlp_bech32 = Lnurl.new(lnurlp_url).to_bech32
+ qr_code = RQRCode::QRCode.new("lightning:" + lnurlp_bech32)
+
+ respond_to do |format|
+ format.svg do
+ qr_svg = qr_code.as_svg(
+ color: "000",
+ shape_rendering: "crispEdges",
+ module_size: 6,
+ standalone: true,
+ use_path: true,
+ svg_attributes: {
+ class: 'inline-block'
+ }
+ )
+ send_data(
+ qr_svg,
+ filename: "bitcoin-lightning-#{current_user.address}.svg",
+ type: "image/svg+xml"
+ )
+ end
+ format.png do
+ qr_png = qr_code.as_png(
+ fill: "white",
+ color: "black",
+ size: 1024,
+ )
+ send_data(
+ qr_png,
+ filename: "bitcoin-lightning-#{current_user.address}.png",
+ type: "image/png"
+ )
+ end
+ end
+ end
+
+ private
+
+ def initialize_lndhub_qr_code
+ qr_code = RQRCode::QRCode.new(@wallet_url)
+ @lndhub_qr_svg = qr_code.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
@@ -22,12 +70,6 @@ class Services::LightningController < ApplicationController
)
end
- def transactions
- @transactions = fetch_transactions
- end
-
- private
-
def authenticate_with_lndhub(options={})
if session[:ln_auth_token].present? && !options[:force_reauth]
@ln_auth_token = session[:ln_auth_token]
diff --git a/app/javascript/controllers/settings/resettable_field_controller.js b/app/javascript/controllers/settings/resettable_field_controller.js
new file mode 100644
index 0000000..608d902
--- /dev/null
+++ b/app/javascript/controllers/settings/resettable_field_controller.js
@@ -0,0 +1,10 @@
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+ static targets = [ "resetButton" ]
+
+ resetField () {
+ const inputEl = this.element.querySelector('input')
+ inputEl.value = inputEl.dataset.defaultValue
+ }
+}
diff --git a/app/models/setting.rb b/app/models/setting.rb
index 1fee361..f32c86f 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -29,6 +29,7 @@ class Setting < RailsSettings::Base
field :xmpp_default_rooms, type: :array, default: []
field :xmpp_autojoin_default_rooms, type: :boolean, default: false
+ field :xmpp_notifications_from_address, type: :string, default: primary_domain
#
# Sentry
@@ -41,13 +42,13 @@ class Setting < RailsSettings::Base
# Discourse
#
- field :discourse_public_url, type: :string, readonly: true,
+ field :discourse_public_url, type: :string,
default: ENV["DISCOURSE_PUBLIC_URL"].presence
field :discourse_enabled, type: :boolean,
default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false)
- field :discourse_connect_secret, type: :string, readonly: true,
+ field :discourse_connect_secret, type: :string,
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
#
@@ -57,10 +58,10 @@ class Setting < RailsSettings::Base
field :ejabberd_enabled, type: :boolean,
default: (ENV["EJABBERD_API_URL"].present?.to_s || false)
- field :ejabberd_api_url, type: :string, readonly: true,
+ field :ejabberd_api_url, type: :string,
default: ENV["EJABBERD_API_URL"].presence
- field :ejabberd_admin_url, type: :string, readonly: true,
+ field :ejabberd_admin_url, type: :string,
default: ENV["EJABBERD_ADMIN_URL"].presence
field :ejabberd_buddy_roster, type: :string,
@@ -70,7 +71,7 @@ class Setting < RailsSettings::Base
# Gitea
#
- field :gitea_public_url, type: :string, readonly: true,
+ field :gitea_public_url, type: :string,
default: ENV["GITEA_PUBLIC_URL"].presence
field :gitea_enabled, type: :boolean,
@@ -80,7 +81,7 @@ class Setting < RailsSettings::Base
# Lightning Network
#
- field :lndhub_api_url, type: :string, readonly: true,
+ field :lndhub_api_url, type: :string,
default: ENV["LNDHUB_API_URL"].presence
field :lndhub_enabled, type: :boolean,
@@ -89,7 +90,7 @@ class Setting < RailsSettings::Base
field :lndhub_admin_enabled, type: :boolean,
default: (ENV["LNDHUB_ADMIN_UI"] || false)
- field :lndhub_public_key, type: :string, readonly: true,
+ field :lndhub_public_key, type: :string,
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
field :lndhub_keysend_enabled, type: :boolean,
@@ -99,7 +100,7 @@ class Setting < RailsSettings::Base
# Mastodon
#
- field :mastodon_public_url, type: :string, readonly: true,
+ field :mastodon_public_url, type: :string,
default: ENV["MASTODON_PUBLIC_URL"].presence
field :mastodon_enabled, type: :boolean,
@@ -109,7 +110,7 @@ class Setting < RailsSettings::Base
# MediaWiki
#
- field :mediawiki_public_url, type: :string, readonly: true,
+ field :mediawiki_public_url, type: :string,
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
field :mediawiki_enabled, type: :boolean,
diff --git a/app/views/admin/settings/services/_discourse.html.erb b/app/views/admin/settings/services/_discourse.html.erb
index 6af5525..a4b557f 100644
--- a/app/views/admin/settings/services/_discourse.html.erb
+++ b/app/views/admin/settings/services/_discourse.html.erb
@@ -8,16 +8,15 @@
description: "Discourse configuration present and features enabled"
) %>
<% if Setting.discourse_enabled? %>
- <%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
- <%= f.text_field :discourse_public_url,
- value: Setting.discourse_public_url,
- class: "w-full", disabled: true %>
- <% end %>
- <%= render FormElements::FieldsetComponent.new(title: "Connect secret") do %>
- <%= f.password_field :discourse_connect_secret,
- value: Setting.discourse_connect_secret,
- class: "w-full", disabled: true %>
- <% end %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :discourse_public_url,
+ title: "Public URL"
+ ) %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :discourse_connect_secret,
+ type: :password,
+ title: "Connect secret"
+ ) %>
<% end %>
<% if Setting.discourse_enabled? %>
diff --git a/app/views/admin/settings/services/_ejabberd.html.erb b/app/views/admin/settings/services/_ejabberd.html.erb
index 81490f9..0faa4b1 100644
--- a/app/views/admin/settings/services/_ejabberd.html.erb
+++ b/app/views/admin/settings/services/_ejabberd.html.erb
@@ -8,16 +8,14 @@
description: "ejabberd configuration present and features enabled"
) %>
<% if Setting.ejabberd_enabled? %>
- <%= render FormElements::FieldsetComponent.new(title: "API URL") do %>
- <%= f.text_field :ejabberd_api_url,
- value: Setting.ejabberd_api_url,
- class: "w-full", disabled: true %>
- <% end %>
- <%= render FormElements::FieldsetComponent.new(title: "Admin URL") do %>
- <%= f.text_field :ejabberd_admin_url,
- value: Setting.ejabberd_admin_url,
- class: "w-full", disabled: true %>
- <% end %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :ejabberd_api_url,
+ title: "API URL"
+ ) %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :ejabberd_admin_url,
+ title: "Admin URL"
+ ) %>
User default settings
@@ -37,12 +35,24 @@
title: "Auto-join default rooms",
description: "Automatically join above default rooms in chat clients"
) %>
- <%= render FormElements::FieldsetComponent.new(
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :ejabberd_buddy_roster,
title: "Contact roster name",
description: "Used when exchanging contacts after signup from invitation"
+ ) %>
+
+Notifications
+
+ <%= render FormElements::FieldsetComponent.new(
+ title: "From address",
+ description: "Address (JID) of the account notifications are sent from",
+ resettable: Setting.get_field(:xmpp_notifications_from_address)[:default] != Setting.xmpp_notifications_from_address
) do %>
- <%= f.text_field :ejabberd_buddy_roster,
- value: Setting.ejabberd_buddy_roster,
+ <%= f.text_field :xmpp_notifications_from_address,
+ value: Setting.xmpp_notifications_from_address,
+ data: {
+ :'default-value' => Setting.get_field(:xmpp_notifications_from_address)[:default]
+ },
class: "w-full" %>
<% end %>
<% end %>
diff --git a/app/views/admin/settings/services/_gitea.html.erb b/app/views/admin/settings/services/_gitea.html.erb
index 7c0238d..1e91cb6 100644
--- a/app/views/admin/settings/services/_gitea.html.erb
+++ b/app/views/admin/settings/services/_gitea.html.erb
@@ -8,10 +8,9 @@
description: "Gitea configuration present and features enabled"
) %>
<% if Setting.gitea_enabled? %>
- <%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
- <%= f.text_field :gitea_public_url,
- value: Setting.gitea_public_url,
- class: "w-full", disabled: true %>
- <% end %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :gitea_public_url,
+ title: "Public URL"
+ ) %>
<% end %>
diff --git a/app/views/admin/settings/services/_lndhub.html.erb b/app/views/admin/settings/services/_lndhub.html.erb
index e9d3c2e..a68ad37 100644
--- a/app/views/admin/settings/services/_lndhub.html.erb
+++ b/app/views/admin/settings/services/_lndhub.html.erb
@@ -8,11 +8,10 @@
description: "LNDHub configuration present and wallet features enabled"
) %>
<% if Setting.lndhub_enabled? %>
- <%= render FormElements::FieldsetComponent.new(title: "API URL") do %>
- <%= f.text_field :lndhub_api_url,
- value: Setting.lndhub_api_url,
- class: "w-full", disabled: true %>
- <% end %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :lndhub_api_url,
+ title: "API URL"
+ ) %>
<% end %>
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
@@ -29,10 +28,10 @@
description: "Allow users to receive invoice-less payments to their Lightning Address"
) %>
<% if Setting.lndhub_keysend_enabled? %>
- <%= render FormElements::FieldsetComponent.new(title: "Public key", description: "The public key of the Lightning node used by LNDHub") do %>
- <%= f.text_field :lndhub_public_key,
- value: Setting.lndhub_public_key,
- class: "w-full", disabled: true %>
- <% end %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :lndhub_public_key,
+ title: "Public key",
+ description: "The public key of the Lightning node used by LNDHub"
+ ) %>
<% end %>
diff --git a/app/views/admin/settings/services/_mastodon.html.erb b/app/views/admin/settings/services/_mastodon.html.erb
index 1270860..0de87d2 100644
--- a/app/views/admin/settings/services/_mastodon.html.erb
+++ b/app/views/admin/settings/services/_mastodon.html.erb
@@ -8,10 +8,9 @@
description: "Mastodon configuration present and features enabled"
) %>
<% if Setting.mastodon_enabled? %>
- <%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
- <%= f.text_field :mastodon_public_url,
- value: Setting.mastodon_public_url,
- class: "w-full", disabled: true %>
- <% end %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :mastodon_public_url,
+ title: "Public URL"
+ ) %>
<% end %>
diff --git a/app/views/admin/settings/services/_mediawiki.html.erb b/app/views/admin/settings/services/_mediawiki.html.erb
index 2456f20..2dc0aa7 100644
--- a/app/views/admin/settings/services/_mediawiki.html.erb
+++ b/app/views/admin/settings/services/_mediawiki.html.erb
@@ -8,10 +8,9 @@
description: "MediaWiki configuration present and features enabled"
) %>
<% if Setting.mediawiki_enabled? %>
- <%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
- <%= f.text_field :mediawiki_public_url,
- value: Setting.mediawiki_public_url,
- class: "w-full", disabled: true %>
- <% end %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :mediawiki_public_url,
+ title: "Public URL"
+ ) %>
<% end %>
diff --git a/app/views/admin/settings/services/_remotestorage.html.erb b/app/views/admin/settings/services/_remotestorage.html.erb
index fdfa3f4..42aea32 100644
--- a/app/views/admin/settings/services/_remotestorage.html.erb
+++ b/app/views/admin/settings/services/_remotestorage.html.erb
@@ -1,4 +1,5 @@
RemoteStorage
+Feature currently in development.
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
@@ -8,10 +9,9 @@
description: "RemoteStorage configuration present and features enabled"
) %>
<% if Setting.remotestorage_enabled? %>
- <%= render FormElements::FieldsetComponent.new(title: "Storage URL") do %>
- <%= f.text_field :rs_storage_url,
- value: Setting.rs_storage_url,
- class: "w-full", disabled: true %>
- <% end %>
+ <%= render FormElements::FieldsetResettableSettingComponent.new(
+ key: :rs_storage_url,
+ title: "Storage URL"
+ ) %>
<% end %>
diff --git a/app/views/services/lightning/index.html.erb b/app/views/services/lightning/index.html.erb
index fda9558..6d18379 100644
--- a/app/views/services/lightning/index.html.erb
+++ b/app/views/services/lightning/index.html.erb
@@ -7,13 +7,25 @@
Lightning Address
-
+
Your Kosmos user address is also a
Lightning Address !
The easiest way to receive sats is by just giving out your address:
-
- <%= current_user.address %>
+
+ disabled="disabled"
+ data-clipboard-target="source" />
+
+
+ <%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
+
+
+ <%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
+
+
@@ -39,7 +51,7 @@
Hide setup QR code
- <%= raw @svg %>
+ <%= raw @lndhub_qr_svg %>
@@ -88,6 +100,24 @@
+
+
+ QR Code for Donations/Tips
+
+ You can print out or publish a QR code for people to scan with their
+ wallet apps, so they can send you sats without a direct personal
+ interaction (for example at a concert, or on your website).
+
+
+ <%= link_to "Download SVG file",
+ qr_lnurlp_services_lightning_index_path(format: "svg"),
+ class: "btn-md btn-blue w-full sm:w-auto"%>
+ or
+ <%= link_to "Download PNG file",
+ qr_lnurlp_services_lightning_index_path(format: "png"),
+ class: "btn-md btn-blue w-full sm:w-auto"%>
+
+
<% end %>