Compare commits
83 Commits
v0.2.1
...
ab29f618f4
| Author | SHA1 | Date | |
|---|---|---|---|
|
ab29f618f4
|
|||
| 94975a1b30 | |||
|
cd8880d9dc
|
|||
|
f59182b9c1
|
|||
|
941cb4a571
|
|||
|
f534898d8b
|
|||
| 18c7c54403 | |||
|
12a9d4674b
|
|||
|
1af8e068c5
|
|||
| 669b163814 | |||
| 46c7affd1f | |||
| 7ab107b689 | |||
| 5aee1a4100 | |||
|
1578fb9976
|
|||
| 8e64a7cf78 | |||
|
8b5bd66598
|
|||
| ac8552362c | |||
|
99c86c42c5
|
|||
|
d0267cb760
|
|||
| 25ddab9241 | |||
|
bf76ac55ee
|
|||
|
40e5c3609e
|
|||
|
1078c034ad
|
|||
|
bfa38ad7b2
|
|||
|
4f20cd0d0a
|
|||
|
e2ee33a1da
|
|||
|
8662a4c8c1
|
|||
|
dbc811b840
|
|||
|
884070a3cb
|
|||
|
3c350155de
|
|||
|
21c6ebc137
|
|||
|
0a1052fcb7
|
|||
|
f94227f9f3
|
|||
| 088961dfec | |||
|
31cf353d3a
|
|||
|
4eb40abc9c
|
|||
|
682c78c7c3
|
|||
|
f9726ad9be
|
|||
|
89188f5081
|
|||
| 6a6ff84ff2 | |||
|
b6949acc96
|
|||
|
814633034f
|
|||
| 260dedb6cf | |||
|
656c887811
|
|||
|
7e9af716ac
|
|||
|
58cc6811f9
|
|||
|
8ad85636d9
|
|||
|
35e2c8cd30
|
|||
| 4526c941b8 | |||
| 4f5ebd5330 | |||
|
d7e4c6f3ae
|
|||
|
14caefe2d1
|
|||
|
0110f27ada
|
|||
|
dc7cf107c2
|
|||
| 4fbfaadb44 | |||
|
a01cb9ae21
|
|||
|
698e4381c2
|
|||
|
8997349186
|
|||
|
92bfc33bf0
|
|||
|
c6eb21faad
|
|||
| 2d9bc90b16 | |||
|
a0c579e319
|
|||
|
f289ee9365
|
|||
| 46a7345ce9 | |||
|
e12d02a988
|
|||
| 5e8618f25a | |||
|
2bdf08a523
|
|||
|
9ddd36c414
|
|||
|
9372ea7343
|
|||
|
c62ce00184
|
|||
|
4d8cd740ba
|
|||
|
9858572a2f
|
|||
|
51edf55ae9
|
|||
| 75485ce8e9 | |||
|
fcbfcc4007
|
|||
|
cdcb7b3aef
|
|||
| bcf5172956 | |||
|
26c6c5a3b2
|
|||
|
4a65573934
|
|||
|
5e2d5c3b28
|
|||
|
2f70bae523
|
|||
|
40f3e8327a
|
|||
|
f3d6e29e4e
|
@@ -1 +1,3 @@
|
||||
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
||||
LNDHUB_API_URL='http://localhost:3023'
|
||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
|
||||
LNDHUB_API_URL='http://10.1.1.163:3023'
|
||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -39,3 +39,8 @@ yarn-debug.log*
|
||||
|
||||
# Ignore local dotenv config file
|
||||
.env
|
||||
|
||||
# Ignore redis dumps from sidekiq
|
||||
dump.rdb
|
||||
|
||||
/config/credentials/development.key
|
||||
|
||||
13
Gemfile
13
Gemfile
@@ -21,15 +21,28 @@ gem 'jbuilder', '~> 2.7'
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem 'bootsnap', '>= 1.4.2', require: false
|
||||
|
||||
# Configuration
|
||||
gem 'dotenv-rails'
|
||||
|
||||
# Security
|
||||
gem 'lockbox'
|
||||
|
||||
# Authentication
|
||||
gem 'warden'
|
||||
gem 'devise'
|
||||
gem 'devise_ldap_authenticatable'
|
||||
gem 'net-ldap'
|
||||
|
||||
# Utilities
|
||||
gem "rqrcode", "~> 2.0"
|
||||
|
||||
# HTTP requests
|
||||
gem 'faraday'
|
||||
|
||||
# Background/scheduled jobs
|
||||
gem 'sidekiq'
|
||||
gem 'sidekiq-scheduler'
|
||||
|
||||
group :development, :test do
|
||||
# Use sqlite3 as the database for Active Record
|
||||
gem 'sqlite3', '~> 1.4'
|
||||
|
||||
300
Gemfile.lock
300
Gemfile.lock
@@ -1,83 +1,91 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
actioncable (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
activejob (= 6.0.3.4)
|
||||
activerecord (= 6.0.3.4)
|
||||
activestorage (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
actionmailbox (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
activejob (= 6.0.4.1)
|
||||
activerecord (= 6.0.4.1)
|
||||
activestorage (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
actionview (= 6.0.3.4)
|
||||
activejob (= 6.0.3.4)
|
||||
actionmailer (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
actionview (= 6.0.4.1)
|
||||
activejob (= 6.0.4.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.3.4)
|
||||
actionview (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
actionpack (6.0.4.1)
|
||||
actionview (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
activerecord (= 6.0.3.4)
|
||||
activestorage (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
actiontext (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
activerecord (= 6.0.4.1)
|
||||
activestorage (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
actionview (6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
activejob (6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
activerecord (6.0.3.4)
|
||||
activemodel (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
activestorage (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
activejob (= 6.0.3.4)
|
||||
activerecord (= 6.0.3.4)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (6.0.3.4)
|
||||
activemodel (6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
activerecord (6.0.4.1)
|
||||
activemodel (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
activestorage (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
activejob (= 6.0.4.1)
|
||||
activerecord (= 6.0.4.1)
|
||||
marcel (~> 1.0.0)
|
||||
activesupport (6.0.4.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.7.0)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
bcrypt (3.1.16)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.5.0)
|
||||
bootsnap (1.9.1)
|
||||
msgpack (~> 1.0)
|
||||
builder (3.2.4)
|
||||
byebug (11.1.3)
|
||||
capybara (3.33.0)
|
||||
capybara (3.36.0)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (~> 1.5)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
concurrent-ruby (1.1.7)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
chunky_png (1.4.0)
|
||||
concurrent-ruby (1.1.9)
|
||||
connection_pool (2.2.5)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
database_cleaner (1.8.5)
|
||||
devise (4.7.3)
|
||||
database_cleaner (2.0.1)
|
||||
database_cleaner-active_record (~> 2.0.0)
|
||||
database_cleaner-active_record (2.0.1)
|
||||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
devise (4.8.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
@@ -87,117 +95,149 @@ GEM
|
||||
devise (>= 3.4.1)
|
||||
net-ldap (>= 0.16.0)
|
||||
diff-lcs (1.4.4)
|
||||
dotenv (2.7.2)
|
||||
dotenv-rails (2.7.2)
|
||||
dotenv (= 2.7.2)
|
||||
railties (>= 3.2, < 6.1)
|
||||
erubi (1.9.0)
|
||||
factory_bot (6.1.0)
|
||||
dotenv (2.7.6)
|
||||
dotenv-rails (2.7.6)
|
||||
dotenv (= 2.7.6)
|
||||
railties (>= 3.2)
|
||||
e2mmap (0.1.0)
|
||||
erubi (1.10.0)
|
||||
et-orbi (1.2.6)
|
||||
tzinfo
|
||||
factory_bot (6.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.1.0)
|
||||
factory_bot (~> 6.1.0)
|
||||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
faraday (0.17.0)
|
||||
faraday (1.8.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0.1)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.1)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.13.1)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
hashdiff (0.4.0)
|
||||
i18n (1.8.5)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
ffi (1.15.4)
|
||||
fugit (1.5.2)
|
||||
et-orbi (~> 1.1, >= 1.1.8)
|
||||
raabro (~> 1.4)
|
||||
globalid (0.5.2)
|
||||
activesupport (>= 5.0)
|
||||
hashdiff (1.0.1)
|
||||
i18n (1.8.11)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jbuilder (2.10.1)
|
||||
jbuilder (2.11.3)
|
||||
activesupport (>= 5.0.0)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
launchy (2.5.0)
|
||||
addressable (~> 2.7)
|
||||
letter_opener (1.7.0)
|
||||
launchy (~> 2.2)
|
||||
letter_opener_web (1.3.4)
|
||||
letter_opener_web (1.4.1)
|
||||
actionmailer (>= 3.2)
|
||||
letter_opener (~> 1.0)
|
||||
railties (>= 3.2)
|
||||
listen (3.2.1)
|
||||
listen (3.7.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
loofah (2.7.0)
|
||||
lockbox (0.6.6)
|
||||
loofah (2.12.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
marcel (1.0.2)
|
||||
matrix (0.4.2)
|
||||
method_source (1.0.0)
|
||||
mimemagic (0.3.5)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.14.2)
|
||||
msgpack (1.3.3)
|
||||
mini_mime (1.1.2)
|
||||
minitest (5.14.4)
|
||||
msgpack (1.4.2)
|
||||
multipart-post (2.1.1)
|
||||
net-ldap (0.16.3)
|
||||
nio4r (2.5.4)
|
||||
nokogiri (1.10.10)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
net-ldap (0.17.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.12.5-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
orm_adapter (0.5.0)
|
||||
pg (1.2.3)
|
||||
public_suffix (4.0.6)
|
||||
puma (4.3.6)
|
||||
puma (4.3.10)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.6.0)
|
||||
rack (2.2.3)
|
||||
rack-proxy (0.6.5)
|
||||
rack-proxy (0.7.0)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.3.4)
|
||||
actioncable (= 6.0.3.4)
|
||||
actionmailbox (= 6.0.3.4)
|
||||
actionmailer (= 6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
actiontext (= 6.0.3.4)
|
||||
actionview (= 6.0.3.4)
|
||||
activejob (= 6.0.3.4)
|
||||
activemodel (= 6.0.3.4)
|
||||
activerecord (= 6.0.3.4)
|
||||
activestorage (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
rails (6.0.4.1)
|
||||
actioncable (= 6.0.4.1)
|
||||
actionmailbox (= 6.0.4.1)
|
||||
actionmailer (= 6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
actiontext (= 6.0.4.1)
|
||||
actionview (= 6.0.4.1)
|
||||
activejob (= 6.0.4.1)
|
||||
activemodel (= 6.0.4.1)
|
||||
activerecord (= 6.0.4.1)
|
||||
activestorage (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.3.4)
|
||||
railties (= 6.0.4.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
rails-html-sanitizer (1.4.2)
|
||||
loofah (~> 2.3)
|
||||
railties (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
railties (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
rake (13.0.1)
|
||||
rb-fsevent (0.10.4)
|
||||
rake (13.0.6)
|
||||
rb-fsevent (0.11.0)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
regexp_parser (1.8.2)
|
||||
redis (4.5.1)
|
||||
regexp_parser (2.1.1)
|
||||
responders (3.0.1)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rspec-core (3.10.0)
|
||||
rexml (3.2.5)
|
||||
rqrcode (2.1.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec-core (3.10.1)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-expectations (3.10.0)
|
||||
rspec-expectations (3.10.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-mocks (3.10.0)
|
||||
rspec-mocks (3.10.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-rails (4.0.1)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-core (~> 3.9)
|
||||
rspec-expectations (~> 3.9)
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-support (3.10.0)
|
||||
safe_yaml (1.0.5)
|
||||
rspec-rails (5.0.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rspec-core (~> 3.10)
|
||||
rspec-expectations (~> 3.10)
|
||||
rspec-mocks (~> 3.10)
|
||||
rspec-support (~> 3.10)
|
||||
rspec-support (3.10.3)
|
||||
ruby2_keywords (0.0.5)
|
||||
rufus-scheduler (3.8.0)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
sass-rails (6.0.0)
|
||||
sassc-rails (~> 2.1, >= 2.1.1)
|
||||
sassc (2.4.0)
|
||||
@@ -208,6 +248,17 @@ GEM
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
sidekiq (6.3.1)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.2.0)
|
||||
sidekiq-scheduler (3.1.0)
|
||||
e2mmap
|
||||
redis (>= 3, < 5)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 3)
|
||||
thwait
|
||||
tilt (>= 1.4.0)
|
||||
spring (2.1.1)
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
@@ -215,43 +266,46 @@ GEM
|
||||
sprockets (4.0.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.2)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets-rails (3.4.0)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.4.2)
|
||||
thor (1.0.1)
|
||||
thor (1.1.0)
|
||||
thread_safe (0.3.6)
|
||||
thwait (0.2.0)
|
||||
e2mmap
|
||||
tilt (2.0.10)
|
||||
turbolinks (5.2.1)
|
||||
turbolinks-source (~> 5.2)
|
||||
turbolinks-source (5.2.0)
|
||||
tzinfo (1.2.7)
|
||||
tzinfo (1.2.9)
|
||||
thread_safe (~> 0.1)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.1.0)
|
||||
web-console (4.2.0)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webmock (3.6.0)
|
||||
addressable (>= 2.3.6)
|
||||
webmock (3.14.0)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webpacker (4.3.0)
|
||||
activesupport (>= 4.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 4.2)
|
||||
websocket-driver (0.7.3)
|
||||
websocket-driver (0.7.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.4.1)
|
||||
zeitwerk (2.5.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
bootsnap (>= 1.4.2)
|
||||
@@ -267,12 +321,16 @@ DEPENDENCIES
|
||||
letter_opener
|
||||
letter_opener_web
|
||||
listen (~> 3.2)
|
||||
lockbox
|
||||
net-ldap
|
||||
pg (~> 1.2.3)
|
||||
puma (~> 4.1)
|
||||
rails (~> 6.0.3, >= 6.0.3.4)
|
||||
rqrcode (~> 2.0)
|
||||
rspec-rails
|
||||
sass-rails (>= 6)
|
||||
sidekiq
|
||||
sidekiq-scheduler
|
||||
spring
|
||||
spring-watcher-listen (~> 2.0.0)
|
||||
sqlite3 (~> 1.4)
|
||||
@@ -284,4 +342,4 @@ DEPENDENCIES
|
||||
webpacker (~> 4.0)
|
||||
|
||||
BUNDLED WITH
|
||||
2.0.2
|
||||
2.2.2
|
||||
|
||||
24
README.md
24
README.md
@@ -11,8 +11,10 @@ credentials, invites, donations, etc..
|
||||
* [x] Log in with admin permissions
|
||||
* [x] View LDAP users as admin
|
||||
* [x] Sign up for a new account via invitation
|
||||
* [ ] List my donations
|
||||
* [ ] Invite new users from your account
|
||||
* [x] List my donations
|
||||
* [x] Invite new users from your account
|
||||
* [x] Create new Lightning (lndhub) wallet for new users
|
||||
* [x] See Lightning wallet balance and setup code for Blue Wallet
|
||||
* [ ] Sign up for a new account by donating upfront
|
||||
* [ ] Sign up for a new account via proving contributions (via cryptographic signature)
|
||||
* [ ] ...
|
||||
@@ -38,6 +40,10 @@ Running the dev server:
|
||||
|
||||
bundle exec rails server
|
||||
|
||||
Running the background workers (requires Redis):
|
||||
|
||||
bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
Running all specs:
|
||||
|
||||
bundle exec rspec
|
||||
@@ -52,6 +58,15 @@ manual LDIF imports etc. (or provide a staging instance)
|
||||
* [Ruby on Rails](https://guides.rubyonrails.org/)
|
||||
* [Sass](https://sass-lang.com/documentation)
|
||||
|
||||
### Front-end
|
||||
|
||||
* [Tailwind CSS](https://tailwindcss.com/)
|
||||
|
||||
**Caveat:** if you only add Tailwind classes/directives to templates or
|
||||
helpers, but there's no change in the stylesheet files, then the new directives
|
||||
won't be compiled in production. In this case, count up the version comment at
|
||||
the top of `app/javascript/stylesheets/application.scss` to trigger compilation.
|
||||
|
||||
### Testing
|
||||
|
||||
* [RSpec](https://rspec.info/documentation/)
|
||||
@@ -62,6 +77,11 @@ manual LDIF imports etc. (or provide a staging instance)
|
||||
* [devise_ldap_authenticatable](https://github.com/cschiewek/devise_ldap_authenticatable)
|
||||
* [net/ldap](https://www.rubydoc.info/gems/net-ldap/Net/LDAP)
|
||||
|
||||
### Asynchronous jobs/workers
|
||||
|
||||
* [Sidekiq](https://github.com/mperham/sidekiq/wiki/)
|
||||
* [ActiveJob](https://github.com/mperham/sidekiq/wiki/Active-Job)
|
||||
|
||||
## License
|
||||
|
||||
[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
html, body, h1, h2, h3, h4, h5, h6, p, pre, a, dl, dt, dd, ol, ul, li {
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
overflow: visible;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
13
app/assets/stylesheets/_variables.scss
Normal file
13
app/assets/stylesheets/_variables.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
$content-width: 800px;
|
||||
$content-max-width: 100%;
|
||||
|
||||
$text-color-body: #222;
|
||||
$text-color-discreet: #888;
|
||||
|
||||
$background-color-notice: #efffc4;
|
||||
$background-color-alert: #fff4c2;
|
||||
|
||||
$color-blue: #0d4f99;
|
||||
$color-purple: #8955a0;
|
||||
$color-red-bright: #c00;
|
||||
$color-red-dark: #990c0e;
|
||||
25
app/assets/stylesheets/admin.scss
Normal file
25
app/assets/stylesheets/admin.scss
Normal file
@@ -0,0 +1,25 @@
|
||||
@import "variables";
|
||||
|
||||
body#admin-panel {
|
||||
#wrapper {
|
||||
> header {
|
||||
background: $color-red-bright;
|
||||
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%),
|
||||
url('/img/bg-1.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
#main-nav {
|
||||
ul {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
||||
li {
|
||||
a {
|
||||
&.active {
|
||||
border-bottom: 2px solid $color-red-bright;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/assets/stylesheets/donations.scss
Normal file
40
app/assets/stylesheets/donations.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
ul.donations {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin-bottom: 2rem;
|
||||
display: grid;
|
||||
grid-row-gap: 0.5rem;
|
||||
grid-column-gap: 2rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-areas:
|
||||
"date amount-btc"
|
||||
"public-name amounts-fiat";
|
||||
|
||||
h3 {
|
||||
grid-area: "date";
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
|
||||
&.amount-btc {
|
||||
grid-area: amount-btc;
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
&.amounts-fiat {
|
||||
grid-area: amounts-fiat;
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
}
|
||||
&.public-name {
|
||||
grid-area: public-name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
src: url('/fonts/raleway-light.woff') format('woff2');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Open Sans", Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family: Raleway, inherit;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
form {
|
||||
.field_with_errors {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-signup {
|
||||
label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type=text], input[type=email], input[type=password] {
|
||||
font-size: 1.25rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
span.at-sign, span.domain {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
color: #bc0101;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.accept-terms {
|
||||
margin-top: 2rem;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5em;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
@import "variables";
|
||||
@import "mediaqueries";
|
||||
|
||||
$content-width: 800px;
|
||||
$content-max-width: 100%;
|
||||
|
||||
body {
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
@@ -14,50 +9,39 @@ body {
|
||||
margin: 0 auto;
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
background: #0d4f99;
|
||||
background: linear-gradient(35deg, #8955a0 0, #0d4f99 100%);
|
||||
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%),
|
||||
url('/img/bg-1.jpg');
|
||||
background-size: cover;
|
||||
|
||||
@include media-max(small) {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
color: #fff;
|
||||
|
||||
span.project-name {
|
||||
display: none;
|
||||
// font-size: .5em;
|
||||
// text-transform: none;
|
||||
// vertical-align: super;
|
||||
}
|
||||
|
||||
span.beta {
|
||||
font-size: .5em;
|
||||
font-style: italic;
|
||||
text-transform: none;
|
||||
vertical-align: super;
|
||||
}
|
||||
span.icon {
|
||||
svg {
|
||||
display: inline-block;
|
||||
height: 1.875rem;
|
||||
vertical-align: top;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
span.bolt {
|
||||
color: #ffd000;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
p.current-user {
|
||||
margin-top: 2rem;
|
||||
color: rgba(255,255,255,0.6);
|
||||
|
||||
@include media-max(small) {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
// color: #ffd000;
|
||||
// color: #ccff40;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -77,18 +61,18 @@ body {
|
||||
padding: 2rem 0;
|
||||
|
||||
&.notice {
|
||||
background: #efffc4;
|
||||
background: $background-color-notice;
|
||||
}
|
||||
|
||||
&.alert {
|
||||
background: #fff4c2;
|
||||
background: $background-color-alert;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
width: $content-width;
|
||||
max-width: $content-max-width;
|
||||
margin: 4rem auto;
|
||||
margin: 4rem auto 6rem auto;
|
||||
text-align: left;
|
||||
|
||||
@include media-max(medium) {
|
||||
@@ -99,18 +83,6 @@ main {
|
||||
margin: 3rem auto;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
@@ -128,19 +100,36 @@ main {
|
||||
}
|
||||
}
|
||||
|
||||
th, td {
|
||||
line-height: 1.5rem;
|
||||
padding-right: 1rem;
|
||||
section {
|
||||
margin-bottom: 3rem;
|
||||
|
||||
h2 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
border-bottom: 1px dotted #ccc;
|
||||
padding-bottom: 4rem;
|
||||
margin-bottom: 4rem;
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
@include media-max(small) {
|
||||
padding-bottom: 3rem;
|
||||
margin-bottom: 3rem;
|
||||
th, td {
|
||||
&.hide-small {
|
||||
@include media-max(small) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
color: $text-color-discreet;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85rem;
|
||||
padding-bottom: 0.825rem;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,18 +145,5 @@ main {
|
||||
@include media-max(small) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
margin-top: 3rem;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
p {
|
||||
color: #888;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
54
app/assets/stylesheets/main_nav.scss
Normal file
54
app/assets/stylesheets/main_nav.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
@import "variables";
|
||||
@import "mediaqueries";
|
||||
|
||||
#main-nav {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background-color: #efefef;
|
||||
|
||||
.wrapper {
|
||||
width: $content-width;
|
||||
max-width: $content-max-width;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include media-max(large) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
li {
|
||||
@include media-min(large) {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@include media-max(large) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
padding: 1.5rem 2rem;
|
||||
text-decoration: none;
|
||||
color: $text-color-discreet;
|
||||
|
||||
@include media-max(large) {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@include media-max(small) {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $text-color-body;
|
||||
border-bottom: 2px solid #4ea2df;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,6 @@ class Admin::BaseController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :authorize_admin
|
||||
|
||||
layout "admin"
|
||||
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
class Admin::DashboardController < Admin::BaseController
|
||||
def index
|
||||
@current_section = :dashboard
|
||||
end
|
||||
end
|
||||
|
||||
79
app/controllers/admin/donations_controller.rb
Normal file
79
app/controllers/admin/donations_controller.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
class Admin::DonationsController < Admin::BaseController
|
||||
before_action :set_donation, only: [:show, :edit, :update, :destroy]
|
||||
before_action :set_current_section, only: [:index, :show, :new, :edit]
|
||||
|
||||
# GET /donations
|
||||
# GET /donations.json
|
||||
def index
|
||||
@donations = Donation.all
|
||||
end
|
||||
|
||||
# GET /donations/1
|
||||
# GET /donations/1.json
|
||||
def show
|
||||
end
|
||||
|
||||
# GET /donations/new
|
||||
def new
|
||||
@donation = Donation.new
|
||||
end
|
||||
|
||||
# GET /donations/1/edit
|
||||
def edit
|
||||
end
|
||||
|
||||
# POST /donations
|
||||
# POST /donations.json
|
||||
def create
|
||||
@donation = Donation.new(donation_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @donation.save
|
||||
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully created.' }
|
||||
format.json { render :show, status: :created, location: @donation }
|
||||
else
|
||||
format.html { render :new }
|
||||
format.json { render json: @donation.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /donations/1
|
||||
# PATCH/PUT /donations/1.json
|
||||
def update
|
||||
respond_to do |format|
|
||||
if @donation.update(donation_params)
|
||||
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully updated.' }
|
||||
format.json { render :show, status: :ok, location: @donation }
|
||||
else
|
||||
format.html { render :edit }
|
||||
format.json { render json: @donation.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /donations/1
|
||||
# DELETE /donations/1.json
|
||||
def destroy
|
||||
@donation.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to admin_donations_url, notice: 'Donation was successfully destroyed.' }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_donation
|
||||
@donation = Donation.find(params[:id])
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def donation_params
|
||||
params.require(:donation).permit(:user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :paid_at)
|
||||
end
|
||||
|
||||
def set_current_section
|
||||
@current_section = :donations
|
||||
end
|
||||
end
|
||||
8
app/controllers/admin/invitations_controller.rb
Normal file
8
app/controllers/admin/invitations_controller.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class Admin::InvitationsController < Admin::BaseController
|
||||
def index
|
||||
@current_section = :invitations
|
||||
@invitations_unused_count = Invitation.unused.count
|
||||
@users_with_referrals_count = Invitation.used.distinct.count(:user_id)
|
||||
@invitations_used = Invitation.used.order('used_at desc')
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,6 @@
|
||||
class Admin::LdapUsersController < Admin::BaseController
|
||||
before_action :set_current_section
|
||||
|
||||
def index
|
||||
attributes = %w{dn cn uid mail admin}
|
||||
filter = Net::LDAP::Filter.eq("uid", "*")
|
||||
@@ -38,4 +40,8 @@ class Admin::LdapUsersController < Admin::BaseController
|
||||
def ldap_config
|
||||
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
||||
end
|
||||
|
||||
def set_current_section
|
||||
@current_section = :ldap_users
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,5 +2,6 @@ class DashboardController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
|
||||
def index
|
||||
@current_section = :dashboard
|
||||
end
|
||||
end
|
||||
|
||||
10
app/controllers/donations_controller.rb
Normal file
10
app/controllers/donations_controller.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class DonationsController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
|
||||
# GET /donations
|
||||
# GET /donations.json
|
||||
def index
|
||||
@donations = current_user.donations.completed
|
||||
@current_section = :contributions
|
||||
end
|
||||
end
|
||||
@@ -8,6 +8,7 @@ class InvitationsController < ApplicationController
|
||||
def index
|
||||
@invitations_unused = current_user.invitations.unused
|
||||
@invitations_used = current_user.invitations.used
|
||||
@current_section = :invitations
|
||||
end
|
||||
|
||||
# GET /invitations/a-random-invitation-token
|
||||
|
||||
57
app/controllers/lnurlpay_controller.rb
Normal file
57
app/controllers/lnurlpay_controller.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
class LnurlpayController < ApplicationController
|
||||
before_action :find_user_by_address
|
||||
|
||||
def index
|
||||
render json: {
|
||||
status: "OK",
|
||||
callback: "https://accounts.kosmos.org/lnurlpay/#{@user.address}/invoice",
|
||||
tag: "payRequest",
|
||||
maxSendable: 1000000,
|
||||
minSendable: 1000,
|
||||
metadata: metadata(@user.address),
|
||||
commentAllowed: 0
|
||||
}
|
||||
end
|
||||
|
||||
def invoice
|
||||
amount = params[:amount].to_i # msats
|
||||
address = params[:address]
|
||||
|
||||
validate_amount(amount)
|
||||
|
||||
payment_request = @user.ln_create_invoice({
|
||||
amount: amount / 1000, # we create invoices in sats
|
||||
description_hash: Digest::SHA2.hexdigest(metadata(address))
|
||||
})
|
||||
|
||||
render json: {
|
||||
status: "OK",
|
||||
successAction: {
|
||||
tag: "message",
|
||||
message: "Payment received. Thanks!"
|
||||
},
|
||||
routes: [],
|
||||
pr: payment_request
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user_by_address
|
||||
address = params[:address].split("@")
|
||||
@user = User.where(cn: address.first, ou: address.last).first
|
||||
http_status :not_found if @user.nil?
|
||||
end
|
||||
|
||||
def metadata(address)
|
||||
"[[\"text/identifier\", \"#{address}\"], [\"text/plain\", \"Sats for #{address}\"]]"
|
||||
end
|
||||
|
||||
def validate_amount(amount)
|
||||
if amount > 1000000 || amount < 1000
|
||||
render json: { status: "ERROR", reason: "Invalid amount" }
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
7
app/controllers/security_controller.rb
Normal file
7
app/controllers/security_controller.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class SecurityController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
|
||||
def index
|
||||
@current_section = :security
|
||||
end
|
||||
end
|
||||
44
app/controllers/wallet_controller.rb
Normal file
44
app/controllers/wallet_controller.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
require "rqrcode"
|
||||
|
||||
class WalletController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
before_action :authenticate_with_lndhub
|
||||
|
||||
def index
|
||||
@current_section = :wallet
|
||||
|
||||
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
||||
|
||||
qrcode = RQRCode::QRCode.new(@wallet_url)
|
||||
@svg = qrcode.as_svg(
|
||||
color: "000",
|
||||
shape_rendering: "crispEdges",
|
||||
module_size: 6,
|
||||
standalone: true,
|
||||
use_path: true
|
||||
)
|
||||
|
||||
@balance = fetch_balance rescue nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate_with_lndhub
|
||||
if session["ln_auth_token"].present?
|
||||
@ln_auth_token = session["ln_auth_token"]
|
||||
else
|
||||
lndhub = Lndhub.new
|
||||
auth_token = lndhub.authenticate(current_user)
|
||||
session["ln_auth_token"] = auth_token
|
||||
@ln_auth_token = auth_token
|
||||
end
|
||||
rescue
|
||||
# TODO add exception tracking
|
||||
end
|
||||
|
||||
def fetch_balance
|
||||
lndhub = Lndhub.new
|
||||
data = lndhub.balance @ln_auth_token
|
||||
data["BTC"]["AvailableBalance"]
|
||||
end
|
||||
end
|
||||
@@ -1,2 +1,5 @@
|
||||
module ApplicationHelper
|
||||
def sats_to_btc(sats)
|
||||
sats.to_f / 100000000
|
||||
end
|
||||
end
|
||||
|
||||
2
app/helpers/donations_helper.rb
Normal file
2
app/helpers/donations_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module DonationsHelper
|
||||
end
|
||||
2
app/helpers/lnurlpay_helper.rb
Normal file
2
app/helpers/lnurlpay_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module LnurlpayHelper
|
||||
end
|
||||
2
app/helpers/wallet_helper.rb
Normal file
2
app/helpers/wallet_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module WalletHelper
|
||||
end
|
||||
@@ -7,7 +7,7 @@ require("@rails/ujs").start()
|
||||
require("turbolinks").start()
|
||||
require("channels")
|
||||
|
||||
|
||||
import "stylesheets/application"
|
||||
// Uncomment to copy all static images under ../images to the output folder and reference
|
||||
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
|
||||
// or the `imagePath` JavaScript helper below.
|
||||
|
||||
9
app/javascript/stylesheets/application.scss
Normal file
9
app/javascript/stylesheets/application.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
// version 3
|
||||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
@import "base";
|
||||
@import "buttons";
|
||||
@import "forms";
|
||||
@import "links";
|
||||
21
app/javascript/stylesheets/base.scss
Normal file
21
app/javascript/stylesheets/base.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
@layer base {
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
@apply font-light;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-3xl uppercase;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-2xl mb-8;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-xl mb-6;
|
||||
}
|
||||
}
|
||||
31
app/javascript/stylesheets/buttons.scss
Normal file
31
app/javascript/stylesheets/buttons.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply font-semibold rounded-md leading-none cursor-pointer
|
||||
transition-colors duration-75 focus:outline-none focus:ring-4;
|
||||
}
|
||||
|
||||
.btn-md {
|
||||
@apply btn;
|
||||
@apply py-2.5 px-5 shadow-md;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@apply btn;
|
||||
@apply py-1 px-2 text-sm shadow-sm;
|
||||
}
|
||||
|
||||
.btn-gray {
|
||||
@apply bg-gray-100 hover:bg-gray-200
|
||||
focus:ring-gray-300 focus:ring-opacity-75;
|
||||
}
|
||||
|
||||
.btn-blue {
|
||||
@apply bg-blue-500 hover:bg-blue-600 text-white
|
||||
focus:ring-blue-400 focus:ring-opacity-75;
|
||||
}
|
||||
|
||||
.btn-red {
|
||||
@apply bg-red-600 hover:bg-red-700 text-white
|
||||
focus:ring-red-500 focus:ring-opacity-75;
|
||||
}
|
||||
}
|
||||
17
app/javascript/stylesheets/forms.scss
Normal file
17
app/javascript/stylesheets/forms.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
@layer components {
|
||||
form {
|
||||
input[type=text], input[type=email], input[type=password],
|
||||
input[type=number], select {
|
||||
@apply mt-1 rounded-md bg-gray-100 focus:bg-white
|
||||
border-transparent focus:border-gray-500 focus:ring-0;
|
||||
}
|
||||
|
||||
.field_with_errors {
|
||||
@apply inline-block;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
@apply text-red-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
app/javascript/stylesheets/links.scss
Normal file
14
app/javascript/stylesheets/links.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
@layer components {
|
||||
.ks-text-link {
|
||||
@apply text-blue-600;
|
||||
&:hover { @apply underline; }
|
||||
&:visited { @apply text-indigo-600; }
|
||||
&:active { @apply text-red-600; }
|
||||
}
|
||||
|
||||
.devise-links {
|
||||
a {
|
||||
@apply ks-text-link;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
app/javascript/stylesheets/tailwind.config.js
Normal file
26
app/javascript/stylesheets/tailwind.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const defaultTheme = require('tailwindcss/defaultTheme')
|
||||
|
||||
module.exports = {
|
||||
purge: {
|
||||
layers: ['base', 'components', 'utilities'],
|
||||
content: [
|
||||
"./app/**/*.html.erb",
|
||||
"./app/helpers/**/*.rb",
|
||||
"./app/javascript/**/*.js"
|
||||
]
|
||||
},
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Open Sans', 'sans-serif']
|
||||
}
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms')
|
||||
],
|
||||
}
|
||||
32
app/jobs/create_ldap_user_job.rb
Normal file
32
app/jobs/create_ldap_user_job.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
class CreateLdapUserJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(username, domain, email, hashed_pw)
|
||||
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
|
||||
attr = {
|
||||
objectclass: ["top", "account", "person", "extensibleObject"],
|
||||
cn: username,
|
||||
sn: username,
|
||||
uid: username,
|
||||
mail: email,
|
||||
userPassword: hashed_pw
|
||||
}
|
||||
|
||||
ldap_client.add(dn: dn, attributes: attr)
|
||||
end
|
||||
|
||||
def ldap_client
|
||||
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
||||
port: ldap_config['port'],
|
||||
encryption: ldap_config['ssl'],
|
||||
auth: {
|
||||
method: :simple,
|
||||
username: ldap_config['admin_user'],
|
||||
password: ldap_config['admin_password']
|
||||
}
|
||||
end
|
||||
|
||||
def ldap_config
|
||||
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
||||
end
|
||||
end
|
||||
13
app/jobs/create_lndhub_wallet_job.rb
Normal file
13
app/jobs/create_lndhub_wallet_job.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class CreateLndhubWalletJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user)
|
||||
return if user.ln_login.present? && user.ln_password.present?
|
||||
|
||||
lndhub = Lndhub.new
|
||||
credentials = lndhub.create({ partnerid: user.ou, accounttype: "user" })
|
||||
|
||||
user.update! ln_login: credentials["login"],
|
||||
ln_password: credentials["password"]
|
||||
end
|
||||
end
|
||||
18
app/jobs/exchange_xmpp_contacts_job.rb
Normal file
18
app/jobs/exchange_xmpp_contacts_job.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
class ExchangeXmppContactsJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(inviter, username, domain)
|
||||
ejabberd = EjabberdApiClient.new
|
||||
|
||||
ejabberd.add_rosteritem({
|
||||
"localuser": username, "localhost": domain,
|
||||
"user": inviter.cn, "host": inviter.ou,
|
||||
"nick": inviter.cn, "group": "Friends", "subs": "both"
|
||||
})
|
||||
ejabberd.add_rosteritem({
|
||||
"localuser": inviter.cn, "localhost": inviter.ou,
|
||||
"user": username, "host": domain,
|
||||
"nick": username, "group": "Friends", "subs": "both"
|
||||
})
|
||||
end
|
||||
end
|
||||
13
app/models/donation.rb
Normal file
13
app/models/donation.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class Donation < ApplicationRecord
|
||||
# Relations
|
||||
belongs_to :user
|
||||
|
||||
# Validations
|
||||
validates_presence_of :amount_sats
|
||||
|
||||
# Hooks
|
||||
# TODO before_create :store_fiat_value
|
||||
|
||||
#Scopes
|
||||
scope :completed, -> { where.not(paid_at: nil) }
|
||||
end
|
||||
@@ -3,12 +3,16 @@ class User < ApplicationRecord
|
||||
|
||||
# Relations
|
||||
has_many :invitations, dependent: :destroy
|
||||
has_many :donations, dependent: :nullify
|
||||
|
||||
validates_uniqueness_of :cn
|
||||
validates_length_of :cn, :minimum => 3
|
||||
validates_uniqueness_of :email
|
||||
validates :email, email: true
|
||||
|
||||
encrypts :ln_login
|
||||
encrypts :ln_password
|
||||
|
||||
# Include default devise modules. Others available are:
|
||||
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||
devise :ldap_authenticatable,
|
||||
@@ -52,4 +56,10 @@ class User < ApplicationRecord
|
||||
self.valid?
|
||||
self.errors[attribute_name].blank?
|
||||
end
|
||||
|
||||
def ln_create_invoice(payload)
|
||||
lndhub = Lndhub.new
|
||||
lndhub.authenticate self
|
||||
lndhub.addinvoice payload
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ class CreateAccount < ApplicationService
|
||||
def call
|
||||
user = create_user_in_database
|
||||
add_ldap_document
|
||||
create_lndhub_wallet
|
||||
|
||||
if @invitation.present?
|
||||
update_invitation(user.id)
|
||||
@@ -33,51 +34,19 @@ class CreateAccount < ApplicationService
|
||||
@invitation.update! invited_user_id: user_id, used_at: DateTime.now
|
||||
end
|
||||
|
||||
# TODO move to confirmation
|
||||
def add_ldap_document
|
||||
dn = "cn=#{@username},ou=kosmos.org,cn=users,dc=kosmos,dc=org"
|
||||
attr = {
|
||||
objectclass: ["top", "account", "person", "extensibleObject"],
|
||||
cn: @username,
|
||||
sn: @username,
|
||||
uid: @username,
|
||||
mail: @email,
|
||||
userPassword: Devise.ldap_auth_password_builder.call(@password)
|
||||
}
|
||||
|
||||
ldap_client.add(dn: dn, attributes: attr)
|
||||
end
|
||||
|
||||
def ldap_client
|
||||
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
||||
port: ldap_config['port'],
|
||||
encryption: ldap_config['ssl'],
|
||||
auth: {
|
||||
method: :simple,
|
||||
username: ldap_config['admin_user'],
|
||||
password: ldap_config['admin_password']
|
||||
}
|
||||
end
|
||||
|
||||
def ldap_config
|
||||
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
||||
hashed_pw = Devise.ldap_auth_password_builder.call(@password)
|
||||
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
|
||||
end
|
||||
|
||||
def exchange_xmpp_contacts
|
||||
#TODO enable in development when we have easy setup of ejabberd etc.
|
||||
return if Rails.env.development?
|
||||
ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain)
|
||||
end
|
||||
|
||||
ejabberd = EjabberdApiClient.new
|
||||
inviter = @invitation.user
|
||||
|
||||
ejabberd.add_rosteritem({
|
||||
"localuser": @username, "localhost": @domain,
|
||||
"user": inviter.cn, "host": inviter.ou,
|
||||
"nick": inviter.cn, "group": "Friends", "subs": "both"
|
||||
})
|
||||
ejabberd.add_rosteritem({
|
||||
"localuser": inviter.cn, "localhost": inviter.ou,
|
||||
"user": @username, "host": @domain,
|
||||
"nick": @username, "group": "Friends", "subs": "both"
|
||||
})
|
||||
def create_lndhub_wallet
|
||||
CreateLndhubWalletJob.perform_later(user)
|
||||
end
|
||||
end
|
||||
|
||||
57
app/services/lndhub.rb
Normal file
57
app/services/lndhub.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
class Lndhub
|
||||
attr_accessor :auth_token
|
||||
|
||||
def initialize
|
||||
@base_url = ENV["LNDHUB_API_URL"]
|
||||
end
|
||||
|
||||
def post(endpoint, payload)
|
||||
headers = { "Content-Type" => "application/json" }
|
||||
if auth_token
|
||||
headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
|
||||
end
|
||||
|
||||
res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
|
||||
|
||||
if res.status != 200
|
||||
Rails.logger.error "[lndhub] API request failed:"
|
||||
Rails.logger.error res.body
|
||||
#TODO add some kind of exception tracking/notifications
|
||||
end
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def get(endpoint, auth_token)
|
||||
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer #{auth_token}"
|
||||
})
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def create(payload)
|
||||
post "create", payload
|
||||
end
|
||||
|
||||
def authenticate(user)
|
||||
credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
|
||||
self.auth_token = credentials["access_token"]
|
||||
self.auth_token
|
||||
end
|
||||
|
||||
def balance(user_token)
|
||||
get "balance", user_token || auth_token
|
||||
end
|
||||
|
||||
def addinvoice(payload)
|
||||
invoice = post "addinvoice", {
|
||||
amt: payload[:amount],
|
||||
description_hash: payload[:description_hash]
|
||||
}
|
||||
|
||||
invoice["payment_request"]
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,3 @@
|
||||
<h2>Admin Panel</h2>
|
||||
<p>
|
||||
Ohai there, admin human.
|
||||
</p>
|
||||
<p>
|
||||
<%= link_to 'LDAP users', admin_ldap_users_path %>
|
||||
<p class="text-center">
|
||||
With great power comes great responsibility.
|
||||
</p>
|
||||
|
||||
2
app/views/admin/donations/_donation.json.jbuilder
Normal file
2
app/views/admin/donations/_donation.json.jbuilder
Normal file
@@ -0,0 +1,2 @@
|
||||
json.extract! donation, :id, :user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :created_at, :updated_at
|
||||
json.url donation_url(donation, format: :json)
|
||||
58
app/views/admin/donations/_form.html.erb
Normal file
58
app/views/admin/donations/_form.html.erb
Normal file
@@ -0,0 +1,58 @@
|
||||
<%= form_with(url: url, model: donation, local: true) do |form| %>
|
||||
<% if donation.errors.any? %>
|
||||
<div id="error_explanation">
|
||||
<h3><%= pluralize(donation.errors.count, "error") %> prohibited this donation from being saved:</h3>
|
||||
<ul>
|
||||
<% donation.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= form.label :user_id %>
|
||||
<%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= form.label :amount_sats, "Amount BTC (sats)" %>
|
||||
<%= form.number_field :amount_sats %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= form.label :amount_eur, "Amount EUR (cents)" %>
|
||||
<%= form.number_field :amount_eur %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= form.label :amount_usd, "Amount USD (cents)"%>
|
||||
<%= form.number_field :amount_usd %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= form.label :public_name %>
|
||||
<%= form.text_field :public_name %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= form.label :paid_at %>
|
||||
<%= form.text_field :paid_at %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="mt-8">
|
||||
<%= form.submit class: 'btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
8
app/views/admin/donations/edit.html.erb
Normal file
8
app/views/admin/donations/edit.html.erb
Normal file
@@ -0,0 +1,8 @@
|
||||
<h2>Editing Donation</h2>
|
||||
|
||||
<%= render 'form', donation: @donation, url: admin_donation_path(@donation) %>
|
||||
|
||||
<p class="mt-8">
|
||||
<%= link_to 'Show', admin_donation_path(@donation), class: 'ks-text-link' %> |
|
||||
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||
<p>
|
||||
41
app/views/admin/donations/index.html.erb
Normal file
41
app/views/admin/donations/index.html.erb
Normal file
@@ -0,0 +1,41 @@
|
||||
<h2>Donations</h2>
|
||||
|
||||
<% if @donations.any? %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Amount BTC</th>
|
||||
<th>in EUR</th>
|
||||
<th>in USD</th>
|
||||
<th>Public name</th>
|
||||
<th>Date</th>
|
||||
<th colspan="3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% @donations.each do |donation| %>
|
||||
<tr>
|
||||
<td><%= donation.user.cn %></td>
|
||||
<td><%= sats_to_btc donation.amount_sats %> BTC</td>
|
||||
<td><%= number_to_currency donation.amount_eur / 100, unit: "" %></td>
|
||||
<td><%= number_to_currency donation.amount_usd / 100, unit: "" %></td>
|
||||
<td><%= donation.public_name %></td>
|
||||
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td>
|
||||
<td><%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||
<td><%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||
<td><%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red', method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p>
|
||||
No donations yet.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<p class="mt-12">
|
||||
<%= link_to 'Record an out-of-system donation', new_admin_donation_path, class: 'btn-md btn-gray' %>
|
||||
</p>
|
||||
1
app/views/admin/donations/index.json.jbuilder
Normal file
1
app/views/admin/donations/index.json.jbuilder
Normal file
@@ -0,0 +1 @@
|
||||
json.array! @donations, partial: "donations/donation", as: :donation
|
||||
7
app/views/admin/donations/new.html.erb
Normal file
7
app/views/admin/donations/new.html.erb
Normal file
@@ -0,0 +1,7 @@
|
||||
<h2>New Donation</h2>
|
||||
|
||||
<%= render 'form', donation: @donation, url: admin_donations_path %>
|
||||
|
||||
<p class="mt-8">
|
||||
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||
</p>
|
||||
36
app/views/admin/donations/show.html.erb
Normal file
36
app/views/admin/donations/show.html.erb
Normal file
@@ -0,0 +1,36 @@
|
||||
<p id="notice"><%= notice %></p>
|
||||
|
||||
<p>
|
||||
<strong>User:</strong>
|
||||
<%= @donation.user_id %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Amount sats:</strong>
|
||||
<%= @donation.amount_sats %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Amount eur:</strong>
|
||||
<%= @donation.amount_eur %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Amount usd:</strong>
|
||||
<%= @donation.amount_usd %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Public name:</strong>
|
||||
<%= @donation.public_name %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Date:</strong>
|
||||
<%= @donation.paid_at %>
|
||||
</p>
|
||||
|
||||
<p class="mt-8">
|
||||
<%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'ks-text-link' %> |
|
||||
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||
</p>
|
||||
1
app/views/admin/donations/show.json.jbuilder
Normal file
1
app/views/admin/donations/show.json.jbuilder
Normal file
@@ -0,0 +1 @@
|
||||
json.partial! "donations/donation", donation: @donation
|
||||
32
app/views/admin/invitations/index.html.erb
Normal file
32
app/views/admin/invitations/index.html.erb
Normal file
@@ -0,0 +1,32 @@
|
||||
<section>
|
||||
<h2>Invitations</h2>
|
||||
<p>
|
||||
There are currently <strong><%= @invitations_unused_count %>
|
||||
unused invitations</strong> available to existing users.
|
||||
<strong><%= @users_with_referrals_count %> users</strong> have successfully
|
||||
invited new users.
|
||||
</p>
|
||||
</section>
|
||||
<% if @invitations_used.any? %>
|
||||
<section>
|
||||
<h3>Accepted (<%= @invitations_used.length %>)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Token</th>
|
||||
<th>Accepted</th>
|
||||
<th>Invited user</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @invitations_used.each do |invitation| %>
|
||||
<tr>
|
||||
<td class="overflow-ellipsis"><%= invitation.token %></td>
|
||||
<td><%= invitation.used_at.strftime("%Y-%m-%d") %></td>
|
||||
<td><%= User.find(invitation.invited_user_id).address %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<% end %>
|
||||
@@ -4,45 +4,55 @@
|
||||
Your Kosmos account and password currently give you access to these
|
||||
services:
|
||||
</p>
|
||||
<div class="grid services">
|
||||
<div class="grid-item chat">
|
||||
<h3><%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat" %></h3>
|
||||
<p>
|
||||
<div class="grid services mt-12">
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Chat rooms and instant messaging (XMPP/Jabber)
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid-item wiki">
|
||||
<h3><%= link_to "Wiki", "https://wiki.kosmos.org" %></h3>
|
||||
<p>
|
||||
Kosmos documentation and knowledge base
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid-item discourse">
|
||||
<h3><%= link_to "Discourse", "https://community.kosmos.org" %></h3>
|
||||
<p>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Discourse", "https://community.kosmos.org", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Kosmos community forums and user support/help site
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid-item gitea">
|
||||
<h3><%= link_to "Gitea", "https://gitea.kosmos.org" %></h3>
|
||||
<p>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<span class="text-yellow-500">🗲</span>
|
||||
<%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Send and receive sats over the Bitcoin Lightning Network
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Wiki", "https://wiki.kosmos.org", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Kosmos documentation and knowledge base
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Gitea", "https://gitea.kosmos.org", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Code hosting and collaboration for software projects
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid-item gitea">
|
||||
<h3><%= link_to "Drone CI", "https://drone.kosmos.org" %></h3>
|
||||
<p>
|
||||
Continuous integration for software projects, tied to our Gitea
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Drone CI", "https://drone.kosmos.org", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Continuous integration for software projects on Gitea
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Password change</h3>
|
||||
<p>
|
||||
<%= form_with(url: settings_reset_password_path, method: :post) do %>
|
||||
<%= submit_tag("Send me a password reset link") %>
|
||||
<% end %>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -2,19 +2,13 @@
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :email, 'Email address' %><br />
|
||||
<%= f.email_field :email, required: true, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<p>
|
||||
<%= f.submit "Resend confirmation instructions" %>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<%= f.label :email, 'Email address', class: 'block mb-1' %>
|
||||
<%= f.email_field :email, required: true, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
|
||||
</p>
|
||||
<p class="mt-8">
|
||||
<%= f.submit "Resend confirmation instructions", class: 'btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
|
||||
@@ -4,22 +4,24 @@
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
<%= f.hidden_field :reset_password_token %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password, "New password" %><br />
|
||||
<% if @minimum_password_length %>
|
||||
<em>(<%= @minimum_password_length %> characters minimum)</em><br />
|
||||
<% end %>
|
||||
<p class="mb-1">
|
||||
<%= f.label :password, "New password" %>
|
||||
</p>
|
||||
<p>
|
||||
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password_confirmation, "Confirm new password" %><br />
|
||||
<% if @minimum_password_length %>
|
||||
<br><em class="text-sm text-gray-500">(<%= @minimum_password_length %> characters minimum)</em>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
<%= f.label :password_confirmation, "Confirm new password" %>
|
||||
</p>
|
||||
<p>
|
||||
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Change my password" %>
|
||||
</div>
|
||||
</p>
|
||||
<p class="mt-8">
|
||||
<%= f.submit "Change my password", class: 'btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
|
||||
@@ -2,26 +2,17 @@
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :cn, 'User' %><br />
|
||||
<%= f.text_field :cn, autofocus: true, autocomplete: "username", required: true %> @ kosmos.org
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :email, 'Email address' %><br />
|
||||
<%= f.email_field :email, autocomplete: "email", required: true %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<p>
|
||||
<%= f.submit "Send me reset password instructions" %>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<%= f.label :cn, 'User', class: 'block' %>
|
||||
<%= f.text_field :cn, autofocus: true, autocomplete: "username", required: true %> @ kosmos.org
|
||||
</p>
|
||||
<p>
|
||||
<%= f.label :email, 'Email address', class: 'block' %>
|
||||
<%= f.email_field :email, autocomplete: "email", required: true %>
|
||||
</p>
|
||||
<p class="mt-8">
|
||||
<%= f.submit "Send me reset password instructions", class: 'btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
<%= f.password_field :current_password, autocomplete: "current-password" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Update" %>
|
||||
</div>
|
||||
<p class="mt-8">
|
||||
<%= f.submit "Update", class: 'btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<h3>Cancel my account</h3>
|
||||
|
||||
@@ -2,24 +2,17 @@
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :cn, 'User' %><br />
|
||||
<%= f.text_field :cn, autofocus: true, autocomplete: "username" %> @ kosmos.org
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :password %><br />
|
||||
<%= f.password_field :password, autocomplete: "current-password" %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<p>
|
||||
<%= f.submit "Log in" %>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<%= f.label :cn, 'User', class: 'block' %>
|
||||
<%= f.text_field :cn, autofocus: true, autocomplete: "username" %> @ kosmos.org
|
||||
</p>
|
||||
<p>
|
||||
<%= f.label :password, class: 'block' %>
|
||||
<%= f.password_field :password, autocomplete: "current-password" %>
|
||||
</p>
|
||||
<p class="mt-8">
|
||||
<%= f.submit "Log in", class: 'btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<div class="devise-links">
|
||||
<div class="devise-links mt-8 text-sm">
|
||||
<%- if controller_name != 'sessions' %>
|
||||
<p>
|
||||
<p class="mb-1.5">
|
||||
<%= link_to "Log in", new_session_path(resource_name) %><br />
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
||||
<p>
|
||||
<p class="mb-1.5">
|
||||
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
||||
<p>
|
||||
<p class="mb-1.5">
|
||||
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
||||
<p>
|
||||
<p class="mb-1.5">
|
||||
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :email %><br />
|
||||
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Resend unlock instructions" %>
|
||||
</div>
|
||||
</p>
|
||||
<p class="mt-8">
|
||||
<%= f.submit "Resend unlock instructions", class: 'btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
|
||||
38
app/views/donations/index.html.erb
Normal file
38
app/views/donations/index.html.erb
Normal file
@@ -0,0 +1,38 @@
|
||||
<section>
|
||||
<h2>Donations</h2>
|
||||
<p>
|
||||
Your financial contributions to the development and
|
||||
upkeep of Kosmos software and services.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<% if @donations.any? %>
|
||||
<ul class="donations">
|
||||
<% @donations.each do |donation| %>
|
||||
<li>
|
||||
<h3>
|
||||
<%= donation.paid_at.strftime("%B %d, %Y") %>
|
||||
</h3>
|
||||
<p class="amount-btc">
|
||||
<%= sats_to_btc donation.amount_sats %> BTC
|
||||
</p>
|
||||
<p class="amounts-fiat">
|
||||
(~ <%= number_to_currency donation.amount_eur / 100, unit: "" %> EUR)
|
||||
</p>
|
||||
<p class="public-name">
|
||||
<% if donation.public_name.present? %>
|
||||
Public name: <%= donation.public_name %>
|
||||
<% else %>
|
||||
Anonymous
|
||||
<% end %>
|
||||
</p>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p>
|
||||
No donations to show.
|
||||
</p>
|
||||
<% end %>
|
||||
</section>
|
||||
1
app/views/donations/index.json.jbuilder
Normal file
1
app/views/donations/index.json.jbuilder
Normal file
@@ -0,0 +1 @@
|
||||
json.array! @donations, partial: "donations/donation", as: :donation
|
||||
@@ -1,5 +1,13 @@
|
||||
<section>
|
||||
<h2>Invitations</h2>
|
||||
<% if @invitations_unused.any? %>
|
||||
<p>
|
||||
Invite your friends to a Kosmos account by sharing an invitation URL with them:
|
||||
</p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<% if @invitations_unused.any? %>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -24,23 +32,25 @@
|
||||
</section>
|
||||
|
||||
<% if @invitations_used.any? %>
|
||||
<h3>Accepted Invitations</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<th>Used at</th>
|
||||
<th>Invited user</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @invitations_used.each do |invitation| %>
|
||||
<section>
|
||||
<h3>Accepted Invitations</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td><%= invitation_url(invitation.token) %></td>
|
||||
<td><%= invitation.used_at %></td>
|
||||
<td><%= User.find(invitation.invited_user_id).address %></td>
|
||||
<th class="hide-small">ID</th>
|
||||
<th>Accepted</th>
|
||||
<th>Invited user</th>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @invitations_used.each do |invitation| %>
|
||||
<tr>
|
||||
<td class="hide-small"><%= invitation.token %></td>
|
||||
<td><%= invitation.used_at.strftime("%Y-%m-%d") %></td>
|
||||
<td><%= User.find(invitation.invited_user_id).address %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
39
app/views/layouts/admin.html.erb
Normal file
39
app/views/layouts/admin.html.erb
Normal file
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Admin Panel | Kosmos Accounts</title>
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet">
|
||||
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
|
||||
</head>
|
||||
<body id="admin-panel">
|
||||
<div id="wrapper">
|
||||
<header>
|
||||
<h1>
|
||||
<span class ="icon"><%= render partial: "shared/icons/comet" %></span>
|
||||
<span class ="project-name">Kosmos</span>
|
||||
<span class ="site-name">Accounts</span>
|
||||
</h1>
|
||||
<%= render partial: 'shared/header_account' %>
|
||||
</header>
|
||||
|
||||
<% if user_signed_in? && current_user.confirmed? %>
|
||||
<%= render partial: 'shared/admin_nav' %>
|
||||
<% end %>
|
||||
|
||||
<% flash.each do |type, msg| %>
|
||||
<div class="flash-msg <%= type %>">
|
||||
<p><%= msg %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<main>
|
||||
<%= yield %>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,29 +4,27 @@
|
||||
<title>Kosmos Accounts</title>
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet">
|
||||
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<header>
|
||||
<h1>
|
||||
<span class ="icon"><%= render partial: "shared/icons/comet" %></span>
|
||||
<span class ="project-name">Kosmos</span>
|
||||
<span class ="site-name">Akkounts</span>
|
||||
<span class="beta"><span class="bolt">⚡</span> beta</span>
|
||||
<span class ="site-name">Account</span>
|
||||
</h1>
|
||||
<% if user_signed_in? %>
|
||||
<p class="current-user">
|
||||
Signed in as <strong><%= current_user.cn %>@kosmos.org</strong>.
|
||||
<%= link_to "Log out", destroy_user_session_path, method: :delete %>
|
||||
</p>
|
||||
<% end %>
|
||||
<%= render partial: 'shared/header_account' %>
|
||||
</header>
|
||||
|
||||
<% if user_signed_in? && current_user.confirmed? %>
|
||||
<%= render partial: 'shared/main_nav' %>
|
||||
<% end %>
|
||||
|
||||
<% flash.each do |type, msg| %>
|
||||
<div class="flash-msg <%= type %>">
|
||||
<p><%= msg %></p>
|
||||
|
||||
@@ -4,20 +4,19 @@
|
||||
<title>Sign up | Kosmos Accounts</title>
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet">
|
||||
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
|
||||
</head>
|
||||
|
||||
<body class="layout-signup">
|
||||
<div id="wrapper">
|
||||
<header>
|
||||
<h1>
|
||||
<span class ="icon"><%= render partial: "shared/icons/comet" %></span>
|
||||
<span class ="project-name">Kosmos</span>
|
||||
<span class ="site-name">Sign Up</span>
|
||||
<!-- <span class="beta"><span class="bolt">⚡</span> beta</span> -->
|
||||
</h1>
|
||||
<% if user_signed_in? %>
|
||||
<p class="current-user">
|
||||
|
||||
12
app/views/security/index.html.erb
Normal file
12
app/views/security/index.html.erb
Normal file
@@ -0,0 +1,12 @@
|
||||
<section>
|
||||
<h2>Security</h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Password change</h3>
|
||||
<p>
|
||||
<%= form_with(url: settings_reset_password_path, method: :post) do %>
|
||||
<%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray') %>
|
||||
<% end %>
|
||||
</p>
|
||||
</section>
|
||||
22
app/views/shared/_admin_nav.html.erb
Normal file
22
app/views/shared/_admin_nav.html.erb
Normal file
@@ -0,0 +1,22 @@
|
||||
<nav id="main-nav">
|
||||
<div class="wrapper">
|
||||
<ul class="pages">
|
||||
<li>
|
||||
<%= link_to "Dashboard", admin_root_path,
|
||||
class: @current_section == :dashboard ? "active" : nil %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Invitations", admin_invitations_path,
|
||||
class: @current_section == :invitations ? "active" : nil %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Donations", admin_donations_path,
|
||||
class: @current_section == :donations ? "active" : nil %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "LDAP Users", admin_ldap_users_path,
|
||||
class: @current_section == :ldap_users ? "active" : nil %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
6
app/views/shared/_header_account.html.erb
Normal file
6
app/views/shared/_header_account.html.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
<% if user_signed_in? %>
|
||||
<p class="current-user mt-8">
|
||||
Signed in as <strong class="text-white font-normal"><%= current_user.cn %>@kosmos.org</strong>.
|
||||
<%= link_to "Log out", destroy_user_session_path, method: :delete, class: 'underline' %>
|
||||
</p>
|
||||
<% end %>
|
||||
26
app/views/shared/_main_nav.html.erb
Normal file
26
app/views/shared/_main_nav.html.erb
Normal file
@@ -0,0 +1,26 @@
|
||||
<nav id="main-nav">
|
||||
<div class="wrapper">
|
||||
<ul class="pages">
|
||||
<li>
|
||||
<%= link_to "Services", root_path,
|
||||
class: @current_section == :dashboard ? "active" : nil %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Invitations", invitations_path,
|
||||
class: @current_section == :invitations ? "active" : nil %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Donations", donations_path,
|
||||
class: @current_section == :contributions ? "active" : nil %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Wallet", wallet_path,
|
||||
class: @current_section == :wallet ? "active" : nil %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Security", security_path,
|
||||
class: @current_section == :security ? "active" : nil %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
1
app/views/shared/icons/_comet.html.erb
Normal file
1
app/views/shared/icons/_comet.html.erb
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="icon-comet" width="65.364" height="55.773" enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 65.364 55.773" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="layer1" transform="translate(28.868 20.259)" fill="#fff"><path id="path2" d="m22.81-9.2546-0.0137-0.0072c-0.0445-0.0196-0.0895-0.04052-0.13335-0.06078l-23.822-10.937s2.0034 9.219 2.914 11.778c0 0-27.292-8.1582-30.623-8.9354 1.0916 4.2618 20.006 40.848 20.006 40.848 3.8225 7.7608 12.677 12.083 21.912 12.083 12.949 0 23.446-10.497 23.446-23.446 6.6e-4 -9.4655-5.609-17.62-13.685-21.323z" fill="#fff" stroke-width=".65365"/></g></svg>
|
||||
|
After Width: | Height: | Size: 627 B |
@@ -7,6 +7,6 @@
|
||||
This invitation can only be used once, and sign-up is currently only possible
|
||||
by invitation. Seems like you have good friends!
|
||||
</p>
|
||||
<p>
|
||||
<%= link_to "Get started", signup_steps_path(1), class: "next-step" %>
|
||||
<p class="mt-12">
|
||||
<%= link_to "Get started", signup_steps_path(1), class: "btn btn-md btn-blue" %>
|
||||
</p>
|
||||
|
||||
@@ -2,60 +2,53 @@
|
||||
<% when 1 %>
|
||||
<h2>Choose a username</h2>
|
||||
<%= form_for @user, :url => signup_validate_url do |f| %>
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :cn, 'Username' %><br />
|
||||
<%= f.text_field :cn, autofocus: true, autocomplete: "username" %>
|
||||
<span class="at-sign">@</span>
|
||||
<span class="domain">kosmos.org</span>
|
||||
</p>
|
||||
<% if @validation_error.present? %>
|
||||
<p class="error-msg">Username <%= @validation_error %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<p><%= f.submit "Continue" %></p>
|
||||
</div>
|
||||
<p>
|
||||
<%= f.label :cn, 'Username', class: 'hidden' %>
|
||||
<%= f.text_field :cn, autofocus: true, autocomplete: "username",
|
||||
class: 'text-xl' %>
|
||||
<span class="text-xl ml-1">@</span>
|
||||
<span class="text-xl">kosmos.org</span>
|
||||
</p>
|
||||
<% if @validation_error.present? %>
|
||||
<p class="error-msg">Username <%= @validation_error %></p>
|
||||
<% end %>
|
||||
<p class="mt-12">
|
||||
<%= f.submit "Continue", class: 'btn btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% when 2 %>
|
||||
<h2>What's your email?</h2>
|
||||
<%= form_for @user, :url => signup_validate_url do |f| %>
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :email, 'Email address' %><br />
|
||||
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
||||
</p>
|
||||
<% if @validation_error.present? %>
|
||||
<p class="error-msg">Email <%= @validation_error %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<p><%= f.submit "Continue" %></p>
|
||||
</div>
|
||||
<p>
|
||||
<%= f.label :email, 'Email address', class: 'hidden' %>
|
||||
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'text-xl' %>
|
||||
</p>
|
||||
<% if @validation_error.present? %>
|
||||
<p class="error-msg">Email <%= @validation_error %></p>
|
||||
<% end %>
|
||||
<p class="mt-12">
|
||||
<%= f.submit "Continue", class: 'btn btn-md btn-blue' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% when 3 %>
|
||||
<h2>Choose a password</h2>
|
||||
|
||||
<%= form_for @user, :url => signup_validate_url do |f| %>
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= f.label :password, 'Password' %><br />
|
||||
<%= f.password_field :password, autofocus: true %>
|
||||
</p>
|
||||
<% if @validation_error.present? %>
|
||||
<p class="error-msg">Password <%= @validation_error %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
<p class="accept-terms">
|
||||
<small>
|
||||
By clicking the button below, you accept our future Terms of Service
|
||||
and Privacy Policy. Don't worry, they will be excellent!
|
||||
</small>
|
||||
<p>
|
||||
<%= f.label :password, 'Password', class: 'hidden' %>
|
||||
<%= f.password_field :password, autofocus: true, class: 'text-xl' %>
|
||||
</p>
|
||||
<% if @validation_error.present? %>
|
||||
<p class="error-msg">Password <%= @validation_error %></p>
|
||||
<% end %>
|
||||
<p class="mt-8 text-sm text-gray-500">
|
||||
By clicking the button below, you accept our future Terms of Service
|
||||
and Privacy Policy. Don't worry, they will be excellent!
|
||||
</p>
|
||||
<p class="mt-8">
|
||||
<%= f.submit "Create account", class: 'btn-md btn-blue' %>
|
||||
</p>
|
||||
<div class="actions">
|
||||
<p><%= f.submit "Create account" %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
59
app/views/wallet/index.html.erb
Normal file
59
app/views/wallet/index.html.erb
Normal file
@@ -0,0 +1,59 @@
|
||||
<section class="w-full grid grid-cols-1 md:grid-cols-12 md:mb-0">
|
||||
<div class="md:col-span-8">
|
||||
<h2>Wallet</h2>
|
||||
<p>
|
||||
Send and receive sats via the Bitcoin Lightning Network.
|
||||
</p>
|
||||
</div>
|
||||
<div class="md:col-span-4 mt-4 md:mt-0">
|
||||
<p class="font-mono md:text-right p-4 bg-gray-100 shadow rounded-lg overflow-hidden">
|
||||
<% if @balance %>
|
||||
<span class="text-xl"><%= @balance %> sats</span><br>
|
||||
<span class="text-sm text-gray-500">Available balance</span>
|
||||
<% else %>
|
||||
<span class="text-xl">n/a sats</span><br>
|
||||
<span class="text-sm text-gray-500">Balance unavailable</span>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Blue Wallet</h3>
|
||||
<p>
|
||||
You can connect
|
||||
<%= link_to "Blue Wallet", "https://bluewallet.io",
|
||||
class: "ks-text-link", target: "_blank" %>
|
||||
(Android or iOS) to your Kosmos lightning wallet. In order to do so,
|
||||
scan the setup QR code from the Import Wallet screen in the app.
|
||||
</p>
|
||||
<p>
|
||||
<a id="show-setup-code" class="ks-text-link cursor-pointer">Show setup code</a>
|
||||
<a id="hide-setup-code" class="ks-text-link cursor-pointer" style="display: none;">Hide setup code</a>
|
||||
</p>
|
||||
<p id="setup-code" style="display: none;">
|
||||
<%= raw @svg %>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<script type="text/javascript">
|
||||
let linkShow = document.querySelector('#show-setup-code');
|
||||
let linkHide = document.querySelector('#hide-setup-code');
|
||||
let setupCode = document.querySelector('#setup-code');
|
||||
|
||||
linkShow.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
setupCode.style.display = 'block';
|
||||
linkShow.style.display = 'none';
|
||||
linkHide.style.display = 'block';
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
});
|
||||
|
||||
linkHide.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
const el = document.querySelector('#setup-code');
|
||||
setupCode.style.display = 'none';
|
||||
linkShow.style.display = 'block';
|
||||
linkHide.style.display = 'none';
|
||||
});
|
||||
</script>
|
||||
@@ -39,5 +39,8 @@ module Akkounts
|
||||
g.fixture_replacement :factory_bot, suffix_factory: 'factory', dir: 'spec/factories'
|
||||
g.stylesheets false
|
||||
end
|
||||
|
||||
config.active_job.queue_adapter = :sidekiq
|
||||
config.action_mailer.deliver_later_queue_name = nil # use "default" queue
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1 +1 @@
|
||||
LWyKwPZq9Kd97rn/7+q3MEkh7kITScDMHD3JvVuaV3A4YIHJHU+460k+PaEGlsH1xkbuClGiAb57rk1XLyDnmVGtbSueYOtinkw6kar8ZfKWZob061LwGjpMVRQkS49TjCUZlqCFrXeKxlH03mXWBnqAj9RUIPrm7eibb3c7qmJFglR1380RSVsfZnp8A3QwGm4Wh9OWtpUa6P2lne0jQsOuSe8ur3DUF0LplzS4CbkMxAUDOom+pXB13AlxOH9NQE7F4dsYHugHkh1tG3r3ER3xAUD/9Kn6UZZP7BnwUs3zqhoZdULRpRgA5dK7ueTIAnO/jtJDF4562VS8ECo7AnNoVxNe8/mBMFIOUfqg+db/72N2pIk3r4lK7Uzm/4jJ5/99ItnQjHQPcApiwZXIr3OyDLUvq5+d0UVmAMXdwcAjvctVQXFx5imG149Y0ISHKWVm1ca37aAspxWPU+CIj8/HW0yEpjp3vhwDUbjCaZeAPm8UQC14MxZwSK3N+EUSQXdltiweFynabDB7zGGQsjMM8LwMtyo9bTBzJA78Cl96MDyd20i1zSF9ntLuKulwGm3oZowpbNuvo2anY6r9yBlDJBOEISbtXv2tLX4SqcM=--bRcsE4K/29XzyZat--+G3iQCLBqgSwLaQ+7+4YvA==
|
||||
PRI1laGy6XiLpsII0ixFOTCpzzDbrRpIQ7GJ2flvA+UEEiPVQQ4e/pt6ZjumaZ/geTBHCRZklLtS1u6HjucVKHGSR4K/lE1q7u6nfi/wAvwGQdCTJeKf8PetdlsiVZg2OW4Wi2YF7qtb1ipGS/uRlOeLP8JOwp5BNVS1J6tvNX4T5gA7rrB4Cvtfwp1dwLWODHiXbeZK7SN2j7V3NPGZl0LbNg2jquC4DzQOBhMOlwu1IAgOAcqKbHh9tDabpVOcOD5pNSaVaoBO01P8ObbX8x0S0Ug7BPkM5nnyJTQdNH7LHKvtNmrUEk7+fd+VZR6WqEp6SFBoAA7XxvOLy9dIOqvXbAYnrNZX7iI4IpgZWRNIQ7G1qSBjpyPLKC7+nSPmvd4IcgCHVUBdYp0+yYDxLzwuuhPVtyAY4JAIdZY1mhYxx7BzGNiMc3p8AlMS6hPeO9lVQZykViBGnTjR+iah9OQYAihX9FYywtBkNvMXLiYqsVZmv6em9Uk/ivKSIsaFwNwrelCI5H4Q/+/hKFJ1JaMjbYheXCJgcGgfhiC+fIHig/8y8rXd9lWGa1T+PrpA8akcq/K8I6gbxYxpLWcy5Vnz/SyaMurLPVfCq4cD/JfpLSfy0HophaiycZ7D6dr43rnHsPfrt6kveDOgWUK9d7CqHxsWdXbCpJeBoh8/1usIsGsmyhfwHq0dEpRx78bR2EJeM4LOAuQgf69/5rFJydIbXq76l1BftTls1Pgh--OI8c/RQGQ30wT1Ff--M/ltnmdl+FRdlNliTb79lg==
|
||||
1
config/credentials/development.yml.enc
Normal file
1
config/credentials/development.yml.enc
Normal file
@@ -0,0 +1 @@
|
||||
Ga2G0A==--RCOGf20OFDHTm7qw--PLI8AZQQylfrplQVYg603Q==
|
||||
@@ -49,4 +49,6 @@ Rails.application.configure do
|
||||
protocol: "https",
|
||||
from: "accounts@kosmos.org"
|
||||
}
|
||||
|
||||
config.active_job.queue_adapter = :test
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'sidekiq/web'
|
||||
|
||||
Rails.application.routes.draw do
|
||||
devise_for :users
|
||||
|
||||
@@ -11,11 +13,26 @@ Rails.application.routes.draw do
|
||||
get 'settings', to: 'settings#index'
|
||||
post 'settings_reset_password', to: 'settings#reset_password'
|
||||
|
||||
get 'security', to: 'security#index'
|
||||
|
||||
resources :invitations, only: ['index', 'show', 'create', 'destroy']
|
||||
|
||||
resources :donations
|
||||
|
||||
get 'wallet', to: 'wallet#index'
|
||||
|
||||
get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/}
|
||||
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/}
|
||||
|
||||
namespace :admin do
|
||||
root to: 'dashboard#index'
|
||||
get 'invitations', to: 'invitations#index'
|
||||
get 'ldap_users', to: 'ldap_users#index'
|
||||
resources :donations
|
||||
end
|
||||
|
||||
authenticate :user, ->(user) { user.is_admin? } do
|
||||
mount Sidekiq::Web => '/sidekiq'
|
||||
end
|
||||
|
||||
# Letter Opener (open "sent" emails in dev and staging)
|
||||
|
||||
3
config/sidekiq.yml
Normal file
3
config/sidekiq.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
:concurrency: 2
|
||||
:queues:
|
||||
- default
|
||||
@@ -8,6 +8,7 @@ class CreateInvitations < ActiveRecord::Migration[6.0]
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :invitations, :user_id
|
||||
add_index :invitations, :invited_user_id
|
||||
end
|
||||
|
||||
15
db/migrate/20201217161544_create_donations.rb
Normal file
15
db/migrate/20201217161544_create_donations.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
class CreateDonations < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :donations do |t|
|
||||
t.integer :user_id
|
||||
t.integer :amount_sats
|
||||
t.integer :amount_eur
|
||||
t.integer :amount_usd
|
||||
t.string :public_name
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :donations, :user_id
|
||||
end
|
||||
end
|
||||
5
db/migrate/20201219121808_add_paid_at_to_donations.rb
Normal file
5
db/migrate/20201219121808_add_paid_at_to_donations.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddPaidAtToDonations < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :donations, :paid_at, :datetime
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
class AddLndhubCredentialsToUser < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :users, :ln_login_ciphertext, :text
|
||||
add_column :users, :ln_password_ciphertext, :text
|
||||
end
|
||||
end
|
||||
16
db/schema.rb
16
db/schema.rb
@@ -10,7 +10,19 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2020_11_30_132533) do
|
||||
ActiveRecord::Schema.define(version: 2021_11_20_010540) do
|
||||
|
||||
create_table "donations", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "amount_sats"
|
||||
t.integer "amount_eur"
|
||||
t.integer "amount_usd"
|
||||
t.string "public_name"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.datetime "paid_at"
|
||||
t.index ["user_id"], name: "index_donations_on_user_id"
|
||||
end
|
||||
|
||||
create_table "invitations", force: :cascade do |t|
|
||||
t.string "token"
|
||||
@@ -33,6 +45,8 @@ ActiveRecord::Schema.define(version: 2020_11_30_132533) do
|
||||
t.datetime "confirmed_at"
|
||||
t.datetime "confirmation_sent_at"
|
||||
t.string "unconfirmed_email"
|
||||
t.text "ln_login_ciphertext"
|
||||
t.text "ln_password_ciphertext"
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
end
|
||||
|
||||
8
lib/tasks/lndhub.rake
Normal file
8
lib/tasks/lndhub.rake
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace :lndhub do
|
||||
desc "Generate wallets for all users"
|
||||
task :generate_wallets => :environment do |t, args|
|
||||
User.all.each do |user|
|
||||
CreateLndhubWalletJob.perform_later(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,10 @@
|
||||
"@rails/actioncable": "^6.0.0",
|
||||
"@rails/ujs": "^6.0.0",
|
||||
"@rails/webpacker": "4.3.0",
|
||||
"@tailwindcss/forms": "^0.2.1",
|
||||
"autoprefixer": "^9",
|
||||
"postcss": "^7",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
|
||||
"turbolinks": "^5.2.0"
|
||||
},
|
||||
"version": "0.1.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require("tailwindcss")("./app/javascript/stylesheets/tailwind.config.js"),
|
||||
require('postcss-import'),
|
||||
require('postcss-flexbugs-fixes'),
|
||||
require('postcss-preset-env')({
|
||||
|
||||
BIN
public/img/bg-1.jpg
Normal file
BIN
public/img/bg-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
9
spec/factories/donations.rb
Normal file
9
spec/factories/donations.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
FactoryBot.define do
|
||||
factory :donation do
|
||||
user_id { 1 }
|
||||
amount_sats { 100000 }
|
||||
amount_eur { 10 }
|
||||
amount_usd { 13 }
|
||||
public_name { nil }
|
||||
end
|
||||
end
|
||||
@@ -10,6 +10,6 @@ RSpec.describe 'Admin dashboard', type: :feature do
|
||||
|
||||
scenario 'View dashboard' do
|
||||
visit admin_root_path
|
||||
expect(page).to have_content('Admin Panel')
|
||||
expect(page).to have_content('great power')
|
||||
end
|
||||
end
|
||||
|
||||
9
spec/helpers/application_helper_spec.rb
Normal file
9
spec/helpers/application_helper_spec.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplicationHelper do
|
||||
describe "sats_to_btc" do
|
||||
it "converts satoshis to BTC" do
|
||||
expect(helper.sats_to_btc(120000000)).to eq(1.2)
|
||||
end
|
||||
end
|
||||
end
|
||||
15
spec/helpers/donations_helper_spec.rb
Normal file
15
spec/helpers/donations_helper_spec.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# Specs in this file have access to a helper object that includes
|
||||
# the DonationsHelper. For example:
|
||||
#
|
||||
# describe DonationsHelper do
|
||||
# describe "string concat" do
|
||||
# it "concats two strings with spaces" do
|
||||
# expect(helper.concat_strings("this","that")).to eq("this that")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
RSpec.describe DonationsHelper, type: :helper do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
34
spec/jobs/create_ldap_user_job_spec.rb
Normal file
34
spec/jobs/create_ldap_user_job_spec.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CreateLdapUserJob, type: :job do
|
||||
let(:ldap_client_mock) { instance_double(Net::LDAP) }
|
||||
|
||||
subject(:job) {
|
||||
described_class.any_instance.stub(:ldap_client).and_return(ldap_client_mock)
|
||||
described_class.perform_later(
|
||||
'halfinney', 'kosmos.org', 'halfinney@example.com',
|
||||
'remember-remember-the-5th-of-november'
|
||||
)
|
||||
}
|
||||
|
||||
it "creates a new document with the correct attributes" do
|
||||
ldap_client_mock.should_receive(:add).with(
|
||||
dn: "cn=halfinney,ou=kosmos.org,cn=users,dc=kosmos,dc=org",
|
||||
attributes: {
|
||||
objectclass: ["top", "account", "person", "extensibleObject"],
|
||||
cn: "halfinney",
|
||||
sn: "halfinney",
|
||||
uid: "halfinney",
|
||||
mail: "halfinney@example.com",
|
||||
userPassword: "remember-remember-the-5th-of-november"
|
||||
}
|
||||
)
|
||||
|
||||
perform_enqueued_jobs { job }
|
||||
end
|
||||
|
||||
after do
|
||||
clear_enqueued_jobs
|
||||
clear_performed_jobs
|
||||
end
|
||||
end
|
||||
48
spec/jobs/create_lndhub_wallet_job_spec.rb
Normal file
48
spec/jobs/create_lndhub_wallet_job_spec.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require 'rails_helper'
|
||||
require 'webmock/rspec'
|
||||
|
||||
RSpec.describe CreateLndhubWalletJob, type: :job do
|
||||
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
||||
|
||||
subject(:job) { described_class.perform_later(user) }
|
||||
|
||||
before do
|
||||
stub_request(:post, "http://localhost:3023/create")
|
||||
.to_return(status: 200, headers: {},
|
||||
body: { login: "abc123", password: "def456" }.to_json)
|
||||
end
|
||||
|
||||
it "creates a new LndHub account" do
|
||||
perform_enqueued_jobs { job }
|
||||
|
||||
expect(WebMock).to have_requested(:post, "http://localhost:3023/create")
|
||||
.with { |req| req.body == '{"partnerid":"kosmos.org","accounttype":"user"}' }
|
||||
|
||||
user.reload
|
||||
expect(user.ln_login).to eq("abc123")
|
||||
expect(user.ln_password).to eq("def456")
|
||||
end
|
||||
|
||||
context "with existing credentials stored" do
|
||||
before do
|
||||
user.ln_login = "foo"
|
||||
user.ln_password = "bar"
|
||||
user.save!
|
||||
end
|
||||
|
||||
it "does not create a new LndHub account" do
|
||||
perform_enqueued_jobs { job }
|
||||
|
||||
expect(WebMock).to_not have_requested(:post, "http://localhost:3023/create")
|
||||
|
||||
user.reload
|
||||
expect(user.ln_login).to eq("foo")
|
||||
expect(user.ln_password).to eq("bar")
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
clear_enqueued_jobs
|
||||
clear_performed_jobs
|
||||
end
|
||||
end
|
||||
29
spec/jobs/exchange_xmpp_contacts_job_spec.rb
Normal file
29
spec/jobs/exchange_xmpp_contacts_job_spec.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
require 'rails_helper'
|
||||
require 'webmock/rspec'
|
||||
|
||||
RSpec.describe ExchangeXmppContactsJob, type: :job do
|
||||
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
||||
|
||||
subject(:job) {
|
||||
described_class.perform_later(user, 'isaacnewton', 'kosmos.org')
|
||||
}
|
||||
|
||||
before do
|
||||
stub_request(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||
.to_return(status: 200, body: "", headers: {})
|
||||
end
|
||||
|
||||
it "posts add_rosteritem commands to the ejabberd API" do
|
||||
perform_enqueued_jobs { job }
|
||||
|
||||
expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||
.with { |req| req.body == '{"localuser":"isaacnewton","localhost":"kosmos.org","user":"willherschel","host":"kosmos.org","nick":"willherschel","group":"Friends","subs":"both"}' }
|
||||
expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||
.with { |req| req.body == '{"localuser":"willherschel","localhost":"kosmos.org","user":"isaacnewton","host":"kosmos.org","nick":"isaacnewton","group":"Friends","subs":"both"}' }
|
||||
end
|
||||
|
||||
after do
|
||||
clear_enqueued_jobs
|
||||
clear_performed_jobs
|
||||
end
|
||||
end
|
||||
5
spec/models/donation_spec.rb
Normal file
5
spec/models/donation_spec.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Donation, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
@@ -69,5 +69,6 @@ RSpec.configure do |config|
|
||||
config.include Devise::Test::ControllerHelpers, :type => :controller
|
||||
config.include Warden::Test::Helpers
|
||||
config.include FactoryBot::Syntax::Methods
|
||||
config.include ActiveJob::TestHelper, type: :job
|
||||
config.extend ControllerMacros, :type => :controller
|
||||
end
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
require 'rails_helper'
|
||||
require 'webmock/rspec'
|
||||
require 'json'
|
||||
|
||||
RSpec.describe CreateAccount, type: :model do
|
||||
let(:ldap_client_mock) { instance_double(Net::LDAP) }
|
||||
|
||||
before do
|
||||
allow(service).to receive(:ldap_client).and_return(ldap_client_mock)
|
||||
end
|
||||
|
||||
describe "#create_user_in_database" do
|
||||
let(:service) { CreateAccount.new(
|
||||
username: 'isaacnewton',
|
||||
@@ -48,30 +40,34 @@ RSpec.describe CreateAccount, type: :model do
|
||||
end
|
||||
|
||||
describe "#add_ldap_document" do
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
let(:service) { CreateAccount.new(
|
||||
username: 'halfinney',
|
||||
email: 'halfinney@example.com',
|
||||
password: 'remember-remember-the-5th-of-november'
|
||||
)}
|
||||
|
||||
it "creates a new document with the correct attributes" do
|
||||
expect(ldap_client_mock).to receive(:add).with(
|
||||
dn: "cn=halfinney,ou=kosmos.org,cn=users,dc=kosmos,dc=org",
|
||||
attributes: {
|
||||
objectclass: ["top", "account", "person", "extensibleObject"],
|
||||
cn: "halfinney",
|
||||
sn: "halfinney",
|
||||
uid: "halfinney",
|
||||
mail: "halfinney@example.com",
|
||||
userPassword: /^{SSHA512}.{171}=/
|
||||
}
|
||||
)
|
||||
|
||||
it "enqueues a job to create the LDAP user document" do
|
||||
service.send(:add_ldap_document)
|
||||
|
||||
expect(enqueued_jobs.size).to eq(1)
|
||||
|
||||
args = enqueued_jobs.first['arguments']
|
||||
expect(args[0]).to eq('halfinney')
|
||||
expect(args[1]).to eq('kosmos.org')
|
||||
expect(args[2]).to eq('halfinney@example.com')
|
||||
expect(args[3]).to match(/^{SSHA512}.{171}=/)
|
||||
end
|
||||
|
||||
after do
|
||||
clear_enqueued_jobs
|
||||
end
|
||||
end
|
||||
|
||||
describe "#exchange_xmpp_contacts" do
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
let(:inviter) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
||||
let(:invitation) { create :invitation, user: inviter }
|
||||
let(:service) { CreateAccount.new(
|
||||
@@ -81,18 +77,19 @@ RSpec.describe CreateAccount, type: :model do
|
||||
invitation: invitation
|
||||
)}
|
||||
|
||||
before do
|
||||
stub_request(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||
.to_return(status: 200, body: "", headers: {})
|
||||
end
|
||||
|
||||
it "posts add_rosteritem commands to the ejabberd API" do
|
||||
it "enqueues a job to exchange XMPP contacts between inviter and invitee" do
|
||||
service.send(:exchange_xmpp_contacts)
|
||||
|
||||
expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||
.with { |req| req.body == '{"localuser":"isaacnewton","localhost":"kosmos.org","user":"willherschel","host":"kosmos.org","nick":"willherschel","group":"Friends","subs":"both"}' }
|
||||
expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||
.with { |req| req.body == '{"localuser":"willherschel","localhost":"kosmos.org","user":"isaacnewton","host":"kosmos.org","nick":"isaacnewton","group":"Friends","subs":"both"}' }
|
||||
expect(enqueued_jobs.size).to eq(1)
|
||||
|
||||
args = enqueued_jobs.first['arguments']
|
||||
expect(args[0]['_aj_globalid']).to match('gid://akkounts/User')
|
||||
expect(args[1]).to eq('isaacnewton')
|
||||
expect(args[2]).to eq('kosmos.org')
|
||||
end
|
||||
|
||||
after do
|
||||
clear_enqueued_jobs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user