Compare commits

...

40 Commits

Author SHA1 Message Date
Râu Cao
99dc36f13a
Make empty donations page prettier
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-01-10 14:13:28 +08:00
Râu Cao
ee74c4847f
Make invitation page prettier when it's empty 2023-01-10 14:13:27 +08:00
Râu Cao
15b63eee73
Add coming-soon note to disabled settings nav items 2023-01-10 14:13:27 +08:00
Râu Cao
c756528d32
Allow to copy invitation URLs via button 2023-01-10 14:13:27 +08:00
Râu Cao
fef29b4fc0
Add more info about project contributions 2023-01-10 14:13:27 +08:00
Râu Cao
38608e053d
Add Zeus to recommended wallet apps 2023-01-10 14:13:26 +08:00
Râu Cao
5f215b8ed8
Replace vanilla JS with new clipboard code 2023-01-10 14:13:26 +08:00
Râu Cao
87aae35974
Add a clipboard controller and wire up the copy button 2023-01-10 14:13:26 +08:00
Râu Cao
6ad02e69a2
WIP Profile settings page
Show the user's user address, and provide a button for copying it to the
clipboard
2023-01-10 14:13:26 +08:00
Râu Cao
94ca0f3764
Rename settings page 2023-01-10 14:13:25 +08:00
Râu Cao
0fec37e0a9
Add inviter and time to admin invitations list 2023-01-10 14:13:25 +08:00
Râu Cao
620befd7c0
Fix devise not rendering errors as flash messages
https://github.com/heartcombo/devise/issues/5446

closes #63
2023-01-10 14:13:25 +08:00
Râu Cao
aba4930696
Set a minimum height for content with sidenav 2023-01-10 14:13:25 +08:00
Râu Cao
0492b42327
Improve button style 2023-01-10 14:13:25 +08:00
Râu Cao
445a1c80a6
Refactor settings routes and menu
Use sub controllers/routes for the sections
2023-01-10 14:13:24 +08:00
Râu Cao
cf48f76553
Fix web container start when offline 2023-01-10 14:13:24 +08:00
Râu Cao
70fa43f5d2
Use tabnav component for wallet view 2023-01-10 14:13:24 +08:00
Râu Cao
b37a0c25a4
Wording 2023-01-10 14:13:23 +08:00
Râu Cao
3197743a55
Change donations to contrbutions, add tabbed nav
Introduces components for tabbed navigation and adds a tab menu and item
for non-financial contributions to the donations/contributions page.
2023-01-10 14:13:23 +08:00
Râu Cao
3f49e4a3b8
Use more appropriate icon in sidenav 2023-01-10 14:13:23 +08:00
2e1d930e0f Merge pull request 'Docker Compose config, local 389ds/dirsrv, LDAP and user seeds' (#74) from feature/docker_compose into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #74
2022-12-27 06:26:43 +00:00
d849d28f62 Merge pull request 'Add support and migration for lndhub.go' (#77) from feature/73-lndhub-go into feature/docker_compose
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #77
2022-12-27 06:25:37 +00:00
Râu Cao
f2a22adf6b
Switch legacy to lndhub.go
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Temporary fix
2022-12-23 17:42:20 +07:00
Râu Cao
e1aaa2c434
Re-authorize when token is invalid 2022-12-23 17:42:17 +07:00
Râu Cao
e62bf67262
Use v2 API for creating new lndhub accounts
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-23 12:39:57 +07:00
Râu Cao
6df3d5933c
Update test env
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-12-22 20:11:38 +07:00
Râu Cao
a5a90c4d83
Add support and migration for lndhub.go
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
Slightly WIP
2022-12-22 20:01:14 +07:00
Râu Cao
80ef75ff42
Improve README, add quick start instructions
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-07 18:15:04 +01:00
Râu Cao
67e2e45dd8
Remove pid dir from git 2022-12-07 18:14:49 +01:00
Râu Cao
3834e5230b
Comment encryption option in admin ldap users controller
Refactor to use the service later
2022-12-07 18:13:58 +01:00
Râu Cao
4cb7c0998f
Add db/user seeds 2022-12-07 18:12:54 +01:00
Râu Cao
20382f7df7
Rename ldap seed task to setup 2022-12-07 18:11:57 +01:00
Râu Cao
add94eee8d
Don't start phpldapadmin by default 2022-12-07 18:11:23 +01:00
Râu Cao
067dc3b63d
Remove obsolete method 2022-12-07 18:11:03 +01:00
Râu Cao
1a470cf1c8
Add flag for creating pre-confirmed users 2022-12-07 18:09:44 +01:00
Râu Cao
f85b7f4f62
Define patch version for Ruby base image
No need to re-download new images for every patch version
2022-12-07 18:07:53 +01:00
Râu Cao
8635413002
Delete admin role manually on reset
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-07 15:20:34 +01:00
Râu Cao
a3da956b48
Add missing ACI and role to LDAP seeds
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-07 14:27:51 +01:00
Râu Cao
3c40dc98ca
Add note about resetting LDAP server
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-06 10:28:34 +01:00
28b31e63f9 Merge pull request 'Update Docker image in CI' (#75) from chore/ci_image_upgrade into feature/docker_compose
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #75
2022-12-06 09:23:05 +00:00
62 changed files with 721 additions and 183 deletions

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -59,6 +59,7 @@ group :development do
gem 'listen', '~> 3.2'
gem 'letter_opener'
gem 'letter_opener_web'
gem 'faker'
end
group :test do

View File

@ -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)

View File

@ -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/

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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 %>

View 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>

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class MainWithTabnavComponent < ViewComponent::Base
def initialize(tabnav_partial:)
@tabnav_partial = tabnav_partial
end
end

View File

@ -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 %>

View File

@ -0,0 +1,3 @@
<%= link_to @path, class: @link_class do %>
<%= @name %>
<% end %>

View 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

View File

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

View File

@ -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'],

View File

@ -1,4 +1,4 @@
class DonationsController < ApplicationController
class Contributions::DonationsController < ApplicationController
before_action :require_user_signed_in
# GET /donations

View File

@ -0,0 +1,8 @@
class Contributions::ProjectsController < ApplicationController
before_action :require_user_signed_in
# GET /contributions
def index
@current_section = :contributions
end
end

View File

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

View 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

View File

@ -0,0 +1,11 @@
class Settings::ProfileController < SettingsController
def index
@user = current_user
end
def update
end
end

View File

@ -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

View 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

View 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

View File

@ -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)

View 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)
}
}

View File

@ -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"]

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -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 %>

View File

@ -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 %>

View 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 %>

View File

@ -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>

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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') %>

View File

@ -1 +0,0 @@
<h2>Settings</h2>

View 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 %>

View File

@ -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) %>

View File

@ -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

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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==

View File

@ -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==

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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"

View File

@ -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

View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

64
public/img/logos/zeus.svg Normal file
View 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

View File

@ -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")

View File

@ -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)

View File