114 Commits

Author SHA1 Message Date
1b17cfb396 Fix typo
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-03 11:32:41 -06:00
e5aa5a665c Merge pull request 'Fix LNURL pay amount validation' (#58) from bugfix/fix-max-receivable-amount into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #58
2022-02-03 17:13:20 +00:00
d37b68a6e5 Fix LNURL pay amount validation
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
We allow receiving of more than 100 sats and less than 1M sats
2022-02-03 17:32:18 +01:00
56936916ff Move SVG images to public folder
All checks were successful
continuous-integration/drone/push Build is passing
Wasn't working in production
2022-01-12 19:37:12 -06:00
c93a460cff Bump style version
All checks were successful
continuous-integration/drone/push Build is passing
Triggers rebuild
2022-01-12 18:52:32 -06:00
f5ceda35c1 Merge pull request 'Add more content/help to wallet page' (#57) from feature/wallet_page_content into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #57
2022-01-13 00:48:22 +00:00
eb0439d6dc Improve Blue Wallet instructions
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-01-12 18:46:14 -06:00
c3dde3506e Add more content/help to wallet page
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* Lighting Address info
* Improve explanation for wallet apps, add Alby
2022-01-10 13:37:04 -06:00
f22ffe373c Merge pull request 'Fix exception during signup' (#56) from bugfix/signup_lndhub into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #56
2022-01-10 15:31:10 +00:00
bc20e89617 Fix exception during signup
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-01-10 09:28:12 -06:00
0f0f296a5e Merge pull request 'Add button for copying lndhub setup code' (#55) from feature/37-copy_setup_code into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #55
2021-12-16 14:23:13 +00:00
78aea5d608 Use Tailwind classes to hide/show elements
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-12-16 15:18:37 +01:00
f1d3e3d8ec Add button for copying lndhub setup code
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
closes #37
2021-12-15 14:54:44 +01:00
2706c76890 Merge pull request 'Improve admin LDAP user index' (#53) from feature/improve_admin_ldap_page into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #53
2021-11-30 14:38:05 +00:00
17f5eb56cd Merge pull request 'Show sats instead of BTC on donation page, refactor CSS' (#54) from feature/45-sats_everywhere into master
Some checks are pending
continuous-integration/drone/push Build is running
Reviewed-on: #54
2021-11-30 14:37:45 +00:00
aa6b677b13 Merge pull request 'Improve task for generating invitations' (#52) from feature/improve_invitation_generation into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #52
2021-11-28 20:44:06 +00:00
9abdab2274 Show sats instead of BTC on donation page, refactor CSS
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* Show sats instead of BTC on donation page
* Use number delimiters on both donation page and wallet page
* Refactor donation page CSS into Tailwind directives
2021-11-28 11:53:20 -06:00
dd49d1208f Remove feature list from README
All checks were successful
continuous-integration/drone/push Build is passing
Not overly useful or impressive IMO.
2021-11-28 11:16:49 -06:00
db9118cb7c Improve admin LDAP user index
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* Show which domain the current list is for
* Render text links as such
2021-11-28 11:11:41 -06:00
89913ba60b Improve task for generating invitations
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Turn the argument into the target number of unused invitations for each
user, thus not generating more invitations for users who already have a
sufficient amount of unused ones.
2021-11-28 10:40:09 -06:00
8cf631fd94 Add preconfigured lockbox credentials for development
All checks were successful
continuous-integration/drone/push Build is passing
2021-11-26 13:05:26 -06:00
d0b359039b Merge pull request 'Wrap global JS into IIFE' (#50) from bugfix/41-turbolinks into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #50
2021-11-26 19:04:34 +00:00
84cf523049 Wrap global JS into IIFE
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
fixes #41
2021-11-26 13:01:39 -06:00
a7390ba00b Merge pull request 'Fixes/improvements for lnurl-pay' (#49) from feature/lnurlp_improvements into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #49
2021-11-26 17:37:37 +00:00
67d148d117 Lower the minimum receivable via lnurlp
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-11-26 11:34:51 -06:00
83ad6f4eef Update README
All checks were successful
continuous-integration/drone/push Build is passing
2021-11-25 19:24:56 -06:00
2e31268698 Change description and success message for lnurlp
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
closes #46
2021-11-25 19:14:43 -06:00
f3b22c02ef Set correct min/max amounts for lnurlp
fixes #47
2021-11-25 19:14:17 -06:00
dbe65b4b5a Merge pull request 'Add lndhub and lockbox configs for test environment' (#48) from bugfix/lndhub_specs into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #48
2021-11-26 00:55:18 +00:00
2871fc0f53 Add lockbox credentials for test env
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-11-25 18:51:40 -06:00
968689a512 Add lndhub config to test environment
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2021-11-25 18:38:54 -06:00
ab29f618f4 Update README
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-24 11:11:36 -06:00
94975a1b30 Merge pull request 'Add Tailwind info to README' (#44) from bugfix/35-tailwind-2 into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #44
2021-11-24 17:10:26 +00:00
cd8880d9dc Add Tailwind info to README
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-24 11:03:42 -06:00
f59182b9c1 Second try, triggering asset compilation
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-24 10:48:29 -06:00
941cb4a571 Minor CSS tweak
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-24 10:45:40 -06:00
f534898d8b Try triggering asset compilation
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-24 10:42:40 -06:00
18c7c54403 Merge pull request 'Various UI improvements' (#43) from ui/misc into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #43
2021-11-24 02:41:55 +00:00
12a9d4674b Fix a couple of oversights
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-23 20:40:42 -06:00
1af8e068c5 Add the wallet to the dashboard as a service
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-23 20:09:30 -06:00
669b163814 Make the wallet balance look a bit nicer 2021-11-23 20:08:54 -06:00
46c7affd1f Add explanatory intro to invitations page 2021-11-23 20:08:33 -06:00
7ab107b689 Hide headings
The main nav entry above is enough context.
2021-11-23 20:07:57 -06:00
5aee1a4100 Merge pull request 'Fix main nav on small screens' (#42) from bugfix/38-navbar into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #42
2021-11-23 22:02:36 +00:00
1578fb9976 Fix main nav on small screens
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
closes #38
2021-11-23 16:00:32 -06:00
8e64a7cf78 Merge pull request 'Fix invoice amount for lnurlp payments' (#40) from bugfix/lnurlp_amounts into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #40
2021-11-23 21:29:33 +00:00
8b5bd66598 Fix invoice amount for lnurlp payments
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
Incoming amount is msats, but we create invoices with sats.
2021-11-23 15:28:16 -06:00
ac8552362c Merge pull request 'Adjust tailwind purge config' (#39) from bugfix/35-tailwind into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #39
2021-11-23 21:27:22 +00:00
99c86c42c5 Adjust tailwind purge config
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-23 15:15:10 -06:00
d0267cb760 Update README
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-22 17:05:46 -06:00
25ddab9241 Merge pull request 'Add LndHub wallets' (#33) from feature/lndhub into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #33
2021-11-22 23:04:19 +00:00
bf76ac55ee Do not allow comments for lnurlp senders
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 17:03:18 -06:00
40e5c3609e Remove obsolete files
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:29:23 -06:00
1078c034ad Remove obsolete comment
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:26:05 -06:00
bfa38ad7b2 Adjust spec for new development config
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:24:42 -06:00
4f20cd0d0a Add Rake task for generating wallets
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:22:53 -06:00
e2ee33a1da Configure LndHub for production
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:19:16 -06:00
8662a4c8c1 Don't overwrite existing lndhub wallet credentials
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2021-11-22 15:51:30 -06:00
dbc811b840 Add LndHub service, lnurl-pay endpoints
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
Enables the lnurl-pay payment workflow
2021-11-22 15:41:05 -06:00
884070a3cb Show available balance on wallet page
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-21 16:47:55 -06:00
3c350155de Formatting
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-21 15:34:24 -06:00
21c6ebc137 Fix small issue with turbolinks
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
The JS was run again, and failed to assign variables using `const` then.
2021-11-21 13:27:55 -06:00
0a1052fcb7 Add wallet page
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
With setup code to connect Blue Wallet to Kosmos account wallets
2021-11-20 16:13:43 -06:00
f94227f9f3 Create LndHub accounts 2021-11-19 20:10:36 -06:00
088961dfec Merge pull request 'Switch to shared Kosmos font(s)' (#30) from feature/webfonts_open-sans into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #30
2021-02-25 17:11:39 +00:00
31cf353d3a Load remote fonts before other stylesheets
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
They won't apply when loaded afterwards.
2021-02-25 18:04:27 +01:00
4eb40abc9c Import webfont from shared URL
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-19 15:45:22 +01:00
682c78c7c3 Move headline styles to tailwind base
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-19 15:11:53 +01:00
f9726ad9be Use Open Sans as default font for everything 2021-02-19 15:02:07 +01:00
89188f5081 Don't purge CSS in base and component layers
All checks were successful
continuous-integration/drone/push Build is passing
The production build was purging input[type=text] styles, because it
couldn't find it in the Rails templates. Change config, so it only
purges utility classes.
2021-02-10 16:37:34 +01:00
6a6ff84ff2 Merge pull request 'Add Tailwind CSS, migrate most of the styles' (#27) from feature/tailwind-css into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #27
2021-02-10 14:29:06 +00:00
b6949acc96 Style forms, migrate more styles to Tailwind
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-09 20:32:10 +01:00
814633034f WIP Add Tailwind CSS
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 02:05:31 +01:00
260dedb6cf Merge pull request 'Set up async workers/jobs via Sidekiq' (#26) from feature/sidekiq into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #26
2021-02-03 18:12:48 +00:00
656c887811 Add missing hook to spec
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-03 19:11:43 +01:00
7e9af716ac Make them colors pop
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-03 13:37:44 +01:00
58cc6811f9 Move XMPP contacts exchange to background job
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-03 13:16:47 +01:00
8ad85636d9 Create LDAP users asynchronously
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-02 21:16:24 +01:00
35e2c8cd30 Add Sidekiq, configure admin access to Web UI
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-02 11:44:17 +01:00
4526c941b8 Merge pull request 'Add invitations page to admin panel' (#24) from feature/admin_invitations into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #24
2021-02-01 22:53:31 +00:00
4f5ebd5330 Merge pull request 'Add cosmic background to header' (#25) from ui/kosmic_sky into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #25
2021-02-01 22:53:15 +00:00
d7e4c6f3ae Add cosmic background to header
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Going back to space, where we belong.
2021-02-01 23:10:54 +01:00
14caefe2d1 Replace yellow menu highlight with blue color 2021-02-01 22:49:42 +01:00
0110f27ada Add invitation stats
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Show some stats about unused invitations and active inviters
2021-02-01 22:35:30 +01:00
dc7cf107c2 New admin page for invitations 2021-02-01 21:53:18 +01:00
4fbfaadb44 Merge pull request 'Various UI improvements' (#23) from ux/various_design_changes into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #23
2021-02-01 18:33:06 +00:00
a01cb9ae21 Adjust site header in admin, signup layouts
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-01 18:58:34 +01:00
698e4381c2 Improve table styles
All checks were successful
continuous-integration/drone/push Build is passing
* Nicer table headers
* Hide invitation IDs on small screens
2021-02-01 18:53:48 +01:00
8997349186 Move password change to new Security tab
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-01 18:39:51 +01:00
92bfc33bf0 Remove bottom border from last section on page 2021-02-01 18:24:01 +01:00
c6eb21faad Change site name to "Account", add comet icon
All checks were successful
continuous-integration/drone/push Build is passing
... and remove the "beta" tag.
2021-02-01 18:17:26 +01:00
2d9bc90b16 Merge pull request 'Use new .local domain for Postgres in production' (#22) from chore/postgres_hostname into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #22
2021-01-23 14:04:45 +00:00
a0c579e319 Use new .local domain for Postgres in production
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-23 15:03:16 +01:00
f289ee9365 Switch menu items
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-29 11:09:04 +01:00
46a7345ce9 Merge pull request 'Add main navigation bar' (#20) from feature/main_nav into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #20
2020-12-29 10:04:42 +00:00
e12d02a988 Fix spec
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Content changed
2020-12-29 11:02:24 +01:00
5e8618f25a Merge pull request 'Add admin layout with admin navigation' (#21) from feature/admin_layout into feature/main_nav
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
Reviewed-on: #21
2020-12-29 09:58:57 +00:00
2bdf08a523 Add admin layout with admin navigation
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
And remove the hacky link list from the dashboard.
2020-12-28 09:32:04 +01:00
9ddd36c414 Add missing section markup
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-27 14:21:16 +01:00
9372ea7343 Add small-screen layout for main navigation 2020-12-27 14:14:53 +01:00
c62ce00184 Add main navigation bar
Make donations and invitations accessible to everyone
2020-12-27 14:03:40 +01:00
4d8cd740ba Argh
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 17:15:46 +01:00
9858572a2f Remove useless bundler version requirement
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 17:03:14 +01:00
51edf55ae9 Use zerotier for connecting to postgres
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 12:24:18 +01:00
75485ce8e9 Merge pull request 'Update postgres master host' (#19) from chore/update_postgres_host into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #19
2020-12-22 10:42:51 +00:00
fcbfcc4007 Update postgres master host
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-22 11:41:40 +01:00
cdcb7b3aef Update README
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-21 15:49:58 +01:00
bcf5172956 Merge pull request 'Add basic donation records' (#18) from feature/donation_records into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #18
2020-12-21 14:46:50 +00:00
26c6c5a3b2 Nullify donation owners when related record destroyed
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-21 13:59:46 +01:00
4a65573934 Format numbers on admin donations page
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
And fix the wrong unit display in the user donations list.
2020-12-19 14:59:16 +01:00
5e2d5c3b28 Add paid_at date to donations
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-19 13:28:47 +01:00
2f70bae523 Format and style user donations 2020-12-19 13:16:04 +01:00
40f3e8327a Basic donation records
Adds donation model/table and basic manual management in the admin
panel, as well as basic listing of users' own donations.
2020-12-17 21:56:51 +01:00
f3d6e29e4e Remove time from used invitations list
Date is enough.
2020-12-17 17:02:30 +01:00
108 changed files with 2128 additions and 561 deletions

View File

@@ -1 +1,3 @@
EJABBERD_API_URL='https://xmpp.kosmos.org/api' EJABBERD_API_URL='https://xmpp.kosmos.org/api'
LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

View File

@@ -1 +1,3 @@
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'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

View File

@@ -1 +1,3 @@
EJABBERD_API_URL='http://xmpp.example.com/api' EJABBERD_API_URL='http://xmpp.example.com/api'
LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

3
.gitignore vendored
View File

@@ -39,3 +39,6 @@ yarn-debug.log*
# Ignore local dotenv config file # Ignore local dotenv config file
.env .env
# Ignore redis dumps from sidekiq
dump.rdb

13
Gemfile
View File

@@ -21,15 +21,28 @@ gem 'jbuilder', '~> 2.7'
# Reduces boot times through caching; required in config/boot.rb # Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false gem 'bootsnap', '>= 1.4.2', require: false
# Configuration
gem 'dotenv-rails' gem 'dotenv-rails'
# Security
gem 'lockbox'
# Authentication
gem 'warden' gem 'warden'
gem 'devise' gem 'devise'
gem 'devise_ldap_authenticatable' gem 'devise_ldap_authenticatable'
gem 'net-ldap' gem 'net-ldap'
# Utilities
gem "rqrcode", "~> 2.0"
# HTTP requests
gem 'faraday' gem 'faraday'
# Background/scheduled jobs
gem 'sidekiq'
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'

View File

@@ -1,83 +1,91 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (6.0.3.4) actioncable (6.0.4.1)
actionpack (= 6.0.3.4) actionpack (= 6.0.4.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.4) actionmailbox (6.0.4.1)
actionpack (= 6.0.3.4) actionpack (= 6.0.4.1)
activejob (= 6.0.3.4) activejob (= 6.0.4.1)
activerecord (= 6.0.3.4) activerecord (= 6.0.4.1)
activestorage (= 6.0.3.4) activestorage (= 6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.0.3.4) actionmailer (6.0.4.1)
actionpack (= 6.0.3.4) actionpack (= 6.0.4.1)
actionview (= 6.0.3.4) actionview (= 6.0.4.1)
activejob (= 6.0.3.4) activejob (= 6.0.4.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.0.3.4) actionpack (6.0.4.1)
actionview (= 6.0.3.4) actionview (= 6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.0.8)
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 (6.0.3.4) actiontext (6.0.4.1)
actionpack (= 6.0.3.4) actionpack (= 6.0.4.1)
activerecord (= 6.0.3.4) activerecord (= 6.0.4.1)
activestorage (= 6.0.3.4) activestorage (= 6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.0.3.4) actionview (6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
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 (6.0.3.4) activejob (6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.0.3.4) activemodel (6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
activerecord (6.0.3.4) activerecord (6.0.4.1)
activemodel (= 6.0.3.4) activemodel (= 6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
activestorage (6.0.3.4) activestorage (6.0.4.1)
actionpack (= 6.0.3.4) actionpack (= 6.0.4.1)
activejob (= 6.0.3.4) activejob (= 6.0.4.1)
activerecord (= 6.0.3.4) activerecord (= 6.0.4.1)
marcel (~> 0.3.1) marcel (~> 1.0.0)
activesupport (6.0.3.4) activesupport (6.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2) zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.7.0) addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
bcrypt (3.1.16) bcrypt (3.1.16)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.5.0) bootsnap (1.9.1)
msgpack (~> 1.0) msgpack (~> 1.0)
builder (3.2.4) builder (3.2.4)
byebug (11.1.3) byebug (11.1.3)
capybara (3.33.0) capybara (3.36.0)
addressable addressable
matrix
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
nokogiri (~> 1.8) nokogiri (~> 1.8)
rack (>= 1.6.0) rack (>= 1.6.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (~> 1.5) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
concurrent-ruby (1.1.7) chunky_png (1.4.0)
crack (0.4.3) concurrent-ruby (1.1.9)
safe_yaml (~> 1.0.0) connection_pool (2.2.5)
crack (0.4.5)
rexml
crass (1.0.6) crass (1.0.6)
database_cleaner (1.8.5) database_cleaner (2.0.1)
devise (4.7.3) database_cleaner-active_record (~> 2.0.0)
database_cleaner-active_record (2.0.1)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
devise (4.8.0)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0) railties (>= 4.1.0)
@@ -87,117 +95,149 @@ GEM
devise (>= 3.4.1) devise (>= 3.4.1)
net-ldap (>= 0.16.0) net-ldap (>= 0.16.0)
diff-lcs (1.4.4) diff-lcs (1.4.4)
dotenv (2.7.2) dotenv (2.7.6)
dotenv-rails (2.7.2) dotenv-rails (2.7.6)
dotenv (= 2.7.2) dotenv (= 2.7.6)
railties (>= 3.2, < 6.1) railties (>= 3.2)
erubi (1.9.0) e2mmap (0.1.0)
factory_bot (6.1.0) erubi (1.10.0)
et-orbi (1.2.6)
tzinfo
factory_bot (6.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.1.0) factory_bot_rails (6.2.0)
factory_bot (~> 6.1.0) factory_bot (~> 6.2.0)
railties (>= 5.0.0) railties (>= 5.0.0)
faraday (0.17.0) faraday (1.8.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
ffi (1.13.1) ruby2_keywords (>= 0.0.4)
globalid (0.4.2) faraday-em_http (1.0.0)
activesupport (>= 4.2.0) faraday-em_synchrony (1.0.0)
hashdiff (0.4.0) faraday-excon (1.1.0)
i18n (1.8.5) faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
ffi (1.15.4)
fugit (1.5.2)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.4)
globalid (0.5.2)
activesupport (>= 5.0)
hashdiff (1.0.1)
i18n (1.8.11)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jbuilder (2.10.1) jbuilder (2.11.3)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
launchy (2.4.3) launchy (2.5.0)
addressable (~> 2.3) addressable (~> 2.7)
letter_opener (1.7.0) letter_opener (1.7.0)
launchy (~> 2.2) launchy (~> 2.2)
letter_opener_web (1.3.4) letter_opener_web (1.4.1)
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
listen (3.2.1) listen (3.7.0)
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)
loofah (2.7.0) lockbox (0.6.6)
loofah (2.12.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)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
marcel (0.3.3) marcel (1.0.2)
mimemagic (~> 0.3.2) matrix (0.4.2)
method_source (1.0.0) method_source (1.0.0)
mimemagic (0.3.5) mini_mime (1.1.2)
mini_mime (1.0.2) minitest (5.14.4)
mini_portile2 (2.4.0) msgpack (1.4.2)
minitest (5.14.2)
msgpack (1.3.3)
multipart-post (2.1.1) multipart-post (2.1.1)
net-ldap (0.16.3) net-ldap (0.17.0)
nio4r (2.5.4) nio4r (2.5.8)
nokogiri (1.10.10) nokogiri (1.12.5-x86_64-linux)
mini_portile2 (~> 2.4.0) 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 (4.0.6)
puma (4.3.6) puma (4.3.10)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.0)
rack (2.2.3) rack (2.2.3)
rack-proxy (0.6.5) rack-proxy (0.7.0)
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (6.0.3.4) rails (6.0.4.1)
actioncable (= 6.0.3.4) actioncable (= 6.0.4.1)
actionmailbox (= 6.0.3.4) actionmailbox (= 6.0.4.1)
actionmailer (= 6.0.3.4) actionmailer (= 6.0.4.1)
actionpack (= 6.0.3.4) actionpack (= 6.0.4.1)
actiontext (= 6.0.3.4) actiontext (= 6.0.4.1)
actionview (= 6.0.3.4) actionview (= 6.0.4.1)
activejob (= 6.0.3.4) activejob (= 6.0.4.1)
activemodel (= 6.0.3.4) activemodel (= 6.0.4.1)
activerecord (= 6.0.3.4) activerecord (= 6.0.4.1)
activestorage (= 6.0.3.4) activestorage (= 6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 6.0.3.4) railties (= 6.0.4.1)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
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.3.0) rails-html-sanitizer (1.4.2)
loofah (~> 2.3) loofah (~> 2.3)
railties (6.0.3.4) railties (6.0.4.1)
actionpack (= 6.0.3.4) actionpack (= 6.0.4.1)
activesupport (= 6.0.3.4) activesupport (= 6.0.4.1)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
rake (13.0.1) rake (13.0.6)
rb-fsevent (0.10.4) rb-fsevent (0.11.0)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
regexp_parser (1.8.2) redis (4.5.1)
regexp_parser (2.1.1)
responders (3.0.1) responders (3.0.1)
actionpack (>= 5.0) actionpack (>= 5.0)
railties (>= 5.0) railties (>= 5.0)
rspec-core (3.10.0) rexml (3.2.5)
rqrcode (2.1.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rspec-core (3.10.1)
rspec-support (~> 3.10.0) rspec-support (~> 3.10.0)
rspec-expectations (3.10.0) rspec-expectations (3.10.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0) rspec-support (~> 3.10.0)
rspec-mocks (3.10.0) rspec-mocks (3.10.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0) rspec-support (~> 3.10.0)
rspec-rails (4.0.1) rspec-rails (5.0.2)
actionpack (>= 4.2) actionpack (>= 5.2)
activesupport (>= 4.2) activesupport (>= 5.2)
railties (>= 4.2) railties (>= 5.2)
rspec-core (~> 3.9) rspec-core (~> 3.10)
rspec-expectations (~> 3.9) rspec-expectations (~> 3.10)
rspec-mocks (~> 3.9) rspec-mocks (~> 3.10)
rspec-support (~> 3.9) rspec-support (~> 3.10)
rspec-support (3.10.0) rspec-support (3.10.3)
safe_yaml (1.0.5) ruby2_keywords (0.0.5)
rufus-scheduler (3.8.0)
fugit (~> 1.1, >= 1.1.6)
sass-rails (6.0.0) sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1) sassc-rails (~> 2.1, >= 2.1.1)
sassc (2.4.0) sassc (2.4.0)
@@ -208,6 +248,17 @@ GEM
sprockets (> 3.0) sprockets (> 3.0)
sprockets-rails sprockets-rails
tilt tilt
sidekiq (6.3.1)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
sidekiq-scheduler (3.1.0)
e2mmap
redis (>= 3, < 5)
rufus-scheduler (~> 3.2)
sidekiq (>= 3)
thwait
tilt (>= 1.4.0)
spring (2.1.1) spring (2.1.1)
spring-watcher-listen (2.0.1) spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0) listen (>= 2.7, < 4.0)
@@ -215,43 +266,46 @@ GEM
sprockets (4.0.2) sprockets (4.0.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.2.2) sprockets-rails (3.4.0)
actionpack (>= 4.0) actionpack (>= 5.2)
activesupport (>= 4.0) activesupport (>= 5.2)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sqlite3 (1.4.2) sqlite3 (1.4.2)
thor (1.0.1) thor (1.1.0)
thread_safe (0.3.6) thread_safe (0.3.6)
thwait (0.2.0)
e2mmap
tilt (2.0.10) tilt (2.0.10)
turbolinks (5.2.1) turbolinks (5.2.1)
turbolinks-source (~> 5.2) turbolinks-source (~> 5.2)
turbolinks-source (5.2.0) turbolinks-source (5.2.0)
tzinfo (1.2.7) tzinfo (1.2.9)
thread_safe (~> 0.1) thread_safe (~> 0.1)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
web-console (4.1.0) web-console (4.2.0)
actionview (>= 6.0.0) actionview (>= 6.0.0)
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.6.0) webmock (3.14.0)
addressable (>= 2.3.6) 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)
webpacker (4.3.0) webpacker (4.3.0)
activesupport (>= 4.2) activesupport (>= 4.2)
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 4.2) railties (>= 4.2)
websocket-driver (0.7.3) websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.4.1) zeitwerk (2.5.1)
PLATFORMS PLATFORMS
ruby ruby
x86_64-linux
DEPENDENCIES DEPENDENCIES
bootsnap (>= 1.4.2) bootsnap (>= 1.4.2)
@@ -267,12 +321,16 @@ DEPENDENCIES
letter_opener letter_opener
letter_opener_web letter_opener_web
listen (~> 3.2) listen (~> 3.2)
lockbox
net-ldap net-ldap
pg (~> 1.2.3) pg (~> 1.2.3)
puma (~> 4.1) puma (~> 4.1)
rails (~> 6.0.3, >= 6.0.3.4) rails (~> 6.0.3, >= 6.0.3.4)
rqrcode (~> 2.0)
rspec-rails rspec-rails
sass-rails (>= 6) sass-rails (>= 6)
sidekiq
sidekiq-scheduler
spring spring
spring-watcher-listen (~> 2.0.0) spring-watcher-listen (~> 2.0.0)
sqlite3 (~> 1.4) sqlite3 (~> 1.4)
@@ -284,4 +342,4 @@ DEPENDENCIES
webpacker (~> 4.0) webpacker (~> 4.0)
BUNDLED WITH BUNDLED WITH
2.0.2 2.2.2

View File

@@ -1,25 +1,10 @@
[![Build Status](https://drone.kosmos.org/api/badges/kosmos/akkounts/status.svg)](https://drone.kosmos.org/kosmos/akkounts)
# Akkounts # Akkounts
This app allows Kosmos/LDAP users to manage their accounts, including This app allows Kosmos/LDAP users to manage their accounts, including
credentials, invites, donations, etc.. credentials, invites, donations, etc..
## Features
* [x] Log in with existing LDAP account
* [x] Reset account password by providing both username and email address
* [x] Reset account password when logged in, via reset email
* [x] Log in with admin permissions
* [x] View LDAP users as admin
* [x] Sign up for a new account via invitation
* [ ] List my donations
* [ ] Invite new users from your account
* [ ] Sign up for a new account by donating upfront
* [ ] Sign up for a new account via proving contributions (via cryptographic signature)
* [ ] ...
_Planned features are not at all a complete or static list, of course.
Suggestions and pull requests welcome!_
## Development ## Development
### Rails app ### Rails app
@@ -38,6 +23,10 @@ Running the dev server:
bundle exec rails server bundle exec rails server
Running the background workers (requires Redis):
bundle exec sidekiq -C config/sidekiq.yml
Running all specs: Running all specs:
bundle exec rspec bundle exec rspec
@@ -52,6 +41,15 @@ manual LDIF imports etc. (or provide a staging instance)
* [Ruby on Rails](https://guides.rubyonrails.org/) * [Ruby on Rails](https://guides.rubyonrails.org/)
* [Sass](https://sass-lang.com/documentation) * [Sass](https://sass-lang.com/documentation)
### Front-end
* [Tailwind CSS](https://tailwindcss.com/)
**Caveat:** if you only add Tailwind classes/directives to templates or
helpers, but there's no change in the stylesheet files, then the new directives
won't be compiled in production. In this case, count up the version comment at
the top of `app/javascript/stylesheets/application.scss` to trigger compilation.
### Testing ### Testing
* [RSpec](https://rspec.info/documentation/) * [RSpec](https://rspec.info/documentation/)
@@ -62,6 +60,11 @@ manual LDIF imports etc. (or provide a staging instance)
* [devise_ldap_authenticatable](https://github.com/cschiewek/devise_ldap_authenticatable) * [devise_ldap_authenticatable](https://github.com/cschiewek/devise_ldap_authenticatable)
* [net/ldap](https://www.rubydoc.info/gems/net-ldap/Net/LDAP) * [net/ldap](https://www.rubydoc.info/gems/net-ldap/Net/LDAP)
### Asynchronous jobs/workers
* [Sidekiq](https://github.com/mperham/sidekiq/wiki/)
* [ActiveJob](https://github.com/mperham/sidekiq/wiki/Active-Job)
## License ## 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/)

View File

@@ -1,13 +0,0 @@
html, body, h1, h2, h3, h4, h5, h6, p, pre, a, dl, dt, dd, ol, ul, li {
font-size: 100%;
vertical-align: baseline;
background: transparent;
box-sizing: border-box;
overflow: visible;
margin: 0;
padding: 0;
}
body {
line-height: 1;
}

View File

@@ -0,0 +1,13 @@
$content-width: 800px;
$content-max-width: 100%;
$text-color-body: #222;
$text-color-discreet: #888;
$background-color-notice: #efffc4;
$background-color-alert: #fff4c2;
$color-blue: #0d4f99;
$color-purple: #8955a0;
$color-red-bright: #c00;
$color-red-dark: #990c0e;

View File

@@ -0,0 +1,25 @@
@import "variables";
body#admin-panel {
#wrapper {
> header {
background: $color-red-bright;
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%),
url('/img/bg-1.jpg');
}
}
#main-nav {
ul {
grid-template-columns: repeat(4, 1fr);
li {
a {
&.active {
border-bottom: 2px solid $color-red-bright;
}
}
}
}
}
}

View File

@@ -1,20 +0,0 @@
@font-face {
font-family: 'Raleway';
src: url('/fonts/raleway-light.woff') format('woff2');
font-weight: 300;
font-style: normal;
}
body {
font-family: "Open Sans", Helvetica, Arial, sans-serif;
font-weight: 400;
}
h1, h2, h3 {
font-family: Raleway, inherit;
font-weight: 300;
}
h1 {
text-transform: uppercase;
}

View File

@@ -1,35 +0,0 @@
form {
.field_with_errors {
display: inline-block;
}
}
.layout-signup {
label {
display: none;
}
input[type=text], input[type=email], input[type=password] {
font-size: 1.25rem;
padding: 0.5rem 1rem;
}
span.at-sign, span.domain {
font-size: 1.25rem;
}
.error-msg {
color: #bc0101;
}
.actions {
margin-top: 2rem;
}
.accept-terms {
margin-top: 2rem;
font-size: 0.85rem;
line-height: 1.5em;
color: #888;
}
}

View File

@@ -1,11 +1,6 @@
@import "variables";
@import "mediaqueries"; @import "mediaqueries";
$content-width: 800px;
$content-max-width: 100%;
body {
}
#wrapper { #wrapper {
width: 100%; width: 100%;
text-align: center; text-align: center;
@@ -14,50 +9,39 @@ body {
margin: 0 auto; margin: 0 auto;
padding: 4rem 0; padding: 4rem 0;
text-align: center; text-align: center;
background: #0d4f99; background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%),
background: linear-gradient(35deg, #8955a0 0, #0d4f99 100%); url('/img/bg-1.jpg');
background-size: cover;
@include media-max(small) { @include media-max(small) {
padding: 3rem 0; padding: 3rem 0;
} }
h1 { h1 {
font-size: 1.8rem;
color: #fff; color: #fff;
span.project-name { span.project-name {
display: none; display: none;
// font-size: .5em;
// text-transform: none;
// vertical-align: super;
} }
span.beta { span.icon {
font-size: .5em; svg {
font-style: italic; display: inline-block;
text-transform: none; height: 1.875rem;
vertical-align: super; vertical-align: top;
} width: auto;
}
span.bolt { margin-right: 0.5rem;
color: #ffd000;
} }
} }
p.current-user { p.current-user {
margin-top: 2rem;
color: rgba(255,255,255,0.6); color: rgba(255,255,255,0.6);
@include media-max(small) { @include media-max(small) {
font-size: 0.85rem; font-size: 0.85rem;
} }
strong {
font-weight: 400;
color: #fff;
// color: #ffd000;
// color: #ccff40;
}
} }
a { a {
@@ -77,18 +61,18 @@ body {
padding: 2rem 0; padding: 2rem 0;
&.notice { &.notice {
background: #efffc4; background: $background-color-notice;
} }
&.alert { &.alert {
background: #fff4c2; background: $background-color-alert;
} }
} }
main { main {
width: $content-width; width: $content-width;
max-width: $content-max-width; max-width: $content-max-width;
margin: 4rem auto; margin: 4rem auto 6rem auto;
text-align: left; text-align: left;
@include media-max(medium) { @include media-max(medium) {
@@ -99,18 +83,6 @@ main {
margin: 3rem auto; margin: 3rem auto;
} }
h2, h3 {
margin-bottom: 1.5em;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
p { p {
line-height: 1.5rem; line-height: 1.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -128,19 +100,36 @@ main {
} }
} }
th, td { section {
line-height: 1.5rem; margin-bottom: 3rem;
padding-right: 1rem;
h2 {
display: none;
}
} }
section { table {
border-bottom: 1px dotted #ccc; width: 100%;
padding-bottom: 4rem;
margin-bottom: 4rem;
@include media-max(small) { th, td {
padding-bottom: 3rem; &.hide-small {
margin-bottom: 3rem; @include media-max(small) {
display: none;
}
}
}
th {
color: $text-color-discreet;
font-weight: normal;
text-transform: uppercase;
font-size: 0.85rem;
padding-bottom: 0.825rem;
}
td {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
} }
} }
} }
@@ -156,18 +145,5 @@ main {
@include media-max(small) { @include media-max(small) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
margin-top: 3rem;
h3 {
margin-bottom: 1rem;
}
.grid-item {
p {
color: #888;
font-size: 0.85rem;
}
}
} }
} }

View File

@@ -0,0 +1,54 @@
@import "variables";
@import "mediaqueries";
#main-nav {
width: 100%;
text-align: center;
background-color: #efefef;
.wrapper {
width: $content-width;
max-width: $content-max-width;
margin: 0 auto;
}
ul {
@include media-max(large) {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
li {
@include media-min(large) {
display: inline;
}
@include media-max(large) {
display: block;
}
a {
display: inline-block;
padding: 1.5rem 2rem;
text-decoration: none;
color: $text-color-discreet;
@include media-max(large) {
display: block;
text-align: center;
padding-left: 0;
padding-right: 0;
}
@include media-max(small) {
font-size: 0.85rem;
}
&.active {
color: $text-color-body;
border-bottom: 2px solid #4ea2df;
}
}
}
}
}

View File

@@ -3,4 +3,6 @@ class Admin::BaseController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :authorize_admin before_action :authorize_admin
layout "admin"
end end

View File

@@ -1,4 +1,5 @@
class Admin::DashboardController < Admin::BaseController class Admin::DashboardController < Admin::BaseController
def index def index
@current_section = :dashboard
end end
end end

View File

@@ -0,0 +1,79 @@
class Admin::DonationsController < Admin::BaseController
before_action :set_donation, only: [:show, :edit, :update, :destroy]
before_action :set_current_section, only: [:index, :show, :new, :edit]
# GET /donations
# GET /donations.json
def index
@donations = Donation.all
end
# GET /donations/1
# GET /donations/1.json
def show
end
# GET /donations/new
def new
@donation = Donation.new
end
# GET /donations/1/edit
def edit
end
# POST /donations
# POST /donations.json
def create
@donation = Donation.new(donation_params)
respond_to do |format|
if @donation.save
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully created.' }
format.json { render :show, status: :created, location: @donation }
else
format.html { render :new }
format.json { render json: @donation.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /donations/1
# PATCH/PUT /donations/1.json
def update
respond_to do |format|
if @donation.update(donation_params)
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully updated.' }
format.json { render :show, status: :ok, location: @donation }
else
format.html { render :edit }
format.json { render json: @donation.errors, status: :unprocessable_entity }
end
end
end
# DELETE /donations/1
# DELETE /donations/1.json
def destroy
@donation.destroy
respond_to do |format|
format.html { redirect_to admin_donations_url, notice: 'Donation was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_donation
@donation = Donation.find(params[:id])
end
# Only allow a list of trusted parameters through.
def donation_params
params.require(:donation).permit(:user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :paid_at)
end
def set_current_section
@current_section = :donations
end
end

View File

@@ -0,0 +1,8 @@
class Admin::InvitationsController < Admin::BaseController
def index
@current_section = :invitations
@invitations_unused_count = Invitation.unused.count
@users_with_referrals_count = Invitation.used.distinct.count(:user_id)
@invitations_used = Invitation.used.order('used_at desc')
end
end

View File

@@ -1,12 +1,12 @@
class Admin::LdapUsersController < Admin::BaseController class Admin::LdapUsersController < Admin::BaseController
before_action :set_current_section
def index def index
attributes = %w{dn cn uid mail admin} attributes = %w{dn cn uid mail admin}
filter = Net::LDAP::Filter.eq("uid", "*") filter = Net::LDAP::Filter.eq("uid", "*")
if params[:ou]
treebase = "ou=#{params[:ou]},cn=users,dc=kosmos,dc=org" @ou = params[:ou] || "kosmos.org"
else treebase = "ou=#{@ou},cn=users,dc=kosmos,dc=org"
treebase = "ou=kosmos.org,cn=users,dc=kosmos,dc=org"
end
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes) entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
entries.sort_by! { |e| e.cn[0] } entries.sort_by! { |e| e.cn[0] }
@@ -38,4 +38,8 @@ class Admin::LdapUsersController < Admin::BaseController
def ldap_config def ldap_config
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env] ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
end end
def set_current_section
@current_section = :ldap_users
end
end end

View File

@@ -2,5 +2,6 @@ class DashboardController < ApplicationController
before_action :require_user_signed_in before_action :require_user_signed_in
def index def index
@current_section = :dashboard
end end
end end

View File

@@ -0,0 +1,10 @@
class DonationsController < ApplicationController
before_action :require_user_signed_in
# GET /donations
# GET /donations.json
def index
@donations = current_user.donations.completed
@current_section = :contributions
end
end

View File

@@ -8,6 +8,7 @@ class InvitationsController < ApplicationController
def index def index
@invitations_unused = current_user.invitations.unused @invitations_unused = current_user.invitations.unused
@invitations_used = current_user.invitations.used @invitations_used = current_user.invitations.used
@current_section = :invitations
end end
# GET /invitations/a-random-invitation-token # GET /invitations/a-random-invitation-token

View File

@@ -0,0 +1,60 @@
class LnurlpayController < ApplicationController
before_action :find_user_by_address
MIN_SATS = 100
MAX_SATS = 1_000_000
def index
render json: {
status: "OK",
callback: "https://accounts.kosmos.org/lnurlpay/#{@user.address}/invoice",
tag: "payRequest",
maxSendable: MAX_SATS * 1000, # msat
minSendable: MIN_SATS * 1000, # msat
metadata: metadata(@user.address),
commentAllowed: 0
}
end
def invoice
amount = params[:amount].to_i / 1000 # msats
address = params[:address]
if !valid_amount?(amount)
render json: { status: "ERROR", reason: "Invalid amount" }
return
end
payment_request = @user.ln_create_invoice({
amount: amount, # we create invoices in sats
description_hash: Digest::SHA2.hexdigest(metadata(address))
})
render json: {
status: "OK",
successAction: {
tag: "message",
message: "Sats received. Thank you!"
},
routes: [],
pr: payment_request
}
end
private
def find_user_by_address
address = params[:address].split("@")
@user = User.where(cn: address.first, ou: address.last).first
http_status :not_found if @user.nil?
end
def metadata(address)
"[[\"text/identifier\", \"#{address}\"], [\"text/plain\", \"Send sats, receive thanks.\"]]"
end
def valid_amount?(amount_in_sats)
amount_in_sats <= MAX_SATS && amount_in_sats >= MIN_SATS
end
end

View File

@@ -0,0 +1,7 @@
class SecurityController < ApplicationController
before_action :require_user_signed_in
def index
@current_section = :security
end
end

View File

@@ -0,0 +1,47 @@
require "rqrcode"
class WalletController < ApplicationController
before_action :require_user_signed_in
before_action :authenticate_with_lndhub
def index
@current_section = :wallet
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
qrcode = RQRCode::QRCode.new(@wallet_url)
@svg = qrcode.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
standalone: true,
use_path: true,
svg_attributes: {
class: 'inline-block'
}
)
@balance = fetch_balance rescue nil
end
private
def authenticate_with_lndhub
if session["ln_auth_token"].present?
@ln_auth_token = session["ln_auth_token"]
else
lndhub = Lndhub.new
auth_token = lndhub.authenticate(current_user)
session["ln_auth_token"] = auth_token
@ln_auth_token = auth_token
end
rescue
# TODO add exception tracking
end
def fetch_balance
lndhub = Lndhub.new
data = lndhub.balance @ln_auth_token
data["BTC"]["AvailableBalance"]
end
end

View File

@@ -1,2 +1,5 @@
module ApplicationHelper module ApplicationHelper
def sats_to_btc(sats)
sats.to_f / 100000000
end
end end

View File

@@ -0,0 +1,2 @@
module DonationsHelper
end

View File

@@ -0,0 +1,2 @@
module LnurlpayHelper
end

View File

@@ -0,0 +1,2 @@
module WalletHelper
end

View File

@@ -7,7 +7,7 @@ require("@rails/ujs").start()
require("turbolinks").start() require("turbolinks").start()
require("channels") require("channels")
import "stylesheets/application"
// Uncomment to copy all static images under ../images to the output folder and reference // Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below. // or the `imagePath` JavaScript helper below.

View File

@@ -0,0 +1,9 @@
// version 6
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import "base";
@import "buttons";
@import "forms";
@import "links";

View File

@@ -0,0 +1,21 @@
@layer base {
body {
line-height: 1;
}
h1, h2, h3 {
@apply font-light;
}
h1 {
@apply text-3xl uppercase;
}
h2 {
@apply text-2xl mb-8;
}
h3 {
@apply text-xl mb-6;
}
}

View File

@@ -0,0 +1,31 @@
@layer components {
.btn {
@apply font-semibold rounded-md leading-none cursor-pointer
transition-colors duration-75 focus:outline-none focus:ring-4;
}
.btn-md {
@apply btn;
@apply py-2.5 px-5 shadow-md;
}
.btn-sm {
@apply btn;
@apply py-1 px-2 text-sm shadow-sm;
}
.btn-gray {
@apply bg-gray-100 hover:bg-gray-200
focus:ring-gray-300 focus:ring-opacity-75;
}
.btn-blue {
@apply bg-blue-500 hover:bg-blue-600 text-white
focus:ring-blue-400 focus:ring-opacity-75;
}
.btn-red {
@apply bg-red-600 hover:bg-red-700 text-white
focus:ring-red-500 focus:ring-opacity-75;
}
}

View File

@@ -0,0 +1,17 @@
@layer components {
form {
input[type=text], input[type=email], input[type=password],
input[type=number], select {
@apply mt-1 rounded-md bg-gray-100 focus:bg-white
border-transparent focus:border-gray-500 focus:ring-0;
}
.field_with_errors {
@apply inline-block;
}
.error-msg {
@apply text-red-700;
}
}
}

View File

@@ -0,0 +1,14 @@
@layer components {
.ks-text-link {
@apply text-blue-600;
&:hover { @apply underline; }
&:visited { @apply text-indigo-600; }
&:active { @apply text-red-600; }
}
.devise-links {
a {
@apply ks-text-link;
}
}
}

View File

@@ -0,0 +1,26 @@
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
purge: {
layers: ['base', 'components', 'utilities'],
content: [
"./app/**/*.html.erb",
"./app/helpers/**/*.rb",
"./app/javascript/**/*.js"
]
},
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
fontFamily: {
sans: ['Open Sans', 'sans-serif']
}
},
},
variants: {
extend: {},
},
plugins: [
require('@tailwindcss/forms')
],
}

View File

@@ -0,0 +1,32 @@
class CreateLdapUserJob < ApplicationJob
queue_as :default
def perform(username, domain, email, hashed_pw)
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
attr = {
objectclass: ["top", "account", "person", "extensibleObject"],
cn: username,
sn: username,
uid: username,
mail: email,
userPassword: hashed_pw
}
ldap_client.add(dn: dn, attributes: attr)
end
def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
encryption: ldap_config['ssl'],
auth: {
method: :simple,
username: ldap_config['admin_user'],
password: ldap_config['admin_password']
}
end
def ldap_config
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
end
end

View File

@@ -0,0 +1,13 @@
class CreateLndhubWalletJob < ApplicationJob
queue_as :default
def perform(user)
return if user.ln_login.present? && user.ln_password.present?
lndhub = Lndhub.new
credentials = lndhub.create({ partnerid: user.ou, accounttype: "user" })
user.update! ln_login: credentials["login"],
ln_password: credentials["password"]
end
end

View File

@@ -0,0 +1,18 @@
class ExchangeXmppContactsJob < ApplicationJob
queue_as :default
def perform(inviter, username, domain)
ejabberd = EjabberdApiClient.new
ejabberd.add_rosteritem({
"localuser": username, "localhost": domain,
"user": inviter.cn, "host": inviter.ou,
"nick": inviter.cn, "group": "Friends", "subs": "both"
})
ejabberd.add_rosteritem({
"localuser": inviter.cn, "localhost": inviter.ou,
"user": username, "host": domain,
"nick": username, "group": "Friends", "subs": "both"
})
end
end

13
app/models/donation.rb Normal file
View File

@@ -0,0 +1,13 @@
class Donation < ApplicationRecord
# Relations
belongs_to :user
# Validations
validates_presence_of :amount_sats
# Hooks
# TODO before_create :store_fiat_value
#Scopes
scope :completed, -> { where.not(paid_at: nil) }
end

View File

@@ -3,12 +3,16 @@ class User < ApplicationRecord
# Relations # Relations
has_many :invitations, dependent: :destroy has_many :invitations, dependent: :destroy
has_many :donations, dependent: :nullify
validates_uniqueness_of :cn validates_uniqueness_of :cn
validates_length_of :cn, :minimum => 3 validates_length_of :cn, :minimum => 3
validates_uniqueness_of :email validates_uniqueness_of :email
validates :email, email: true validates :email, email: true
encrypts :ln_login
encrypts :ln_password
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :ldap_authenticatable, devise :ldap_authenticatable,
@@ -52,4 +56,10 @@ class User < ApplicationRecord
self.valid? self.valid?
self.errors[attribute_name].blank? self.errors[attribute_name].blank?
end end
def ln_create_invoice(payload)
lndhub = Lndhub.new
lndhub.authenticate self
lndhub.addinvoice payload
end
end end

View File

@@ -10,6 +10,7 @@ class CreateAccount < ApplicationService
def call def call
user = create_user_in_database user = create_user_in_database
add_ldap_document add_ldap_document
create_lndhub_wallet(user)
if @invitation.present? if @invitation.present?
update_invitation(user.id) update_invitation(user.id)
@@ -33,51 +34,19 @@ class CreateAccount < ApplicationService
@invitation.update! invited_user_id: user_id, used_at: DateTime.now @invitation.update! invited_user_id: user_id, used_at: DateTime.now
end end
# TODO move to confirmation
def add_ldap_document def add_ldap_document
dn = "cn=#{@username},ou=kosmos.org,cn=users,dc=kosmos,dc=org" hashed_pw = Devise.ldap_auth_password_builder.call(@password)
attr = { CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
objectclass: ["top", "account", "person", "extensibleObject"],
cn: @username,
sn: @username,
uid: @username,
mail: @email,
userPassword: Devise.ldap_auth_password_builder.call(@password)
}
ldap_client.add(dn: dn, attributes: attr)
end
def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
encryption: ldap_config['ssl'],
auth: {
method: :simple,
username: ldap_config['admin_user'],
password: ldap_config['admin_password']
}
end
def ldap_config
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
end end
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)
end
ejabberd = EjabberdApiClient.new def create_lndhub_wallet(user)
inviter = @invitation.user CreateLndhubWalletJob.perform_later(user)
ejabberd.add_rosteritem({
"localuser": @username, "localhost": @domain,
"user": inviter.cn, "host": inviter.ou,
"nick": inviter.cn, "group": "Friends", "subs": "both"
})
ejabberd.add_rosteritem({
"localuser": inviter.cn, "localhost": inviter.ou,
"user": @username, "host": @domain,
"nick": @username, "group": "Friends", "subs": "both"
})
end end
end end

57
app/services/lndhub.rb Normal file
View File

@@ -0,0 +1,57 @@
class Lndhub
attr_accessor :auth_token
def initialize
@base_url = ENV["LNDHUB_API_URL"]
end
def post(endpoint, payload)
headers = { "Content-Type" => "application/json" }
if auth_token
headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
end
res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
if res.status != 200
Rails.logger.error "[lndhub] API request failed:"
Rails.logger.error res.body
#TODO add some kind of exception tracking/notifications
end
JSON.parse(res.body)
end
def get(endpoint, auth_token)
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
"Content-Type" => "application/json",
"Accept" => "application/json",
"Authorization" => "Bearer #{auth_token}"
})
JSON.parse(res.body)
end
def create(payload)
post "create", payload
end
def authenticate(user)
credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
self.auth_token = credentials["access_token"]
self.auth_token
end
def balance(user_token)
get "balance", user_token || auth_token
end
def addinvoice(payload)
invoice = post "addinvoice", {
amt: payload[:amount],
description_hash: payload[:description_hash]
}
invoice["payment_request"]
end
end

View File

@@ -1,7 +1,3 @@
<h2>Admin Panel</h2> <p class="text-center">
<p> With great power comes great responsibility.
Ohai there, admin human.
</p>
<p>
<%= link_to 'LDAP users', admin_ldap_users_path %>
</p> </p>

View File

@@ -0,0 +1,2 @@
json.extract! donation, :id, :user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :created_at, :updated_at
json.url donation_url(donation, format: :json)

View File

@@ -0,0 +1,58 @@
<%= form_with(url: url, model: donation, local: true) do |form| %>
<% if donation.errors.any? %>
<div id="error_explanation">
<h3><%= pluralize(donation.errors.count, "error") %> prohibited this donation from being saved:</h3>
<ul>
<% donation.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<p>
<%= form.label :user_id %>
<%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_sats, "Amount BTC (sats)" %>
<%= form.number_field :amount_sats %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_eur, "Amount EUR (cents)" %>
<%= form.number_field :amount_eur %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_usd, "Amount USD (cents)"%>
<%= form.number_field :amount_usd %>
</p>
</div>
<div class="field">
<p>
<%= form.label :public_name %>
<%= form.text_field :public_name %>
</p>
</div>
<div class="field">
<p>
<%= form.label :paid_at %>
<%= form.text_field :paid_at %>
</p>
</div>
<p class="mt-8">
<%= form.submit class: 'btn-md btn-blue' %>
</p>
<% end %>

View File

@@ -0,0 +1,8 @@
<h2>Editing Donation</h2>
<%= render 'form', donation: @donation, url: admin_donation_path(@donation) %>
<p class="mt-8">
<%= link_to 'Show', admin_donation_path(@donation), class: 'ks-text-link' %> |
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
<p>

View File

@@ -0,0 +1,41 @@
<h2>Donations</h2>
<% if @donations.any? %>
<table>
<thead>
<tr>
<th>User</th>
<th>Amount BTC</th>
<th>in EUR</th>
<th>in USD</th>
<th>Public name</th>
<th>Date</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @donations.each do |donation| %>
<tr>
<td><%= donation.user.cn %></td>
<td><%= sats_to_btc donation.amount_sats %> BTC</td>
<td><%= number_to_currency donation.amount_eur / 100, unit: "" %></td>
<td><%= number_to_currency donation.amount_usd / 100, unit: "" %></td>
<td><%= donation.public_name %></td>
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td>
<td><%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
<td><%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
<td><%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red', method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>
No donations yet.
</p>
<% end %>
<p class="mt-12">
<%= link_to 'Record an out-of-system donation', new_admin_donation_path, class: 'btn-md btn-gray' %>
</p>

View File

@@ -0,0 +1 @@
json.array! @donations, partial: "donations/donation", as: :donation

View File

@@ -0,0 +1,7 @@
<h2>New Donation</h2>
<%= render 'form', donation: @donation, url: admin_donations_path %>
<p class="mt-8">
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
</p>

View File

@@ -0,0 +1,36 @@
<p id="notice"><%= notice %></p>
<p>
<strong>User:</strong>
<%= @donation.user_id %>
</p>
<p>
<strong>Amount sats:</strong>
<%= @donation.amount_sats %>
</p>
<p>
<strong>Amount eur:</strong>
<%= @donation.amount_eur %>
</p>
<p>
<strong>Amount usd:</strong>
<%= @donation.amount_usd %>
</p>
<p>
<strong>Public name:</strong>
<%= @donation.public_name %>
</p>
<p>
<strong>Date:</strong>
<%= @donation.paid_at %>
</p>
<p class="mt-8">
<%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'ks-text-link' %> |
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
</p>

View File

@@ -0,0 +1 @@
json.partial! "donations/donation", donation: @donation

View File

@@ -0,0 +1,32 @@
<section>
<h2>Invitations</h2>
<p>
There are currently <strong><%= @invitations_unused_count %>
unused invitations</strong> available to existing users.
<strong><%= @users_with_referrals_count %> users</strong> have successfully
invited new users.
</p>
</section>
<% if @invitations_used.any? %>
<section>
<h3>Accepted (<%= @invitations_used.length %>)</h3>
<table>
<thead>
<tr>
<th>Token</th>
<th>Accepted</th>
<th>Invited user</th>
</tr>
</thead>
<tbody>
<% @invitations_used.each do |invitation| %>
<tr>
<td class="overflow-ellipsis"><%= invitation.token %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d") %></td>
<td><%= User.find(invitation.invited_user_id).address %></td>
</tr>
<% end %>
</tbody>
</table>
</section>
<% end %>

View File

@@ -1,8 +1,13 @@
<h2>LDAP users</h2> <h2>LDAP users: <%= @ou %></h2>
<ul> <h3 class="hidden">Domains</h3>
<li><%= link_to 'kosmos.org', admin_ldap_users_path %></li> <ul class="mb-10">
<li><%= link_to '5apps.com', admin_ldap_users_path(ou: '5apps.com') %></li> <li class="inline-block">
<%= link_to 'kosmos.org', admin_ldap_users_path, class: "ks-text-link" %>
</li>
<li class="inline-block ml-6">
<%= link_to '5apps.com', admin_ldap_users_path(ou: '5apps.com'), class: "ks-text-link" %>
</li>
</ul> </ul>
<table> <table>

View File

@@ -4,45 +4,55 @@
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"> <div class="grid services mt-12">
<div class="grid-item chat"> <div>
<h3><%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat" %></h3> <h3 class="mb-3.5">
<p> <%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Chat rooms and instant messaging (XMPP/Jabber) Chat rooms and instant messaging (XMPP/Jabber)
</p> </p>
</div> </div>
<div class="grid-item wiki"> <div>
<h3><%= link_to "Wiki", "https://wiki.kosmos.org" %></h3> <h3 class="mb-3.5">
<p> <%= link_to "Discourse", "https://community.kosmos.org", class: "ks-text-link" %>
Kosmos documentation and knowledge base </h3>
</p> <p class="text-gray-500">
</div>
<div class="grid-item discourse">
<h3><%= link_to "Discourse", "https://community.kosmos.org" %></h3>
<p>
Kosmos community forums and user support/help site Kosmos community forums and user support/help site
</p> </p>
</div> </div>
<div class="grid-item gitea"> <div>
<h3><%= link_to "Gitea", "https://gitea.kosmos.org" %></h3> <h3 class="mb-3.5">
<p> <span class="text-yellow-500">🗲</span>
<%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Send and receive sats over the Bitcoin Lightning Network
</p>
</div>
<div>
<h3 class="mb-3.5">
<%= link_to "Wiki", "https://wiki.kosmos.org", class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Kosmos documentation and knowledge base
</p>
</div>
<div>
<h3 class="mb-3.5">
<%= link_to "Gitea", "https://gitea.kosmos.org", class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Code hosting and collaboration for software projects Code hosting and collaboration for software projects
</p> </p>
</div> </div>
<div class="grid-item gitea"> <div>
<h3><%= link_to "Drone CI", "https://drone.kosmos.org" %></h3> <h3 class="mb-3.5">
<p> <%= link_to "Drone CI", "https://drone.kosmos.org", class: "ks-text-link" %>
Continuous integration for software projects, tied to our Gitea </h3>
<p class="text-gray-500">
Continuous integration for software projects on Gitea
</p> </p>
</div> </div>
</div> </div>
</section> </section>
<section>
<h3>Password change</h3>
<p>
<%= form_with(url: settings_reset_password_path, method: :post) do %>
<%= submit_tag("Send me a password reset link") %>
<% end %>
</p>
</section>

View File

@@ -2,19 +2,13 @@
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %> <%= render "devise/shared/error_messages", resource: resource %>
<p>
<div class="field"> <%= f.label :email, 'Email address', class: 'block mb-1' %>
<p> <%= f.email_field :email, required: true, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
<%= f.label :email, 'Email address' %><br /> </p>
<%= f.email_field :email, required: true, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> <p class="mt-8">
</p> <%= f.submit "Resend confirmation instructions", class: 'btn-md btn-blue' %>
</div> </p>
<div class="actions">
<p>
<%= f.submit "Resend confirmation instructions" %>
</p>
</div>
<% end %> <% end %>
<%= render "devise/shared/links" %> <%= render "devise/shared/links" %>

View File

@@ -4,22 +4,24 @@
<%= render "devise/shared/error_messages", resource: resource %> <%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %> <%= f.hidden_field :reset_password_token %>
<div class="field"> <p class="mb-1">
<%= f.label :password, "New password" %><br /> <%= f.label :password, "New password" %>
<% if @minimum_password_length %> </p>
<em>(<%= @minimum_password_length %> characters minimum)</em><br /> <p>
<% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
</div> <% if @minimum_password_length %>
<br><em class="text-sm text-gray-500">(<%= @minimum_password_length %> characters minimum)</em>
<div class="field"> <% end %>
<%= f.label :password_confirmation, "Confirm new password" %><br /> </p>
<p class="mb-1">
<%= f.label :password_confirmation, "Confirm new password" %>
</p>
<p>
<%= f.password_field :password_confirmation, autocomplete: "new-password" %> <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div> </p>
<p class="mt-8">
<div class="actions"> <%= f.submit "Change my password", class: 'btn-md btn-blue' %>
<%= f.submit "Change my password" %> </p>
</div>
<% end %> <% end %>
<%= render "devise/shared/links" %> <%= render "devise/shared/links" %>

View File

@@ -2,26 +2,17 @@
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %> <%= render "devise/shared/error_messages", resource: resource %>
<p>
<div class="field"> <%= f.label :cn, 'User', class: 'block' %>
<p> <%= f.text_field :cn, autofocus: true, autocomplete: "username", required: true %> @ kosmos.org
<%= f.label :cn, 'User' %><br /> </p>
<%= f.text_field :cn, autofocus: true, autocomplete: "username", required: true %> @ kosmos.org <p>
</p> <%= f.label :email, 'Email address', class: 'block' %>
</div> <%= f.email_field :email, autocomplete: "email", required: true %>
</p>
<div class="field"> <p class="mt-8">
<p> <%= f.submit "Send me reset password instructions", class: 'btn-md btn-blue' %>
<%= f.label :email, 'Email address' %><br /> </p>
<%= f.email_field :email, autocomplete: "email", required: true %>
</p>
</div>
<div class="actions">
<p>
<%= f.submit "Send me reset password instructions" %>
</p>
</div>
<% end %> <% end %>
<%= render "devise/shared/links" %> <%= render "devise/shared/links" %>

View File

@@ -31,9 +31,9 @@
<%= f.password_field :current_password, autocomplete: "current-password" %> <%= f.password_field :current_password, autocomplete: "current-password" %>
</div> </div>
<div class="actions"> <p class="mt-8">
<%= f.submit "Update" %> <%= f.submit "Update", class: 'btn-md btn-blue' %>
</div> </p>
<% end %> <% end %>
<h3>Cancel my account</h3> <h3>Cancel my account</h3>

View File

@@ -2,24 +2,17 @@
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %> <%= render "devise/shared/error_messages", resource: resource %>
<p>
<div class="field"> <%= f.label :cn, 'User', class: 'block' %>
<p> <%= f.text_field :cn, autofocus: true, autocomplete: "username" %> @ kosmos.org
<%= f.label :cn, 'User' %><br /> </p>
<%= f.text_field :cn, autofocus: true, autocomplete: "username" %> @ kosmos.org <p>
</p> <%= f.label :password, class: 'block' %>
</div> <%= f.password_field :password, autocomplete: "current-password" %>
<div class="field"> </p>
<p> <p class="mt-8">
<%= f.label :password %><br /> <%= f.submit "Log in", class: 'btn-md btn-blue' %>
<%= f.password_field :password, autocomplete: "current-password" %> </p>
</p>
</div>
<div class="actions">
<p>
<%= f.submit "Log in" %>
</p>
</div>
<% end %> <% end %>
<%= render "devise/shared/links" %> <%= render "devise/shared/links" %>

View File

@@ -1,24 +1,24 @@
<div class="devise-links"> <div class="devise-links mt-8 text-sm">
<%- if controller_name != 'sessions' %> <%- if controller_name != 'sessions' %>
<p> <p class="mb-1.5">
<%= link_to "Log in", new_session_path(resource_name) %><br /> <%= link_to "Log in", new_session_path(resource_name) %><br />
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<p> <p class="mb-1.5">
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br /> <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<p> <p class="mb-1.5">
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br /> <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<p> <p class="mb-1.5">
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br /> <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
</p> </p>
<% end %> <% end %>

View File

@@ -2,15 +2,13 @@
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %> <%= render "devise/shared/error_messages", resource: resource %>
<p>
<div class="field">
<%= f.label :email %><br /> <%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %> <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div> </p>
<p class="mt-8">
<div class="actions"> <%= f.submit "Resend unlock instructions", class: 'btn-md btn-blue' %>
<%= f.submit "Resend unlock instructions" %> </p>
</div>
<% end %> <% end %>
<%= render "devise/shared/links" %> <%= render "devise/shared/links" %>

View File

@@ -0,0 +1,41 @@
<section>
<h2>Donations</h2>
<p>
Your financial contributions to the development and
upkeep of Kosmos software and services.
</p>
</section>
<section>
<% if @donations.any? %>
<ul class="donations list-none">
<% @donations.each do |donation| %>
<li class="mb-8 grid gap-y-2 gap-x-8 grid-cols-2 items-center">
<h3 class="mb-0">
<%= donation.paid_at.strftime("%B %d, %Y") %>
</h3>
<p class="row-span-2 font-mono text-right mb-0">
<span class="text-xl">
<%= number_with_delimiter donation.amount_sats %> sats
</span>
<br>
<span class="text-sm text-gray-500">
(~ <%= number_to_currency donation.amount_eur / 100, unit: "" %> EUR)
</span>
</p>
<p class="mb-0">
<% if donation.public_name.present? %>
Public name: <%= donation.public_name %>
<% else %>
Anonymous
<% end %>
</p>
</li>
<% end %>
</ul>
<% else %>
<p>
No donations to show.
</p>
<% end %>
</section>

View File

@@ -0,0 +1 @@
json.array! @donations, partial: "donations/donation", as: :donation

View File

@@ -1,5 +1,13 @@
<section> <section>
<h2>Invitations</h2> <h2>Invitations</h2>
<% if @invitations_unused.any? %>
<p>
Invite your friends to a Kosmos account by sharing an invitation URL with them:
</p>
<% end %>
</section>
<section>
<% if @invitations_unused.any? %> <% if @invitations_unused.any? %>
<table> <table>
<thead> <thead>
@@ -24,23 +32,25 @@
</section> </section>
<% if @invitations_used.any? %> <% if @invitations_used.any? %>
<h3>Accepted Invitations</h3> <section>
<table> <h3>Accepted Invitations</h3>
<thead> <table>
<tr> <thead>
<th>URL</th>
<th>Used at</th>
<th>Invited user</th>
</tr>
</thead>
<tbody>
<% @invitations_used.each do |invitation| %>
<tr> <tr>
<td><%= invitation_url(invitation.token) %></td> <th class="hide-small">ID</th>
<td><%= invitation.used_at %></td> <th>Accepted</th>
<td><%= User.find(invitation.invited_user_id).address %></td> <th>Invited user</th>
</tr> </tr>
<% end %> </thead>
</tbody> <tbody>
</table> <% @invitations_used.each do |invitation| %>
<tr>
<td class="hide-small"><%= invitation.token %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d") %></td>
<td><%= User.find(invitation.invited_user_id).address %></td>
</tr>
<% end %>
</tbody>
</table>
</section>
<% end %> <% end %>

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Admin Panel | Kosmos Accounts</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet">
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body id="admin-panel">
<div id="wrapper">
<header>
<h1>
<span class ="icon"><%= render partial: "shared/icons/comet" %></span>
<span class ="project-name">Kosmos</span>
<span class ="site-name">Accounts</span>
</h1>
<%= render partial: 'shared/header_account' %>
</header>
<% if user_signed_in? && current_user.confirmed? %>
<%= render partial: 'shared/admin_nav' %>
<% end %>
<% flash.each do |type, msg| %>
<div class="flash-msg <%= type %>">
<p><%= msg %></p>
</div>
<% end %>
<main>
<%= yield %>
</main>
</div>
</body>
</html>

View File

@@ -4,29 +4,27 @@
<title>Kosmos Accounts</title> <title>Kosmos Accounts</title>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
<%= 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">
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head> </head>
<body> <body>
<div id="wrapper"> <div id="wrapper">
<header> <header>
<h1> <h1>
<span class ="icon"><%= render partial: "shared/icons/comet" %></span>
<span class ="project-name">Kosmos</span> <span class ="project-name">Kosmos</span>
<span class ="site-name">Akkounts</span> <span class ="site-name">Account</span>
<span class="beta"><span class="bolt">⚡</span> beta</span>
</h1> </h1>
<% if user_signed_in? %> <%= render partial: 'shared/header_account' %>
<p class="current-user">
Signed in as <strong><%= current_user.cn %>@kosmos.org</strong>.
<%= link_to "Log out", destroy_user_session_path, method: :delete %>
</p>
<% end %>
</header> </header>
<% if user_signed_in? && current_user.confirmed? %>
<%= render partial: 'shared/main_nav' %>
<% end %>
<% flash.each do |type, msg| %> <% flash.each do |type, msg| %>
<div class="flash-msg <%= type %>"> <div class="flash-msg <%= type %>">
<p><%= msg %></p> <p><%= msg %></p>

View File

@@ -4,20 +4,19 @@
<title>Sign up | Kosmos Accounts</title> <title>Sign up | Kosmos Accounts</title>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
<%= 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">
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head> </head>
<body class="layout-signup"> <body class="layout-signup">
<div id="wrapper"> <div id="wrapper">
<header> <header>
<h1> <h1>
<span class ="icon"><%= render partial: "shared/icons/comet" %></span>
<span class ="project-name">Kosmos</span> <span class ="project-name">Kosmos</span>
<span class ="site-name">Sign Up</span> <span class ="site-name">Sign Up</span>
<!-- <span class="beta"><span class="bolt">⚡</span> beta</span> -->
</h1> </h1>
<% if user_signed_in? %> <% if user_signed_in? %>
<p class="current-user"> <p class="current-user">

View File

@@ -0,0 +1,12 @@
<section>
<h2>Security</h2>
</section>
<section>
<h3>Password change</h3>
<p>
<%= form_with(url: settings_reset_password_path, method: :post) do %>
<%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray') %>
<% end %>
</p>
</section>

View File

@@ -0,0 +1,22 @@
<nav id="main-nav">
<div class="wrapper">
<ul class="pages">
<li>
<%= link_to "Dashboard", admin_root_path,
class: @current_section == :dashboard ? "active" : nil %>
</li>
<li>
<%= link_to "Invitations", admin_invitations_path,
class: @current_section == :invitations ? "active" : nil %>
</li>
<li>
<%= link_to "Donations", admin_donations_path,
class: @current_section == :donations ? "active" : nil %>
</li>
<li>
<%= link_to "LDAP Users", admin_ldap_users_path,
class: @current_section == :ldap_users ? "active" : nil %>
</li>
</ul>
</div>
</nav>

View File

@@ -0,0 +1,6 @@
<% if user_signed_in? %>
<p class="current-user mt-8">
Signed in as <strong class="text-white font-normal"><%= current_user.cn %>@kosmos.org</strong>.
<%= link_to "Log out", destroy_user_session_path, method: :delete, class: 'underline' %>
</p>
<% end %>

View File

@@ -0,0 +1,26 @@
<nav id="main-nav">
<div class="wrapper">
<ul class="pages">
<li>
<%= link_to "Services", root_path,
class: @current_section == :dashboard ? "active" : nil %>
</li>
<li>
<%= link_to "Invitations", invitations_path,
class: @current_section == :invitations ? "active" : nil %>
</li>
<li>
<%= link_to "Donations", donations_path,
class: @current_section == :contributions ? "active" : nil %>
</li>
<li>
<%= link_to "Wallet", wallet_path,
class: @current_section == :wallet ? "active" : nil %>
</li>
<li>
<%= link_to "Security", security_path,
class: @current_section == :security ? "active" : nil %>
</li>
</ul>
</div>
</nav>

View File

@@ -0,0 +1 @@
<svg id="icon-comet" width="65.364" height="55.773" enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 65.364 55.773" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="layer1" transform="translate(28.868 20.259)" fill="#fff"><path id="path2" d="m22.81-9.2546-0.0137-0.0072c-0.0445-0.0196-0.0895-0.04052-0.13335-0.06078l-23.822-10.937s2.0034 9.219 2.914 11.778c0 0-27.292-8.1582-30.623-8.9354 1.0916 4.2618 20.006 40.848 20.006 40.848 3.8225 7.7608 12.677 12.083 21.912 12.083 12.949 0 23.446-10.497 23.446-23.446 6.6e-4 -9.4655-5.609-17.62-13.685-21.323z" fill="#fff" stroke-width=".65365"/></g></svg>

After

Width:  |  Height:  |  Size: 627 B

View File

@@ -7,6 +7,6 @@
This invitation can only be used once, and sign-up is currently only possible This invitation can only be used once, and sign-up is currently only possible
by invitation. Seems like you have good friends! by invitation. Seems like you have good friends!
</p> </p>
<p> <p class="mt-12">
<%= link_to "Get started", signup_steps_path(1), class: "next-step" %> <%= link_to "Get started", signup_steps_path(1), class: "btn btn-md btn-blue" %>
</p> </p>

View File

@@ -2,60 +2,53 @@
<% when 1 %> <% when 1 %>
<h2>Choose a username</h2> <h2>Choose a username</h2>
<%= form_for @user, :url => signup_validate_url do |f| %> <%= form_for @user, :url => signup_validate_url do |f| %>
<div class="field"> <p>
<p> <%= f.label :cn, 'Username', class: 'hidden' %>
<%= f.label :cn, 'Username' %><br /> <%= f.text_field :cn, autofocus: true, autocomplete: "username",
<%= f.text_field :cn, autofocus: true, autocomplete: "username" %> class: 'text-xl' %>
<span class="at-sign">@</span> <span class="text-xl ml-1">@</span>
<span class="domain">kosmos.org</span> <span class="text-xl">kosmos.org</span>
</p> </p>
<% if @validation_error.present? %> <% if @validation_error.present? %>
<p class="error-msg">Username <%= @validation_error %></p> <p class="error-msg">Username <%= @validation_error %></p>
<% end %> <% end %>
</div> <p class="mt-12">
<div class="actions"> <%= f.submit "Continue", class: 'btn btn-md btn-blue' %>
<p><%= f.submit "Continue" %></p> </p>
</div>
<% end %> <% end %>
<% when 2 %> <% when 2 %>
<h2>What's your email?</h2> <h2>What's your email?</h2>
<%= form_for @user, :url => signup_validate_url do |f| %> <%= form_for @user, :url => signup_validate_url do |f| %>
<div class="field"> <p>
<p> <%= f.label :email, 'Email address', class: 'hidden' %>
<%= f.label :email, 'Email address' %><br /> <%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'text-xl' %>
<%= f.email_field :email, autofocus: true, autocomplete: "email" %> </p>
</p> <% if @validation_error.present? %>
<% if @validation_error.present? %> <p class="error-msg">Email <%= @validation_error %></p>
<p class="error-msg">Email <%= @validation_error %></p> <% end %>
<% end %> <p class="mt-12">
</div> <%= f.submit "Continue", class: 'btn btn-md btn-blue' %>
<div class="actions"> </p>
<p><%= f.submit "Continue" %></p>
</div>
<% end %> <% end %>
<% when 3 %> <% when 3 %>
<h2>Choose a password</h2> <h2>Choose a password</h2>
<%= form_for @user, :url => signup_validate_url do |f| %> <%= form_for @user, :url => signup_validate_url do |f| %>
<div class="field"> <p>
<p> <%= f.label :password, 'Password', class: 'hidden' %>
<%= f.label :password, 'Password' %><br /> <%= f.password_field :password, autofocus: true, class: 'text-xl' %>
<%= f.password_field :password, autofocus: true %> </p>
</p> <% if @validation_error.present? %>
<% if @validation_error.present? %> <p class="error-msg">Password <%= @validation_error %></p>
<p class="error-msg">Password <%= @validation_error %></p> <% end %>
<% end %> <p class="mt-8 text-sm text-gray-500">
</div> By clicking the button below, you accept our future Terms of Service
<p class="accept-terms"> and Privacy Policy. Don't worry, they will be excellent!
<small> </p>
By clicking the button below, you accept our future Terms of Service <p class="mt-8">
and Privacy Policy. Don't worry, they will be excellent! <%= f.submit "Create account", class: 'btn-md btn-blue' %>
</small>
</p> </p>
<div class="actions">
<p><%= f.submit "Create account" %></p>
</div>
<% end %> <% end %>
<% end %> <% end %>

View File

@@ -0,0 +1,120 @@
<section class="w-full grid grid-cols-1 md:grid-cols-12 md:mb-0">
<div class="md:col-span-8">
<h2>Wallet</h2>
<p>
Send and receive sats via the Bitcoin Lightning Network.
</p>
</div>
<div class="md:col-span-4 mt-4 md:mt-0">
<p class="font-mono md:text-right 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>
<section>
<h3>Lightning Address</h3>
<p>
Your Kosmos user address is also a
<a class="ks-text-link" href="https://lightningaddress.com/" target="_blank">Lightning Address</a>!
The easiest way to receive sats is by just giving out your address:
</p>
<p>
<strong><%= current_user.address %></strong>
</p>
</section>
<section>
<h3>Wallet Apps</h3>
<p>
You can connect various wallet apps to your Kosmos account. This allows
you to both receive and send sats. Any wallet that supports
<a href="https://bluewallet.io/lndhub/" class="ks-text-link" target="_blank">LNDHub</a>
accounts should be able to add/import your account using our setup
code/URL:
</p>
<p class="my-6 text-center md:text-left">
<button id="copy-setup-code" class="btn-md btn-blue">Copy setup code/URL</button>
<span class="mx-2 my-2 md:my-0 block md:inline">or</span>
<button id="show-setup-code" class="btn-md btn-blue">Show setup QR code</button>
<button id="hide-setup-code" class="btn-md btn-blue hidden">Hide setup QR code</button>
</p>
<p id="setup-code" class="hidden my-10 w-full text-center">
<%= raw @svg %>
</p>
</section>
<section>
<h3>Recommended Apps</h3>
<div class="w-full grid grid-cols-1 gap-y-4 md:grid-cols-12
md:gap-y-6 md:gap-x-4 md:items-center">
<h4 class="md:col-span-3">
<a href="https://bluewallet.io" class="ks-text-link text-xl"
title="Blue Wallet" target="_blank">
<%= image_tag("/img/logos/bluewallet.svg", class: 'h-16') %>
</a>
</h4>
<p class="md:col-span-4 mb-0 text-gray-500">
Android / iOS / macOS
</p>
<p class="md:col-span-5 mb-0">
When adding a wallet, choose "Import wallet" on the bottom of the screen,
then scan the setup QR code.
</p>
<h4 class="md:col-span-3">
<a href="https://getalby.com/" class="ks-text-link text-xl"
title="Alby" target="_blank">
<%= image_tag("/img/logos/alby.svg", class: 'h-16') %>
</a>
</h4>
<p class="md:col-span-4 mb-0 text-gray-500">
Firefox / Chrome (Opera, Brave, Chromium-based browsers)
</p>
<p class="md:col-span-5 mb-0">
Choose "LNDHub (Bluewallet)" in the connect dialog and paste the setup
URL in the "LNDHub Export URI" field.
</p>
</div>
</section>
<script type="text/javascript">
(function () {
const buttonShow = document.querySelector('#show-setup-code');
const buttonHide = document.querySelector('#hide-setup-code');
const buttonCopy = document.querySelector('#copy-setup-code');
const setupCode = document.querySelector('#setup-code');
buttonShow.addEventListener('click', function(ev) {
ev.preventDefault();
setupCode.classList.remove('hidden');
buttonHide.classList.remove('hidden');
buttonShow.classList.add('hidden');
setupCode.scrollIntoView({behavior: "smooth", block: "nearest"});
});
buttonHide.addEventListener('click', function(ev) {
ev.preventDefault();
const el = document.querySelector('#setup-code');
setupCode.classList.add('hidden');
buttonHide.classList.add('hidden');
buttonShow.classList.remove('hidden');
});
buttonCopy.addEventListener('click', function(ev) {
ev.preventDefault();
navigator.clipboard.writeText('<%= @wallet_url %>').then(() => {
const buttonText = buttonCopy.innerText;
buttonCopy.innerText = 'Copied ✔';
setTimeout(() => {
buttonCopy.innerText = buttonText;
}, 2000);
});
});
})();
</script>

View File

@@ -39,5 +39,8 @@ module Akkounts
g.fixture_replacement :factory_bot, suffix_factory: 'factory', dir: 'spec/factories' g.fixture_replacement :factory_bot, suffix_factory: 'factory', dir: 'spec/factories'
g.stylesheets false g.stylesheets false
end end
config.active_job.queue_adapter = :sidekiq
config.action_mailer.deliver_later_queue_name = nil # use "default" queue
end end
end end

View File

@@ -1 +1 @@
LWyKwPZq9Kd97rn/7+q3MEkh7kITScDMHD3JvVuaV3A4YIHJHU+460k+PaEGlsH1xkbuClGiAb57rk1XLyDnmVGtbSueYOtinkw6kar8ZfKWZob061LwGjpMVRQkS49TjCUZlqCFrXeKxlH03mXWBnqAj9RUIPrm7eibb3c7qmJFglR1380RSVsfZnp8A3QwGm4Wh9OWtpUa6P2lne0jQsOuSe8ur3DUF0LplzS4CbkMxAUDOom+pXB13AlxOH9NQE7F4dsYHugHkh1tG3r3ER3xAUD/9Kn6UZZP7BnwUs3zqhoZdULRpRgA5dK7ueTIAnO/jtJDF4562VS8ECo7AnNoVxNe8/mBMFIOUfqg+db/72N2pIk3r4lK7Uzm/4jJ5/99ItnQjHQPcApiwZXIr3OyDLUvq5+d0UVmAMXdwcAjvctVQXFx5imG149Y0ISHKWVm1ca37aAspxWPU+CIj8/HW0yEpjp3vhwDUbjCaZeAPm8UQC14MxZwSK3N+EUSQXdltiweFynabDB7zGGQsjMM8LwMtyo9bTBzJA78Cl96MDyd20i1zSF9ntLuKulwGm3oZowpbNuvo2anY6r9yBlDJBOEISbtXv2tLX4SqcM=--bRcsE4K/29XzyZat--+G3iQCLBqgSwLaQ+7+4YvA== PRI1laGy6XiLpsII0ixFOTCpzzDbrRpIQ7GJ2flvA+UEEiPVQQ4e/pt6ZjumaZ/geTBHCRZklLtS1u6HjucVKHGSR4K/lE1q7u6nfi/wAvwGQdCTJeKf8PetdlsiVZg2OW4Wi2YF7qtb1ipGS/uRlOeLP8JOwp5BNVS1J6tvNX4T5gA7rrB4Cvtfwp1dwLWODHiXbeZK7SN2j7V3NPGZl0LbNg2jquC4DzQOBhMOlwu1IAgOAcqKbHh9tDabpVOcOD5pNSaVaoBO01P8ObbX8x0S0Ug7BPkM5nnyJTQdNH7LHKvtNmrUEk7+fd+VZR6WqEp6SFBoAA7XxvOLy9dIOqvXbAYnrNZX7iI4IpgZWRNIQ7G1qSBjpyPLKC7+nSPmvd4IcgCHVUBdYp0+yYDxLzwuuhPVtyAY4JAIdZY1mhYxx7BzGNiMc3p8AlMS6hPeO9lVQZykViBGnTjR+iah9OQYAihX9FYywtBkNvMXLiYqsVZmv6em9Uk/ivKSIsaFwNwrelCI5H4Q/+/hKFJ1JaMjbYheXCJgcGgfhiC+fIHig/8y8rXd9lWGa1T+PrpA8akcq/K8I6gbxYxpLWcy5Vnz/SyaMurLPVfCq4cD/JfpLSfy0HophaiycZ7D6dr43rnHsPfrt6kveDOgWUK9d7CqHxsWdXbCpJeBoh8/1usIsGsmyhfwHq0dEpRx78bR2EJeM4LOAuQgf69/5rFJydIbXq76l1BftTls1Pgh--OI8c/RQGQ30wT1Ff--M/ltnmdl+FRdlNliTb79lg==

View File

@@ -0,0 +1 @@
2a8a17892dd9f41ea50c61310c83240b

View File

@@ -0,0 +1 @@
zdpQvlbfqXzaExLzw2LGZkXj97HH42jWZFSTlINpf/HlFr6NubPxLkVxeGsittJj5rm8yL+V21zPxp30Z7Q7R45qyCKFtevtVeqb+u1nZ/FsVfkwb/7wDW9scurgXw==--SB6C0aaNf8qPdteG--9hP+6tpsMnyiAVVvAIq+7w==

View File

@@ -0,0 +1 @@
6b101c9addbfa5f959b5859f756bc9d7

View File

@@ -0,0 +1 @@
xgPOFd8315z7lFtTR5/nD6WDBM2M6Grt/pmkCPdaqlw0WAmFKzbiRGFsXoUQ02JNzvT1/FVtBSsAcyK1Pdr1QQztlWC+/ywaflloMBS4//D8IEXvEgCK6uff5gcf1A==--WbFrw9advCJ4mqsK--HTVHZqO0ddG1toFpY0KKgQ==

View File

@@ -49,4 +49,6 @@ Rails.application.configure do
protocol: "https", protocol: "https",
from: "accounts@kosmos.org" from: "accounts@kosmos.org"
} }
config.active_job.queue_adapter = :test
end end

View File

@@ -1,3 +1,5 @@
require 'sidekiq/web'
Rails.application.routes.draw do Rails.application.routes.draw do
devise_for :users devise_for :users
@@ -11,11 +13,26 @@ Rails.application.routes.draw do
get 'settings', to: 'settings#index' get 'settings', to: 'settings#index'
post 'settings_reset_password', to: 'settings#reset_password' post 'settings_reset_password', to: 'settings#reset_password'
get 'security', to: 'security#index'
resources :invitations, only: ['index', 'show', 'create', 'destroy'] resources :invitations, only: ['index', 'show', 'create', 'destroy']
resources :donations
get 'wallet', to: 'wallet#index'
get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/}
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/}
namespace :admin do namespace :admin do
root to: 'dashboard#index' root to: 'dashboard#index'
get 'invitations', to: 'invitations#index'
get 'ldap_users', to: 'ldap_users#index' get 'ldap_users', to: 'ldap_users#index'
resources :donations
end
authenticate :user, ->(user) { user.is_admin? } do
mount Sidekiq::Web => '/sidekiq'
end end
# Letter Opener (open "sent" emails in dev and staging) # Letter Opener (open "sent" emails in dev and staging)

3
config/sidekiq.yml Normal file
View File

@@ -0,0 +1,3 @@
:concurrency: 2
:queues:
- default

View File

@@ -8,6 +8,7 @@ class CreateInvitations < ActiveRecord::Migration[6.0]
t.timestamps t.timestamps
end end
add_index :invitations, :user_id add_index :invitations, :user_id
add_index :invitations, :invited_user_id add_index :invitations, :invited_user_id
end end

View File

@@ -0,0 +1,15 @@
class CreateDonations < ActiveRecord::Migration[6.0]
def change
create_table :donations do |t|
t.integer :user_id
t.integer :amount_sats
t.integer :amount_eur
t.integer :amount_usd
t.string :public_name
t.timestamps
end
add_index :donations, :user_id
end
end

View File

@@ -0,0 +1,5 @@
class AddPaidAtToDonations < ActiveRecord::Migration[6.0]
def change
add_column :donations, :paid_at, :datetime
end
end

View File

@@ -0,0 +1,6 @@
class AddLndhubCredentialsToUser < ActiveRecord::Migration[6.0]
def change
add_column :users, :ln_login_ciphertext, :text
add_column :users, :ln_password_ciphertext, :text
end
end

View File

@@ -10,7 +10,19 @@
# #
# 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: 2020_11_30_132533) do ActiveRecord::Schema.define(version: 2021_11_20_010540) do
create_table "donations", force: :cascade do |t|
t.integer "user_id"
t.integer "amount_sats"
t.integer "amount_eur"
t.integer "amount_usd"
t.string "public_name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "paid_at"
t.index ["user_id"], name: "index_donations_on_user_id"
end
create_table "invitations", force: :cascade do |t| create_table "invitations", force: :cascade do |t|
t.string "token" t.string "token"
@@ -33,6 +45,8 @@ ActiveRecord::Schema.define(version: 2020_11_30_132533) do
t.datetime "confirmed_at" t.datetime "confirmed_at"
t.datetime "confirmation_sent_at" t.datetime "confirmation_sent_at"
t.string "unconfirmed_email" t.string "unconfirmed_email"
t.text "ln_login_ciphertext"
t.text "ln_password_ciphertext"
t.index ["email"], name: "index_users_on_email", unique: true t.index ["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

View File

@@ -2,12 +2,21 @@ namespace :invitations do
desc "Generate invitations for all users" desc "Generate invitations for all users"
task :generate_for_all_users, [:amount_per_user] => :environment do |t, args| task :generate_for_all_users, [:amount_per_user] => :environment do |t, args|
count = 0 count = 0
User.all.each do |user| User.all.each do |user|
args[:amount_per_user].to_i.times do amt_to_create = args[:amount_per_user].to_i - user.invitations.unused.count
next unless amt_to_create > 0
amt_created = 0
amt_to_create.times do
user.invitations << Invitation.create(user: user) user.invitations << Invitation.create(user: user)
count += 1 count += 1
amt_created += 1
end end
puts "Created #{amt_created} new invitations for #{user.address}"
end end
puts "Created #{count} new invitations"
puts "---\nCreated #{count} new invitations overall"
end end
end end

8
lib/tasks/lndhub.rake Normal file
View File

@@ -0,0 +1,8 @@
namespace :lndhub do
desc "Generate wallets for all users"
task :generate_wallets => :environment do |t, args|
User.all.each do |user|
CreateLndhubWalletJob.perform_later(user)
end
end
end

View File

@@ -5,6 +5,10 @@
"@rails/actioncable": "^6.0.0", "@rails/actioncable": "^6.0.0",
"@rails/ujs": "^6.0.0", "@rails/ujs": "^6.0.0",
"@rails/webpacker": "4.3.0", "@rails/webpacker": "4.3.0",
"@tailwindcss/forms": "^0.2.1",
"autoprefixer": "^9",
"postcss": "^7",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
"turbolinks": "^5.2.0" "turbolinks": "^5.2.0"
}, },
"version": "0.1.0", "version": "0.1.0",

View File

@@ -1,5 +1,6 @@
module.exports = { module.exports = {
plugins: [ plugins: [
require("tailwindcss")("./app/javascript/stylesheets/tailwind.config.js"),
require('postcss-import'), require('postcss-import'),
require('postcss-flexbugs-fixes'), require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({ require('postcss-preset-env')({

BIN
public/img/bg-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

12
public/img/logos/alby.svg Normal file
View File

@@ -0,0 +1,12 @@
<svg width="1210" height="525" viewBox="0 0 1210 525" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle r="36.675" transform="matrix(-1 0 0 1 94.1325 138.143)" fill="black"/>
<path d="M88.02 132.641L156.48 201.101" stroke="black" stroke-width="18.3375"/>
<circle cx="393.645" cy="138.143" r="36.675" fill="black"/>
<path d="M400.369 132.641L331.909 201.101" stroke="black" stroke-width="18.3375"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M104.543 356.993C84.7762 347.583 73.2723 326.451 77.1258 304.901C93.6652 212.406 162.593 143.032 245.111 143.032C327.83 143.032 396.891 212.743 413.216 305.573C417.013 327.163 405.416 348.284 385.581 357.618C343.14 377.591 295.735 388.755 245.723 388.755C195.191 388.755 147.32 377.357 104.543 356.993Z" fill="#FFDF6F"/>
<path d="M413.216 305.573L404.186 307.161L413.216 305.573ZM385.581 357.618L381.677 349.322L381.677 349.322L385.581 357.618ZM86.1514 306.515C102.133 217.14 168.195 152.201 245.111 152.201V133.864C156.991 133.864 85.1975 207.672 68.1002 303.287L86.1514 306.515ZM245.111 152.201C322.214 152.201 388.411 217.457 404.186 307.161L422.246 303.985C405.372 208.028 333.446 133.864 245.111 133.864V152.201ZM381.677 349.322C340.432 368.732 294.358 379.586 245.723 379.586V397.924C297.113 397.924 345.848 386.449 389.485 365.914L381.677 349.322ZM245.723 379.586C196.583 379.586 150.056 368.505 108.484 348.714L100.602 365.271C144.584 386.21 193.798 397.924 245.723 397.924V379.586ZM404.186 307.161C407.23 324.47 397.962 341.659 381.677 349.322L389.485 365.914C412.869 354.91 426.796 329.855 422.246 303.985L404.186 307.161ZM68.1002 303.287C63.4826 329.11 77.2985 354.178 100.602 365.271L108.484 348.714C92.254 340.988 83.062 323.792 86.1514 306.515L68.1002 303.287Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M139.812 333.998C123.901 327.517 114.463 310.589 119.976 294.317C136.978 244.138 186.576 207.825 245.112 207.825C303.648 207.825 353.246 244.138 370.249 294.317C375.762 310.589 366.324 327.517 350.413 333.998C317.915 347.233 282.365 354.525 245.112 354.525C207.859 354.525 172.309 347.233 139.812 333.998Z" fill="black"/>
<ellipse cx="287.288" cy="286.065" rx="30.5625" ry="24.45" fill="white"/>
<ellipse cx="199.795" cy="286.08" rx="30.5625" ry="24.45" fill="white"/>
<path d="M623.9 134.4H682.14L763.42 349.44L706.14 355.2L690.46 309.12H615.26L600.22 352H542.3L623.9 134.4ZM673.82 263.36L652.06 204.16L631.26 263.36H673.82ZM837.64 352H779.4V129.6L837.64 126.08V352ZM899.003 352H869.563V129.6L927.803 126.08V195.52C931.429 194.24 935.163 193.28 939.003 192.64C942.843 191.787 946.789 191.36 950.843 191.36C966.203 191.36 979.856 194.24 991.803 200C1003.75 205.76 1013.14 214.613 1019.96 226.56C1027 238.507 1030.52 253.76 1030.52 272.32C1030.52 290.667 1027 306.027 1019.96 318.4C1013.14 330.773 1003.54 340.053 991.163 346.24C979.003 352.427 964.709 355.52 948.283 355.52C941.456 355.52 934.736 354.453 928.123 352.32C921.723 350.187 915.323 346.773 908.923 342.08L899.003 352ZM941.883 236.16C937.616 236.16 932.923 237.76 927.803 240.96V306.24C933.563 309.227 939.109 310.72 944.443 310.72C951.696 310.72 957.776 307.413 962.683 300.8C967.803 293.973 970.363 284.587 970.363 272.64C970.363 260.693 967.909 251.627 963.003 245.44C958.096 239.253 951.056 236.16 941.883 236.16ZM1088.12 352.64L1089.4 349.12L1033.72 206.4L1087.48 191.04L1119.16 284.48L1148.92 196.8H1206.84L1138.69 370.24C1135.48 378.56 1130.26 386.24 1123 393.28C1115.75 400.32 1107.33 406.293 1097.72 411.2C1088.12 416.107 1078.2 419.733 1067.96 422.08L1048.44 376.96C1053.56 374.827 1058.9 372.48 1064.44 369.92C1070.2 367.36 1075.32 364.587 1079.8 361.6C1084.28 358.613 1087.06 355.627 1088.12 352.64Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,9 @@
FactoryBot.define do
factory :donation do
user_id { 1 }
amount_sats { 100000 }
amount_eur { 10 }
amount_usd { 13 }
public_name { nil }
end
end

View File

@@ -10,6 +10,6 @@ RSpec.describe 'Admin dashboard', type: :feature do
scenario 'View dashboard' do scenario 'View dashboard' do
visit admin_root_path visit admin_root_path
expect(page).to have_content('Admin Panel') expect(page).to have_content('great power')
end end
end end

View File

@@ -0,0 +1,9 @@
require 'rails_helper'
describe ApplicationHelper do
describe "sats_to_btc" do
it "converts satoshis to BTC" do
expect(helper.sats_to_btc(120000000)).to eq(1.2)
end
end
end

Some files were not shown because too many files have changed in this diff Show More