Compare commits
69 Commits
v0.3.0
...
a1663b9f9d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1663b9f9d
|
||
|
|
aa3c2b4fa2
|
||
|
|
4c0d8283e3
|
||
|
|
d4a3f8dadb
|
||
|
|
9e988e92d1
|
||
|
|
4232df302b
|
||
|
|
2c8b3cdacc
|
||
|
|
51952ecdc2
|
||
|
|
68e0d00f6e
|
||
| 2e1d930e0f | |||
| d849d28f62 | |||
|
|
f2a22adf6b
|
||
|
|
e1aaa2c434
|
||
|
|
e62bf67262
|
||
|
|
6df3d5933c
|
||
|
|
a5a90c4d83
|
||
|
|
80ef75ff42
|
||
|
|
67e2e45dd8
|
||
|
|
3834e5230b
|
||
|
|
4cb7c0998f
|
||
|
|
20382f7df7
|
||
|
|
add94eee8d
|
||
|
|
067dc3b63d
|
||
|
|
1a470cf1c8
|
||
|
|
f85b7f4f62
|
||
|
|
8635413002
|
||
|
|
a3da956b48
|
||
|
|
3c40dc98ca
|
||
| 28b31e63f9 | |||
|
|
efafd38f68
|
||
|
|
537e1a4774
|
||
|
|
c3b9ff8b4a
|
||
|
|
93d56f79d5
|
||
|
|
1a30345f46
|
||
|
|
778babcc05
|
||
|
|
fa3b53d3b3
|
||
|
|
0ca85656b7
|
||
|
|
f7183f68d5
|
||
| 87027b514b | |||
|
|
16ad621365
|
||
| 33e87d6472 | |||
|
03dc6c7a9c
|
|||
|
897b5bf4ea
|
|||
|
caea2d0121
|
|||
|
e1ff5c479e
|
|||
|
9b3386de30
|
|||
|
f2287c1186
|
|||
| b29197cf4e | |||
|
5c48055ac8
|
|||
|
5ead3476b7
|
|||
| fbf163740a | |||
|
|
1fc1457e97 | ||
| 1f57bbd9c2 | |||
|
2a2793ae44
|
|||
|
8773bf5f9e
|
|||
|
d9970c126a
|
|||
|
4e0d4bf86d
|
|||
|
333bcbfe7e
|
|||
| 875af6d14c | |||
| 8f87a03060 | |||
|
7838fe5f34
|
|||
|
512798d122
|
|||
|
384c28aaaa
|
|||
|
8e5d6dabdc
|
|||
|
ade9261c2c
|
|||
|
bd2a161306
|
|||
|
78c243c985
|
|||
|
cf62bfc5c2
|
|||
|
10f179a095
|
@@ -17,7 +17,7 @@ steps:
|
|||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
- name: rspec
|
- name: rspec
|
||||||
image: guildeducation/rails:2.7.2-12.22.0
|
image: guildeducation/rails:2.7.2-14.20.0
|
||||||
environment:
|
environment:
|
||||||
RAILS_ENV: test
|
RAILS_ENV: test
|
||||||
commands:
|
commands:
|
||||||
@@ -29,9 +29,6 @@ steps:
|
|||||||
- yarn install
|
- yarn install
|
||||||
- rake css:build
|
- rake css:build
|
||||||
- rake spec
|
- rake spec
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
- name: rebuild-cache
|
- name: rebuild-cache
|
||||||
image: drillster/drone-volume-cache
|
image: drillster/drone-volume-cache
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
10
.env.example
@@ -1,3 +1,13 @@
|
|||||||
|
LDAP_HOST=localhost
|
||||||
|
LDAP_PORT=389
|
||||||
|
LDAP_ADMIN_PASSWORD=passthebutter
|
||||||
|
LDAP_SUFFIX="dc=kosmos,dc=org"
|
||||||
|
|
||||||
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
||||||
|
|
||||||
|
BTCPAY_API_URL='http://localhost:23001/api/v1'
|
||||||
|
|
||||||
LNDHUB_API_URL='http://localhost:3023'
|
LNDHUB_API_URL='http://localhost:3023'
|
||||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||||
|
|
||||||
|
WEBHOOKS_ALLOWED_IPS='10.1.1.163'
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
|
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
|
||||||
LNDHUB_API_URL='http://10.1.1.163:3023'
|
|
||||||
|
BTCPAY_API_URL='http://10.1.1.163:23001/api/v1'
|
||||||
|
|
||||||
|
LNDHUB_LEGACY_API_URL='http://10.1.1.163:3026'
|
||||||
|
LNDHUB_API_URL='http://10.1.1.163:3026'
|
||||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||||
|
|
||||||
|
WEBHOOKS_ALLOWED_IPS='10.1.1.163'
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
EJABBERD_API_URL='http://xmpp.example.com/api'
|
EJABBERD_API_URL='http://xmpp.example.com/api'
|
||||||
LNDHUB_API_URL='http://localhost:3023'
|
|
||||||
|
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||||
|
|
||||||
|
LNDHUB_LEGACY_API_URL='http://localhost:3023'
|
||||||
|
LNDHUB_API_URL='http://localhost:3026'
|
||||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||||
|
|
||||||
|
WEBHOOKS_ALLOWED_IPS='10.1.1.23'
|
||||||
|
|||||||
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM ruby:2.7.6
|
||||||
|
RUN apt-get update -qq && apt-get install -y curl ldap-utils
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||||
|
RUN apt-get update && apt-get install -y nodejs
|
||||||
|
WORKDIR /akkounts
|
||||||
|
COPY Gemfile /akkounts/Gemfile
|
||||||
|
COPY Gemfile.lock /akkounts/Gemfile.lock
|
||||||
|
COPY package.json /akkounts/package.json
|
||||||
|
RUN bundle install
|
||||||
|
RUN npm install -g yarn
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
# Add a script to be executed every time the container starts.
|
||||||
|
COPY docker/entrypoint.sh /usr/bin/
|
||||||
|
RUN chmod +x /usr/bin/entrypoint.sh
|
||||||
|
ENTRYPOINT ["entrypoint.sh"]
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Configure the main process to run when running the image
|
||||||
|
CMD ["bin", "dev"]
|
||||||
4
Gemfile
@@ -43,13 +43,14 @@ gem "rqrcode", "~> 2.0"
|
|||||||
gem 'faraday'
|
gem 'faraday'
|
||||||
|
|
||||||
# Background/scheduled jobs
|
# Background/scheduled jobs
|
||||||
gem 'sidekiq'
|
gem 'sidekiq', '< 7'
|
||||||
gem 'sidekiq-scheduler'
|
gem 'sidekiq-scheduler'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
# Use sqlite3 as the database for Active Record
|
# Use sqlite3 as the database for Active Record
|
||||||
gem 'sqlite3', '~> 1.4'
|
gem 'sqlite3', '~> 1.4'
|
||||||
gem 'rspec-rails'
|
gem 'rspec-rails'
|
||||||
|
gem "byebug", "~> 11.1"
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
@@ -58,6 +59,7 @@ group :development do
|
|||||||
gem 'listen', '~> 3.2'
|
gem 'listen', '~> 3.2'
|
||||||
gem 'letter_opener'
|
gem 'letter_opener'
|
||||||
gem 'letter_opener_web'
|
gem 'letter_opener_web'
|
||||||
|
gem 'faker'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
|||||||
282
Gemfile.lock
@@ -1,77 +1,78 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.0.2.2)
|
actioncable (7.0.4)
|
||||||
actionpack (= 7.0.2.2)
|
actionpack (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (7.0.2.2)
|
actionmailbox (7.0.4)
|
||||||
actionpack (= 7.0.2.2)
|
actionpack (= 7.0.4)
|
||||||
activejob (= 7.0.2.2)
|
activejob (= 7.0.4)
|
||||||
activerecord (= 7.0.2.2)
|
activerecord (= 7.0.4)
|
||||||
activestorage (= 7.0.2.2)
|
activestorage (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.0.2.2)
|
actionmailer (7.0.4)
|
||||||
actionpack (= 7.0.2.2)
|
actionpack (= 7.0.4)
|
||||||
actionview (= 7.0.2.2)
|
actionview (= 7.0.4)
|
||||||
activejob (= 7.0.2.2)
|
activejob (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (7.0.2.2)
|
actionpack (7.0.4)
|
||||||
actionview (= 7.0.2.2)
|
actionview (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
rack (~> 2.0, >= 2.2.0)
|
rack (~> 2.0, >= 2.2.0)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (7.0.2.2)
|
actiontext (7.0.4)
|
||||||
actionpack (= 7.0.2.2)
|
actionpack (= 7.0.4)
|
||||||
activerecord (= 7.0.2.2)
|
activerecord (= 7.0.4)
|
||||||
activestorage (= 7.0.2.2)
|
activestorage (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.0.2.2)
|
actionview (7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
activejob (7.0.2.2)
|
activejob (7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.0.2.2)
|
activemodel (7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
activerecord (7.0.2.2)
|
activerecord (7.0.4)
|
||||||
activemodel (= 7.0.2.2)
|
activemodel (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
activestorage (7.0.2.2)
|
activestorage (7.0.4)
|
||||||
actionpack (= 7.0.2.2)
|
actionpack (= 7.0.4)
|
||||||
activejob (= 7.0.2.2)
|
activejob (= 7.0.4)
|
||||||
activerecord (= 7.0.2.2)
|
activerecord (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (7.0.2.2)
|
activesupport (7.0.4)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
addressable (2.8.0)
|
addressable (2.8.1)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
bcrypt (3.1.16)
|
bcrypt (3.1.18)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
capybara (3.36.0)
|
byebug (11.1.3)
|
||||||
|
capybara (3.38.0)
|
||||||
addressable
|
addressable
|
||||||
matrix
|
matrix
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
@@ -81,12 +82,12 @@ GEM
|
|||||||
regexp_parser (>= 1.5, < 3.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
chunky_png (1.4.0)
|
chunky_png (1.4.0)
|
||||||
concurrent-ruby (1.1.9)
|
concurrent-ruby (1.1.10)
|
||||||
connection_pool (2.2.5)
|
connection_pool (2.3.0)
|
||||||
crack (0.4.5)
|
crack (0.4.5)
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
cssbundling-rails (1.0.0)
|
cssbundling-rails (1.1.1)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
database_cleaner (2.0.1)
|
database_cleaner (2.0.1)
|
||||||
database_cleaner-active_record (~> 2.0.0)
|
database_cleaner-active_record (~> 2.0.0)
|
||||||
@@ -104,44 +105,43 @@ GEM
|
|||||||
devise (>= 3.4.1)
|
devise (>= 3.4.1)
|
||||||
net-ldap (>= 0.16.0)
|
net-ldap (>= 0.16.0)
|
||||||
diff-lcs (1.5.0)
|
diff-lcs (1.5.0)
|
||||||
digest (3.1.0)
|
dotenv (2.8.1)
|
||||||
dotenv (2.7.6)
|
dotenv-rails (2.8.1)
|
||||||
dotenv-rails (2.7.6)
|
dotenv (= 2.8.1)
|
||||||
dotenv (= 2.7.6)
|
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
e2mmap (0.1.0)
|
erubi (1.11.0)
|
||||||
erubi (1.10.0)
|
et-orbi (1.2.7)
|
||||||
et-orbi (1.2.6)
|
|
||||||
tzinfo
|
tzinfo
|
||||||
factory_bot (6.2.0)
|
factory_bot (6.2.1)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
factory_bot_rails (6.2.0)
|
factory_bot_rails (6.2.0)
|
||||||
factory_bot (~> 6.2.0)
|
factory_bot (~> 6.2.0)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
faraday (2.2.0)
|
faker (3.0.0)
|
||||||
faraday-net_http (~> 2.0)
|
i18n (>= 1.8.11, < 2)
|
||||||
|
faraday (2.7.1)
|
||||||
|
faraday-net_http (>= 2.0, < 3.1)
|
||||||
ruby2_keywords (>= 0.0.4)
|
ruby2_keywords (>= 0.0.4)
|
||||||
faraday-net_http (2.0.1)
|
faraday-net_http (3.0.2)
|
||||||
ffi (1.15.5)
|
ffi (1.15.5)
|
||||||
fugit (1.5.2)
|
fugit (1.7.2)
|
||||||
et-orbi (~> 1.1, >= 1.1.8)
|
et-orbi (~> 1, >= 1.2.7)
|
||||||
raabro (~> 1.4)
|
raabro (~> 1.4)
|
||||||
globalid (1.0.0)
|
globalid (1.0.0)
|
||||||
activesupport (>= 5.0)
|
activesupport (>= 5.0)
|
||||||
hashdiff (1.0.1)
|
hashdiff (1.0.1)
|
||||||
i18n (1.9.1)
|
i18n (1.12.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
importmap-rails (1.0.2)
|
importmap-rails (1.1.5)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
io-wait (0.2.1)
|
|
||||||
jbuilder (2.11.5)
|
jbuilder (2.11.5)
|
||||||
actionview (>= 5.0.0)
|
actionview (>= 5.0.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
launchy (2.5.0)
|
launchy (2.5.0)
|
||||||
addressable (~> 2.7)
|
addressable (~> 2.7)
|
||||||
letter_opener (1.7.0)
|
letter_opener (1.8.1)
|
||||||
launchy (~> 2.2)
|
launchy (>= 2.2, < 3)
|
||||||
letter_opener_web (2.0.0)
|
letter_opener_web (2.0.0)
|
||||||
actionmailer (>= 5.2)
|
actionmailer (>= 5.2)
|
||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.7)
|
||||||
@@ -150,8 +150,8 @@ GEM
|
|||||||
listen (3.7.1)
|
listen (3.7.1)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
lockbox (0.6.8)
|
lockbox (1.1.0)
|
||||||
loofah (2.14.0)
|
loofah (2.19.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.7.1)
|
mail (2.7.1)
|
||||||
@@ -160,130 +160,129 @@ GEM
|
|||||||
matrix (0.4.2)
|
matrix (0.4.2)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
minitest (5.15.0)
|
mini_portile2 (2.8.0)
|
||||||
net-imap (0.2.3)
|
minitest (5.16.3)
|
||||||
digest
|
net-imap (0.3.1)
|
||||||
net-protocol
|
net-protocol
|
||||||
strscan
|
net-ldap (0.17.1)
|
||||||
net-ldap (0.17.0)
|
net-pop (0.1.2)
|
||||||
net-pop (0.1.1)
|
|
||||||
digest
|
|
||||||
net-protocol
|
net-protocol
|
||||||
|
net-protocol (0.1.3)
|
||||||
timeout
|
timeout
|
||||||
net-protocol (0.1.2)
|
net-smtp (0.3.3)
|
||||||
io-wait
|
|
||||||
timeout
|
|
||||||
net-smtp (0.3.1)
|
|
||||||
digest
|
|
||||||
net-protocol
|
net-protocol
|
||||||
timeout
|
|
||||||
nio4r (2.5.8)
|
nio4r (2.5.8)
|
||||||
nokogiri (1.13.1-x86_64-linux)
|
nokogiri (1.13.9)
|
||||||
|
mini_portile2 (~> 2.8.0)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.13.9-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
pg (1.2.3)
|
pg (1.2.3)
|
||||||
public_suffix (4.0.6)
|
public_suffix (5.0.0)
|
||||||
puma (4.3.11)
|
puma (4.3.12)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.6.0)
|
racc (1.6.0)
|
||||||
rack (2.2.3)
|
rack (2.2.4)
|
||||||
rack-test (1.1.0)
|
rack-test (2.0.2)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.3)
|
||||||
rails (7.0.2.2)
|
rails (7.0.4)
|
||||||
actioncable (= 7.0.2.2)
|
actioncable (= 7.0.4)
|
||||||
actionmailbox (= 7.0.2.2)
|
actionmailbox (= 7.0.4)
|
||||||
actionmailer (= 7.0.2.2)
|
actionmailer (= 7.0.4)
|
||||||
actionpack (= 7.0.2.2)
|
actionpack (= 7.0.4)
|
||||||
actiontext (= 7.0.2.2)
|
actiontext (= 7.0.4)
|
||||||
actionview (= 7.0.2.2)
|
actionview (= 7.0.4)
|
||||||
activejob (= 7.0.2.2)
|
activejob (= 7.0.4)
|
||||||
activemodel (= 7.0.2.2)
|
activemodel (= 7.0.4)
|
||||||
activerecord (= 7.0.2.2)
|
activerecord (= 7.0.4)
|
||||||
activestorage (= 7.0.2.2)
|
activestorage (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.0.2.2)
|
railties (= 7.0.4)
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.0.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.4.2)
|
rails-html-sanitizer (1.4.3)
|
||||||
loofah (~> 2.3)
|
loofah (~> 2.3)
|
||||||
railties (7.0.2.2)
|
railties (7.0.4)
|
||||||
actionpack (= 7.0.2.2)
|
actionpack (= 7.0.4)
|
||||||
activesupport (= 7.0.2.2)
|
activesupport (= 7.0.4)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.5)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
rb-fsevent (0.11.1)
|
rb-fsevent (0.11.2)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
redis (4.6.0)
|
redis (5.0.5)
|
||||||
regexp_parser (2.2.1)
|
redis-client (>= 0.9.0)
|
||||||
|
redis-client (0.11.2)
|
||||||
|
connection_pool
|
||||||
|
regexp_parser (2.6.1)
|
||||||
responders (3.0.1)
|
responders (3.0.1)
|
||||||
actionpack (>= 5.0)
|
actionpack (>= 5.0)
|
||||||
railties (>= 5.0)
|
railties (>= 5.0)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rqrcode (2.1.1)
|
rqrcode (2.1.2)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 1.0)
|
rqrcode_core (~> 1.0)
|
||||||
rqrcode_core (1.2.0)
|
rqrcode_core (1.2.0)
|
||||||
rspec-core (3.11.0)
|
rspec-core (3.12.0)
|
||||||
rspec-support (~> 3.11.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-expectations (3.11.0)
|
rspec-expectations (3.12.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.11.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-mocks (3.11.0)
|
rspec-mocks (3.12.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.11.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-rails (5.1.0)
|
rspec-rails (6.0.1)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 6.1)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 6.1)
|
||||||
rspec-core (~> 3.10)
|
rspec-core (~> 3.11)
|
||||||
rspec-expectations (~> 3.10)
|
rspec-expectations (~> 3.11)
|
||||||
rspec-mocks (~> 3.10)
|
rspec-mocks (~> 3.11)
|
||||||
rspec-support (~> 3.10)
|
rspec-support (~> 3.11)
|
||||||
rspec-support (3.11.0)
|
rspec-support (3.12.0)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rufus-scheduler (3.8.1)
|
rufus-scheduler (3.8.2)
|
||||||
fugit (~> 1.1, >= 1.1.6)
|
fugit (~> 1.1, >= 1.1.6)
|
||||||
sidekiq (6.4.1)
|
sidekiq (6.5.5)
|
||||||
connection_pool (>= 2.2.2)
|
connection_pool (>= 2.2.2)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
|
redis (>= 4.5.0)
|
||||||
|
sidekiq-scheduler (4.0.3)
|
||||||
redis (>= 4.2.0)
|
redis (>= 4.2.0)
|
||||||
sidekiq-scheduler (3.1.1)
|
|
||||||
e2mmap
|
|
||||||
redis (>= 3, < 5)
|
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 3)
|
sidekiq (>= 4, < 7)
|
||||||
thwait
|
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sprockets (4.0.2)
|
sprockets (4.1.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
sprockets-rails (3.4.2)
|
sprockets-rails (3.4.2)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
sqlite3 (1.4.2)
|
sqlite3 (1.5.4)
|
||||||
stimulus-rails (1.0.2)
|
mini_portile2 (~> 2.8.0)
|
||||||
|
sqlite3 (1.5.4-x86_64-linux)
|
||||||
|
stimulus-rails (1.2.1)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
strscan (3.0.1)
|
|
||||||
thor (1.2.1)
|
thor (1.2.1)
|
||||||
thwait (0.2.0)
|
tilt (2.0.11)
|
||||||
e2mmap
|
timeout (0.3.0)
|
||||||
tilt (2.0.10)
|
turbo-rails (1.3.2)
|
||||||
timeout (0.2.0)
|
|
||||||
turbo-rails (1.0.1)
|
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
|
activejob (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
tzinfo (2.0.4)
|
tzinfo (2.0.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
view_component (2.49.0)
|
view_component (2.78.0)
|
||||||
activesupport (>= 5.0.0, < 8.0)
|
activesupport (>= 5.0.0, < 8.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
@@ -292,7 +291,7 @@ GEM
|
|||||||
activemodel (>= 6.0.0)
|
activemodel (>= 6.0.0)
|
||||||
bindex (>= 0.4.0)
|
bindex (>= 0.4.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
webmock (3.14.0)
|
webmock (3.18.1)
|
||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
@@ -301,12 +300,14 @@ GEM
|
|||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.5.4)
|
zeitwerk (2.6.6)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
ruby
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
byebug (~> 11.1)
|
||||||
capybara
|
capybara
|
||||||
cssbundling-rails
|
cssbundling-rails
|
||||||
database_cleaner
|
database_cleaner
|
||||||
@@ -314,6 +315,7 @@ DEPENDENCIES
|
|||||||
devise_ldap_authenticatable
|
devise_ldap_authenticatable
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
|
faker
|
||||||
faraday
|
faraday
|
||||||
importmap-rails
|
importmap-rails
|
||||||
jbuilder (~> 2.7)
|
jbuilder (~> 2.7)
|
||||||
@@ -327,7 +329,7 @@ DEPENDENCIES
|
|||||||
rails (~> 7.0.2)
|
rails (~> 7.0.2)
|
||||||
rqrcode (~> 2.0)
|
rqrcode (~> 2.0)
|
||||||
rspec-rails
|
rspec-rails
|
||||||
sidekiq
|
sidekiq (< 7)
|
||||||
sidekiq-scheduler
|
sidekiq-scheduler
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
sqlite3 (~> 1.4)
|
sqlite3 (~> 1.4)
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
web: bin/rails server -p 3000
|
web: bin/rails server -b 0.0.0.0 -p 3000
|
||||||
css: yarn build:css --watch
|
css: yarn build:css --watch
|
||||||
|
|||||||
51
README.md
@@ -7,6 +7,27 @@ credentials, invites, donations, etc..
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
|
||||||
|
The easiest way to get a working development setup is using Docker Compose like
|
||||||
|
so:
|
||||||
|
|
||||||
|
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
||||||
|
Docker Desktop)
|
||||||
|
2. Uncomment the `web` section in `docker-compose.yml`
|
||||||
|
3. Run `docker compose up` and wait until 389ds announces its successful start
|
||||||
|
in the log output
|
||||||
|
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
||||||
|
5. `docker compose run web rails ldap:setup`
|
||||||
|
5. `docker compose run web rails db:setup`
|
||||||
|
|
||||||
|
After these steps, you should have a working Rails app with a handful of test
|
||||||
|
users running on [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
|
Log in with username "admin" and password "admin is admin". All users listed on
|
||||||
|
[http://localhost:3000/admin/ldap_users](http://localhost:3000/admin/ldap_users)
|
||||||
|
have the password "user is user".
|
||||||
|
|
||||||
### Rails app
|
### Rails app
|
||||||
|
|
||||||
Installing dependencies:
|
Installing dependencies:
|
||||||
@@ -31,10 +52,32 @@ Running all specs:
|
|||||||
|
|
||||||
bundle exec rspec
|
bundle exec rspec
|
||||||
|
|
||||||
### LDAP server
|
### Docker (Compose)
|
||||||
|
|
||||||
TODO make it easy to run a local Kosmos LDAP server for development, without
|
There is a working Docker Compose config file, which allows you to spin up both
|
||||||
manual LDIF imports etc. (or provide a staging instance)
|
an app server for Rails as well as a local 389ds (LDAP) server.
|
||||||
|
|
||||||
|
By default, `docker-compose up` will only start the LDAP server, listening on
|
||||||
|
port 389 on your machine. Uncomment other services in `docker-compose.yml` if
|
||||||
|
you want to use them.
|
||||||
|
|
||||||
|
#### LDAP server
|
||||||
|
|
||||||
|
After creating the Docker container for the first time (or after deleting it),
|
||||||
|
you need to run the following command once, in order to create the dirsrv
|
||||||
|
back-end:
|
||||||
|
|
||||||
|
docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"
|
||||||
|
|
||||||
|
Now you can seed the back-end with data using this Rails task:
|
||||||
|
|
||||||
|
bundle exec rails ldap:setup
|
||||||
|
|
||||||
|
The setup task will first delete any existing entries in the directory tree
|
||||||
|
("dc=kosmos,dc=org"), and then create our development entries.
|
||||||
|
|
||||||
|
Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over
|
||||||
|
with a fresh installation, delete both that directory as well as the container.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@@ -63,3 +106,5 @@ manual LDIF imports etc. (or provide a staging instance)
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)
|
[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)
|
||||||
|
|
||||||
|
[1]: https://docs.docker.com/compose/install/
|
||||||
|
|||||||
@@ -7,3 +7,4 @@
|
|||||||
@import "components/forms";
|
@import "components/forms";
|
||||||
@import "components/links";
|
@import "components/links";
|
||||||
@import "components/notifications";
|
@import "components/notifications";
|
||||||
|
@import "components/tables";
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
body {
|
html {
|
||||||
@apply leading-none
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* h1, h2, h3 { */
|
body {
|
||||||
/* @apply font-light; */
|
@apply leading-none bg-cover bg-fixed;
|
||||||
/* } */
|
background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%), url('/img/bg-1.jpg');
|
||||||
|
}
|
||||||
|
|
||||||
|
body#admin {
|
||||||
|
background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%), url('/img/bg-1.jpg');
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-3xl uppercase;
|
@apply text-3xl uppercase;
|
||||||
@@ -26,4 +31,16 @@
|
|||||||
main section:first-of-type {
|
main section:first-of-type {
|
||||||
@apply pt-0;
|
@apply pt-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main p {
|
||||||
|
@apply mb-4 leading-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
main ul {
|
||||||
|
@apply mb-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
main ul li {
|
||||||
|
@apply leading-6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
app/assets/stylesheets/components/tables.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@layer components {
|
||||||
|
table {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead tr {
|
||||||
|
@apply text-left;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
@apply pb-3.5 text-sm font-normal uppercase text-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th:not(:last-of-type),
|
||||||
|
table td:not(:last-of-type) {
|
||||||
|
@apply pr-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
@apply py-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
@import "legacy/layout";
|
|
||||||
@import "legacy/main_nav";
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
@import "variables";
|
|
||||||
@import "mediaqueries";
|
|
||||||
|
|
||||||
body {
|
|
||||||
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;
|
|
||||||
background-attachment: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
body#admin {
|
|
||||||
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%),
|
|
||||||
url('/img/bg-1.jpg');
|
|
||||||
background-size: cover;
|
|
||||||
background-attachment: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ks-site-icon {
|
|
||||||
svg {
|
|
||||||
display: inline-block;
|
|
||||||
height: 1.875rem;
|
|
||||||
vertical-align: top;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#wrapper {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> header {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 4rem 0;
|
|
||||||
text-align: center;
|
|
||||||
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 {
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
span.project-name {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.current-user {
|
|
||||||
color: rgba(255,255,255,0.6);
|
|
||||||
|
|
||||||
@include media-max(small) {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgba(255,255,255,0.6);
|
|
||||||
transition: color 0.1s linear;
|
|
||||||
|
|
||||||
&:hover, &:active {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
p {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
&.notice {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
|
|
||||||
li {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
&.services {
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
grid-row-gap: 1rem;
|
|
||||||
grid-column-gap: 2rem;
|
|
||||||
|
|
||||||
@include media-max(small) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
$breakpoints-max: (
|
|
||||||
small: 600px,
|
|
||||||
medium: 960px,
|
|
||||||
large: 1280px
|
|
||||||
);
|
|
||||||
|
|
||||||
$breakpoints-min: (
|
|
||||||
small: 601px,
|
|
||||||
medium: 961px,
|
|
||||||
large: 1281px
|
|
||||||
);
|
|
||||||
|
|
||||||
@mixin media-max($screen-size) {
|
|
||||||
@if map-has-key($breakpoints-max, $screen-size) {
|
|
||||||
@media (max-width: map-get($breakpoints-max, $screen-size)) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
// Debugging
|
|
||||||
@warn "'#{$screen-size}' has not been declared as a breakpoint."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin media-min($screen-size) {
|
|
||||||
@if map-has-key($breakpoints-min, $screen-size) {
|
|
||||||
@media (min-width: map-get($breakpoints-min, $screen-size)) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
// Debugging
|
|
||||||
@warn "'#{$screen-size}' has not been declared as a breakpoint."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
$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;
|
|
||||||
18
app/components/wallet_summary_component.html.erb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<section class="w-full grid grid-cols-1 md:grid-cols-12 md:mb-0">
|
||||||
|
<div class="md:col-span-8">
|
||||||
|
<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 mb-0 p-4 border border-gray-300 rounded-lg overflow-hidden">
|
||||||
|
<% if @balance %>
|
||||||
|
<span class="text-xl"><%= number_with_delimiter @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>
|
||||||
8
app/components/wallet_summary_component.rb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class WalletSummaryComponent < ViewComponent::Base
|
||||||
|
def initialize(balance:)
|
||||||
|
@balance = balance
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -27,7 +27,7 @@ class Admin::LdapUsersController < Admin::BaseController
|
|||||||
def ldap_client
|
def ldap_client
|
||||||
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
||||||
port: ldap_config['port'],
|
port: ldap_config['port'],
|
||||||
encryption: ldap_config['ssl'],
|
# encryption: ldap_config['ssl'],
|
||||||
auth: {
|
auth: {
|
||||||
method: :simple,
|
method: :simple,
|
||||||
username: ldap_config['admin_user'],
|
username: ldap_config['admin_user'],
|
||||||
|
|||||||
5
app/controllers/api/base_controller.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class Api::BaseController < ApplicationController
|
||||||
|
|
||||||
|
layout false
|
||||||
|
|
||||||
|
end
|
||||||
13
app/controllers/api/kredits_controller.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class Api::KreditsController < Api::BaseController
|
||||||
|
|
||||||
|
def onchain_btc_balance
|
||||||
|
btcpay = BtcPay.new
|
||||||
|
balance = btcpay.onchain_wallet_balance
|
||||||
|
render json: balance
|
||||||
|
rescue => error
|
||||||
|
Rails.logger.warn "Failed to fetch kredits BTC wallet balance: #{error.message}"
|
||||||
|
render json: { error: 'Failed to fetch wallet balance' },
|
||||||
|
status: 500
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
class LnurlpayController < ApplicationController
|
class LnurlpayController < ApplicationController
|
||||||
before_action :find_user_by_address
|
before_action :find_user_by_address
|
||||||
|
|
||||||
MIN_SATS = 100
|
MIN_SATS = 10
|
||||||
MAX_SATS = 1_000_000
|
MAX_SATS = 1_000_000
|
||||||
MAX_COMMENT_CHARS = 100
|
MAX_COMMENT_CHARS = 100
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ require "rqrcode"
|
|||||||
class WalletController < ApplicationController
|
class WalletController < ApplicationController
|
||||||
before_action :require_user_signed_in
|
before_action :require_user_signed_in
|
||||||
before_action :authenticate_with_lndhub
|
before_action :authenticate_with_lndhub
|
||||||
|
before_action :set_current_section
|
||||||
|
before_action :fetch_balance
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@current_section = :wallet
|
|
||||||
|
|
||||||
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
||||||
|
|
||||||
qrcode = RQRCode::QRCode.new(@wallet_url)
|
qrcode = RQRCode::QRCode.new(@wallet_url)
|
||||||
@@ -20,28 +20,71 @@ class WalletController < ApplicationController
|
|||||||
class: 'inline-block'
|
class: 'inline-block'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@balance = fetch_balance rescue nil
|
def transactions
|
||||||
|
@transactions = fetch_transactions
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def authenticate_with_lndhub
|
def authenticate_with_lndhub(options={})
|
||||||
if session["ln_auth_token"].present?
|
if session[:ln_auth_token].present? && !options[:force_reauth]
|
||||||
@ln_auth_token = session["ln_auth_token"]
|
@ln_auth_token = session[:ln_auth_token]
|
||||||
else
|
else
|
||||||
lndhub = Lndhub.new
|
lndhub = Lndhub.new
|
||||||
auth_token = lndhub.authenticate(current_user)
|
auth_token = lndhub.authenticate(current_user)
|
||||||
session["ln_auth_token"] = auth_token
|
session[:ln_auth_token] = auth_token
|
||||||
@ln_auth_token = auth_token
|
@ln_auth_token = auth_token
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
# TODO add exception tracking
|
# TODO add exception tracking
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_current_section
|
||||||
|
@current_section = :wallet
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_balance
|
def fetch_balance
|
||||||
lndhub = Lndhub.new
|
lndhub = Lndhub.new
|
||||||
data = lndhub.balance @ln_auth_token
|
data = lndhub.balance @ln_auth_token
|
||||||
data["BTC"]["AvailableBalance"]
|
@balance = data["BTC"]["AvailableBalance"] rescue nil
|
||||||
|
rescue
|
||||||
|
authenticate_with_lndhub(force_reauth: true)
|
||||||
|
return nil if @fetch_balance_retried
|
||||||
|
@fetch_balance_retried = true
|
||||||
|
fetch_balance
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_transactions
|
||||||
|
lndhub = Lndhub.new
|
||||||
|
txs = lndhub.gettxs @ln_auth_token
|
||||||
|
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
|
||||||
|
process_transactions(txs + invoices)
|
||||||
|
rescue
|
||||||
|
authenticate_with_lndhub(force_reauth: true)
|
||||||
|
return [] if @fetch_transactions_retried
|
||||||
|
@fetch_transactions_retried = true
|
||||||
|
fetch_transactions
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_transactions(txs)
|
||||||
|
txs.collect do |tx|
|
||||||
|
if tx["type"] == "bitcoind_tx"
|
||||||
|
tx["amount_sats"] = (tx["amount"] * 100000000).to_i
|
||||||
|
tx["datetime"] = Time.at(tx["time"].to_i)
|
||||||
|
tx["title"] = "Received"
|
||||||
|
tx["description"] = "On-chain topup"
|
||||||
|
tx["received"] = true
|
||||||
|
else
|
||||||
|
tx["amount_sats"] = tx["value"] || tx["amt"]
|
||||||
|
tx["datetime"] = Time.at(tx["timestamp"].to_i)
|
||||||
|
tx["title"] = tx["type"] == "paid_invoice" ? "Sent" : "Received"
|
||||||
|
tx["description"] = tx["memo"] || tx["description"]
|
||||||
|
tx["received"] = tx["type"] == "user_invoice"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
txs.sort{ |a,b| b["datetime"] <=> a["datetime"] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
40
app/controllers/webhooks_controller.rb
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
class WebhooksController < ApplicationController
|
||||||
|
skip_forgery_protection
|
||||||
|
|
||||||
|
before_action :authorize_request
|
||||||
|
|
||||||
|
def lndhub
|
||||||
|
begin
|
||||||
|
payload = JSON.parse(request.body.read, symbolize_names: true)
|
||||||
|
head :no_content and return unless payload[:type] == "incoming"
|
||||||
|
rescue
|
||||||
|
head :unprocessable_entity and return
|
||||||
|
end
|
||||||
|
|
||||||
|
user = User.find_by(ln_account: payload[:user_login])
|
||||||
|
|
||||||
|
# TODO make configurable
|
||||||
|
notify_xmpp(user.address, payload[:amount], payload[:memo])
|
||||||
|
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def notify_xmpp(address, amt_sats, memo)
|
||||||
|
payload = {
|
||||||
|
type: "normal",
|
||||||
|
from: "kosmos.org", # TODO domain config
|
||||||
|
to: address,
|
||||||
|
subject: "Sats received!",
|
||||||
|
body: "#{amt_sats} sats received in your wallet. Memo: \"#{memo}\""
|
||||||
|
}
|
||||||
|
XmppSendMessageJob.perform_later(payload)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_request
|
||||||
|
if !ENV['WEBHOOKS_ALLOWED_IPS'].split(',').include?(request.remote_ip)
|
||||||
|
head :forbidden and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -18,7 +18,7 @@ class CreateLdapUserJob < ApplicationJob
|
|||||||
def ldap_client
|
def ldap_client
|
||||||
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
||||||
port: ldap_config['port'],
|
port: ldap_config['port'],
|
||||||
encryption: ldap_config['ssl'],
|
# encryption: ldap_config['ssl'],
|
||||||
auth: {
|
auth: {
|
||||||
method: :simple,
|
method: :simple,
|
||||||
username: ldap_config['admin_user'],
|
username: ldap_config['admin_user'],
|
||||||
|
|||||||
14
app/jobs/create_lndhub_account_job.rb
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
class CreateLndhubAccountJob < ApplicationJob
|
||||||
|
queue_as :default
|
||||||
|
|
||||||
|
def perform(user)
|
||||||
|
return if user.ln_login.present? && user.ln_password.present?
|
||||||
|
|
||||||
|
lndhub = LndhubV2.new
|
||||||
|
credentials = lndhub.create_account
|
||||||
|
|
||||||
|
user.update! ln_account: credentials["login"],
|
||||||
|
ln_login: credentials["login"], # TODO remove when production is migrated
|
||||||
|
ln_password: credentials["password"]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
class ExchangeXmppContactsJob < ApplicationJob
|
class XmppExchangeContactsJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :default
|
||||||
|
|
||||||
def perform(inviter, username, domain)
|
def perform(inviter, username, domain)
|
||||||
8
app/jobs/xmpp_send_message_job.rb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
class XmppSendMessageJob < ApplicationJob
|
||||||
|
queue_as :default
|
||||||
|
|
||||||
|
def perform(payload)
|
||||||
|
ejabberd = EjabberdApiClient.new
|
||||||
|
ejabberd.send_message payload
|
||||||
|
end
|
||||||
|
end
|
||||||
32
app/services/btc_pay.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#
|
||||||
|
# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/
|
||||||
|
#
|
||||||
|
class BtcPay
|
||||||
|
def initialize
|
||||||
|
@base_url = ENV["BTCPAY_API_URL"]
|
||||||
|
@store_id = Rails.application.credentials.btcpay[:store_id]
|
||||||
|
@auth_token = Rails.application.credentials.btcpay[:auth_token]
|
||||||
|
end
|
||||||
|
|
||||||
|
def onchain_wallet_balance
|
||||||
|
res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet"
|
||||||
|
|
||||||
|
{
|
||||||
|
balance: res["balance"].to_f,
|
||||||
|
unconfirmed_balance: res["unconfirmedBalance"].to_f,
|
||||||
|
confirmed_balance: res["confirmedBalance"].to_f
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def get(endpoint)
|
||||||
|
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
|
||||||
|
"Content-Type" => "application/json",
|
||||||
|
"Accept" => "application/json",
|
||||||
|
"Authorization" => "token #{@auth_token}"
|
||||||
|
})
|
||||||
|
|
||||||
|
JSON.parse(res.body)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -5,12 +5,13 @@ class CreateAccount < ApplicationService
|
|||||||
@email = args[:email]
|
@email = args[:email]
|
||||||
@password = args[:password]
|
@password = args[:password]
|
||||||
@invitation = args[:invitation]
|
@invitation = args[:invitation]
|
||||||
|
@confirmed = args[:confirmed]
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
user = create_user_in_database
|
user = create_user_in_database
|
||||||
add_ldap_document
|
add_ldap_document
|
||||||
create_lndhub_wallet(user)
|
create_lndhub_account(user)
|
||||||
|
|
||||||
if @invitation.present?
|
if @invitation.present?
|
||||||
update_invitation(user.id)
|
update_invitation(user.id)
|
||||||
@@ -26,7 +27,8 @@ class CreateAccount < ApplicationService
|
|||||||
ou: @domain,
|
ou: @domain,
|
||||||
email: @email,
|
email: @email,
|
||||||
password: @password,
|
password: @password,
|
||||||
password_confirmation: @password
|
password_confirmation: @password,
|
||||||
|
confirmed_at: @confirmed ? DateTime.now : nil
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -35,6 +37,7 @@ class CreateAccount < ApplicationService
|
|||||||
end
|
end
|
||||||
|
|
||||||
# TODO move to confirmation
|
# TODO move to confirmation
|
||||||
|
# (and/or add email_confirmed to entry and use in login filter)
|
||||||
def add_ldap_document
|
def add_ldap_document
|
||||||
hashed_pw = Devise.ldap_auth_password_builder.call(@password)
|
hashed_pw = Devise.ldap_auth_password_builder.call(@password)
|
||||||
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
|
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
|
||||||
@@ -43,24 +46,12 @@ class CreateAccount < ApplicationService
|
|||||||
def exchange_xmpp_contacts
|
def exchange_xmpp_contacts
|
||||||
#TODO enable in development when we have easy setup of ejabberd etc.
|
#TODO enable in development when we have easy setup of ejabberd etc.
|
||||||
return if Rails.env.development?
|
return if Rails.env.development?
|
||||||
ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain)
|
XmppExchangeContactsJob.perform_later(@invitation.user, @username, @domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_lndhub_wallet(user)
|
def create_lndhub_account(user)
|
||||||
CreateLndhubWalletJob.perform_later(user)
|
#TODO enable in development when we have a local lndhub (mock?) API
|
||||||
end
|
return if Rails.env.development?
|
||||||
|
CreateLndhubAccountJob.perform_later(user)
|
||||||
def exchange_xmpp_contacts_between_inviter_and_invitee
|
|
||||||
ejabberd = EjabberdApiClient.new
|
|
||||||
|
|
||||||
EjabberdApiClient.add_roster_item({
|
|
||||||
"localuser": @username,
|
|
||||||
"localhost": @domain,
|
|
||||||
"user": @inviter.cn,
|
|
||||||
"host": @inviter.ou,
|
|
||||||
"nick": @username,
|
|
||||||
"group": "Friends",
|
|
||||||
"subs": "both"
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,4 +17,8 @@ class EjabberdApiClient
|
|||||||
def add_rosteritem(payload)
|
def add_rosteritem(payload)
|
||||||
post "add_rosteritem", payload
|
post "add_rosteritem", payload
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def send_message(payload)
|
||||||
|
post "send_message", payload
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
135
app/services/ldap_service.rb
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
class LdapService < ApplicationService
|
||||||
|
def initialize
|
||||||
|
@suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_entry(dn, attrs, interactive=false)
|
||||||
|
puts "Adding entry: #{dn}" if interactive
|
||||||
|
res = ldap_client.add dn: dn, attributes: attrs
|
||||||
|
puts res.inspect if interactive && !res
|
||||||
|
res
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_attribute(dn, attr, value)
|
||||||
|
ldap_client.add_attribute dn, attr, value
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_entry(dn, interactive=false)
|
||||||
|
puts "Deleting entry: #{dn}" if interactive
|
||||||
|
res = ldap_client.delete dn: dn
|
||||||
|
puts res.inspect if interactive && !res
|
||||||
|
res
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_all_entries!
|
||||||
|
if Rails.env.production?
|
||||||
|
raise "Mass deletion of entries not allowed in production"
|
||||||
|
end
|
||||||
|
|
||||||
|
filter = Net::LDAP::Filter.eq("objectClass", "*")
|
||||||
|
entries = ldap_client.search(base: @suffix, filter: filter, attributes: %w{dn})
|
||||||
|
entries.sort_by!{ |e| e.dn.length }.reverse!
|
||||||
|
|
||||||
|
entries.each do |e|
|
||||||
|
delete_entry e.dn, true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_users(args={})
|
||||||
|
if args[:ou]
|
||||||
|
treebase = "ou=#{args[:ou]},cn=users,#{@suffix}"
|
||||||
|
else
|
||||||
|
treebase = ldap_config["base"]
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes = %w{dn cn uid mail admin}
|
||||||
|
filter = Net::LDAP::Filter.eq("uid", "*")
|
||||||
|
|
||||||
|
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
|
||||||
|
entries.sort_by! { |e| e.cn[0] }
|
||||||
|
|
||||||
|
entries = entries.collect do |e|
|
||||||
|
{
|
||||||
|
uid: e.uid.first,
|
||||||
|
mail: e.try(:mail) ? e.mail.first : nil,
|
||||||
|
admin: e.try(:admin) ? 'admin' : nil
|
||||||
|
# password: e.userpassword.first
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_organizations
|
||||||
|
attributes = %w{dn ou description}
|
||||||
|
filter = Net::LDAP::Filter.eq("objectClass", "organizationalUnit")
|
||||||
|
# filter = Net::LDAP::Filter.eq("objectClass", "*")
|
||||||
|
treebase = "cn=users,#{@suffix}"
|
||||||
|
|
||||||
|
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
|
||||||
|
|
||||||
|
entries.sort_by! { |e| e.ou[0] }
|
||||||
|
|
||||||
|
entries = entries.collect do |e|
|
||||||
|
{
|
||||||
|
dn: e.dn,
|
||||||
|
ou: e.ou.first,
|
||||||
|
description: e.try(:description) ? e.description.first : nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_organization(ou, description, interactive=false)
|
||||||
|
dn = "ou=#{ou},cn=users,#{@suffix}"
|
||||||
|
|
||||||
|
aci = <<-EOS
|
||||||
|
(target="ldap:///cn=*,ou=#{ou},cn=users,#{@suffix}")(targetattr="cn || sn || uid || mail || userPassword || nsRole || objectClass") (version 3.0; acl "service-#{ou.gsub(".", "-")}-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=#{ou},cn=applications,#{@suffix}";)
|
||||||
|
EOS
|
||||||
|
|
||||||
|
attrs = {
|
||||||
|
objectClass: ["top", "organizationalUnit"],
|
||||||
|
description: description,
|
||||||
|
ou: ou,
|
||||||
|
aci: aci
|
||||||
|
}
|
||||||
|
|
||||||
|
add_entry dn, attrs, interactive
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_directory!
|
||||||
|
if Rails.env.production?
|
||||||
|
raise "Resetting the directory not allowed in production"
|
||||||
|
end
|
||||||
|
|
||||||
|
delete_all_entries!
|
||||||
|
|
||||||
|
user_read_aci = <<-EOS
|
||||||
|
(target="ldap:///#{@suffix}")(targetattr="*") (version 3.0; acl "user-read-search-own-attributes"; allow (read,search) userdn="ldap:///self";)
|
||||||
|
EOS
|
||||||
|
|
||||||
|
add_entry @suffix, {
|
||||||
|
dc: "kosmos", objectClass: ["top", "domain"], aci: user_read_aci
|
||||||
|
}, true
|
||||||
|
|
||||||
|
add_entry "cn=users,#{@suffix}", {
|
||||||
|
cn: "users", objectClass: ["top", "organizationalRole"]
|
||||||
|
}, true
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ldap_client
|
||||||
|
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
||||||
|
port: ldap_config['port'],
|
||||||
|
# TODO has to be :simple_tls if TLS is enabled
|
||||||
|
# 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
|
||||||
@@ -2,7 +2,7 @@ class Lndhub
|
|||||||
attr_accessor :auth_token
|
attr_accessor :auth_token
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@base_url = ENV["LNDHUB_API_URL"]
|
@base_url = ENV["LNDHUB_LEGACY_API_URL"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def post(endpoint, payload)
|
def post(endpoint, payload)
|
||||||
@@ -28,8 +28,13 @@ class Lndhub
|
|||||||
"Accept" => "application/json",
|
"Accept" => "application/json",
|
||||||
"Authorization" => "Bearer #{auth_token}"
|
"Authorization" => "Bearer #{auth_token}"
|
||||||
})
|
})
|
||||||
|
data = JSON.parse(res.body)
|
||||||
|
|
||||||
JSON.parse(res.body)
|
if data.is_a?(Hash) && data["error"] && data["message"] == "bad auth"
|
||||||
|
raise "BAD_AUTH"
|
||||||
|
else
|
||||||
|
data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(payload)
|
def create(payload)
|
||||||
@@ -42,10 +47,18 @@ class Lndhub
|
|||||||
self.auth_token
|
self.auth_token
|
||||||
end
|
end
|
||||||
|
|
||||||
def balance(user_token)
|
def balance(user_token=nil)
|
||||||
get "balance", user_token || auth_token
|
get "balance", user_token || auth_token
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def gettxs(user_token=nil)
|
||||||
|
get "gettxs", user_token || auth_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def getuserinvoices(user_token=nil)
|
||||||
|
get "getuserinvoices", user_token || auth_token
|
||||||
|
end
|
||||||
|
|
||||||
def addinvoice(payload)
|
def addinvoice(payload)
|
||||||
invoice = post "addinvoice", {
|
invoice = post "addinvoice", {
|
||||||
amt: payload[:amount],
|
amt: payload[:amount],
|
||||||
|
|||||||
81
app/services/lndhub_v2.rb
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
class LndhubV2
|
||||||
|
attr_accessor :auth_token
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@base_url = ENV["LNDHUB_API_URL"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def post(endpoint, payload, options={})
|
||||||
|
headers = { "Content-Type" => "application/json" }
|
||||||
|
if auth_token
|
||||||
|
headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
|
||||||
|
elsif options[:admin_token]
|
||||||
|
headers.merge!({ "Authorization" => "Bearer #{options[:admin_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=nil)
|
||||||
|
get "balance", user_token || auth_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def gettxs(user_token)
|
||||||
|
get "gettxs", user_token || auth_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def getuserinvoices(user_token)
|
||||||
|
get "getuserinvoices", user_token || auth_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def addinvoice(payload)
|
||||||
|
invoice = post "addinvoice", {
|
||||||
|
amt: payload[:amount],
|
||||||
|
memo: payload[:memo],
|
||||||
|
description_hash: payload[:description_hash]
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice["payment_request"]
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# V2
|
||||||
|
#
|
||||||
|
|
||||||
|
def create_account(payload={})
|
||||||
|
post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token]
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_invoice(payload)
|
||||||
|
# Payload: { amount: 1000, description: "", description_hash: "" }
|
||||||
|
post "v2/invoices", payload
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<% if @donations.any? %>
|
<% if @donations.any? %>
|
||||||
<table class="w-full">
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left">
|
<tr>
|
||||||
<th>User</th>
|
<th>User</th>
|
||||||
<th>Amount BTC</th>
|
<th class="text-right">Amount BTC</th>
|
||||||
<th>in EUR</th>
|
<th class="text-right">in EUR</th>
|
||||||
<th>in USD</th>
|
<th class="text-right">in USD</th>
|
||||||
<th>Public name</th>
|
<th class="pl-2">Public name</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th colspan="3"></th>
|
<th colspan="3"></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -19,10 +19,10 @@
|
|||||||
<% @donations.each do |donation| %>
|
<% @donations.each do |donation| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= donation.user.address %></td>
|
<td><%= donation.user.address %></td>
|
||||||
<td><%= sats_to_btc donation.amount_sats %> BTC</td>
|
<td class="text-right"><%= sats_to_btc donation.amount_sats %></td>
|
||||||
<td><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td>
|
<td class="text-right"><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td>
|
||||||
<td><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td>
|
<td class="text-right"><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td>
|
||||||
<td><%= donation.public_name %></td>
|
<td class="pl-2"><%= donation.public_name %></td>
|
||||||
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></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 '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 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<h3>Accepted (<%= @invitations_used.length %>)</h3>
|
<h3>Accepted (<%= @invitations_used.length %>)</h3>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left">
|
<tr>
|
||||||
<th>Token</th>
|
<th>Token</th>
|
||||||
<th>Accepted</th>
|
<th>Accepted</th>
|
||||||
<th>Invited user</th>
|
<th>Invited user</th>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left">
|
<tr>
|
||||||
<th>UID</th>
|
<th>UID</th>
|
||||||
<th>E-Mail</th>
|
<th>E-Mail</th>
|
||||||
<th>Admin</th>
|
<th>Admin</th>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
Your Kosmos account and password currently give you access to these
|
Your Kosmos account and password currently give you access to these
|
||||||
services:
|
services:
|
||||||
</p>
|
</p>
|
||||||
<div class="grid services mt-12">
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 services mt-12">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-3.5">
|
<h3 class="mb-3.5">
|
||||||
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
|
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-3.5">
|
<h3 class="mb-3.5">
|
||||||
<span class="text-yellow-500">🗲</span>
|
<%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 inline" } %>
|
||||||
<%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %>
|
<%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-500">
|
<p class="text-gray-500">
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link-2"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link-2 <%= custom_class %>"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 376 B |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link <%= custom_class %>"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 392 B |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap <%= custom_class %>"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 303 B |
@@ -8,7 +8,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<table class="mt-12">
|
<table class="mt-12">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left">
|
<tr>
|
||||||
<th>URL</th>
|
<th>URL</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<h3>Accepted Invitations</h3>
|
<h3>Accepted Invitations</h3>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left">
|
<tr>
|
||||||
<th class="hidden md:block">ID</th>
|
<th class="hidden md:block">ID</th>
|
||||||
<th>Accepted</th>
|
<th>Accepted</th>
|
||||||
<th>Invited user</th>
|
<th>Invited user</th>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
<%= csp_meta_tag %>
|
<%= csp_meta_tag %>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet">
|
<link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet">
|
||||||
<%= stylesheet_link_tag 'legacy', "data-turbo-track": "reload" %>
|
|
||||||
<%= stylesheet_link_tag 'application', "data-turbo-track": "reload" %>
|
<%= stylesheet_link_tag 'application', "data-turbo-track": "reload" %>
|
||||||
<%= javascript_importmap_tags %>
|
<%= javascript_importmap_tags %>
|
||||||
</head>
|
</head>
|
||||||
@@ -18,7 +17,7 @@
|
|||||||
<div class="flex items-center justify-between h-16 px-4 sm:px-0">
|
<div class="flex items-center justify-between h-16 px-4 sm:px-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="ks-site-icon flex-shrink-0">
|
<div class="ks-site-icon flex-shrink-0">
|
||||||
<%= render partial: "shared/icons/comet" %>
|
<%= render partial: "shared/icons/comet", locals: { custom_class: "inline-block align-top w-auto h-7" } %>
|
||||||
</div>
|
</div>
|
||||||
<% if user_signed_in? && current_user.confirmed? %>
|
<% if user_signed_in? && current_user.confirmed? %>
|
||||||
<div class="hidden md:block">
|
<div class="hidden md:block">
|
||||||
|
|||||||
@@ -1 +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>
|
<svg id="icon-comet" class="<%= custom_class %>" 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>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 627 B After Width: | Height: | Size: 655 B |
@@ -1,22 +1,14 @@
|
|||||||
<%= render HeaderComponent.new(title: "Wallet") %>
|
<%= render HeaderComponent.new(title: "Wallet") %>
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<section class="w-full grid grid-cols-1 md:grid-cols-12 md:mb-0">
|
<%= render WalletSummaryComponent.new(balance: @balance) %>
|
||||||
<div class="md:col-span-8">
|
|
||||||
<p>
|
<section>
|
||||||
Send and receive sats via the Bitcoin Lightning Network.
|
<div class="border-b border-gray-200">
|
||||||
</p>
|
<nav class="-mb-px flex" aria-label="Tabs">
|
||||||
</div>
|
<%= link_to "Info", wallet_path, class: "border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
|
||||||
<div class="md:col-span-4 mt-4 md:mt-0">
|
<%= link_to "Transactions", wallet_transactions_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2" %>
|
||||||
<p class="font-mono md:text-right mb-0 p-4 border border-gray-300 rounded-lg overflow-hidden">
|
</nav>
|
||||||
<% if @balance %>
|
|
||||||
<span class="text-xl"><%= number_with_delimiter @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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
57
app/views/wallet/transactions.html.erb
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<%= render HeaderComponent.new(title: "Wallet") %>
|
||||||
|
|
||||||
|
<%= render MainSimpleComponent.new do %>
|
||||||
|
<%= render WalletSummaryComponent.new(balance: @balance) %>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="border-b border-gray-200">
|
||||||
|
<nav class="-mb-px flex" aria-label="Tabs">
|
||||||
|
<%= link_to "Info", wallet_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2" %>
|
||||||
|
<%= link_to "Transactions", wallet_transactions_path, class: "border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3 class="hidden">Transactions</h3>
|
||||||
|
|
||||||
|
<% if @transactions.any? %>
|
||||||
|
<ul class="list-none">
|
||||||
|
<% @transactions.each do |tx| %>
|
||||||
|
<li class="py-4 md:py-4 grid gap-y-1 gap-x-2 grid-cols-4 border-b border-dotted border-gray-300">
|
||||||
|
<h3 class="col-span-2 md:col-span-3 mb-0">
|
||||||
|
<% if tx["type"] == "bitcoind_tx" %>
|
||||||
|
<span class="inline-block">
|
||||||
|
<%= render partial: "icons/link-2", locals: { custom_class: "text-emerald-500 h-4 w-4 mr-0.5" } %>
|
||||||
|
</span>
|
||||||
|
<% else %>
|
||||||
|
<span class="inline-block">
|
||||||
|
<%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 mr-0.5" } %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<%= tx["title"] %>
|
||||||
|
</h3>
|
||||||
|
<p class="col-span-2 md:col-span-1 mb-0 text-right">
|
||||||
|
<span class="text-xl font-mono <%= tx["received"] ? "text-emerald-600" : "" %>">
|
||||||
|
<%= tx["received"] ? "+" : "" %><%= number_with_delimiter tx["amount_sats"] %>
|
||||||
|
<span class="hidden md:inline">sats</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p class="col-span-4 md:col-span-3 mb-0 text-gray-500">
|
||||||
|
<%= tx["description"].present? ? tx["description"] : raw("<span class='text-gray-400'>No memo</span>") %>
|
||||||
|
</p>
|
||||||
|
<p class="col-span-4 md:col-span-1 md:text-right mb-0">
|
||||||
|
<span class="col-span-2 md:col-span-1 text-sm text-gray-500">
|
||||||
|
<%= tx["datetime"].strftime("%B %e, %H:%M") %>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% else %>
|
||||||
|
<p class="text-gray-500">
|
||||||
|
No transactions yet. As soon as you start receiving sats, you will find some entries here.
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</section>
|
||||||
|
<% end %>
|
||||||
@@ -1 +1 @@
|
|||||||
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==
|
yEs5CyuAbqphlDWgtw/YQvkPn+EN4ecen2dAjs7zvYErkRRWp99FinGlQIMe6NRkMLLLSIj2BwR/wlscn1kLpIfwGpxfSZ89srK3do6Mb5QogpxdUsnQB8qv5PTGRQFBcjM47s1Q5m0t+OKxGvOnLyKnQp+cVS2KFJMbSzQarW8wIZSz2gKArn9Ttk0kqUHMlJWNY7Yh6xIrrxlEalaTOVzPdtnF7u8Tobminu15eeWHMormMRz4dYSaDc6hUtfpdy1NzOHaeXIU9A9RY/iytxuIQNgcMAlcWbPe//rVk/unH2F8xqSOfed4h/nC08F/qq4z8va3kEXBSdW/G91aIDMu1mo0kX3YNibq8s25C/CfGpzw39ozJ9erTBH7hy6nfmxU6qZuWcTGDj3NOfKe/XIfDcpOjsqkT2IOFARrYodb67q23IuOufraK1/FD4LXu8l0S8/Oi0cqMjtPPs7tS0M1C3DrbmlEzGKETrHpmoKHqjA0rgOmK4ZZM9LeI+l8Z+fDpYcCak9fLGGxnjf+nKiYMSUtm9+1dwycG2lpBV6fbmIKHJWngO2jVGcycODkc525oUaAO4hdPMqrz1AdU3AzYmLJTxW3aZ4uL5NyEJ7TbUBC0HT7h2gEi/tUry4cfD2EsM9bCrCUNuMBrnPqd4r8AvORoqqYIw1IEsP0RgWa2+hfeG1QCjBRPFHQOcqo+W25CelivMe79qI08w0iC8S4hfOQO4QrmMgtd1BhcR+wVpVE3X9EJZi3Hl7z14hXcSic+gkswJMtVZcnJL4rmZ0iEW1mpqUuegsX5vB/4qPxiQyeB80pg8Q33shvUbixzSBkl6znmLSiIffsiDsGOsnuzfl/MUT+JBs3UswNt4tSp7nEwhUjKFHrZHrAJiGCdtIS6yDPGe3HfQv1JkQ+9A8zv88hRmzeIx2JyT/shtIqGo+4ZTJd5cma--Lij/n0+cpstyZD28--FOUhwW3y+0jdaYkKvG2xrg==
|
||||||
@@ -1 +0,0 @@
|
|||||||
2a8a17892dd9f41ea50c61310c83240b
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
zdpQvlbfqXzaExLzw2LGZkXj97HH42jWZFSTlINpf/HlFr6NubPxLkVxeGsittJj5rm8yL+V21zPxp30Z7Q7R45qyCKFtevtVeqb+u1nZ/FsVfkwb/7wDW9scurgXw==--SB6C0aaNf8qPdteG--9hP+6tpsMnyiAVVvAIq+7w==
|
|
||||||
@@ -1 +1 @@
|
|||||||
xgPOFd8315z7lFtTR5/nD6WDBM2M6Grt/pmkCPdaqlw0WAmFKzbiRGFsXoUQ02JNzvT1/FVtBSsAcyK1Pdr1QQztlWC+/ywaflloMBS4//D8IEXvEgCK6uff5gcf1A==--WbFrw9advCJ4mqsK--HTVHZqO0ddG1toFpY0KKgQ==
|
vqH5By5qFLImVjdlWj+7FwGg8APKnr/AEd7WqekG7L0vNA32WGBpwS1uGzs02LIcATRwGj8DyJxiBOB/w9z8cwoO+t6Woi5hAnOSCQwFWKLT0dZq7jgtT8pxK0Yu/Nf91PEFN1rc/8ZFy2KKVpbtMbMPyivT38e/ctBZD/lHrWkndvLXYvFVhqWjUnDOGbhwl/U0RZgqBBjvlm3B0JkQfiN8VXPlCJL2Cd8kd0+MpRCRTgtcxA==--OdVXnDP7OhzJxCsP--+8SI6IFIeXyDxXb+WpqhIQ==
|
||||||
@@ -63,4 +63,7 @@ Rails.application.configure do
|
|||||||
config.action_mailer.raise_delivery_errors = false
|
config.action_mailer.raise_delivery_errors = false
|
||||||
# Base URL to be used by email template link helpers
|
# Base URL to be used by email template link helpers
|
||||||
config.action_mailer.default_url_options = { host: "localhost:3000", protocol: "http" }
|
config.action_mailer.default_url_options = { host: "localhost:3000", protocol: "http" }
|
||||||
|
|
||||||
|
# Allow requests from any IP
|
||||||
|
config.web_console.whiny_requests = false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ authorizations: &AUTHORIZATIONS
|
|||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
development:
|
development:
|
||||||
host: 192.168.56.5
|
host: <%= ENV["LDAP_HOST"] || "localhost" %>
|
||||||
port: 389
|
port: <%= ENV["LDAP_PORT"] || "389" %>
|
||||||
attribute: cn
|
attribute: cn
|
||||||
base: ou=kosmos.org,cn=users,dc=kosmos,dc=org
|
base: <%= ENV["LDAP_BASE"] || "ou=kosmos.org,cn=users,dc=kosmos,dc=org" %>
|
||||||
admin_user: "cn=Directory Manager"
|
admin_user: "cn=Directory Manager"
|
||||||
admin_password: localpass
|
admin_password: <%= ENV["LDAP_ADMIN_PASSWORD"] %>
|
||||||
# ssl: false
|
ssl: <%= ENV["LDAP_USE_TLS"] || "false" %>
|
||||||
# <<: *AUTHORIZATIONS
|
# <<: *AUTHORIZATIONS
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@@ -46,11 +46,11 @@ test:
|
|||||||
# <<: *AUTHORIZATIONS
|
# <<: *AUTHORIZATIONS
|
||||||
|
|
||||||
production:
|
production:
|
||||||
host: ldap.kosmos.org
|
host: ldap.kosmos.local
|
||||||
port: 636
|
port: 389
|
||||||
attribute: cn
|
attribute: cn
|
||||||
base: ou=kosmos.org,cn=users,dc=kosmos,dc=org
|
base: ou=kosmos.org,cn=users,dc=kosmos,dc=org
|
||||||
admin_user: <%= Rails.application.credentials.ldap[:username] rescue nil %>
|
admin_user: <%= Rails.application.credentials.ldap[:username] rescue nil %>
|
||||||
admin_password: <%= Rails.application.credentials.ldap[:password] rescue nil %>
|
admin_password: <%= Rails.application.credentials.ldap[:password] rescue nil %>
|
||||||
ssl: simple_tls
|
# ssl: false
|
||||||
# <<: *AUTHORIZATIONS
|
# <<: *AUTHORIZATIONS
|
||||||
|
|||||||
@@ -20,10 +20,17 @@ Rails.application.routes.draw do
|
|||||||
resources :donations
|
resources :donations
|
||||||
|
|
||||||
get 'wallet', to: 'wallet#index'
|
get 'wallet', to: 'wallet#index'
|
||||||
|
get 'wallet/transactions', to: 'wallet#transactions'
|
||||||
|
|
||||||
get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/}
|
get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/}
|
||||||
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/}
|
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/}
|
||||||
|
|
||||||
|
post 'webhooks/lndhub', to: 'webhooks#lndhub'
|
||||||
|
|
||||||
|
namespace :api do
|
||||||
|
get 'kredits/onchain_btc_balance', to: 'kredits#onchain_btc_balance'
|
||||||
|
end
|
||||||
|
|
||||||
namespace :admin do
|
namespace :admin do
|
||||||
root to: 'dashboard#index'
|
root to: 'dashboard#index'
|
||||||
get 'invitations', to: 'invitations#index'
|
get 'invitations', to: 'invitations#index'
|
||||||
|
|||||||
9
db/migrate/20230111113139_add_ln_account_to_users.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class AddLnAccountToUsers < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :users, :ln_account, :string
|
||||||
|
|
||||||
|
User.all.each do |user|
|
||||||
|
user.update! ln_account: user.ln_login
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
32
db/schema.rb
@@ -2,25 +2,24 @@
|
|||||||
# of editing this file, please use the migrations feature of Active Record to
|
# of editing this file, please use the migrations feature of Active Record to
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
# incrementally modify your database, and then regenerate this schema definition.
|
||||||
#
|
#
|
||||||
# This file is the source Rails uses to define your schema when running `rails
|
# This file is the source Rails uses to define your schema when running `bin/rails
|
||||||
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
|
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
||||||
# be faster and is potentially less error prone than running all of your
|
# be faster and is potentially less error prone than running all of your
|
||||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
# migrations from scratch. Old migrations may fail to apply correctly if those
|
||||||
# migrations use external dependencies or application code.
|
# migrations use external dependencies or application code.
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2021_11_20_010540) do
|
ActiveRecord::Schema[7.0].define(version: 2023_01_11_113139) do
|
||||||
|
|
||||||
create_table "donations", force: :cascade do |t|
|
create_table "donations", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "amount_sats"
|
t.integer "amount_sats"
|
||||||
t.integer "amount_eur"
|
t.integer "amount_eur"
|
||||||
t.integer "amount_usd"
|
t.integer "amount_usd"
|
||||||
t.string "public_name"
|
t.string "public_name"
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", null: false
|
||||||
t.datetime "paid_at"
|
t.datetime "paid_at", precision: nil
|
||||||
t.index ["user_id"], name: "index_donations_on_user_id"
|
t.index ["user_id"], name: "index_donations_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -28,25 +27,28 @@ ActiveRecord::Schema.define(version: 2021_11_20_010540) do
|
|||||||
t.string "token"
|
t.string "token"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "invited_user_id"
|
t.integer "invited_user_id"
|
||||||
t.datetime "used_at"
|
t.datetime "used_at", precision: nil
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["invited_user_id"], name: "index_invitations_on_invited_user_id"
|
||||||
|
t.index ["user_id"], name: "index_invitations_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
create_table "users", force: :cascade do |t|
|
||||||
t.string "cn"
|
t.string "cn"
|
||||||
t.string "ou"
|
t.string "ou"
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "email", default: "", null: false
|
t.string "email", default: "", null: false
|
||||||
t.string "reset_password_token"
|
t.string "reset_password_token"
|
||||||
t.datetime "reset_password_sent_at"
|
t.datetime "reset_password_sent_at", precision: nil
|
||||||
t.string "confirmation_token"
|
t.string "confirmation_token"
|
||||||
t.datetime "confirmed_at"
|
t.datetime "confirmed_at", precision: nil
|
||||||
t.datetime "confirmation_sent_at"
|
t.datetime "confirmation_sent_at", precision: nil
|
||||||
t.string "unconfirmed_email"
|
t.string "unconfirmed_email"
|
||||||
t.text "ln_login_ciphertext"
|
t.text "ln_login_ciphertext"
|
||||||
t.text "ln_password_ciphertext"
|
t.text "ln_password_ciphertext"
|
||||||
|
t.string "ln_account"
|
||||||
t.index ["email"], name: "index_users_on_email", unique: true
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||||
end
|
end
|
||||||
|
|||||||
29
db/seeds.rb
@@ -1,7 +1,22 @@
|
|||||||
# This file should contain all the record creation needed to seed the database with its default values.
|
require 'sidekiq/testing'
|
||||||
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
|
|
||||||
#
|
ldap = LdapService.new
|
||||||
# Examples:
|
|
||||||
#
|
Sidekiq::Testing.inline! do
|
||||||
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
|
CreateAccount.call(
|
||||||
# Character.create(name: 'Luke', movie: movies.first)
|
username: "admin", domain: "kosmos.org", email: "admin@example.com",
|
||||||
|
password: "admin is admin", confirmed: true
|
||||||
|
)
|
||||||
|
|
||||||
|
ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true"
|
||||||
|
|
||||||
|
5.times do |n|
|
||||||
|
username = Faker::Name.unique.first_name.downcase
|
||||||
|
email = Faker::Internet.unique.email
|
||||||
|
|
||||||
|
CreateAccount.call(
|
||||||
|
username: username, domain: "kosmos.org", email: email,
|
||||||
|
password: "user is user", confirmed: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
34
docker-compose.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
services:
|
||||||
|
ldap:
|
||||||
|
image: 4teamwork/389ds:latest
|
||||||
|
volumes:
|
||||||
|
- ./tmp/389ds:/data
|
||||||
|
ports:
|
||||||
|
- "389:3389"
|
||||||
|
environment:
|
||||||
|
DS_DM_PASSWORD: passthebutter
|
||||||
|
SUFFIX_NAME: "dc=kosmos,dc=org"
|
||||||
|
# phpldapadmin:
|
||||||
|
# image: osixia/phpldapadmin:0.9.0
|
||||||
|
# ports:
|
||||||
|
# - "8389:80"
|
||||||
|
# environment:
|
||||||
|
# PHPLDAPADMIN_HTTPS: false
|
||||||
|
# PHPLDAPADMIN_LDAP_HOSTS: "#PYTHON2BASH:[{'ldap': [{'server': [{'tls': False}, {'port': 3389}]}, {'login': [{'bind_id': 'cn=Directory Manager'}, {'bind_pass': 'passthebutter'}]}]}]"
|
||||||
|
# PHPLDAPADMIN_LDAP_CLIENT_TLS: false
|
||||||
|
# web:
|
||||||
|
# build: .
|
||||||
|
# tty: true
|
||||||
|
# command: bash -c "sleep 5 && rm -f tmp/pids/server.pid && bin/dev"
|
||||||
|
# volumes:
|
||||||
|
# - .:/akkounts
|
||||||
|
# ports:
|
||||||
|
# - "3000:3000"
|
||||||
|
# environment:
|
||||||
|
# RAILS_ENV: development
|
||||||
|
# LDAP_HOST: ldap
|
||||||
|
# LDAP_PORT: 3389
|
||||||
|
# LDAP_ADMIN_PASSWORD: passthebutter
|
||||||
|
# LDAP_USE_TLS: "false"
|
||||||
|
# depends_on:
|
||||||
|
# - ldap
|
||||||
8
docker/entrypoint.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Remove a potentially pre-existing server.pid for Rails.
|
||||||
|
rm -f /myapp/tmp/pids/server.pid
|
||||||
|
|
||||||
|
# Then exec the container's main process (what's set as CMD in the Dockerfile).
|
||||||
|
exec "$@"
|
||||||
28
lib/tasks/ldap.rake
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace :ldap do
|
||||||
|
desc "Reset the LDAP directory and set up base entries and default org"
|
||||||
|
task setup: :environment do |t, args|
|
||||||
|
ldap = LdapService.new
|
||||||
|
|
||||||
|
ldap.delete_entry "cn=admin_role,ou=kosmos.org,cn=users,dc=kosmos,dc=org", true
|
||||||
|
|
||||||
|
# Delete all existing entries and re-add base entries
|
||||||
|
ldap.reset_directory!
|
||||||
|
|
||||||
|
ldap.add_organization "kosmos.org", "Kosmos", true
|
||||||
|
|
||||||
|
# add admin role
|
||||||
|
ldap.add_entry "cn=admin_role,ou=kosmos.org,cn=users,dc=kosmos,dc=org", {
|
||||||
|
objectClass: %w{top LDAPsubentry nsRoleDefinition nsComplexRoleDefinition nsFilteredRoleDefinition},
|
||||||
|
cn: "admin_role",
|
||||||
|
nsRoleFilter: "(&(objectclass=person)(admin=true))",
|
||||||
|
description: "filtered role for admins"
|
||||||
|
}, true
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "List user domains/organizations"
|
||||||
|
task list_organizations: :environment do |t, args|
|
||||||
|
ldap = LdapService.new
|
||||||
|
orgs = ldap.fetch_organizations
|
||||||
|
puts orgs.inspect
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -5,4 +5,47 @@ namespace :lndhub do
|
|||||||
CreateLndhubWalletJob.perform_later(user)
|
CreateLndhubWalletJob.perform_later(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc "List wallet balances"
|
||||||
|
task :balances => :environment do |t, args|
|
||||||
|
sum = 0
|
||||||
|
User.all.each do |user|
|
||||||
|
lndhub = Lndhub.new
|
||||||
|
auth_token = lndhub.authenticate(user)
|
||||||
|
data = lndhub.balance(auth_token)
|
||||||
|
balance = data["BTC"]["AvailableBalance"] rescue nil
|
||||||
|
if balance && balance > 0
|
||||||
|
sum += balance
|
||||||
|
puts "#{user.address}: #{balance} sats"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
puts "--\nSum of user balances: #{sum} sats"
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Migrate existing accounts to lndhub.go"
|
||||||
|
task :migrate => :environment do |t, args|
|
||||||
|
# user = User.find_by cn: "jimmy"
|
||||||
|
User.all.each do |user|
|
||||||
|
puts "Migrating #{user.cn}"
|
||||||
|
puts "Creating account..."
|
||||||
|
lndhub_v2 = LndhubV2.new
|
||||||
|
res = lndhub_v2.create_account login: user.ln_login, password: user.ln_password
|
||||||
|
puts res.inspect
|
||||||
|
|
||||||
|
lndhub = Lndhub.new
|
||||||
|
lndhub.authenticate(user)
|
||||||
|
data = lndhub.balance
|
||||||
|
balance = data["BTC"]["AvailableBalance"] rescue 0
|
||||||
|
|
||||||
|
if balance > 0
|
||||||
|
lndhub_v2.authenticate(user)
|
||||||
|
desc = "Balance migration from old Kosmos Lightning back-end"
|
||||||
|
res = lndhub_v2.create_invoice amount: balance, description: desc
|
||||||
|
puts "Payment request for #{user.cn} (#{balance} sats):"
|
||||||
|
puts res["payment_request"]
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "---"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
20
package.json
@@ -2,20 +2,18 @@
|
|||||||
"name": "akkounts",
|
"name": "akkounts",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/forms": "^0.4.0",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.13",
|
||||||
"postcss": "^8.4.6",
|
"postcss": "^8.4.19",
|
||||||
"postcss-flexbugs-fixes": "^5.0.2",
|
"postcss-flexbugs-fixes": "^5.0.2",
|
||||||
"postcss-import": "^14.0.2",
|
"postcss-import": "^15.0.1",
|
||||||
"postcss-nested": "^5.0.6",
|
"postcss-nested": "^6.0.0",
|
||||||
"postcss-preset-env": "^7.3.1",
|
"postcss-preset-env": "^7.8.3",
|
||||||
"sass": "^1.49.7",
|
"tailwindcss": "^3.2.4"
|
||||||
"tailwindcss": "^3.0.22"
|
|
||||||
},
|
},
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:css:sass": "sass ./app/assets/stylesheets/legacy.sass.scss ./app/assets/builds/legacy.css --no-source-map --load-path=node_modules",
|
|
||||||
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
||||||
"build:css": "yarn run build:css:sass && yarn run build:css:tailwind"
|
"build:css": "yarn run build:css:tailwind"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
spec/components/wallet_summary_component_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe WalletSummaryComponent, type: :component do
|
||||||
|
it "renders the balance as a human-readable number" do
|
||||||
|
expect(
|
||||||
|
render_inline(described_class.new(balance: 2301000)) {}.css("section").to_html
|
||||||
|
).to include(
|
||||||
|
"2,301,000 sats"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,8 +2,10 @@ FactoryBot.define do
|
|||||||
factory :user do
|
factory :user do
|
||||||
id { 1 }
|
id { 1 }
|
||||||
cn { "jimmy" }
|
cn { "jimmy" }
|
||||||
|
ou { "kosmos.org" }
|
||||||
email { "jimmy@example.com" }
|
email { "jimmy@example.com" }
|
||||||
password { "dis-muh-password" }
|
password { "dis-muh-password" }
|
||||||
confirmed_at { DateTime.now }
|
confirmed_at { DateTime.now }
|
||||||
|
ln_account { "123456" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
19
spec/fixtures/lndhub/incoming.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"id": 58,
|
||||||
|
"type": "incoming",
|
||||||
|
"user_login": "123456abcdef",
|
||||||
|
"amount": 12300,
|
||||||
|
"fee": 0,
|
||||||
|
"memo": "Buy you some beers",
|
||||||
|
"description_hash": "106af234beebd478206535486051b4f212bd31d2ed0f93e3efce7b5e7603d743",
|
||||||
|
"payment_request": "lnbc1u1p3mull3pp5qw4x46ew6kjknudypyjsg8maw935tr5kkuz7t6h7pugp3pt4msyqhp5zp40yd97a028sgr9x4yxq5d57gft6vwja58e8cl0eea4uasr6apscqzpgxqyz5vqsp53m2n8h6yeflgukv5fhwm802kur6un9w8nvycl7auk67w5g2u008q9qyyssqml8rfmxyvp32qd5939qx7uu0w6ppjuujlpwsrz28m9u0dzp799hz5j72w0xm8pg97hd4hdvwh9zxaw2hewnnmzewvc550f9y3qsfaegphmk0mu",
|
||||||
|
"destination_pubkey_hex": "024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946",
|
||||||
|
"r_hash": "03aa6aeb2ed5a569f1a40925041f7d7163458e96b705e5eafe0f10188575dc08",
|
||||||
|
"preimage": "3539663535656537343331663432653165396430623966633664656664646563",
|
||||||
|
"keysend": false,
|
||||||
|
"state": "settled",
|
||||||
|
"created_at": "2023-01-11T09:22:57.546364Z",
|
||||||
|
"expires_at": "2023-01-12T09:22:57.547209Z",
|
||||||
|
"updated_at": "2023-01-11T09:22:58.046236131Z",
|
||||||
|
"settled_at": "2023-01-11T09:22:58.046232174Z"
|
||||||
|
}
|
||||||
19
spec/fixtures/lndhub/outgoing.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"id": 59,
|
||||||
|
"type": "outgoing",
|
||||||
|
"user_login": "123456abcdef",
|
||||||
|
"amount": 12400,
|
||||||
|
"fee": 10,
|
||||||
|
"memo": "Top up mobile phone",
|
||||||
|
"description_hash": "106af234beebd478206535486051b4f212bd31d2ed0f93e3efce7b5e7603d743",
|
||||||
|
"payment_request": "lnbc1u1p3mull3pp5qw4x46ew6kjknudypyjsg8maw935tr5kkuz7t6h7pugp3pt4msyqhp5zp40yd97a028sgr9x4yxq5d57gft6vwja58e8cl0eea4uasr6apscqzpgxqyz5vqsp53m2n8h6yeflgukv5fhwm802kur6un9w8nvycl7auk67w5g2u008q9qyyssqml8rfmxyvp32qd5939qx7uu0w6ppjuujlpwsrz28m9u0dzp799hz5j72w0xm8pg97hd4hdvwh9zxaw2hewnnmzewvc550f9y3qsfaegphmk0mu",
|
||||||
|
"destination_pubkey_hex": "024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946",
|
||||||
|
"r_hash": "03aa6aeb2ed5a569f1a40925041f7d7163458e96b705e5eafe0f10188575dc08",
|
||||||
|
"preimage": "3539663535656537343331663432653165396430623966633664656664646563",
|
||||||
|
"keysend": false,
|
||||||
|
"state": "settled",
|
||||||
|
"created_at": "2023-01-11T09:22:57.546364Z",
|
||||||
|
"expires_at": "2023-01-12T09:22:57.547209Z",
|
||||||
|
"updated_at": "2023-01-11T09:22:58.046236131Z",
|
||||||
|
"settled_at": "2023-01-11T09:22:58.046232174Z"
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
require 'webmock/rspec'
|
require 'webmock/rspec'
|
||||||
|
|
||||||
RSpec.describe CreateLndhubWalletJob, type: :job do
|
RSpec.describe CreateLndhubAccountJob, type: :job do
|
||||||
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
||||||
|
|
||||||
subject(:job) { described_class.perform_later(user) }
|
subject(:job) { described_class.perform_later(user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, "http://localhost:3023/create")
|
stub_request(:post, "http://localhost:3026/v2/users")
|
||||||
.to_return(status: 200, headers: {},
|
.to_return(status: 200, headers: {},
|
||||||
body: { login: "abc123", password: "def456" }.to_json)
|
body: { login: "abc123", password: "def456" }.to_json)
|
||||||
end
|
end
|
||||||
@@ -15,8 +15,8 @@ RSpec.describe CreateLndhubWalletJob, type: :job do
|
|||||||
it "creates a new LndHub account" do
|
it "creates a new LndHub account" do
|
||||||
perform_enqueued_jobs { job }
|
perform_enqueued_jobs { job }
|
||||||
|
|
||||||
expect(WebMock).to have_requested(:post, "http://localhost:3023/create")
|
expect(WebMock).to have_requested(:post, "http://localhost:3026/v2/users")
|
||||||
.with { |req| req.body == '{"partnerid":"kosmos.org","accounttype":"user"}' }
|
.with { |req| req.body == '{}' }
|
||||||
|
|
||||||
user.reload
|
user.reload
|
||||||
expect(user.ln_login).to eq("abc123")
|
expect(user.ln_login).to eq("abc123")
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
require 'webmock/rspec'
|
require 'webmock/rspec'
|
||||||
|
|
||||||
RSpec.describe ExchangeXmppContactsJob, type: :job do
|
RSpec.describe XmppExchangeContactsJob, type: :job do
|
||||||
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
||||||
|
|
||||||
subject(:job) {
|
subject(:job) {
|
||||||
@@ -10,6 +10,8 @@ require 'capybara'
|
|||||||
require 'devise'
|
require 'devise'
|
||||||
require 'support/controller_macros'
|
require 'support/controller_macros'
|
||||||
require 'support/database_cleaner'
|
require 'support/database_cleaner'
|
||||||
|
require "view_component/test_helpers"
|
||||||
|
require "capybara/rspec"
|
||||||
|
|
||||||
# Requires supporting ruby files with custom matchers and macros, etc, in
|
# Requires supporting ruby files with custom matchers and macros, etc, in
|
||||||
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
||||||
@@ -70,5 +72,8 @@ RSpec.configure do |config|
|
|||||||
config.include Warden::Test::Helpers
|
config.include Warden::Test::Helpers
|
||||||
config.include FactoryBot::Syntax::Methods
|
config.include FactoryBot::Syntax::Methods
|
||||||
config.include ActiveJob::TestHelper, type: :job
|
config.include ActiveJob::TestHelper, type: :job
|
||||||
|
config.include ViewComponent::TestHelpers, type: :component
|
||||||
|
config.include Capybara::RSpecMatchers, type: :component
|
||||||
|
|
||||||
config.extend ControllerMacros, :type => :controller
|
config.extend ControllerMacros, :type => :controller
|
||||||
end
|
end
|
||||||
|
|||||||
43
spec/requests/api/kredits_spec.rb
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
require 'webmock/rspec'
|
||||||
|
|
||||||
|
RSpec.describe "/api/kredits", type: :request do
|
||||||
|
|
||||||
|
describe "GET /onchain_btc_balance" do
|
||||||
|
before do
|
||||||
|
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet")
|
||||||
|
.to_return(status: 200, headers: {}, body: {
|
||||||
|
balance: 0.91108606,
|
||||||
|
unconfirmedBalance: 0,
|
||||||
|
confirmedBalance: 0.91108606
|
||||||
|
}.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a formatted result for the onchain wallet balance" do
|
||||||
|
get api_kredits_onchain_btc_balance_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
res = JSON.parse(response.body)
|
||||||
|
expect(res["balance"]).to eq(0.91108606)
|
||||||
|
expect(res["unconfirmed_balance"]).to eq(0)
|
||||||
|
expect(res["confirmed_balance"]).to eq(0.91108606)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "upstream request error" do
|
||||||
|
before do
|
||||||
|
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet")
|
||||||
|
.to_return(status: 500, headers: {}, body: "")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a formatted error" do
|
||||||
|
get api_kredits_onchain_btc_balance_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:server_error)
|
||||||
|
|
||||||
|
res = JSON.parse(response.body)
|
||||||
|
expect(res["error"]).not_to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
77
spec/requests/webhooks_spec.rb
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe "Webhooks", type: :request do
|
||||||
|
describe "Allowed IP addresses" do
|
||||||
|
context "IP not allowed" do
|
||||||
|
it "returns a 403 status" do
|
||||||
|
post "/webhooks/lndhub"
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "IP allowed" do
|
||||||
|
before do
|
||||||
|
ENV['WEBHOOKS_ALLOWED_IPS'] = '127.0.0.1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not return a 403 status" do
|
||||||
|
post "/webhooks/lndhub"
|
||||||
|
expect(response).not_to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Webhooks from lndhub.go
|
||||||
|
describe "/webhooks/lndhub" do
|
||||||
|
before do
|
||||||
|
ENV['WEBHOOKS_ALLOWED_IPS'] = '127.0.0.1'
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Payload cannot be processed as JSON" do
|
||||||
|
before do
|
||||||
|
post "/webhooks/lndhub", params: "Foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 422 status" do
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Valid payload for outgoing payment" do
|
||||||
|
let(:payload) { JSON.parse(File.read(File.expand_path("../fixtures/lndhub/outgoing.json", File.dirname(__FILE__)))) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
post "/webhooks/lndhub", params: payload.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 204 status" do
|
||||||
|
expect(response).to have_http_status(:no_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Valid payload for incoming payment" do
|
||||||
|
let(:user) { create :user, ln_account: "123456abcdef" }
|
||||||
|
let(:payload) { JSON.parse(File.read(File.expand_path("../fixtures/lndhub/incoming.json", File.dirname(__FILE__)))) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
user.save! #FIXME this should not be necessary
|
||||||
|
post "/webhooks/lndhub", params: payload.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 200 status" do
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sends an XMPP message to the account owner's JID" do
|
||||||
|
expect(enqueued_jobs.size).to eq(1)
|
||||||
|
|
||||||
|
msg = enqueued_jobs.first['arguments'].first
|
||||||
|
expect(msg["type"]).to eq('normal')
|
||||||
|
expect(msg["from"]).to eq('kosmos.org')
|
||||||
|
expect(msg["to"]).to eq(user.address)
|
||||||
|
expect(msg["subject"]).to eq('Sats received!')
|
||||||
|
expect(msg["body"]).to match(/^12300 sats received/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -93,7 +93,7 @@ RSpec.describe CreateAccount, type: :model do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#create_lndhub_wallet" do
|
describe "#create_lndhub_account" do
|
||||||
include ActiveJob::TestHelper
|
include ActiveJob::TestHelper
|
||||||
|
|
||||||
let(:service) { CreateAccount.new(
|
let(:service) { CreateAccount.new(
|
||||||
@@ -102,8 +102,8 @@ RSpec.describe CreateAccount, type: :model do
|
|||||||
)}
|
)}
|
||||||
let(:new_user) { create :user, cn: "halfinney", ou: "kosmos.org" }
|
let(:new_user) { create :user, cn: "halfinney", ou: "kosmos.org" }
|
||||||
|
|
||||||
it "enqueues a job to create an LndHub wallet" do
|
it "enqueues a job to create an LndHub account" do
|
||||||
service.send(:create_lndhub_wallet, new_user)
|
service.send(:create_lndhub_account, new_user)
|
||||||
|
|
||||||
expect(enqueued_jobs.size).to eq(1)
|
expect(enqueued_jobs.size).to eq(1)
|
||||||
|
|
||||||
|
|||||||