Compare commits
40 Commits
7f5b8c22b7
...
99dc36f13a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99dc36f13a | ||
|
|
ee74c4847f | ||
|
|
15b63eee73 | ||
|
|
c756528d32 | ||
|
|
fef29b4fc0 | ||
|
|
38608e053d | ||
|
|
5f215b8ed8 | ||
|
|
87aae35974 | ||
|
|
6ad02e69a2 | ||
|
|
94ca0f3764 | ||
|
|
0fec37e0a9 | ||
|
|
620befd7c0 | ||
|
|
aba4930696 | ||
|
|
0492b42327 | ||
|
|
445a1c80a6 | ||
|
|
cf48f76553 | ||
|
|
70fa43f5d2 | ||
|
|
b37a0c25a4 | ||
|
|
3197743a55 | ||
|
|
3f49e4a3b8 | ||
| 2e1d930e0f | |||
| d849d28f62 | |||
|
|
f2a22adf6b | ||
|
|
e1aaa2c434 | ||
|
|
e62bf67262 | ||
|
|
6df3d5933c | ||
|
|
a5a90c4d83 | ||
|
|
80ef75ff42 | ||
|
|
67e2e45dd8 | ||
|
|
3834e5230b | ||
|
|
4cb7c0998f | ||
|
|
20382f7df7 | ||
|
|
add94eee8d | ||
|
|
067dc3b63d | ||
|
|
1a470cf1c8 | ||
|
|
f85b7f4f62 | ||
|
|
8635413002 | ||
|
|
a3da956b48 | ||
|
|
3c40dc98ca | ||
| 28b31e63f9 |
@ -1,4 +1,7 @@
|
||||
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
|
||||
|
||||
BTCPAY_API_URL='http://10.1.1.163:23001/api/v1'
|
||||
LNDHUB_API_URL='http://10.1.1.163:3023'
|
||||
|
||||
LNDHUB_LEGACY_API_URL='http://10.1.1.163:3026'
|
||||
LNDHUB_API_URL='http://10.1.1.163:3026'
|
||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
EJABBERD_API_URL='http://xmpp.example.com/api'
|
||||
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||
LNDHUB_API_URL='http://localhost:3023'
|
||||
LNDHUB_LEGACY_API_URL='http://localhost:3023'
|
||||
LNDHUB_API_URL='http://localhost:3026'
|
||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM ruby:2.7
|
||||
FROM ruby:2.7.6
|
||||
RUN apt-get update -qq && apt-get install -y curl ldap-utils
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
@ -8,6 +8,7 @@ COPY Gemfile /akkounts/Gemfile
|
||||
COPY Gemfile.lock /akkounts/Gemfile.lock
|
||||
COPY package.json /akkounts/package.json
|
||||
RUN bundle install
|
||||
RUN gem install foreman
|
||||
RUN npm install -g yarn
|
||||
RUN yarn install
|
||||
|
||||
|
||||
1
Gemfile
@ -59,6 +59,7 @@ group :development do
|
||||
gem 'listen', '~> 3.2'
|
||||
gem 'letter_opener'
|
||||
gem 'letter_opener_web'
|
||||
gem 'faker'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
||||
@ -117,6 +117,8 @@ GEM
|
||||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (3.0.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (2.7.1)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
@ -313,6 +315,7 @@ DEPENDENCIES
|
||||
devise_ldap_authenticatable
|
||||
dotenv-rails
|
||||
factory_bot_rails
|
||||
faker
|
||||
faraday
|
||||
importmap-rails
|
||||
jbuilder (~> 2.7)
|
||||
|
||||
42
README.md
@ -7,6 +7,27 @@ credentials, invites, donations, etc..
|
||||
|
||||
## Development
|
||||
|
||||
### Quick Start
|
||||
|
||||
The easiest way to get a working development setup is using Docker Compose like
|
||||
so:
|
||||
|
||||
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
||||
Docker Desktop)
|
||||
2. Uncomment the `web` section in `docker-compose.yml`
|
||||
3. Run `docker compose up` and wait until 389ds announces its successful start
|
||||
in the log output
|
||||
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
||||
5. `docker compose run web rails ldap:setup`
|
||||
5. `docker compose run web rails db:setup`
|
||||
|
||||
After these steps, you should have a working Rails app with a handful of test
|
||||
users running on [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
Log in with username "admin" and password "admin is admin". All users listed on
|
||||
[http://localhost:3000/admin/ldap_users](http://localhost:3000/admin/ldap_users)
|
||||
have the password "user is user".
|
||||
|
||||
### Rails app
|
||||
|
||||
Installing dependencies:
|
||||
@ -33,16 +54,14 @@ Running all specs:
|
||||
|
||||
### Docker (Compose)
|
||||
|
||||
There is a working Dockr Compose config file, which allows you to spin up both
|
||||
There is a working Docker Compose config file, which allows you to spin up both
|
||||
an app server for Rails as well as a local 389ds (LDAP) server.
|
||||
|
||||
By default, `docker-compose up` will only start the LDAP server, listening on
|
||||
port 389 on your machine. Uncomment other services in `docker-compose.yml`.
|
||||
port 389 on your machine. Uncomment other services in `docker-compose.yml` if
|
||||
you want to use them.
|
||||
|
||||
### LDAP server
|
||||
|
||||
See the previous section for quickly spinning up an LDAP server with Docker (or
|
||||
edit your environment configuration to use an existing one).
|
||||
#### LDAP server
|
||||
|
||||
After creating the Docker container for the first time (or after deleting it),
|
||||
you need to run the following command once, in order to create the dirsrv
|
||||
@ -52,10 +71,13 @@ back-end:
|
||||
|
||||
Now you can seed the back-end with data using this Rails task:
|
||||
|
||||
bundle exec rails ldap:seed
|
||||
bundle exec rails ldap:setup
|
||||
|
||||
The seeds task will first delete any existing entries in the directory tree
|
||||
("dc=kosmos,dc=org"), and then create our example/development entries.
|
||||
The setup task will first delete any existing entries in the directory tree
|
||||
("dc=kosmos,dc=org"), and then create our development entries.
|
||||
|
||||
Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over
|
||||
with a fresh installation, delete both that directory as well as the container.
|
||||
|
||||
## Documentation
|
||||
|
||||
@ -84,3 +106,5 @@ The seeds task will first delete any existing entries in the directory tree
|
||||
## License
|
||||
|
||||
[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)
|
||||
|
||||
[1]: https://docs.docker.com/compose/install/
|
||||
|
||||
@ -6,12 +6,16 @@
|
||||
|
||||
.btn-md {
|
||||
@apply btn;
|
||||
@apply py-2.5 px-5 shadow-md;
|
||||
@apply py-3 px-6;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@apply btn;
|
||||
@apply py-1 px-2 text-sm shadow-sm;
|
||||
@apply py-1 px-2 text-sm;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
@apply px-3;
|
||||
}
|
||||
|
||||
.btn-gray {
|
||||
@ -28,4 +32,8 @@
|
||||
@apply bg-red-600 hover:bg-red-700 text-white
|
||||
focus:ring-red-500 focus:ring-opacity-75;
|
||||
}
|
||||
|
||||
input[type=text]:disabled {
|
||||
@apply text-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
|
||||
<div class="md:min-h-[50vh] bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
|
||||
<%= content %>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
|
||||
<div class="md:min-h-[50vh] divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
|
||||
<aside class="py-6 sm:py-8 lg:col-span-3">
|
||||
<nav class="space-y-1">
|
||||
<%= render partial: @sidenav_partial %>
|
||||
|
||||
10
app/components/main_with_tabnav_component.html.erb
Normal file
@ -0,0 +1,10 @@
|
||||
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="px-6 sm:px-12 pt-2 sm:pt-4">
|
||||
<%= render partial: @tabnav_partial %>
|
||||
</div>
|
||||
<div class="px-6 sm:px-12 py-8 sm:py-12">
|
||||
<%= content %>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
7
app/components/main_with_tabnav_component.rb
Normal file
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class MainWithTabnavComponent < ViewComponent::Base
|
||||
def initialize(tabnav_partial:)
|
||||
@tabnav_partial = tabnav_partial
|
||||
end
|
||||
end
|
||||
@ -1,4 +1,4 @@
|
||||
<%= link_to @path, class: @link_class do %>
|
||||
<%= link_to @path, class: @link_class, title: (@disabled ? "Coming soon" : nil) do %>
|
||||
<%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %>
|
||||
<span class="truncate"><%= @name %></span>
|
||||
<% end %>
|
||||
|
||||
3
app/components/tabnav_link_component.html.erb
Normal file
@ -0,0 +1,3 @@
|
||||
<%= link_to @path, class: @link_class do %>
|
||||
<%= @name %>
|
||||
<% end %>
|
||||
21
app/components/tabnav_link_component.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TabnavLinkComponent < ViewComponent::Base
|
||||
def initialize(name:, path:, active: false, disabled: false)
|
||||
@name = name
|
||||
@path = path
|
||||
@active = active
|
||||
@disabled = disabled
|
||||
@link_class = class_names_link(path)
|
||||
end
|
||||
|
||||
def class_names_link(path)
|
||||
if @active
|
||||
"border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2"
|
||||
elsif @disabled
|
||||
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2"
|
||||
else
|
||||
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2"
|
||||
end
|
||||
end
|
||||
end
|
||||
7
app/controllers/account_controller.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class AccountController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
|
||||
def index
|
||||
@current_section = :account
|
||||
end
|
||||
end
|
||||
@ -27,7 +27,7 @@ class Admin::LdapUsersController < Admin::BaseController
|
||||
def ldap_client
|
||||
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
||||
port: ldap_config['port'],
|
||||
encryption: ldap_config['ssl'],
|
||||
# encryption: ldap_config['ssl'],
|
||||
auth: {
|
||||
method: :simple,
|
||||
username: ldap_config['admin_user'],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
class DonationsController < ApplicationController
|
||||
class Contributions::DonationsController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
|
||||
# GET /donations
|
||||
8
app/controllers/contributions/projects_controller.rb
Normal file
@ -0,0 +1,8 @@
|
||||
class Contributions::ProjectsController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
|
||||
# GET /contributions
|
||||
def index
|
||||
@current_section = :contributions
|
||||
end
|
||||
end
|
||||
@ -1,7 +0,0 @@
|
||||
class SecurityController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
|
||||
def index
|
||||
@current_section = :security
|
||||
end
|
||||
end
|
||||
13
app/controllers/settings/account_controller.rb
Normal file
@ -0,0 +1,13 @@
|
||||
class Settings::AccountController < SettingsController
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def reset_password
|
||||
current_user.send_reset_password_instructions
|
||||
sign_out current_user
|
||||
msg = "We have sent you an email with a link to reset your password."
|
||||
redirect_to check_your_email_path, notice: msg
|
||||
end
|
||||
|
||||
end
|
||||
11
app/controllers/settings/profile_controller.rb
Normal file
@ -0,0 +1,11 @@
|
||||
class Settings::ProfileController < SettingsController
|
||||
|
||||
def index
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@ -1,13 +1,13 @@
|
||||
class SettingsController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
before_action :set_current_section
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def reset_password
|
||||
current_user.send_reset_password_instructions
|
||||
sign_out current_user
|
||||
msg = "We have sent you an email with a link to reset your password."
|
||||
redirect_to check_your_email_path, notice: msg
|
||||
private
|
||||
|
||||
def set_current_section
|
||||
@current_section = :settings
|
||||
end
|
||||
end
|
||||
|
||||
18
app/controllers/turbo_controller.rb
Normal file
@ -0,0 +1,18 @@
|
||||
class TurboController < ApplicationController
|
||||
class Responder < ActionController::Responder
|
||||
def to_turbo_stream
|
||||
controller.render(options.merge(formats: :html))
|
||||
rescue ActionView::MissingTemplate => error
|
||||
if get?
|
||||
raise error
|
||||
elsif has_errors? && default_action
|
||||
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
|
||||
else
|
||||
redirect_to navigation_location
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.responder = Responder
|
||||
respond_to :html, :turbo_stream
|
||||
end
|
||||
18
app/controllers/users/devise_controller.rb
Normal file
@ -0,0 +1,18 @@
|
||||
class Users::DeviseController < ApplicationController
|
||||
class Responder < ActionController::Responder
|
||||
def to_turbo_stream
|
||||
controller.render(options.merge(formats: :html))
|
||||
rescue ActionView::MissingTemplate => error
|
||||
if get?
|
||||
raise error
|
||||
elsif has_errors? && default_action
|
||||
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
|
||||
else
|
||||
redirect_to navigation_location
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.responder = Responder
|
||||
respond_to :html, :turbo_stream
|
||||
end
|
||||
@ -28,13 +28,13 @@ class WalletController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def authenticate_with_lndhub
|
||||
if session["ln_auth_token"].present?
|
||||
@ln_auth_token = session["ln_auth_token"]
|
||||
def authenticate_with_lndhub(options={})
|
||||
if session[:ln_auth_token].present? && !options[:force_reauth]
|
||||
@ln_auth_token = session[:ln_auth_token]
|
||||
else
|
||||
lndhub = Lndhub.new
|
||||
auth_token = lndhub.authenticate(current_user)
|
||||
session["ln_auth_token"] = auth_token
|
||||
session[:ln_auth_token] = auth_token
|
||||
@ln_auth_token = auth_token
|
||||
end
|
||||
rescue
|
||||
@ -49,14 +49,23 @@ class WalletController < ApplicationController
|
||||
lndhub = Lndhub.new
|
||||
data = lndhub.balance @ln_auth_token
|
||||
@balance = data["BTC"]["AvailableBalance"] rescue nil
|
||||
rescue
|
||||
authenticate_with_lndhub(force_reauth: true)
|
||||
return nil if @fetch_balance_retried
|
||||
@fetch_balance_retried = true
|
||||
fetch_balance
|
||||
end
|
||||
|
||||
def fetch_transactions
|
||||
lndhub = Lndhub.new
|
||||
txs = lndhub.gettxs @ln_auth_token
|
||||
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
|
||||
|
||||
process_transactions(txs + invoices)
|
||||
rescue
|
||||
authenticate_with_lndhub(force_reauth: true)
|
||||
return [] if @fetch_transactions_retried
|
||||
@fetch_transactions_retried = true
|
||||
fetch_transactions
|
||||
end
|
||||
|
||||
def process_transactions(txs)
|
||||
|
||||
16
app/javascript/controllers/clipboard_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["source", "trigger"]
|
||||
|
||||
copy (event) {
|
||||
event.preventDefault();
|
||||
navigator.clipboard.writeText(this.sourceTarget.value);
|
||||
this.triggerTarget.querySelector('.content-initial').classList.add('hidden');
|
||||
this.triggerTarget.querySelector('.content-active').classList.remove('hidden');
|
||||
setTimeout(() => {
|
||||
this.triggerTarget.querySelector('.content-initial').classList.remove('hidden');
|
||||
this.triggerTarget.querySelector('.content-active').classList.add('hidden');
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
class CreateLndhubWalletJob < ApplicationJob
|
||||
class CreateLndhubAccountJob < 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" })
|
||||
lndhub = LndhubV2.new
|
||||
credentials = lndhub.create_account
|
||||
|
||||
user.update! ln_login: credentials["login"],
|
||||
ln_password: credentials["password"]
|
||||
@ -5,12 +5,13 @@ class CreateAccount < ApplicationService
|
||||
@email = args[:email]
|
||||
@password = args[:password]
|
||||
@invitation = args[:invitation]
|
||||
@confirmed = args[:confirmed]
|
||||
end
|
||||
|
||||
def call
|
||||
user = create_user_in_database
|
||||
add_ldap_document
|
||||
create_lndhub_wallet(user)
|
||||
create_lndhub_account(user)
|
||||
|
||||
if @invitation.present?
|
||||
update_invitation(user.id)
|
||||
@ -26,7 +27,8 @@ class CreateAccount < ApplicationService
|
||||
ou: @domain,
|
||||
email: @email,
|
||||
password: @password,
|
||||
password_confirmation: @password
|
||||
password_confirmation: @password,
|
||||
confirmed_at: @confirmed ? DateTime.now : nil
|
||||
)
|
||||
end
|
||||
|
||||
@ -35,6 +37,7 @@ class CreateAccount < ApplicationService
|
||||
end
|
||||
|
||||
# TODO move to confirmation
|
||||
# (and/or add email_confirmed to entry and use in login filter)
|
||||
def add_ldap_document
|
||||
hashed_pw = Devise.ldap_auth_password_builder.call(@password)
|
||||
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
|
||||
@ -46,21 +49,9 @@ class CreateAccount < ApplicationService
|
||||
ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain)
|
||||
end
|
||||
|
||||
def create_lndhub_wallet(user)
|
||||
CreateLndhubWalletJob.perform_later(user)
|
||||
end
|
||||
|
||||
def exchange_xmpp_contacts_between_inviter_and_invitee
|
||||
ejabberd = EjabberdApiClient.new
|
||||
|
||||
EjabberdApiClient.add_roster_item({
|
||||
"localuser": @username,
|
||||
"localhost": @domain,
|
||||
"user": @inviter.cn,
|
||||
"host": @inviter.ou,
|
||||
"nick": @username,
|
||||
"group": "Friends",
|
||||
"subs": "both"
|
||||
})
|
||||
def create_lndhub_account(user)
|
||||
#TODO enable in development when we have a local lndhub (mock?) API
|
||||
return if Rails.env.development?
|
||||
CreateLndhubAccountJob.perform_later(user)
|
||||
end
|
||||
end
|
||||
|
||||
@ -10,6 +10,10 @@ class LdapService < ApplicationService
|
||||
res
|
||||
end
|
||||
|
||||
def add_attribute(dn, attr, value)
|
||||
ldap_client.add_attribute dn, attr, value
|
||||
end
|
||||
|
||||
def delete_entry(dn, interactive=false)
|
||||
puts "Deleting entry: #{dn}" if interactive
|
||||
res = ldap_client.delete dn: dn
|
||||
@ -17,7 +21,7 @@ class LdapService < ApplicationService
|
||||
res
|
||||
end
|
||||
|
||||
def delete_all_entries
|
||||
def delete_all_entries!
|
||||
if Rails.env.production?
|
||||
raise "Mass deletion of entries not allowed in production"
|
||||
end
|
||||
@ -90,6 +94,26 @@ class LdapService < ApplicationService
|
||||
add_entry dn, attrs, interactive
|
||||
end
|
||||
|
||||
def reset_directory!
|
||||
if Rails.env.production?
|
||||
raise "Resetting the directory not allowed in production"
|
||||
end
|
||||
|
||||
delete_all_entries!
|
||||
|
||||
user_read_aci = <<-EOS
|
||||
(target="ldap:///#{@suffix}")(targetattr="*") (version 3.0; acl "user-read-search-own-attributes"; allow (read,search) userdn="ldap:///self";)
|
||||
EOS
|
||||
|
||||
add_entry @suffix, {
|
||||
dc: "kosmos", objectClass: ["top", "domain"], aci: user_read_aci
|
||||
}, true
|
||||
|
||||
add_entry "cn=users,#{@suffix}", {
|
||||
cn: "users", objectClass: ["top", "organizationalRole"]
|
||||
}, true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ldap_client
|
||||
|
||||
@ -2,7 +2,7 @@ class Lndhub
|
||||
attr_accessor :auth_token
|
||||
|
||||
def initialize
|
||||
@base_url = ENV["LNDHUB_API_URL"]
|
||||
@base_url = ENV["LNDHUB_LEGACY_API_URL"]
|
||||
end
|
||||
|
||||
def post(endpoint, payload)
|
||||
@ -28,8 +28,13 @@ class Lndhub
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer #{auth_token}"
|
||||
})
|
||||
data = JSON.parse(res.body)
|
||||
|
||||
JSON.parse(res.body)
|
||||
if data.is_a?(Hash) && data["error"] && data["message"] == "bad auth"
|
||||
raise "BAD_AUTH"
|
||||
else
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
def create(payload)
|
||||
@ -42,15 +47,15 @@ class Lndhub
|
||||
self.auth_token
|
||||
end
|
||||
|
||||
def balance(user_token)
|
||||
def balance(user_token=nil)
|
||||
get "balance", user_token || auth_token
|
||||
end
|
||||
|
||||
def gettxs(user_token)
|
||||
def gettxs(user_token=nil)
|
||||
get "gettxs", user_token || auth_token
|
||||
end
|
||||
|
||||
def getuserinvoices(user_token)
|
||||
def getuserinvoices(user_token=nil)
|
||||
get "getuserinvoices", user_token || auth_token
|
||||
end
|
||||
|
||||
|
||||
81
app/services/lndhub_v2.rb
Normal file
@ -0,0 +1,81 @@
|
||||
class LndhubV2
|
||||
attr_accessor :auth_token
|
||||
|
||||
def initialize
|
||||
@base_url = ENV["LNDHUB_API_URL"]
|
||||
end
|
||||
|
||||
def post(endpoint, payload, options={})
|
||||
headers = { "Content-Type" => "application/json" }
|
||||
if auth_token
|
||||
headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
|
||||
elsif options[:admin_token]
|
||||
headers.merge!({ "Authorization" => "Bearer #{options[:admin_token]}" })
|
||||
end
|
||||
|
||||
res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
|
||||
|
||||
if res.status != 200
|
||||
Rails.logger.error "[lndhub] API request failed:"
|
||||
Rails.logger.error res.body
|
||||
#TODO add some kind of exception tracking/notifications
|
||||
end
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def get(endpoint, auth_token)
|
||||
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer #{auth_token}"
|
||||
})
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def create(payload)
|
||||
post "create", payload
|
||||
end
|
||||
|
||||
def authenticate(user)
|
||||
credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
|
||||
self.auth_token = credentials["access_token"]
|
||||
self.auth_token
|
||||
end
|
||||
|
||||
def balance(user_token=nil)
|
||||
get "balance", user_token || auth_token
|
||||
end
|
||||
|
||||
def gettxs(user_token)
|
||||
get "gettxs", user_token || auth_token
|
||||
end
|
||||
|
||||
def getuserinvoices(user_token)
|
||||
get "getuserinvoices", user_token || auth_token
|
||||
end
|
||||
|
||||
def addinvoice(payload)
|
||||
invoice = post "addinvoice", {
|
||||
amt: payload[:amount],
|
||||
memo: payload[:memo],
|
||||
description_hash: payload[:description_hash]
|
||||
}
|
||||
|
||||
invoice["payment_request"]
|
||||
end
|
||||
|
||||
#
|
||||
# V2
|
||||
#
|
||||
|
||||
def create_account(payload={})
|
||||
post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token]
|
||||
end
|
||||
|
||||
def create_invoice(payload)
|
||||
# Payload: { amount: 1000, description: "", description_hash: "" }
|
||||
post "v2/invoices", payload
|
||||
end
|
||||
end
|
||||
@ -17,6 +17,7 @@
|
||||
<tr>
|
||||
<th>Token</th>
|
||||
<th>Accepted</th>
|
||||
<th>Inviter</th>
|
||||
<th>Invited user</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -24,7 +25,8 @@
|
||||
<% @invitations_used.each do |invitation| %>
|
||||
<tr>
|
||||
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
|
||||
<td><%= invitation.used_at.strftime("%Y-%m-%d") %></td>
|
||||
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
||||
<td><%= invitation.user.address %></td>
|
||||
<td><%= User.find(invitation.invited_user_id).address %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<%= render HeaderComponent.new(title: "Donations") %>
|
||||
<%= render HeaderComponent.new(title: "Contributions") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
|
||||
<section>
|
||||
<p class="mb-12">
|
||||
Your financial contributions to the development and upkeep of Kosmos
|
||||
software and services.
|
||||
</p>
|
||||
<% if @donations.any? %>
|
||||
<p class="mb-12">
|
||||
Your financial contributions to the development and upkeep of Kosmos
|
||||
software and services.
|
||||
</p>
|
||||
<ul class="list-none">
|
||||
<% @donations.each do |donation| %>
|
||||
<li class="mb-8 grid gap-y-2 gap-x-8 grid-cols-2 items-center">
|
||||
@ -33,9 +33,19 @@
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p class="text-gray-500">
|
||||
No donations to show.
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<p class="mt-8 mb-12 inline-flex align-center items-center">
|
||||
<%= image_tag("/img/illustrations/undraw_savings_re_eq4w.svg", class: 'h-48') %>
|
||||
</p>
|
||||
<h3>
|
||||
No donations yet
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
The donation process is not automated yet.<br>Please
|
||||
<a href="https://wiki.kosmos.org/Main_Page#Community_.2F_Getting_in_touch_.2F_Getting_involved" class="ks-text-link" target="_blank">contact us</a>
|
||||
if you'd like to contribute this way right now.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
49
app/views/contributions/projects/index.html.erb
Normal file
@ -0,0 +1,49 @@
|
||||
<%= render HeaderComponent.new(title: "Contributions") %>
|
||||
|
||||
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
|
||||
<section>
|
||||
<p class="mb-8">
|
||||
Project contributions are how we develop and run all Kosmos software and
|
||||
services. Everything we create and provide is free and open-source
|
||||
software, even the page you're looking at right now!
|
||||
</p>
|
||||
<h3>Start contributing</h3>
|
||||
<p>
|
||||
Check out our
|
||||
<a href="https://kosmos.org/projects/" target="_blank" class="ks-text-link">projects page</a>
|
||||
for some (but not all) potential places that can use your help.
|
||||
</p>
|
||||
<p>
|
||||
There's something to do for everyone, especially non-programmers! For
|
||||
example, we need more help with graphics, UI/UX design, and
|
||||
content/copywriting. We also need moderators for social media. And beta
|
||||
testers for our software. The list doesn't end there.
|
||||
</p>
|
||||
<p>
|
||||
A good way to get started is to join one of our
|
||||
<a href="https://community.kosmos.org/t/kosmos-weekly-call/36" target="_blank" class="ks-text-link">weekly calls</a>
|
||||
and introduce yourself. Alternatively, you can also ping us on any other
|
||||
medium, or even just grab an open issue on
|
||||
<a href="https://github.com/67P/" target="_blank" class="ks-text-link">GitHub</a>
|
||||
or our
|
||||
<a href="https://gitea.kosmos.org/kosmos/" target="_blank" class="ks-text-link">Gitea</a>
|
||||
and dive right in (be sure to comment first, to prevent double efforts).
|
||||
</p>
|
||||
<p class="mb-8">
|
||||
Last but not least, if you want to help by proposing new features or
|
||||
services, head over to the
|
||||
<a href="https://community.kosmos.org/" target="_blank" class="ks-text-link">community forums</a>,
|
||||
where you can do just that.
|
||||
</p>
|
||||
<h3>Open Source Grants</h3>
|
||||
<p>
|
||||
Money coming in from financial contributions is first used to pay for our
|
||||
bills. Additional funds are being paid out directly to our contributors,
|
||||
including you, according to their rough share of contributions.
|
||||
</p>
|
||||
<p>
|
||||
We have run two 6-month trials so far, with the next trial period
|
||||
starting sometime in Q1 2023. Watch your email for notifications about it!
|
||||
</p>
|
||||
</section>
|
||||
<% end %>
|
||||
@ -2,7 +2,7 @@
|
||||
<div id="error_explanation">
|
||||
<ul>
|
||||
<% resource.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<li class="text-red-600"><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -1 +0,0 @@
|
||||
json.array! @donations, partial: "donations/donation", as: :donation
|
||||
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check <%= custom_class %>"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 283 B |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-copy"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-copy <%= custom_class %>"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 372 B |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user <%= custom_class %>"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 334 B |
@ -3,28 +3,41 @@
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<% if @invitations_unused.any? %>
|
||||
<p>
|
||||
<p class="mb-8">
|
||||
Invite your friends to a Kosmos account by sharing an invitation URL with them:
|
||||
</p>
|
||||
<table class="mt-12">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @invitations_unused.each do |invitation| %>
|
||||
<tr>
|
||||
<td class="font-mono"><%= invitation_url(invitation.token) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<ul>
|
||||
<% @invitations_unused.each do |invitation| %>
|
||||
<li class="font-mono mb-1 flex gap-1 md:block"
|
||||
data-controller="clipboard">
|
||||
<input type="text" disabled class="md:w-3/4 flex-1"
|
||||
value="<%= invitation_url(invitation.token) %>"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue flex-none w-auto"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p>
|
||||
You do not have any invitations to give away yet. All good
|
||||
things come in time.
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<p class="my-12 inline-flex align-center items-center">
|
||||
<%= image_tag("/img/illustrations/undraw_loading_re_5axr.svg", class: 'h-48') %>
|
||||
</p>
|
||||
<h3>
|
||||
No invitations available yet
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
We will notify you, as soon as you can invite others.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %>
|
||||
<section>
|
||||
<h3>Password</h3>
|
||||
<p class="mb-12">Use the following button to request an email with a password reset link:</p>
|
||||
<p class="mb-8">Use the following button to request an email with a password reset link:</p>
|
||||
<p>
|
||||
<%= form_with(url: settings_reset_password_path, method: :post) do %>
|
||||
<%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray w-full sm:w-auto') %>
|
||||
@ -1 +0,0 @@
|
||||
<h2>Settings</h2>
|
||||
34
app/views/settings/profile/index.html.erb
Normal file
@ -0,0 +1,34 @@
|
||||
<%= render HeaderComponent.new(title: "Settings") %>
|
||||
|
||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %>
|
||||
<section>
|
||||
<h3>Profile</h3>
|
||||
<p class="mb-1">
|
||||
<%= label :user_address, 'User address', class: 'font-bold' %>
|
||||
</p>
|
||||
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:block">
|
||||
<input type="text" id="user_address" class="flex-1 sm:w-3/5"
|
||||
value=<%= @user.address %> disabled="disabled"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue flex-none w-auto"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
Your user address for Chat and Lightning Network.
|
||||
</p>
|
||||
|
||||
<%# <%= form_for(@user, as: "profile", url: settings_profile_path) do |f| %>
|
||||
<%# <p class="mt-8">
|
||||
<%# <%= f.submit "Save changes", class: 'btn-md btn-blue w-full sm:w-auto' %>
|
||||
<%# </p>
|
||||
<%# <% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
@ -1,10 +1,10 @@
|
||||
<%= link_to "Services", root_path,
|
||||
class: main_nav_class(@current_section, :dashboard) %>
|
||||
<%= link_to "Contributions", contributions_donations_path,
|
||||
class: main_nav_class(@current_section, :contributions) %>
|
||||
<%= link_to "Invitations", invitations_path,
|
||||
class: main_nav_class(@current_section, :invitations) %>
|
||||
<%= link_to "Donations", donations_path,
|
||||
class: main_nav_class(@current_section, :contributions) %>
|
||||
<%= link_to "Wallet", wallet_path,
|
||||
class: main_nav_class(@current_section, :wallet) %>
|
||||
<%= link_to "Settings", security_path,
|
||||
class: main_nav_class(@current_section, :security) %>
|
||||
<%= link_to "Settings", settings_profile_path,
|
||||
class: main_nav_class(@current_section, :settings) %>
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "Account", path: "#", icon: "settings", disabled: true
|
||||
name: "Profile", path: settings_profile_path, icon: "user",
|
||||
active: current_page?(settings_profile_path)
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "Password", path: security_path, icon: "key",
|
||||
active: current_page?(security_path)
|
||||
name: "Account", path: settings_account_path, icon: "key",
|
||||
active: current_page?(settings_account_path)
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "Security", path: "#", icon: "shield", disabled: true
|
||||
|
||||
12
app/views/shared/_tabnav_contributions.html.erb
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<%= render TabnavLinkComponent.new(
|
||||
name: "Donations", path: contributions_donations_path,
|
||||
active: current_page?(contributions_donations_path)
|
||||
) %>
|
||||
<%= render TabnavLinkComponent.new(
|
||||
name: "Projects", path: contributions_projects_path,
|
||||
active: current_page?(contributions_projects_path)
|
||||
) %>
|
||||
</nav>
|
||||
</div>
|
||||
14
app/views/shared/_tabnav_wallet.html.erb
Normal file
@ -0,0 +1,14 @@
|
||||
<section>
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<%= render TabnavLinkComponent.new(
|
||||
name: "Info", path: wallet_path,
|
||||
active: current_page?(wallet_path)
|
||||
) %>
|
||||
<%= render TabnavLinkComponent.new(
|
||||
name: "Transactions", path: wallet_transactions_path,
|
||||
active: current_page?(wallet_transactions_path)
|
||||
) %>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
@ -3,14 +3,7 @@
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<%= render WalletSummaryComponent.new(balance: @balance) %>
|
||||
|
||||
<section>
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<%= link_to "Info", wallet_path, class: "border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
|
||||
<%= link_to "Transactions", wallet_transactions_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2" %>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
<%= render partial: "shared/tabnav_wallet" %>
|
||||
|
||||
<section>
|
||||
<h3>Lightning Address</h3>
|
||||
@ -33,8 +26,14 @@
|
||||
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 w-full sm:w-auto">Copy setup code/URL</button>
|
||||
<p data-controller="clipboard" class="my-6 text-center md:text-left">
|
||||
<input type="text" disabled class="hidden" aria-hidden=true
|
||||
value="<%= @wallet_url%>" data-clipboard-target="source" />
|
||||
<button id="copy-setup-code" class="btn-md btn-blue w-full sm:w-auto"
|
||||
data-action="clipboard#copy" data-clipboard-target="trigger">
|
||||
<span class="content-initial">Copy setup code/URL</span>
|
||||
<span class="content-active hidden">Copied ✔</span>
|
||||
</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 w-full sm:w-auto">Show setup QR code</button>
|
||||
<button id="hide-setup-code" class="hidden btn-md btn-blue w-full sm:w-auto">Hide setup QR code</button>
|
||||
@ -48,19 +47,6 @@
|
||||
<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">
|
||||
@ -74,6 +60,32 @@
|
||||
Choose "LNDHub (Bluewallet)" in the connect dialog and paste the setup
|
||||
URL in the "LNDHub Export URI" field.
|
||||
</p>
|
||||
<h4 class="md:col-span-3 mt-4 mb:mt-0">
|
||||
<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 mt-4 mb:mt-0">
|
||||
<a href="https://zeusln.app" class="ks-text-link text-xl"
|
||||
title="Zeus" target="_blank">
|
||||
<%= image_tag("/img/logos/zeus.svg", class: 'h-16') %>
|
||||
</a>
|
||||
</h4>
|
||||
<p class="md:col-span-4 mb-0 text-gray-500">
|
||||
Android / iOS
|
||||
</p>
|
||||
<p class="md:col-span-5 mb-0">
|
||||
On first launch, tap "Scan node config" and scan the setup QR code.
|
||||
Add your Lightning address as a nickname, then "Save node config".
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
@ -82,7 +94,6 @@
|
||||
(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) {
|
||||
@ -100,16 +111,5 @@
|
||||
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>
|
||||
|
||||
@ -3,14 +3,7 @@
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<%= render WalletSummaryComponent.new(balance: @balance) %>
|
||||
|
||||
<section>
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<%= link_to "Info", wallet_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2" %>
|
||||
<%= link_to "Transactions", wallet_transactions_path, class: "border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
<%= render partial: "shared/tabnav_wallet" %>
|
||||
|
||||
<section>
|
||||
<h3 class="hidden">Transactions</h3>
|
||||
|
||||
@ -1 +1 @@
|
||||
oDf4FcihlyfHQuf9SUwfr+UVA0kXoECtHV3vEwtBp2TktCWkWz7SPbSZ2cLT0+EknOKhuI9xutrs311YDU2EzqZba4fZ0+a6/ohVH4jUbk1XfiHZWBp4zh+9TZ5m5Tp2RxcXpdcWPY38mP4zHWFzobTOR/brLjuemozvh8MiBSPY4NN5NR6rbFo87auK6fvYO8ik/1Qwf6pQMoVDjcTh2983po1RU/gSevUmaYsmTTHcQ5T9O9wMIBc101iZyKhkAZG46a6eNYok8yqRm18AHWr+De2j7LlBaqYSz/BZA385RWhhqoeONArwyo3Az30Bv74VttUSJAPurkRg2wDF9t8A+cvf8CeYkZ6u398JLSJbZZ6YdQS5T4IcrnONRXtp3d/m+yw+XEzpluK3MvFbV02AhZk/xzkGK6xonhaTSh1ek9hXoYrUTBzu8HBzXwKjJMnvrAodldu++/rMwLsgVmFHqXC3dydVogatLev8z6ziuGkmeMAR1d9kzGBHM3FWgsWLD7j0Ug7MTMyNWioI3r6J2QTnxkyJGh3pKBlq8Fb/Q0ypERxOfSZVQQh2gB79RMEDIemdCmN5mCU3ojsxqAAip4v9C1BZMWPtom1sHLYQSd9Bh6i0nncrNEtZXcxe5Z8JCWQolHvvfoIF2rfJh2oXYLxNx5n/1fzaoSqBdLBgvsAMA0ZWfV1wa/5V/DCa9vaJjumzHYKcfCCYbVz9PjN9OUSfwrE3nWZu8Y0awsNgmeRQwI10j2+oYYSBp040I05Vj5FM7nCgHLpdupctwaH2/VlIq83OVI6VlbzxYEay7+R5ANmpiJ+vC466DzLv8LAeFOqh/XeNeOITUm9EzhGSgf98yuDc7vsi3gmc2g7atgQ3gpSje6vEfhigm3ukydaGfA==--IYQBMD+Tar1g+srm--01E5mujYFWvcpT8Qd9HxiA==
|
||||
yEs5CyuAbqphlDWgtw/YQvkPn+EN4ecen2dAjs7zvYErkRRWp99FinGlQIMe6NRkMLLLSIj2BwR/wlscn1kLpIfwGpxfSZ89srK3do6Mb5QogpxdUsnQB8qv5PTGRQFBcjM47s1Q5m0t+OKxGvOnLyKnQp+cVS2KFJMbSzQarW8wIZSz2gKArn9Ttk0kqUHMlJWNY7Yh6xIrrxlEalaTOVzPdtnF7u8Tobminu15eeWHMormMRz4dYSaDc6hUtfpdy1NzOHaeXIU9A9RY/iytxuIQNgcMAlcWbPe//rVk/unH2F8xqSOfed4h/nC08F/qq4z8va3kEXBSdW/G91aIDMu1mo0kX3YNibq8s25C/CfGpzw39ozJ9erTBH7hy6nfmxU6qZuWcTGDj3NOfKe/XIfDcpOjsqkT2IOFARrYodb67q23IuOufraK1/FD4LXu8l0S8/Oi0cqMjtPPs7tS0M1C3DrbmlEzGKETrHpmoKHqjA0rgOmK4ZZM9LeI+l8Z+fDpYcCak9fLGGxnjf+nKiYMSUtm9+1dwycG2lpBV6fbmIKHJWngO2jVGcycODkc525oUaAO4hdPMqrz1AdU3AzYmLJTxW3aZ4uL5NyEJ7TbUBC0HT7h2gEi/tUry4cfD2EsM9bCrCUNuMBrnPqd4r8AvORoqqYIw1IEsP0RgWa2+hfeG1QCjBRPFHQOcqo+W25CelivMe79qI08w0iC8S4hfOQO4QrmMgtd1BhcR+wVpVE3X9EJZi3Hl7z14hXcSic+gkswJMtVZcnJL4rmZ0iEW1mpqUuegsX5vB/4qPxiQyeB80pg8Q33shvUbixzSBkl6znmLSiIffsiDsGOsnuzfl/MUT+JBs3UswNt4tSp7nEwhUjKFHrZHrAJiGCdtIS6yDPGe3HfQv1JkQ+9A8zv88hRmzeIx2JyT/shtIqGo+4ZTJd5cma--Lij/n0+cpstyZD28--FOUhwW3y+0jdaYkKvG2xrg==
|
||||
@ -1 +1 @@
|
||||
IIjYiPSeZeMFhH8i8v8akXN4JrtGU+OsMQ8GAao/gVdesggriCBAQ8z+Vd0cmTf1SKYeT3OQDgygEekupr325P4eD9fZ+yi56EA/UMXQXMDVZAvZw7iwvKaOXpqisbWdJnomr1GXrHyR415Ce/Fxft3fgXDwMHJW2u+dDJgpE09uORnB9GXycFwHQmoIdXo=--iQ/Vcm0VcwHgUkwQ--tKHQW/45gM/s/NplqGPaxw==
|
||||
vqH5By5qFLImVjdlWj+7FwGg8APKnr/AEd7WqekG7L0vNA32WGBpwS1uGzs02LIcATRwGj8DyJxiBOB/w9z8cwoO+t6Woi5hAnOSCQwFWKLT0dZq7jgtT8pxK0Yu/Nf91PEFN1rc/8ZFy2KKVpbtMbMPyivT38e/ctBZD/lHrWkndvLXYvFVhqWjUnDOGbhwl/U0RZgqBBjvlm3B0JkQfiN8VXPlCJL2Cd8kd0+MpRCRTgtcxA==--OdVXnDP7OhzJxCsP--+8SI6IFIeXyDxXb+WpqhIQ==
|
||||
@ -3,6 +3,21 @@ require 'digest'
|
||||
require 'securerandom'
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Create custom failure for turbo
|
||||
class TurboFailureApp < Devise::FailureApp
|
||||
def respond
|
||||
if request_format == :turbo_stream
|
||||
redirect
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def skip_format?
|
||||
%w(html turbo_stream */*).include? request_format.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Assuming you have not yet modified this file, each configuration option below
|
||||
# is set to its default value. Note that some are commented out while others
|
||||
# are not: uncommented lines are intended to protect your configuration from
|
||||
@ -44,6 +59,7 @@ Devise.setup do |config|
|
||||
# ==> Controller configuration
|
||||
# Configure the parent class to the devise controllers.
|
||||
# config.parent_controller = 'DeviseController'
|
||||
config.parent_controller = 'TurboController'
|
||||
|
||||
# ==> Mailer Configuration
|
||||
# Configure the e-mail address which will be shown in Devise::Mailer,
|
||||
@ -289,6 +305,7 @@ Devise.setup do |config|
|
||||
#
|
||||
# The "*/*" below is required to match Internet Explorer requests.
|
||||
# config.navigational_formats = ['*/*', :html]
|
||||
config.navigational_formats = ['*/*', :html, :turbo_stream]
|
||||
|
||||
# The default HTTP method used to sign out a resource. Default is :delete.
|
||||
config.sign_out_via = :get
|
||||
@ -302,10 +319,11 @@ Devise.setup do |config|
|
||||
# If you want to use other strategies, that are not supported by Devise, or
|
||||
# change the failure app, you can configure them inside the config.warden block.
|
||||
#
|
||||
# config.warden do |manager|
|
||||
# manager.intercept_401 = false
|
||||
# manager.default_strategies(scope: :user).unshift :some_external_strategy
|
||||
# end
|
||||
config.warden do |manager|
|
||||
manager.failure_app = TurboFailureApp
|
||||
# manager.intercept_401 = false
|
||||
# manager.default_strategies(scope: :user).unshift :some_external_strategy
|
||||
end
|
||||
|
||||
# ==> Mountable engine configurations
|
||||
# When using Devise inside an engine, let's call it `MyEngine`, and this engine
|
||||
|
||||
@ -10,15 +10,21 @@ Rails.application.routes.draw do
|
||||
match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post]
|
||||
post 'signup_validate', to: 'signup#validate'
|
||||
|
||||
get 'settings', to: 'settings#index'
|
||||
post 'settings_reset_password', to: 'settings#reset_password'
|
||||
namespace :settings do
|
||||
get 'profile', to: 'profile#index'
|
||||
post 'profile', to: 'profile#update'
|
||||
get 'account', to: 'account#index'
|
||||
post 'reset_password', to: 'account#reset_password'
|
||||
end
|
||||
|
||||
get 'security', to: 'security#index'
|
||||
namespace :contributions do
|
||||
root to: 'donations#index'
|
||||
get 'projects', to: 'projects#index'
|
||||
resources :donations, only: ['index']
|
||||
end
|
||||
|
||||
resources :invitations, only: ['index', 'show', 'create', 'destroy']
|
||||
|
||||
resources :donations
|
||||
|
||||
get 'wallet', to: 'wallet#index'
|
||||
get 'wallet/transactions', to: 'wallet#transactions'
|
||||
|
||||
|
||||
29
db/seeds.rb
@ -1,7 +1,22 @@
|
||||
# This file should contain all the record creation needed to seed the database with its default values.
|
||||
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
|
||||
# Character.create(name: 'Luke', movie: movies.first)
|
||||
require 'sidekiq/testing'
|
||||
|
||||
ldap = LdapService.new
|
||||
|
||||
Sidekiq::Testing.inline! do
|
||||
CreateAccount.call(
|
||||
username: "admin", domain: "kosmos.org", email: "admin@example.com",
|
||||
password: "admin is admin", confirmed: true
|
||||
)
|
||||
|
||||
ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true"
|
||||
|
||||
5.times do |n|
|
||||
username = Faker::Name.unique.first_name.downcase
|
||||
email = Faker::Internet.unique.email
|
||||
|
||||
CreateAccount.call(
|
||||
username: username, domain: "kosmos.org", email: email,
|
||||
password: "user is user", confirmed: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
namespace :ldap do
|
||||
desc "Set up base entries for LDAP directory"
|
||||
task seed: :environment do |t, args|
|
||||
desc "Reset the LDAP directory and set up base entries and default org"
|
||||
task setup: :environment do |t, args|
|
||||
ldap = LdapService.new
|
||||
|
||||
ldap.delete_all_entries
|
||||
ldap.delete_entry "cn=admin_role,ou=kosmos.org,cn=users,dc=kosmos,dc=org", true
|
||||
|
||||
ldap.add_entry "dc=kosmos,dc=org", {
|
||||
dc: "kosmos", objectClass: ["top", "domain"]
|
||||
}, true
|
||||
ldap.add_entry "cn=users,dc=kosmos,dc=org", {
|
||||
cn: "users", objectClass: ["top", "organizationalRole"]
|
||||
}, true
|
||||
# Delete all existing entries and re-add base entries
|
||||
ldap.reset_directory!
|
||||
|
||||
ldap.add_organization "kosmos.org", "Kosmos", true
|
||||
|
||||
# add admin role
|
||||
ldap.add_entry "cn=admin_role,ou=kosmos.org,cn=users,dc=kosmos,dc=org", {
|
||||
objectClass: %w{top LDAPsubentry nsRoleDefinition nsComplexRoleDefinition nsFilteredRoleDefinition},
|
||||
cn: "admin_role",
|
||||
nsRoleFilter: "(&(objectclass=person)(admin=true))",
|
||||
description: "filtered role for admins"
|
||||
}, true
|
||||
end
|
||||
|
||||
desc "List user domains/organizations"
|
||||
|
||||
@ -21,4 +21,31 @@ namespace :lndhub do
|
||||
end
|
||||
puts "--\nSum of user balances: #{sum} sats"
|
||||
end
|
||||
|
||||
desc "Migrate existing accounts to lndhub.go"
|
||||
task :migrate => :environment do |t, args|
|
||||
# user = User.find_by cn: "jimmy"
|
||||
User.all.each do |user|
|
||||
puts "Migrating #{user.cn}"
|
||||
puts "Creating account..."
|
||||
lndhub_v2 = LndhubV2.new
|
||||
res = lndhub_v2.create_account login: user.ln_login, password: user.ln_password
|
||||
puts res.inspect
|
||||
|
||||
lndhub = Lndhub.new
|
||||
lndhub.authenticate(user)
|
||||
data = lndhub.balance
|
||||
balance = data["BTC"]["AvailableBalance"] rescue 0
|
||||
|
||||
if balance > 0
|
||||
lndhub_v2.authenticate(user)
|
||||
desc = "Balance migration from old Kosmos Lightning back-end"
|
||||
res = lndhub_v2.create_invoice amount: balance, description: desc
|
||||
puts "Payment request for #{user.cn} (#{balance} sats):"
|
||||
puts res["payment_request"]
|
||||
end
|
||||
|
||||
puts "---"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
1
public/img/illustrations/undraw_loading_re_5axr.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="430.91406" height="559.70956" viewBox="0 0 430.91406 559.70956" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M689.29823,324.68445q0,4.785-.31006,9.49a143.75442,143.75442,0,0,1-13.46973,52.19c-.06006.14-.13037.27-.18994.4-.36035.76-.73047,1.52-1.11035,2.27a142.03868,142.03868,0,0,1-7.6499,13.5,144.462,144.462,0,0,1-118.56006,66.72l1.43018,82.24,18.6499-9.82,3.33008,6.33-21.83985,11.5,2.66992,152.74.02979,2.04-14.41992,1.21.02978-.05,4.54-246.18a144.17482,144.17482,0,0,1-102-44.38c-.90967-.94-1.81006-1.91-2.68994-2.87-.04-.04-.06982-.08-.1001-.11a144.76758,144.76758,0,0,1-26.33984-40.76c.14014.16.29.31.43017.47a144.642,144.642,0,0,1,68.57959-186.38c.5-.25,1.01026-.49,1.51026-.74a144.75207,144.75207,0,0,1,187.52978,56.93c.88037,1.48005,1.73047,2.99006,2.5503,4.51A143.85218,143.85218,0,0,1,689.29823,324.68445Z" transform="translate(-384.54297 -170.14522)" fill="#e5e5e5"/><circle cx="198.2848" cy="502.61836" r="43.06733" fill="#2f2e41"/><rect x="210.6027" y="532.22265" width="38.58356" height="13.08374" fill="#2f2e41"/><ellipse cx="249.45884" cy="534.4033" rx="4.08868" ry="10.90314" fill="#2f2e41"/><rect x="201.6027" y="531.22265" width="38.58356" height="13.08374" fill="#2f2e41"/><ellipse cx="240.45884" cy="533.4033" rx="4.08868" ry="10.90314" fill="#2f2e41"/><path d="M541.051,632.71229c-3.47748-15.5738,7.63866-31.31043,24.82866-35.14881s33.94421,5.67511,37.42169,21.2489-7.91492,21.31769-25.10486,25.156S544.5285,648.28608,541.051,632.71229Z" transform="translate(-384.54297 -170.14522)" fill="#14b8a6"/><path d="M599.38041,670.31119a10.75135,10.75135,0,0,1-10.33984-7.12305,1,1,0,0,1,1.896-.63672c1.51416,4.50782,6.69825,6.86524,11.55457,5.25342a9.60826,9.60826,0,0,0,5.57251-4.74756,8.23152,8.23152,0,0,0,.48547-6.33789,1,1,0,0,1,1.896-.63672,10.217,10.217,0,0,1-.59229,7.86817,11.62362,11.62362,0,0,1-6.73218,5.75244A11.87976,11.87976,0,0,1,599.38041,670.31119Z" transform="translate(-384.54297 -170.14522)" fill="#fff"/><path d="M618.56452,676.16463a9.57244,9.57244,0,1,1-17.04506,8.71737h0l-.00855-.01674c-2.40264-4.70921.91734-7.63227,5.62657-10.03485S616.162,671.45547,618.56452,676.16463Z" transform="translate(-384.54297 -170.14522)" fill="#fff"/><path d="M772.27559,716.2189h-381a1,1,0,0,1,0-2h381a1,1,0,0,1,0,2Z" transform="translate(-384.54297 -170.14522)" fill="#3f3d56"/><ellipse cx="567.22606" cy="706.64241" rx="7.50055" ry="23.89244" transform="translate(-543.03826 -6.10526) rotate(-14.4613)" fill="#2f2e41"/><path d="M645.50888,621.42349H629.12323a.77274.77274,0,0,1-.51881-1.3455l14.90017-13.49467h-13.7669a.77274.77274,0,0,1,0-1.54548h15.77119a.77275.77275,0,0,1,.51881,1.34551L631.12753,619.878h14.38135a.77274.77274,0,1,1,0,1.54548Z" transform="translate(-384.54297 -170.14522)" fill="#cbcbcb"/><path d="M666.37288,597.46853H649.98723a.77275.77275,0,0,1-.51881-1.34551l14.90017-13.49466h-13.7669a.77274.77274,0,0,1,0-1.54548h15.77119a.77274.77274,0,0,1,.51881,1.3455l-14.90016,13.49467h14.38135a.77274.77274,0,1,1,0,1.54548Z" transform="translate(-384.54297 -170.14522)" fill="#cbcbcb"/><path d="M657.1,571.19534H640.71434a.77274.77274,0,0,1-.51881-1.3455l14.90017-13.49467H641.3288a.77274.77274,0,0,1,0-1.54548H657.1a.77275.77275,0,0,1,.51881,1.34551l-14.90016,13.49466H657.1a.77274.77274,0,0,1,0,1.54548Z" transform="translate(-384.54297 -170.14522)" fill="#cbcbcb"/><path d="M770.66217,347.522,783.457,337.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,770.66217,347.522Z" transform="translate(-384.54297 -170.14522)" fill="#3f3d56"/><path d="M403.66217,180.522,416.457,170.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,403.66217,180.522Z" transform="translate(-384.54297 -170.14522)" fill="#3f3d56"/><path d="M802.66217,215.522,815.457,205.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,802.66217,215.522Z" transform="translate(-384.54297 -170.14522)" fill="#3f3d56"/></svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
1
public/img/illustrations/undraw_savings_re_eq4w.svg
Normal file
|
After Width: | Height: | Size: 14 KiB |
64
public/img/logos/zeus.svg
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="330"
|
||||
height="160"
|
||||
viewBox="0 0 330 160"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg12"
|
||||
sodipodi:docname="zeus.svg"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview9"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.3383653"
|
||||
inkscape:cx="179.61266"
|
||||
inkscape:cy="89.164854"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg12" />
|
||||
<defs
|
||||
id="defs16" />
|
||||
<g
|
||||
id="g120"
|
||||
transform="matrix(0.8832226,0,0,0.89599484,21.917939,12.35239)">
|
||||
<path
|
||||
d="m 215.456,79.5133 c 0,0 10.514,-46.5605 32.708,-76.34661 L 324,0 c 0,0 -36.958,34.6773 -47.661,53.4896 L 320.6,50.1974 c 0,0 -69.855,49.9466 -93.339,100.8026 0,0 -5.1,-41.857 18.857,-74.6534 z"
|
||||
fill="#ffd93f"
|
||||
id="path2" />
|
||||
<path
|
||||
d="m 121.235,19.9094 c 0,0 -13.033,5.6124 -27.9858,17.0879 -14.9532,11.4755 -23.2325,24.4873 -23.2325,24.4873 0,0 4.5332,0.3762 3.0851,2.6023 -1.4481,2.2262 -5.572,6.1767 -5.572,6.1767 0,0 7.5868,4.954 30.0007,14.705 22.4145,9.7511 25.6565,11.4442 25.9085,10.5662 0.283,-0.8779 10.577,1.7559 10.577,1.7559 0,0 -26.821,-15.2694 -40.2946,-27.3092 0,0 3.2425,-2.8219 11.3956,-5.6437 8.154,-2.8219 12.466,-3.6057 12.624,-4.609 0.157,-1.0033 -2.487,-1.4423 -2.487,-1.4423 l 9.444,-2.9159 c 0,0 -18.07,-0.6898 -37.2727,3.637 0,0 9.0664,-14.3913 18.6047,-21.6027 9.539,-7.2114 13.631,-9.124 13.789,-10.5036 0.157,-1.3795 -2.078,-2.2261 -2.078,-2.2261 0,0 0.535,-2.6964 3.494,-4.7658 z"
|
||||
fill="white"
|
||||
id="path4"
|
||||
style="fill:#333333" />
|
||||
<path
|
||||
d="m 57.9283,43.0484 c 0,0 -11.2699,12.1653 -22.7602,29.9429 C 23.6777,90.769 8.69313,113.5 12.2819,115.789 c 3.5887,2.289 9.2552,1.443 9.2552,1.443 0,0 -0.7555,2.539 0.9129,2.759 1.6685,0.219 13.6939,-9.908 27.986,-15.364 14.2921,-5.4866 25.688,-8.5593 26.0028,-10.0329 0.3148,-1.505 -4.3758,-2.3829 -4.0925,-2.3829 0.2519,0 5.3517,-1.2855 5.7294,-2.9472 0.4093,-1.6618 -3.3054,-2.3829 -8.9089,-1.7245 -5.6035,0.6584 -30.7248,8.152 -30.7248,8.152 0,0 18.416,-33.047 28.6471,-46.5605 10.2311,-13.5135 13.568,-16.1472 13.5365,-18.1852 -0.0314,-2.038 -8.4367,-1.7871 -8.4367,-1.7871 0,0 -1.1018,-2.3202 -2.3295,-2.3516 -1.2277,-0.0313 -32.4247,1.411 -54.1147,5.6437 C -5.94522,36.6836 0.319361,36.9344 2.68038,38.9097 5.00993,40.9163 1.3897,44.961 1.3897,44.961 c 0,0 10.6403,-0.4389 10.4829,1.7558 -0.1574,2.1948 -3.46281,4.6717 -3.46281,4.6717 0,0 25.24721,-6.8978 49.51851,-8.3401 z"
|
||||
fill="white"
|
||||
id="path6"
|
||||
style="fill:#333333" />
|
||||
<path
|
||||
d="m 130.585,21.8848 c 0,0 3.589,19.0004 4.753,35.5866 1.165,16.5861 0.063,31.3537 0.913,31.8554 0.819,0.5017 8.091,-2.5397 17.566,-1.505 9.476,1.0347 16.496,1.3482 17.283,0.4076 0.787,-0.9406 0.032,-3.1354 0.032,-3.1354 0,0 1.825,0.2822 2.172,-1.0033 0.346,-1.2855 -1.417,-2.665 -1.417,-2.665 0,0 0,-13.0433 -0.755,-28.877 -0.724,-15.8023 -5.604,-29.6606 -5.604,-29.6606 0,0 -3.211,53.2388 -4.124,56.4369 0,0 -8.751,-3.01 -15.488,-2.4143 0,0 -3.809,-21.6028 -6.957,-36.4645 -3.148,-14.8617 -4.03,-17.809 -4.785,-17.6522 -0.661,0.1254 -1.26,2.8845 -1.26,2.8845 z"
|
||||
fill="white"
|
||||
id="path8"
|
||||
style="fill:#333333" />
|
||||
<path
|
||||
d="m 243.001,47.9712 c 0,0 -25.058,-14.1406 -41.208,-17.9658 -16.149,-3.8252 -19.203,-2.9786 -17.975,-2.0694 1.228,0.8779 4.187,2.5711 4.187,2.5711 0,0 -10.924,-0.2508 -9.948,1.0974 0.976,1.3482 4.093,11.5695 4.03,22.2925 -0.063,10.723 -0.063,18.0598 0.566,18.342 0.63,0.2822 6.202,-0.1881 6.202,-0.1881 0,0 -1.385,2.1634 -0.063,2.3829 1.322,0.2194 7.713,-1.1288 14.481,-0.6898 6.768,0.4389 9.885,1.2855 9.885,1.2855 0,0 0.661,14.7676 -3.526,25.6785 0,0 -31.795,-12.698 -32.866,-11.3185 -1.07,1.3482 0.819,4.3896 0.819,4.3896 l -12.277,4.9853 c 0,0 16.747,3.2606 30.567,8.7476 13.82,5.487 21.753,10.19 23.044,9.72 1.29,-0.471 3.462,-6.428 6.925,-24.4249 3.463,-17.9971 1.511,-26.8702 0.158,-27.9363 -3.022,-2.3515 -34.251,-1.0033 -34.251,-1.0033 0,0 1.417,-12.7297 0.283,-20.4113 0.032,-0.0314 1.354,-4.7658 50.967,4.515 z"
|
||||
fill="white"
|
||||
id="path10"
|
||||
style="fill:#333333" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@ -1,13 +1,13 @@
|
||||
require 'rails_helper'
|
||||
require 'webmock/rspec'
|
||||
|
||||
RSpec.describe CreateLndhubWalletJob, type: :job do
|
||||
RSpec.describe CreateLndhubAccountJob, type: :job do
|
||||
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
||||
|
||||
subject(:job) { described_class.perform_later(user) }
|
||||
|
||||
before do
|
||||
stub_request(:post, "http://localhost:3023/create")
|
||||
stub_request(:post, "http://localhost:3026/v2/users")
|
||||
.to_return(status: 200, headers: {},
|
||||
body: { login: "abc123", password: "def456" }.to_json)
|
||||
end
|
||||
@ -15,8 +15,8 @@ RSpec.describe CreateLndhubWalletJob, type: :job do
|
||||
it "creates a new LndHub account" do
|
||||
perform_enqueued_jobs { job }
|
||||
|
||||
expect(WebMock).to have_requested(:post, "http://localhost:3023/create")
|
||||
.with { |req| req.body == '{"partnerid":"kosmos.org","accounttype":"user"}' }
|
||||
expect(WebMock).to have_requested(:post, "http://localhost:3026/v2/users")
|
||||
.with { |req| req.body == '{}' }
|
||||
|
||||
user.reload
|
||||
expect(user.ln_login).to eq("abc123")
|
||||
@ -93,7 +93,7 @@ RSpec.describe CreateAccount, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#create_lndhub_wallet" do
|
||||
describe "#create_lndhub_account" do
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
let(:service) { CreateAccount.new(
|
||||
@ -102,8 +102,8 @@ RSpec.describe CreateAccount, type: :model do
|
||||
)}
|
||||
let(:new_user) { create :user, cn: "halfinney", ou: "kosmos.org" }
|
||||
|
||||
it "enqueues a job to create an LndHub wallet" do
|
||||
service.send(:create_lndhub_wallet, new_user)
|
||||
it "enqueues a job to create an LndHub account" do
|
||||
service.send(:create_lndhub_account, new_user)
|
||||
|
||||
expect(enqueued_jobs.size).to eq(1)
|
||||
|
||||
|
||||