Compare commits
10 Commits
v0.5.0
...
595bb03c5a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
595bb03c5a
|
||
|
|
62cd0eb7d1
|
||
|
|
f19baaf22a
|
||
|
|
23821f9e65
|
||
|
|
a33410eeb4
|
||
|
|
334b47353e
|
||
|
|
6848bd739c
|
||
|
|
7f77ad5528
|
||
| 6f2160b479 | |||
|
|
fe1dfd8ec8
|
1
Gemfile
1
Gemfile
@@ -66,6 +66,7 @@ group :development do
|
|||||||
gem 'letter_opener'
|
gem 'letter_opener'
|
||||||
gem 'letter_opener_web'
|
gem 'letter_opener_web'
|
||||||
gem 'faker'
|
gem 'faker'
|
||||||
|
gem 'solargraph'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
|||||||
49
Gemfile.lock
49
Gemfile.lock
@@ -68,7 +68,10 @@ GEM
|
|||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
addressable (2.8.1)
|
addressable (2.8.1)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
|
ast (2.4.2)
|
||||||
|
backport (1.2.0)
|
||||||
bcrypt (3.1.18)
|
bcrypt (3.1.18)
|
||||||
|
benchmark (0.2.1)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
byebug (11.1.3)
|
byebug (11.1.3)
|
||||||
@@ -109,6 +112,7 @@ GEM
|
|||||||
dotenv-rails (2.8.1)
|
dotenv-rails (2.8.1)
|
||||||
dotenv (= 2.8.1)
|
dotenv (= 2.8.1)
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
|
e2mmap (0.1.0)
|
||||||
erubi (1.11.0)
|
erubi (1.11.0)
|
||||||
et-orbi (1.2.7)
|
et-orbi (1.2.7)
|
||||||
tzinfo
|
tzinfo
|
||||||
@@ -135,9 +139,15 @@ GEM
|
|||||||
importmap-rails (1.1.5)
|
importmap-rails (1.1.5)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
|
jaro_winkler (1.5.4)
|
||||||
jbuilder (2.11.5)
|
jbuilder (2.11.5)
|
||||||
actionview (>= 5.0.0)
|
actionview (>= 5.0.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
|
json (2.6.3)
|
||||||
|
kramdown (2.4.0)
|
||||||
|
rexml
|
||||||
|
kramdown-parser-gfm (1.1.0)
|
||||||
|
kramdown (~> 2.0)
|
||||||
launchy (2.5.0)
|
launchy (2.5.0)
|
||||||
addressable (~> 2.7)
|
addressable (~> 2.7)
|
||||||
letter_opener (1.8.1)
|
letter_opener (1.8.1)
|
||||||
@@ -179,6 +189,9 @@ GEM
|
|||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
pagy (6.0.2)
|
pagy (6.0.2)
|
||||||
|
parallel (1.22.1)
|
||||||
|
parser (3.2.1.1)
|
||||||
|
ast (~> 2.4.1)
|
||||||
pg (1.2.3)
|
pg (1.2.3)
|
||||||
public_suffix (5.0.0)
|
public_suffix (5.0.0)
|
||||||
puma (4.3.12)
|
puma (4.3.12)
|
||||||
@@ -217,6 +230,7 @@ GEM
|
|||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.5)
|
||||||
|
rainbow (3.1.1)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
rb-fsevent (0.11.2)
|
rb-fsevent (0.11.2)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
@@ -229,6 +243,8 @@ GEM
|
|||||||
responders (3.1.0)
|
responders (3.1.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
|
reverse_markdown (2.1.1)
|
||||||
|
nokogiri
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rqrcode (2.1.2)
|
rqrcode (2.1.2)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
@@ -251,6 +267,19 @@ GEM
|
|||||||
rspec-mocks (~> 3.11)
|
rspec-mocks (~> 3.11)
|
||||||
rspec-support (~> 3.11)
|
rspec-support (~> 3.11)
|
||||||
rspec-support (3.12.0)
|
rspec-support (3.12.0)
|
||||||
|
rubocop (1.48.1)
|
||||||
|
json (~> 2.3)
|
||||||
|
parallel (~> 1.10)
|
||||||
|
parser (>= 3.2.0.0)
|
||||||
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
|
rexml (>= 3.2.5, < 4.0)
|
||||||
|
rubocop-ast (>= 1.26.0, < 2.0)
|
||||||
|
ruby-progressbar (~> 1.7)
|
||||||
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
|
rubocop-ast (1.28.0)
|
||||||
|
parser (>= 3.2.1.0)
|
||||||
|
ruby-progressbar (1.13.0)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rufus-scheduler (3.8.2)
|
rufus-scheduler (3.8.2)
|
||||||
fugit (~> 1.1, >= 1.1.6)
|
fugit (~> 1.1, >= 1.1.6)
|
||||||
@@ -268,6 +297,21 @@ GEM
|
|||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 4, < 7)
|
sidekiq (>= 4, < 7)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
|
solargraph (0.48.0)
|
||||||
|
backport (~> 1.2)
|
||||||
|
benchmark
|
||||||
|
bundler (>= 1.17.2)
|
||||||
|
diff-lcs (~> 1.4)
|
||||||
|
e2mmap
|
||||||
|
jaro_winkler (~> 1.5)
|
||||||
|
kramdown (~> 2.3)
|
||||||
|
kramdown-parser-gfm (~> 1.1)
|
||||||
|
parser (~> 3.0)
|
||||||
|
reverse_markdown (>= 1.0.5, < 3)
|
||||||
|
rubocop (>= 0.52)
|
||||||
|
thor (~> 1.0)
|
||||||
|
tilt (~> 2.0)
|
||||||
|
yard (~> 0.9, >= 0.9.24)
|
||||||
sprockets (4.1.1)
|
sprockets (4.1.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
@@ -289,6 +333,7 @@ GEM
|
|||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
tzinfo (2.0.5)
|
tzinfo (2.0.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
|
unicode-display_width (2.4.2)
|
||||||
view_component (2.78.0)
|
view_component (2.78.0)
|
||||||
activesupport (>= 5.0.0, < 8.0)
|
activesupport (>= 5.0.0, < 8.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
@@ -304,11 +349,14 @@ GEM
|
|||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
|
webrick (1.7.0)
|
||||||
websocket-driver (0.7.5)
|
websocket-driver (0.7.5)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
|
yard (0.9.28)
|
||||||
|
webrick (~> 1.7.0)
|
||||||
zeitwerk (2.6.6)
|
zeitwerk (2.6.6)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
@@ -344,6 +392,7 @@ DEPENDENCIES
|
|||||||
sentry-ruby
|
sentry-ruby
|
||||||
sidekiq (< 7)
|
sidekiq (< 7)
|
||||||
sidekiq-scheduler
|
sidekiq-scheduler
|
||||||
|
solargraph
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
sqlite3 (~> 1.4)
|
sqlite3 (~> 1.4)
|
||||||
stimulus-rails
|
stimulus-rails
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -23,7 +23,6 @@ so:
|
|||||||
|
|
||||||
After these steps, you should have a working Rails app with a handful of test
|
After these steps, you should have a working Rails app with a handful of test
|
||||||
users running on [http://localhost:3000](http://localhost:3000).
|
users running on [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
Log in with username "admin" and password "admin is admin". All users listed on
|
Log in with username "admin" and password "admin is admin". All users listed on
|
||||||
[http://localhost:3000/admin/ldap_users](http://localhost:3000/admin/ldap_users)
|
[http://localhost:3000/admin/ldap_users](http://localhost:3000/admin/ldap_users)
|
||||||
have the password "user is user".
|
have the password "user is user".
|
||||||
@@ -79,6 +78,15 @@ The setup task will first delete any existing entries in the directory tree
|
|||||||
Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over
|
Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over
|
||||||
with a fresh installation, delete both that directory as well as the container.
|
with a fresh installation, delete both that directory as well as the container.
|
||||||
|
|
||||||
|
#### Solargraph
|
||||||
|
|
||||||
|
[Solargraph](https://solargraph.org/) is a Ruby language server, which you may
|
||||||
|
use with your editor to add features like auto-completion and syntax
|
||||||
|
validation. You can add inline documentation for bundled gems with this
|
||||||
|
command:
|
||||||
|
|
||||||
|
bundle exec yard gems
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
### Rails
|
### Rails
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<%= tag.public_send(@tag, class: "mb-6 last:mb-0") do %>
|
<%= tag.public_send(@tag, class: "mb-6 last:mb-0") do %>
|
||||||
|
<% if @positioning == :vertical %>
|
||||||
<label class="block">
|
<label class="block">
|
||||||
<p class="font-bold <%= @descripton.present? ? "mb-1" : "mb-2" %>">
|
<p class="font-bold <%= @descripton.present? ? "mb-1" : "mb-2" %>">
|
||||||
<%= @title %>
|
<%= @title %>
|
||||||
@@ -10,4 +11,19 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<%= content %>
|
<%= content %>
|
||||||
</label>
|
</label>
|
||||||
|
<% elsif @positioning == :horizontal %>
|
||||||
|
<label class="block flex items-center justify-between">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label class="font-bold mb-1"><%= @title %></label>
|
||||||
|
<% if @descripton.present? %>
|
||||||
|
<p class="text-gray-500"><%= @descripton %></p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="relative ml-4 inline-flex flex-shrink-0">
|
||||||
|
<%= content %>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<% else %>
|
||||||
|
<p>Invalid <code>positioning<code> argument for <code>FieldsetComponent</code>.</p>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
module FormElements
|
module FormElements
|
||||||
class FieldsetComponent < ViewComponent::Base
|
class FieldsetComponent < ViewComponent::Base
|
||||||
def initialize(tag: "li", title:, description: nil)
|
def initialize(tag: "li", positioning: :vertical, title:, description: nil)
|
||||||
@tag = tag
|
@tag = tag
|
||||||
|
@positioning = positioning
|
||||||
@title = title
|
@title = title
|
||||||
@descripton = description
|
@descripton = description
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<%= tag.public_send @tag, class: "flex items-center justify-between mb-6 last:mb-0",
|
<%= tag.public_send @tag, class: "flex items-center justify-between mb-6 last:mb-0",
|
||||||
data: @form.present? ? {
|
data: @form_enabled ? {
|
||||||
controller: "settings--toggle",
|
controller: "settings--toggle",
|
||||||
:'settings--toggle-switch-enabled-value' => @enabled.to_s
|
:'settings--toggle-switch-enabled-value' => @enabled.to_s
|
||||||
} : nil do %>
|
} : nil do %>
|
||||||
@@ -11,16 +11,23 @@
|
|||||||
<%= render FormElements::ToggleComponent.new(
|
<%= render FormElements::ToggleComponent.new(
|
||||||
enabled: @enabled,
|
enabled: @enabled,
|
||||||
input_enabled: @input_enabled,
|
input_enabled: @input_enabled,
|
||||||
class_names: @form.present? ? "hidden" : nil,
|
class_names: @form_enabled ? "hidden" : nil,
|
||||||
data: {
|
data: {
|
||||||
:'settings--toggle-target' => "button",
|
:'settings--toggle-target' => "button",
|
||||||
action: "settings--toggle#toggleSwitch"
|
action: "settings--toggle#toggleSwitch"
|
||||||
}) %>
|
}) %>
|
||||||
<% if @form.present? %>
|
<% if @form_enabled %>
|
||||||
|
<% if @attribute.present? %>
|
||||||
<%= @form.check_box @attribute, {
|
<%= @form.check_box @attribute, {
|
||||||
checked: @enabled,
|
checked: @enabled,
|
||||||
data: { :'settings--toggle-target' => "checkbox" }
|
data: { :'settings--toggle-target' => "checkbox" }
|
||||||
}, "true", "false" %>
|
}, "true", "false" %>
|
||||||
|
<% else %>
|
||||||
|
<input name="<%= @field_name %>" type="hidden" value="false" autocomplete="off">
|
||||||
|
<%= check_box_tag @field_name, "true", @enabled, {
|
||||||
|
data: { :'settings--toggle-target' => "checkbox" }
|
||||||
|
} %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
module FormElements
|
module FormElements
|
||||||
class FieldsetToggleComponent < ViewComponent::Base
|
class FieldsetToggleComponent < ViewComponent::Base
|
||||||
def initialize(form: nil, attribute: nil, tag: "li", enabled: false,
|
def initialize(tag: "li", form: nil, attribute: nil, field_name: nil,
|
||||||
input_enabled: true, title:, description:)
|
enabled: false, input_enabled: true, title:, description:)
|
||||||
|
@tag = tag
|
||||||
@form = form
|
@form = form
|
||||||
@attribute = attribute
|
@attribute = attribute
|
||||||
@tag = tag
|
@field_name = field_name
|
||||||
|
@form_enabled = @form.present? || @field_name.present?
|
||||||
@enabled = enabled
|
@enabled = enabled
|
||||||
@input_enabled = input_enabled
|
@input_enabled = input_enabled
|
||||||
@title = title
|
@title = title
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
class Settings::AccountController < SettingsController
|
|
||||||
|
|
||||||
def index
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_password
|
|
||||||
current_user.send_reset_password_instructions
|
|
||||||
sign_out current_user
|
|
||||||
msg = "We have sent you an email with a link to reset your password."
|
|
||||||
redirect_to check_your_email_path, notice: msg
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
class Settings::ProfileController < SettingsController
|
|
||||||
|
|
||||||
def index
|
|
||||||
@user = current_user
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,13 +1,52 @@
|
|||||||
class SettingsController < ApplicationController
|
class SettingsController < ApplicationController
|
||||||
before_action :require_user_signed_in
|
before_action :authenticate_user!
|
||||||
before_action :set_current_section
|
before_action :set_main_nav_section
|
||||||
|
before_action :set_settings_section, only: ['show', 'update']
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
redirect_to setting_path(:profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@user = current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@user = current_user
|
||||||
|
@user.preferences.merge! user_params[:preferences]
|
||||||
|
@user.save!
|
||||||
|
|
||||||
|
redirect_to setting_path(@settings_section), flash: {
|
||||||
|
success: 'Settings saved.'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_password
|
||||||
|
current_user.send_reset_password_instructions
|
||||||
|
sign_out current_user
|
||||||
|
msg = "We have sent you an email with a link to reset your password."
|
||||||
|
redirect_to check_your_email_path, notice: msg
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_current_section
|
def set_main_nav_section
|
||||||
@current_section = :settings
|
@current_section = :settings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_settings_section
|
||||||
|
@settings_section = params[:section]
|
||||||
|
allowed_sections = [:profile, :account, :lightning, :xmpp]
|
||||||
|
|
||||||
|
unless allowed_sections.include?(@settings_section.to_sym)
|
||||||
|
redirect_to setting_path(:profile)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_params
|
||||||
|
params.require(:user).permit(preferences: [
|
||||||
|
lightning: [:notify_sats_received],
|
||||||
|
xmpp: [:exchange_contacts_with_invitees]
|
||||||
|
])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
include EmailValidatable
|
include EmailValidatable
|
||||||
|
|
||||||
|
serialize :preferences, Hash, default: {}
|
||||||
|
|
||||||
# Relations
|
# Relations
|
||||||
has_many :invitations, dependent: :destroy
|
has_many :invitations, dependent: :destroy
|
||||||
has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id'
|
has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id'
|
||||||
@@ -55,13 +57,16 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def devise_after_confirmation
|
def devise_after_confirmation
|
||||||
enable_service %w[ discourse ejabberd gitea mediawiki ]
|
enable_service %w[ discourse gitea mediawiki xmpp ]
|
||||||
|
|
||||||
#TODO enable in development when we have easy setup of ejabberd etc.
|
#TODO enable in development when we have easy setup of ejabberd etc.
|
||||||
return if Rails.env.development?
|
return if Rails.env.development?
|
||||||
|
|
||||||
if inviter.present?
|
if inviter.present?
|
||||||
exchange_xmpp_contact_with_inviter if Setting.ejabberd_enabled?
|
if Setting.ejabberd_enabled? &&
|
||||||
|
inviter.pref_enabled?("xmpp:exchange_contacts_with_invitees")
|
||||||
|
exchange_xmpp_contact_with_inviter
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -133,9 +138,14 @@ class User < ApplicationRecord
|
|||||||
ldap.delete_attribute(dn,:service)
|
ldap.delete_attribute(dn,:service)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pref_enabled?(key)
|
||||||
|
value = preferences.dig(*key.split(":"))
|
||||||
|
[true, "true", "enabled", 1].include?(value)
|
||||||
|
end
|
||||||
|
|
||||||
def exchange_xmpp_contact_with_inviter
|
def exchange_xmpp_contact_with_inviter
|
||||||
return unless inviter.services_enabled.include?("ejabberd") &&
|
return unless inviter.services_enabled.include?("xmpp") &&
|
||||||
services_enabled.include?("ejabberd")
|
services_enabled.include?("xmpp")
|
||||||
XmppExchangeContactsJob.perform_later(inviter, self.cn, self.ou)
|
XmppExchangeContactsJob.perform_later(inviter, self.cn, self.ou)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
<td>XMPP (ejabberd)</td>
|
<td>XMPP (ejabberd)</td>
|
||||||
<td>
|
<td>
|
||||||
<%= render FormElements::ToggleComponent.new(
|
<%= render FormElements::ToggleComponent.new(
|
||||||
enabled: @services_enabled.include?("ejabberd"),
|
enabled: @services_enabled.include?("xmpp"),
|
||||||
input_enabled: false
|
input_enabled: false
|
||||||
) %>
|
) %>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell <%= custom_class %>"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 342 B |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-message-circle"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-message-circle <%= custom_class %>"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 428 B After Width: | Height: | Size: 449 B |
19
app/views/settings/_account.html.erb
Normal file
19
app/views/settings/_account.html.erb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<section>
|
||||||
|
<h3>E-Mail</h3>
|
||||||
|
<p class="mb-2">
|
||||||
|
<%= label :email, 'Address', class: 'font-bold' %>
|
||||||
|
</p>
|
||||||
|
<p class="flex gap-1 mb-2 sm:w-3/5">
|
||||||
|
<input type="text" id="email" class="grow"
|
||||||
|
value=<%= current_user.email %> disabled="disabled" />
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>Password</h3>
|
||||||
|
<p class="mb-8">Use the following button to request an email with a password reset link:</p>
|
||||||
|
<%= form_with(url: reset_password_settings_path, method: :post) do %>
|
||||||
|
<p>
|
||||||
|
<%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray w-full sm:w-auto') %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</section>
|
||||||
27
app/views/settings/_lightning.html.erb
Normal file
27
app/views/settings/_lightning.html.erb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<%= form_for @user, url: setting_path(:lightning), html: { :method => :put } do |f| %>
|
||||||
|
<section>
|
||||||
|
<h3>Notifications</h3>
|
||||||
|
<ul role="list">
|
||||||
|
<%= render FormElements::FieldsetComponent.new(
|
||||||
|
positioning: :horizontal,
|
||||||
|
title: "Sats received",
|
||||||
|
description: "Notify me when sats are sent to my Lightning Address"
|
||||||
|
) do %>
|
||||||
|
<% f.fields_for :preferences do |p| %>
|
||||||
|
<% p.fields_for :lightning do |l| %>
|
||||||
|
<%= l.select :notify_sats_received, options_for_select([
|
||||||
|
["off", "off"],
|
||||||
|
["Chat (Jabber)", "xmpp"],
|
||||||
|
["E-Mail", "email"]
|
||||||
|
], selected: @user.preferences.dig('lightning', 'notify_sats_received')) %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<p class="pt-6 border-t border-gray-200 text-right">
|
||||||
|
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<% end %>
|
||||||
16
app/views/settings/_notifications.html.erb
Normal file
16
app/views/settings/_notifications.html.erb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<section>
|
||||||
|
<h3>Lightning Wallet</h3>
|
||||||
|
|
||||||
|
<ul role="list">
|
||||||
|
<%= render FormElements::FieldsetComponent.new(
|
||||||
|
positioning: :horizontal,
|
||||||
|
title: "Sats received",
|
||||||
|
description: "Notify when sats are sent to my Lightning Address"
|
||||||
|
) do %>
|
||||||
|
<%= select_tag :sats_received, options_for_select([
|
||||||
|
["off", "off"],
|
||||||
|
["Chat (Jabber)", "xmpp"]
|
||||||
|
]) %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
30
app/views/settings/_profile.html.erb
Normal file
30
app/views/settings/_profile.html.erb
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<section>
|
||||||
|
<h3>Profile</h3>
|
||||||
|
<p class="mb-2">
|
||||||
|
<%= label :user_address, 'User address', class: 'font-bold' %>
|
||||||
|
</p>
|
||||||
|
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:w-3/5">
|
||||||
|
<input type="text" id="user_address" class="grow"
|
||||||
|
value=<%= @user.address %> disabled="disabled"
|
||||||
|
data-clipboard-target="source" />
|
||||||
|
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0"
|
||||||
|
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||||
|
title="Copy to clipboard">
|
||||||
|
<span class="content-initial">
|
||||||
|
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||||
|
</span>
|
||||||
|
<span class="content-active hidden">
|
||||||
|
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
Your user address for Chat and Lightning Network.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<%# <%= form_for(@user, as: "profile", url: settings_profile_path) do |f| %>
|
||||||
|
<%# <p class="mt-8">
|
||||||
|
<%# <%= f.submit "Save changes", class: 'btn-md btn-blue w-full sm:w-auto' %>
|
||||||
|
<%# </p>
|
||||||
|
<%# <% end %>
|
||||||
|
</section>
|
||||||
18
app/views/settings/_xmpp.html.erb
Normal file
18
app/views/settings/_xmpp.html.erb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<%= form_for @user, url: setting_path(:xmpp), html: { :method => :put } do |f| %>
|
||||||
|
<section>
|
||||||
|
<h3>Contacts</h3>
|
||||||
|
<ul role="list">
|
||||||
|
<%= render FormElements::FieldsetToggleComponent.new(
|
||||||
|
field_name: "user[preferences][xmpp][exchange_contacts_with_invitees]",
|
||||||
|
enabled: @user.pref_enabled?("xmpp:exchange_contacts_with_invitees"),
|
||||||
|
title: "Exchange contacts when invited user signs up",
|
||||||
|
description: "Add each others contacts, so you can chat with them immediately"
|
||||||
|
) %>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<p class="pt-6 border-t border-gray-200 text-right">
|
||||||
|
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<% end %>
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<%= render HeaderComponent.new(title: "Settings") %>
|
|
||||||
|
|
||||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %>
|
|
||||||
<section>
|
|
||||||
<h3>E-Mail</h3>
|
|
||||||
<p class="mb-2">
|
|
||||||
<%= label :email, 'Address', class: 'font-bold' %>
|
|
||||||
</p>
|
|
||||||
<p class="flex gap-1 mb-2 sm:w-3/5">
|
|
||||||
<input type="text" id="email" class="grow"
|
|
||||||
value=<%= current_user.email %> disabled="disabled" />
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h3>Password</h3>
|
|
||||||
<p class="mb-8">Use the following button to request an email with a password reset link:</p>
|
|
||||||
<%= form_with(url: settings_reset_password_path, method: :post) do %>
|
|
||||||
<p>
|
|
||||||
<%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray w-full sm:w-auto') %>
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<%= render HeaderComponent.new(title: "Settings") %>
|
|
||||||
|
|
||||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %>
|
|
||||||
<section>
|
|
||||||
<h3>Profile</h3>
|
|
||||||
<p class="mb-2">
|
|
||||||
<%= label :user_address, 'User address', class: 'font-bold' %>
|
|
||||||
</p>
|
|
||||||
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:w-3/5">
|
|
||||||
<input type="text" id="user_address" class="grow"
|
|
||||||
value=<%= @user.address %> disabled="disabled"
|
|
||||||
data-clipboard-target="source" />
|
|
||||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0"
|
|
||||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
|
||||||
title="Copy to clipboard">
|
|
||||||
<span class="content-initial">
|
|
||||||
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
|
||||||
</span>
|
|
||||||
<span class="content-active hidden">
|
|
||||||
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p class="text-sm text-gray-500">
|
|
||||||
Your user address for Chat and Lightning Network.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<%# <%= form_for(@user, as: "profile", url: settings_profile_path) do |f| %>
|
|
||||||
<%# <p class="mt-8">
|
|
||||||
<%# <%= f.submit "Save changes", class: 'btn-md btn-blue w-full sm:w-auto' %>
|
|
||||||
<%# </p>
|
|
||||||
<%# <% end %>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
5
app/views/settings/show.html.erb
Normal file
5
app/views/settings/show.html.erb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<%= render HeaderComponent.new(title: "Settings") %>
|
||||||
|
|
||||||
|
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %>
|
||||||
|
<%= render partial: @settings_section %>
|
||||||
|
<% end %>
|
||||||
@@ -6,5 +6,5 @@
|
|||||||
class: main_nav_class(@current_section, :invitations) %>
|
class: main_nav_class(@current_section, :invitations) %>
|
||||||
<%= link_to "Wallet", wallet_path,
|
<%= link_to "Wallet", wallet_path,
|
||||||
class: main_nav_class(@current_section, :wallet) %>
|
class: main_nav_class(@current_section, :wallet) %>
|
||||||
<%= link_to "Settings", settings_profile_path,
|
<%= link_to "Settings", settings_path,
|
||||||
class: main_nav_class(@current_section, :settings) %>
|
class: main_nav_class(@current_section, :settings) %>
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
name: "Profile", path: settings_profile_path, icon: "user",
|
name: "Profile", path: setting_path(:profile), icon: "user",
|
||||||
active: current_page?(settings_profile_path)
|
active: current_page?(setting_path(:profile))
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
name: "Account", path: settings_account_path, icon: "key",
|
name: "Account", path: setting_path(:account), icon: "key",
|
||||||
active: current_page?(settings_account_path)
|
active: current_page?(setting_path(:account))
|
||||||
) %>
|
) %>
|
||||||
|
<% if Setting.ejabberd_enabled %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
name: "Security", path: "#", icon: "shield", disabled: true
|
name: "Chat", path: setting_path(:xmpp), icon: "message-circle",
|
||||||
|
active: current_page?(setting_path(:xmpp))
|
||||||
) %>
|
) %>
|
||||||
|
<% end %>
|
||||||
|
<% if Setting.lndhub_enabled %>
|
||||||
|
<%= render SidenavLinkComponent.new(
|
||||||
|
name: "Wallet", path: setting_path(:lightning), icon: "zap",
|
||||||
|
active: current_page?(setting_path(:lightning))
|
||||||
|
) %>
|
||||||
|
<% end %>
|
||||||
|
|||||||
@@ -10,13 +10,6 @@ Rails.application.routes.draw do
|
|||||||
match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post]
|
match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post]
|
||||||
post 'signup_validate', to: 'signup#validate'
|
post 'signup_validate', to: 'signup#validate'
|
||||||
|
|
||||||
namespace :settings do
|
|
||||||
get 'profile', to: 'profile#index'
|
|
||||||
post 'profile', to: 'profile#update'
|
|
||||||
get 'account', to: 'account#index'
|
|
||||||
post 'reset_password', to: 'account#reset_password'
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace :contributions do
|
namespace :contributions do
|
||||||
root to: 'donations#index'
|
root to: 'donations#index'
|
||||||
get 'projects', to: 'projects#index'
|
get 'projects', to: 'projects#index'
|
||||||
@@ -28,6 +21,12 @@ Rails.application.routes.draw do
|
|||||||
get 'wallet', to: 'wallet#index'
|
get 'wallet', to: 'wallet#index'
|
||||||
get 'wallet/transactions', to: 'wallet#transactions'
|
get 'wallet/transactions', to: 'wallet#transactions'
|
||||||
|
|
||||||
|
resources :settings, param: 'section', only: ['index', 'show', 'update'] do
|
||||||
|
collection do
|
||||||
|
post 'reset_password'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
get 'lnurlpay/:address', to: 'lnurlpay#index',
|
get 'lnurlpay/:address', to: 'lnurlpay#index',
|
||||||
as: 'lightning_address', constraints: { address: /[^\/]+/}
|
as: 'lightning_address', constraints: { address: /[^\/]+/}
|
||||||
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice',
|
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice',
|
||||||
|
|||||||
5
db/migrate/20230403135149_add_preferences_to_users.rb
Normal file
5
db/migrate/20230403135149_add_preferences_to_users.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class AddPreferencesToUsers < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :users, :preferences, :text
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2023_03_19_101128) do
|
ActiveRecord::Schema[7.0].define(version: 2023_04_03_135149) do
|
||||||
create_table "donations", force: :cascade do |t|
|
create_table "donations", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "amount_sats"
|
t.integer "amount_sats"
|
||||||
@@ -57,8 +57,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_19_101128) do
|
|||||||
t.text "ln_login_ciphertext"
|
t.text "ln_login_ciphertext"
|
||||||
t.text "ln_password_ciphertext"
|
t.text "ln_password_ciphertext"
|
||||||
t.string "ln_account"
|
t.string "ln_account"
|
||||||
|
t.string "nostr_pubkey"
|
||||||
t.datetime "remember_created_at"
|
t.datetime "remember_created_at"
|
||||||
t.string "remember_token"
|
t.string "remember_token"
|
||||||
|
t.text "preferences"
|
||||||
t.index ["email"], name: "index_users_on_email", unique: true
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ RSpec.describe User, type: :model do
|
|||||||
|
|
||||||
before do
|
before do
|
||||||
Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now
|
Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now
|
||||||
allow_any_instance_of(User).to receive(:services_enabled).and_return(%w[ ejabberd ])
|
allow_any_instance_of(User).to receive(:services_enabled).and_return(%w[ xmpp ])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "enqueues a job to exchange XMPP contacts between inviter and invitee" do
|
it "enqueues a job to exchange XMPP contacts between inviter and invitee" do
|
||||||
@@ -131,14 +131,16 @@ RSpec.describe User, type: :model do
|
|||||||
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
||||||
|
|
||||||
it "enables default services" do
|
it "enables default services" do
|
||||||
expect(user).to receive(:enable_service).with(%w[ discourse ejabberd gitea mediawiki ])
|
expect(user).to receive(:enable_service).with(%w[ discourse gitea mediawiki xmpp ])
|
||||||
user.send(:devise_after_confirmation)
|
user.send(:devise_after_confirmation)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "for invited user with ejabberd enabled" do
|
context "for invited user with xmpp enabled" do
|
||||||
let(:guest) { create :user, id: 2, cn: "isaacnewton", ou: "kosmos.org", email: "newt@example.com" }
|
let(:guest) { create :user, id: 2, cn: "isaacnewton", ou: "kosmos.org", email: "newt@example.com" }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
# TODO remove when defaults are implemented
|
||||||
|
user.update! preferences: {"xmpp" => { "exchange_contacts_with_invitees" => true }}
|
||||||
Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now
|
Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now
|
||||||
allow_any_instance_of(User).to receive(:enable_service).and_return(true)
|
allow_any_instance_of(User).to receive(:enable_service).and_return(true)
|
||||||
end
|
end
|
||||||
@@ -147,6 +149,63 @@ RSpec.describe User, type: :model do
|
|||||||
expect(guest).to receive(:exchange_xmpp_contact_with_inviter)
|
expect(guest).to receive(:exchange_xmpp_contact_with_inviter)
|
||||||
guest.send(:devise_after_confirmation)
|
guest.send(:devise_after_confirmation)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "automatic contact exchange disabled" do
|
||||||
|
before do
|
||||||
|
user.update! preferences: {"xmpp" => { "exchange_contacts_with_invitees" => false }}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not exchange XMPP contacts with the inviter" do
|
||||||
|
expect(guest).to_not receive(:exchange_xmpp_contact_with_inviter)
|
||||||
|
guest.send(:devise_after_confirmation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#pref_enabled?" do
|
||||||
|
describe "preference not set" do
|
||||||
|
# TODO return default value
|
||||||
|
it "returns false" do
|
||||||
|
expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "preference is set" do
|
||||||
|
it "returns true for boolean true" do
|
||||||
|
user.preferences.merge!({"lightning" => {"notify_sats_received" => true}})
|
||||||
|
expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true for string 'true'" do
|
||||||
|
user.preferences.merge!({"lightning" => {"notify_sats_received" => "true"}})
|
||||||
|
expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true for string 'enabled'" do
|
||||||
|
user.preferences.merge!({"lightning" => {"notify_sats_received" => "enabled"}})
|
||||||
|
expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true for integer 1" do
|
||||||
|
user.preferences.merge!({"lightning" => {"notify_sats_received" => 1}})
|
||||||
|
expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false for boolean false" do
|
||||||
|
user.preferences.merge!({"lightning" => {"notify_sats_received" => false}})
|
||||||
|
expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false for string 'false'" do
|
||||||
|
user.preferences.merge!({"lightning" => {"notify_sats_received" => "false"}})
|
||||||
|
expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false for integer 0" do
|
||||||
|
user.preferences.merge!({"lightning" => {"notify_sats_received" => 0}})
|
||||||
|
expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user