diff --git a/.env.test b/.env.test index d939774..0c94493 100644 --- a/.env.test +++ b/.env.test @@ -2,7 +2,6 @@ EJABBERD_API_URL='http://xmpp.example.com/api' BTCPAY_API_URL='http://btcpay.example.com/api/v1' -LNDHUB_LEGACY_API_URL='http://localhost:3023' LNDHUB_API_URL='http://localhost:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' diff --git a/Gemfile b/Gemfile index 169f39e..97c230e 100644 --- a/Gemfile +++ b/Gemfile @@ -39,6 +39,7 @@ gem 'net-ldap' # Utilities gem "rqrcode", "~> 2.0" gem 'rails-settings-cached', '~> 2.8.3' +gem 'pagy', '~> 6.0', '>= 6.0.2' # HTTP requests gem 'faraday' diff --git a/Gemfile.lock b/Gemfile.lock index 5a658b5..3e28d0f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -178,6 +178,7 @@ GEM nokogiri (1.13.9-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) + pagy (6.0.2) pg (1.2.3) public_suffix (5.0.0) puma (4.3.12) @@ -327,6 +328,7 @@ DEPENDENCIES listen (~> 3.2) lockbox net-ldap + pagy (~> 6.0, >= 6.0.2) pg (~> 1.2.3) puma (~> 4.1) rails (~> 7.0.2) diff --git a/README.md b/README.md index fca3437..b2e7205 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ so: 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` +6. `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). @@ -81,12 +81,15 @@ with a fresh installation, delete both that directory as well as the container. ## Documentation +### Rails + * [Ruby on Rails](https://guides.rubyonrails.org/) -* [Sass](https://sass-lang.com/documentation) +* [Pagination](https://ddnexus.github.io/pagy/) ### Front-end * [Tailwind CSS](https://tailwindcss.com/) +* [Sass](https://sass-lang.com/documentation) ### Testing diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index 7a9da2e..acf8aa9 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -4,7 +4,9 @@ @import "components/base"; @import "components/buttons"; +@import "components/dashboard_services"; @import "components/forms"; @import "components/links"; @import "components/notifications"; +@import "components/pagination"; @import "components/tables"; diff --git a/app/assets/stylesheets/components/base.css b/app/assets/stylesheets/components/base.css index b8fbc69..b4fa491 100644 --- a/app/assets/stylesheets/components/base.css +++ b/app/assets/stylesheets/components/base.css @@ -36,10 +36,18 @@ @apply mb-4 leading-6; } + main p:last-child { + @apply mb-0; + } + main ul { @apply mb-6; } + main ul:last-child { + @apply mb-0; + } + main ul li { @apply leading-6; } diff --git a/app/assets/stylesheets/components/buttons.css b/app/assets/stylesheets/components/buttons.css index da79cec..5da9116 100644 --- a/app/assets/stylesheets/components/buttons.css +++ b/app/assets/stylesheets/components/buttons.css @@ -1,6 +1,6 @@ @layer components { .btn { - @apply font-semibold rounded-md leading-none cursor-pointer text-center + @apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center transition-colors duration-75 focus:outline-none focus:ring-4; } diff --git a/app/assets/stylesheets/components/dashboard_services.css b/app/assets/stylesheets/components/dashboard_services.css new file mode 100644 index 0000000..f725347 --- /dev/null +++ b/app/assets/stylesheets/components/dashboard_services.css @@ -0,0 +1,5 @@ +@layer components { + .services > div > a { + background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 0, rgba(255,255,255,0.88) 100%); + } +} diff --git a/app/assets/stylesheets/components/pagination.css b/app/assets/stylesheets/components/pagination.css new file mode 100644 index 0000000..f6ab6c6 --- /dev/null +++ b/app/assets/stylesheets/components/pagination.css @@ -0,0 +1,45 @@ +@layer components { + .pagy-nav.pagination { + @apply isolate inline-flex -space-x-px rounded-md shadow-sm; + } + + .pagy-nav .page:not(.prev):not(.next) { + @apply hidden sm:inline-block; + } + + .pagy-nav .page.next a { + @apply relative inline-flex items-center rounded-r-md border + border-gray-300 bg-white px-3 py-2 text-sm font-medium + text-gray-500 hover:bg-gray-100 focus:z-20; + } + + .pagy-nav .page.prev a { + @apply relative inline-flex items-center rounded-l-md border + border-gray-300 bg-white px-3 py-2 text-sm font-medium + text-gray-500 hover:bg-gray-100 focus:z-20; + } + + .pagy-nav .page.next.disabled { + @apply relative inline-flex items-center rounded-r-md border + border-gray-300 bg-gray-100 px-3 py-2 text-sm font-medium + text-gray-400 focus:z-20; + } + + .pagy-nav .page.prev.disabled { + @apply relative inline-flex items-center rounded-l-md border + border-gray-300 bg-gray-100 px-3 py-2 text-sm font-medium + text-gray-400 focus:z-20; + } + + .pagy-nav .page a, .page.gap { + @apply bg-white border-gray-300 text-gray-500 hover:bg-gray-100 relative + inline-flex items-center border px-4 py-2 text-sm font-medium + focus:z-20; + } + + .pagy-nav .page.active { + @apply z-10 border-indigo-500 bg-indigo-50 text-indigo-600 relative + inline-flex items-center border px-4 py-2 text-sm font-medium + focus:z-20; + } +} diff --git a/app/assets/stylesheets/components/tables.css b/app/assets/stylesheets/components/tables.css index 9b5e1fc..14fb4af 100644 --- a/app/assets/stylesheets/components/tables.css +++ b/app/assets/stylesheets/components/tables.css @@ -7,16 +7,30 @@ @apply text-left; } - table th { + table thead th { @apply pb-3.5 text-sm font-normal uppercase text-gray-500; } + table tbody th { + @apply text-left font-normal text-gray-500; + } + table th:not(:last-of-type), table td:not(:last-of-type) { @apply pr-2; } - table td { + table td, tbody th { @apply py-2; } + + table.divided { + @apply divide-y divide-gray-300; + } + table.divided tbody { + @apply divide-y divide-gray-200; + } + table.divided td, table.divided tbody th { + @apply py-3; + } } diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 957328e..d504235 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -1,4 +1,5 @@ class Admin::BaseController < ApplicationController + include Pagy::Backend before_action :authenticate_user! before_action :authorize_admin @@ -7,5 +8,4 @@ class Admin::BaseController < ApplicationController def set_context @context = :admin end - end diff --git a/app/controllers/admin/donations_controller.rb b/app/controllers/admin/donations_controller.rb index ffbae14..c29fb80 100644 --- a/app/controllers/admin/donations_controller.rb +++ b/app/controllers/admin/donations_controller.rb @@ -5,7 +5,8 @@ class Admin::DonationsController < Admin::BaseController # GET /donations # GET /donations.json def index - @donations = Donation.all.order('created_at desc') + @pagy, @donations = pagy(Donation.all.order('created_at desc')) + @stats = { overall_sats: @donations.all.sum("amount_sats"), donor_count: Donation.distinct.count(:user_id) diff --git a/app/controllers/admin/invitations_controller.rb b/app/controllers/admin/invitations_controller.rb index ca9b407..97a33b3 100644 --- a/app/controllers/admin/invitations_controller.rb +++ b/app/controllers/admin/invitations_controller.rb @@ -1,7 +1,8 @@ class Admin::InvitationsController < Admin::BaseController def index @current_section = :invitations - @invitations_used = Invitation.used.order('used_at desc') + @pagy, @invitations_used = pagy(Invitation.used.order('used_at desc')) + @stats = { available: Invitation.unused.count, accepted: @invitations_used.length, diff --git a/app/controllers/admin/ldap_users_controller.rb b/app/controllers/admin/ldap_users_controller.rb deleted file mode 100644 index 5109041..0000000 --- a/app/controllers/admin/ldap_users_controller.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Admin::LdapUsersController < Admin::BaseController - before_action :set_current_section - - def index - ldap = LdapService.new - @ou = params[:ou] || "kosmos.org" - @orgs = ldap.fetch_organizations - @entries = ldap.fetch_users(ou: @ou) - @stats = { - users_confirmed: User.where(ou: @ou).confirmed.count, - users_pending: User.where(ou: @ou).pending.count - } - end - - private - - def set_current_section - @current_section = :ldap_users - end -end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb new file mode 100644 index 0000000..7544f55 --- /dev/null +++ b/app/controllers/admin/users_controller.rb @@ -0,0 +1,35 @@ +class Admin::UsersController < Admin::BaseController + before_action :set_user, only: [:show] + before_action :set_current_section + + def index + ldap = LdapService.new + @ou = params[:ou] || "kosmos.org" + @orgs = ldap.fetch_organizations + @pagy, @users = pagy(User.where(ou: @ou).order(cn: :asc)) + + @stats = { + users_confirmed: User.where(ou: @ou).confirmed.count, + users_pending: User.where(ou: @ou).pending.count + } + end + + def show + if Setting.lndhub_admin_enabled? + @lndhub_user = @user.lndhub_user + end + + @services_enabled = @user.services_enabled + end + + private + + def set_user + address = params[:address].split("@") + @user = User.where(cn: address.first, ou: address.last).first + end + + def set_current_section + @current_section = :users + end +end diff --git a/app/controllers/users/confirmations_controller.rb b/app/controllers/users/confirmations_controller.rb new file mode 100644 index 0000000..340b538 --- /dev/null +++ b/app/controllers/users/confirmations_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Users::ConfirmationsController < Devise::ConfirmationsController + # GET /resource/confirmation?confirmation_token=abcdef + def show + self.resource = resource_class.confirm_by_token(params[:confirmation_token]) + yield resource if block_given? + + if resource.errors.empty? + set_flash_message!(:success, :confirmed) + resource.devise_after_confirmation + respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) } + else + respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new } + end + end +end diff --git a/app/controllers/wallet_controller.rb b/app/controllers/wallet_controller.rb index 535eed1..a7920d4 100644 --- a/app/controllers/wallet_controller.rb +++ b/app/controllers/wallet_controller.rb @@ -7,7 +7,7 @@ class WalletController < ApplicationController before_action :fetch_balance def index - @wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}" + @wallet_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}" qrcode = RQRCode::QRCode.new(@wallet_url) @svg = qrcode.as_svg( diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 248d897..b4b4f69 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,4 +1,6 @@ module ApplicationHelper + include Pagy::Frontend + def sats_to_btc(sats) sats.to_f / 100000000 end @@ -10,5 +12,10 @@ module ApplicationHelper "text-gray-300 hover:bg-gray-900/30 hover:text-white active:bg-gray-900/30 active:text-white px-3 py-2 rounded-md font-medium text-base md:text-sm block md:inline-block" end end -end + # Colors available: gray, red, yellow, green, blue, purple, pink + # (Add more colors by adding classes to the safelist in tailwind.config.js) + def badge(text, color) + tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800" + end +end diff --git a/app/helpers/ldap_users_helper.rb b/app/helpers/ldap_users_helper.rb deleted file mode 100644 index 81e2eff..0000000 --- a/app/helpers/ldap_users_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module LdapUsersHelper -end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/app/jobs/create_lndhub_account_job.rb b/app/jobs/create_lndhub_account_job.rb index 52d2a2c..6569bcf 100644 --- a/app/jobs/create_lndhub_account_job.rb +++ b/app/jobs/create_lndhub_account_job.rb @@ -2,13 +2,12 @@ class CreateLndhubAccountJob < ApplicationJob queue_as :default def perform(user) - return if user.ln_login.present? && user.ln_password.present? + return if user.ln_account.present? && user.ln_password.present? lndhub = LndhubV2.new credentials = lndhub.create_account user.update! ln_account: credentials["login"], - ln_login: credentials["login"], # TODO remove when production is migrated ln_password: credentials["password"] end end diff --git a/app/models/invitation.rb b/app/models/invitation.rb index e4910a5..cfff859 100644 --- a/app/models/invitation.rb +++ b/app/models/invitation.rb @@ -1,6 +1,7 @@ class Invitation < ApplicationRecord # Relations belongs_to :user + belongs_to :invitee, class_name: "User", foreign_key: 'invited_user_id', optional: true # Validations validates_presence_of :user diff --git a/app/models/lndhub_user.rb b/app/models/lndhub_user.rb index e5645c5..f467089 100644 --- a/app/models/lndhub_user.rb +++ b/app/models/lndhub_user.rb @@ -10,6 +10,18 @@ class LndhubUser < LndhubBase foreign_key: "login" def balance - accounts.current.first.ledgers.sum("account_ledgers.amount") + accounts.current.first.ledgers.sum("account_ledgers.amount").to_i.abs + end + + def sum_outgoing + accounts.outgoing.first.ledgers.sum("account_ledgers.amount").to_i.abs + end + + def sum_incoming + accounts.incoming.first.ledgers.sum("account_ledgers.amount").to_i.abs + end + + def sum_fees + accounts.fees.first.ledgers.sum("account_ledgers.amount").to_i.abs end end diff --git a/app/models/setting.rb b/app/models/setting.rb index d85b82e..0bca086 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -7,7 +7,7 @@ class Setting < RailsSettings::Base # field :reserved_usernames, type: :array, default: %w[ - account accounts admin donations mail webmaster support + account accounts donations mail webmaster support ] # diff --git a/app/models/user.rb b/app/models/user.rb index e123ff4..31599c4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,6 +3,10 @@ class User < ApplicationRecord # Relations has_many :invitations, dependent: :destroy + has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id' + has_one :inviter, through: :invitation, source: :user + has_many :invitees, through: :invitations + has_many :donations, dependent: :nullify has_one :lndhub_user, class_name: "LndhubUser", inverse_of: "user", @@ -27,8 +31,7 @@ class User < ApplicationRecord scope :confirmed, -> { where.not(confirmed_at: nil) } scope :pending, -> { where(confirmed_at: nil) } - lockbox_encrypts :ln_login - lockbox_encrypts :ln_password + has_encrypted :ln_login, :ln_password # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable @@ -39,9 +42,9 @@ class User < ApplicationRecord def ldap_before_save self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first - - dn = Devise::LDAP::Adapter.get_ldap_param(self.cn, "dn") - self.ou = dn.split(',').select{|e| e[0..1] == "ou"}.first.delete_prefix("ou=") + self.ou = dn.split(',') + .select{|e| e[0..1] == "ou"}.first + .delete_prefix("ou=") if self.confirmed_at.blank? && self.confirmation_token.blank? # User had an account with a trusted email address before akkounts was a thing @@ -49,6 +52,10 @@ class User < ApplicationRecord end end + def devise_after_confirmation + enable_service %w[discourse gitea wiki xmpp] + end + def reset_password(new_password, new_password_confirmation) self.password = new_password self.password_confirmation = new_password_confirmation @@ -81,4 +88,42 @@ class User < ApplicationRecord lndhub.authenticate self lndhub.addinvoice payload end + + def dn + return @dn if defined?(@dn) + @dn = Devise::LDAP::Adapter.get_dn(self.cn) + end + + def ldap_entry + ldap.fetch_users(uid: self.cn, ou: self.ou).first + end + + def services_enabled + ldap_entry[:service] || [] + end + + def enable_service(service) + current_services = services_enabled + new_services = Array(service).map(&:to_s) + services = (current_services + new_services).uniq + ldap.replace_attribute(dn, :service, services) + end + + def disable_service(service) + current_services = services_enabled + disabled_services = Array(service).map(&:to_s) + services = (current_services - disabled_services).uniq + ldap.replace_attribute(dn, :service, services) + end + + def disable_all_services + ldap.delete_attribute(dn,:service) + end + + private + + def ldap + return @ldap_service if defined?(@ldap_service) + @ldap_service = LdapService.new + end end diff --git a/app/services/ldap_service.rb b/app/services/ldap_service.rb index d68b992..5a572c5 100644 --- a/app/services/ldap_service.rb +++ b/app/services/ldap_service.rb @@ -3,6 +3,18 @@ class LdapService < ApplicationService @suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org" end + def add_attribute(dn, attr, values) + ldap_client.add_attribute dn, attr, values + end + + def replace_attribute(dn, attr, values) + ldap_client.replace_attribute dn, attr, values + end + + def delete_attribute(dn, attr) + ldap_client.delete_attribute dn, attr + end + def add_entry(dn, attrs, interactive=false) puts "Adding entry: #{dn}" if interactive res = ldap_client.add dn: dn, attributes: attrs @@ -10,10 +22,6 @@ 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 @@ -42,18 +50,17 @@ class LdapService < ApplicationService treebase = ldap_config["base"] end - attributes = %w{dn cn uid mail admin} - filter = Net::LDAP::Filter.eq("uid", "*") + attributes = %w{dn cn uid mail admin service} + filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*") entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes) entries.sort_by! { |e| e.cn[0] } - entries = entries.collect do |e| { uid: e.uid.first, mail: e.try(:mail) ? e.mail.first : nil, - admin: e.try(:admin) ? 'admin' : nil - # password: e.userpassword.first + admin: e.try(:admin) ? 'admin' : nil, + service: e.try(:service) } end end @@ -131,5 +138,4 @@ class LdapService < ApplicationService def ldap_config ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env] end - end diff --git a/app/services/lndhub.rb b/app/services/lndhub.rb index c9547d3..9febebf 100644 --- a/app/services/lndhub.rb +++ b/app/services/lndhub.rb @@ -2,7 +2,7 @@ class Lndhub attr_accessor :auth_token def initialize - @base_url = ENV["LNDHUB_LEGACY_API_URL"] + @base_url = ENV["LNDHUB_API_URL"] end def post(endpoint, payload) @@ -42,7 +42,7 @@ class Lndhub end def authenticate(user) - credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password } + credentials = post "auth?type=auth", { login: user.ln_account, password: user.ln_password } self.auth_token = credentials["access_token"] self.auth_token end diff --git a/app/services/lndhub_v2.rb b/app/services/lndhub_v2.rb index bb4c4b9..693f812 100644 --- a/app/services/lndhub_v2.rb +++ b/app/services/lndhub_v2.rb @@ -39,7 +39,7 @@ class LndhubV2 end def authenticate(user) - credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password } + credentials = post "auth?type=auth", { login: user.ln_account, password: user.ln_password } self.auth_token = credentials["access_token"] self.auth_token end diff --git a/app/views/admin/donations/_form.html.erb b/app/views/admin/donations/_form.html.erb index 1405b08..0522cb0 100644 --- a/app/views/admin/donations/_form.html.erb +++ b/app/views/admin/donations/_form.html.erb @@ -10,46 +10,24 @@ <% end %> -
-

- <%= form.label :user_id %> - <%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn %> -

-
+
+ <%= form.label :user_id %> + <%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn, {} %> -
-

- <%= form.label :amount_sats, "Amount BTC (sats)" %> - <%= form.number_field :amount_sats %> -

-
+ <%= form.label :amount_sats, "Amount BTC (sats)" %> + <%= form.number_field :amount_sats %> -
-

- <%= form.label :amount_eur, "Amount EUR (cents)" %> - <%= form.number_field :amount_eur %> -

-
+ <%= form.label :amount_eur, "Amount EUR (cents)" %> + <%= form.number_field :amount_eur %> -
-

- <%= form.label :amount_usd, "Amount USD (cents)"%> - <%= form.number_field :amount_usd %> -

-
+ <%= form.label :amount_usd, "Amount USD (cents)"%> + <%= form.number_field :amount_usd %> -
-

- <%= form.label :public_name %> - <%= form.text_field :public_name %> -

-
+ <%= form.label :public_name %> + <%= form.text_field :public_name %> -
-

- <%= form.label :paid_at %> - <%= form.text_field :paid_at %> -

+ <%= form.label :paid_at %> + <%= form.text_field :paid_at %>

diff --git a/app/views/admin/donations/edit.html.erb b/app/views/admin/donations/edit.html.erb index ba590b2..27340f1 100644 --- a/app/views/admin/donations/edit.html.erb +++ b/app/views/admin/donations/edit.html.erb @@ -1,12 +1,9 @@ -<%= render HeaderComponent.new(title: "Donations") %> +<%= render HeaderComponent.new(title: "Donation ##{@donation.id}") %> <%= render MainSimpleComponent.new do %> -

Editing Donation

- <%= render 'form', donation: @donation, url: admin_donation_path(@donation) %>

- <%= link_to 'Show', admin_donation_path(@donation), class: 'ks-text-link' %> | - <%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %> + <%= link_to 'Cancel', admin_donation_path(@donation), class: 'btn-sm btn-gray' %>

<% end %> diff --git a/app/views/admin/donations/index.html.erb b/app/views/admin/donations/index.html.erb index 0f57949..f13e94b 100644 --- a/app/views/admin/donations/index.html.erb +++ b/app/views/admin/donations/index.html.erb @@ -21,7 +21,7 @@

<% if @donations.any? %>

Recent Donations

- +
@@ -33,11 +33,10 @@ - <% @donations.each do |donation| %> - + @@ -53,6 +52,7 @@ <% end %>
User
<%= donation.user.address %><%= link_to donation.user.address, admin_user_path(donation.user.address), class: 'ks-text-link' %> <%= sats_to_btc donation.amount_sats %> <% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %> <% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %>
+ <%== pagy_nav @pagy %> <% else %>

No donations yet. diff --git a/app/views/admin/donations/new.html.erb b/app/views/admin/donations/new.html.erb index d831813..176b659 100644 --- a/app/views/admin/donations/new.html.erb +++ b/app/views/admin/donations/new.html.erb @@ -1,8 +1,6 @@ -<%= render HeaderComponent.new(title: "Donations") %> +<%= render HeaderComponent.new(title: "Add Donation") %> <%= render MainSimpleComponent.new do %> -

New Donation

- <%= render 'form', donation: @donation, url: admin_donations_path %>

diff --git a/app/views/admin/donations/show.html.erb b/app/views/admin/donations/show.html.erb index 0c1d54c..1526789 100644 --- a/app/views/admin/donations/show.html.erb +++ b/app/views/admin/donations/show.html.erb @@ -1,38 +1,41 @@ -<%= render HeaderComponent.new(title: "Donations") %> +<%= render HeaderComponent.new(title: "Donation ##{@donation.id}") %> <%= render MainSimpleComponent.new do %> -

- User: - <%= @donation.user.address %> -

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
User<%= link_to @donation.user.address, admin_user_path(@donation.user.address), class: 'ks-text-link' %>
Amount sats<%= @donation.amount_sats %>
Amount EUR<%= @donation.amount_eur %>
Amount USD<%= @donation.amount_usd %>
Public name<%= @donation.public_name %>
Date<%= @donation.paid_at.strftime("%Y-%m-%d (%H:%M UTC)") %>
+
-

- Amount sats: - <%= @donation.amount_sats %> -

- -

- Amount eur: - <%= @donation.amount_eur %> -

- -

- Amount usd: - <%= @donation.amount_usd %> -

- -

- Public name: - <%= @donation.public_name %> -

- -

- Date: - <%= @donation.paid_at %> -

- -

- <%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'ks-text-link' %> | - <%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %> -

+
+

+ <%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'btn-md btn-blue mr-1' %> + <%= link_to 'Back', admin_donations_path, class: 'btn-md btn-gray' %> +

+
<% end %> diff --git a/app/views/admin/invitations/index.html.erb b/app/views/admin/invitations/index.html.erb index 1bc5517..19e51be 100644 --- a/app/views/admin/invitations/index.html.erb +++ b/app/views/admin/invitations/index.html.erb @@ -24,7 +24,7 @@ <% if @invitations_used.any? %>

Recently Accepted

- +
@@ -38,12 +38,13 @@ - - + + <% end %>
Token
<%= invitation.token %> <%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %><%= invitation.user.address %><%= User.find(invitation.invited_user_id).address %><%= link_to invitation.user.address, admin_user_path(invitation.user.address), class: "ks-text-link" %><%= link_to invitation.invitee.address, admin_user_path(invitation.invitee.address), class: "ks-text-link" %>
+ <%== pagy_nav @pagy %>
<% end %> <% end %> diff --git a/app/views/admin/lightning/index.html.erb b/app/views/admin/lightning/index.html.erb index 2c099cb..8cb0757 100644 --- a/app/views/admin/lightning/index.html.erb +++ b/app/views/admin/lightning/index.html.erb @@ -20,7 +20,7 @@

Accounts

- +
@@ -36,7 +36,7 @@ diff --git a/app/views/admin/settings/registrations/index.html.erb b/app/views/admin/settings/registrations/index.html.erb index 2131e41..68e1827 100644 --- a/app/views/admin/settings/registrations/index.html.erb +++ b/app/views/admin/settings/registrations/index.html.erb @@ -22,14 +22,14 @@ <%= f.text_area :reserved_usernames, value: Setting.reserved_usernames.join("\n"), class: "h-44 mb-2" %> -

+

One username per line

-

+

<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>

diff --git a/app/views/admin/settings/services/index.html.erb b/app/views/admin/settings/services/index.html.erb index c0dad64..a4a4fec 100644 --- a/app/views/admin/settings/services/index.html.erb +++ b/app/views/admin/settings/services/index.html.erb @@ -18,7 +18,7 @@
  • -

    LNDHub configuration present and wallet features enabled

    +

    LNDHub configuration present and wallet features enabled

    <%= f.check_box :lndhub_enabled, checked: Setting.lndhub_enabled?, disabled: true, @@ -27,7 +27,7 @@
  • -

    LNDHub database configuration present and admin panel enabled

    +

    LNDHub database configuration present and admin panel enabled

    <%= f.check_box :lndhub_admin_enabled, checked: Setting.lndhub_admin_enabled?, disabled: true, diff --git a/app/views/admin/ldap_users/index.html.erb b/app/views/admin/users/index.html.erb similarity index 62% rename from app/views/admin/ldap_users/index.html.erb rename to app/views/admin/users/index.html.erb index e39bd6f..e0eb90d 100644 --- a/app/views/admin/ldap_users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -1,4 +1,4 @@ -<%= render HeaderComponent.new(title: "LDAP Users: #{@ou}") %> +<%= render HeaderComponent.new(title: "Users: #{@ou}") %> <%= render MainSimpleComponent.new do %>
    @@ -22,7 +22,7 @@
      <% @orgs.each do |org| %>
    • - <%= link_to org[:ou], admin_ldap_users_path(ou: org[:ou]), class: "ks-text-link" %> + <%= link_to org[:ou], admin_users_path(ou: org[:ou]), class: "ks-text-link" %>
    • <% end %>
    @@ -30,25 +30,25 @@ <% end %>
    -
  • LN Account <% if user = @users.find{ |u| u[2] == account.login } %> - <%= "#{user[0]}@#{user[1]}" %> + <%= link_to "#{user[0]}@#{user[1]}", admin_user_path("#{user[0]}@#{user[1]}"), class: "ks-text-link" %> <% end %> <%= number_with_delimiter account.balance.to_i.to_s %>
    +
    - - + + - <% @entries.each do |entry| %> + <% @users.each do |user| %> - - - - + + + <% end %>
    UIDE-MailAdminStatusRoles
    <%= entry[:uid] %><%= entry[:mail] %><%= entry[:admin] %><%= link_to(user.cn, admin_user_path(user.address), class: 'ks-text-link') %><%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %><%= user.is_admin? ? badge("admin", :red) : "" %>
    + <%== pagy_nav @pagy %>
    <% end %> diff --git a/app/views/admin/users/show.html.erb b/app/views/admin/users/show.html.erb new file mode 100644 index 0000000..4679e6f --- /dev/null +++ b/app/views/admin/users/show.html.erb @@ -0,0 +1,123 @@ +<%= render HeaderComponent.new(title: "User: #{@user.address}") %> + +<%= render MainSimpleComponent.new do %> +
    +
    +

    Account

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Created at<%= @user.created_at.strftime("%Y-%m-%d (%H:%M UTC)") %>
    Confirmed at + <% if @user.confirmed_at %> + <%= @user.confirmed_at.strftime("%Y-%m-%d (%H:%M UTC)") %> + <% else %> + <%= badge "pending", :yellow %> + <% end %> +
    Email<%= @user.email %>
    Roles<%= @user.is_admin? ? badge("admin", :red) : "—" %>
    Invited by + <% if @user.inviter %> + <%= link_to @user.inviter.address, admin_user_path(@user.inviter.address), class: 'ks-text-link' %> + <% else %>—<% end %> +
    Invitations available + <%= @user.invitations.count %> +
    Invited users + <% if @user.invitees.length > 0 %> +
      + <% @user.invitees.order(cn: :asc).each do |invitee| %> +
    • <%= link_to invitee.address, admin_user_path(invitee.address), class: 'ks-text-link' %>
    • + <% end %> +
    + <% else %>—<% end %> +
    +
    + +
    + +
    +
    + +
    +

    Services

    + + + + + + + + + + + + + + + + + + + + + + + +
    Discourse<%= check_box_tag 'service_discourse', 'enabled', @services_enabled.include?("discourse"), disabled: true %>
    Gitea<%= check_box_tag 'service_gitea', 'enabled', @services_enabled.include?("gitea"), disabled: true %>
    Mastodon<%= check_box_tag 'service_mastodon', 'enabled', @services_enabled.include?("mastodon"), disabled: true %>
    Wiki<%= check_box_tag 'service_wiki', 'enabled', @services_enabled.include?("wiki"), disabled: true %>
    XMPP<%= check_box_tag 'service_xmpp', 'enabled', @services_enabled.include?("xmpp"), disabled: true %>
    +
    + + <% if Setting.lndhub_admin_enabled? && @user.confirmed? %> +
    +

    LndHub

    + <% if @lndhub_user %> + + + + + + + + + + + + + + + + + + + +
    AccountBalanceIncomingOutgoingFees
    <%= @user.ln_account %><%= number_with_delimiter @lndhub_user.balance %> sats<%= number_with_delimiter @lndhub_user.sum_incoming %> sats<%= number_with_delimiter @lndhub_user.sum_outgoing %> sats<%= number_with_delimiter @lndhub_user.sum_fees %> sats
    + <% else %> +

    No LndHub user found for account <%= @user.ln_account %>. + <% end %> +

    + <% end %> +<% end %> diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb index 9f39bb4..9e24f78 100644 --- a/app/views/dashboard/index.html.erb +++ b/app/views/dashboard/index.html.erb @@ -2,60 +2,87 @@ <%= render MainSimpleComponent.new do %>
    -

    +

    Your Kosmos account and password currently give you access to these services:

    -
    -
    -

    - <%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %> -

    -

    - Chat rooms and instant messaging (XMPP/Jabber) -

    +
    +
    + <%= link_to "https://wiki.kosmos.org/Services:Chat", + class: "block h-full px-6 py-6 rounded-md" do %> +

    Chat

    +

    + Federated chat rooms and instant messaging +

    + <% end %>
    -
    -

    - <%= link_to "Discourse", "https://community.kosmos.org", class: "ks-text-link" %> -

    -

    - Kosmos community forums and user support/help site -

    +
    + <%= link_to "https://community.kosmos.org", + class: "block h-full px-6 py-6 rounded-md" do %> +

    Discourse

    +

    + Kosmos community forums and user support/help site +

    + <% end %>
    -
    -

    - <%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 inline" } %> - <%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %> -

    -

    - Send and receive sats over the Bitcoin Lightning Network -

    +
    + <%= link_to "https://wiki.kosmos.org", + class: "block h-full px-6 py-6 rounded-md" do %> +

    Wiki

    +

    + Kosmos documentation and knowledge base +

    + <% end %>
    -
    -

    - <%= link_to "Wiki", "https://wiki.kosmos.org", class: "ks-text-link" %> -

    -

    - Kosmos documentation and knowledge base -

    +
    + <%= link_to wallet_path, + class: "block h-full px-6 py-6 rounded-md" do %> +

    Wallet

    +

    + Send and receive sats over the Bitcoin Lightning Network +

    + <% end %>
    -
    -

    - <%= link_to "Gitea", "https://gitea.kosmos.org", class: "ks-text-link" %> -

    -

    - Code hosting and collaboration for software projects -

    +
    + <%= link_to "https://gitea.kosmos.org", + class: "block h-full px-6 py-6 rounded-md" do %> +

    Gitea

    +

    + Code hosting and collaboration for software projects +

    + <% end %>
    -
    -

    - <%= link_to "Drone CI", "https://drone.kosmos.org", class: "ks-text-link" %> -

    -

    - Continuous integration for software projects on Gitea -

    +
    + <%= link_to "https://drone.kosmos.org", + class: "block h-full px-6 py-6 rounded-md" do %> +

    Drone CI

    +

    + Continuous integration for software projects on Gitea +

    + <% end %>
    + + + + + + + + + +
    <% end %> diff --git a/app/views/shared/_admin_nav.html.erb b/app/views/shared/_admin_nav.html.erb index dd8c8b8..7762431 100644 --- a/app/views/shared/_admin_nav.html.erb +++ b/app/views/shared/_admin_nav.html.erb @@ -1,7 +1,7 @@ <%= link_to "Dashboard", admin_root_path, class: main_nav_class(@current_section, :dashboard) %> -<%= link_to "Users", admin_ldap_users_path, - class: main_nav_class(@current_section, :ldap_users) %> +<%= link_to "Users", admin_users_path, + class: main_nav_class(@current_section, :users) %> <%= link_to "Invitations", admin_invitations_path, class: main_nav_class(@current_section, :invitations) %> <%= link_to "Donations", admin_donations_path, diff --git a/config/application.rb b/config/application.rb index 2e9e8ed..3731f5a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -22,7 +22,7 @@ Bundler.require(*Rails.groups) module Akkounts class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 6.0 + config.load_defaults 7.0 # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers diff --git a/config/initializers/pagy.rb b/config/initializers/pagy.rb new file mode 100644 index 0000000..16b8843 --- /dev/null +++ b/config/initializers/pagy.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +# Pagy initializer file (6.0.2) +# Customize only what you really need and notice that the core Pagy works also without any of the following lines. +# Should you just cherry pick part of this file, please maintain the require-order of the extras + + +# Pagy DEFAULT Variables +# See https://ddnexus.github.io/pagy/docs/api/pagy#variables +# All the Pagy::DEFAULT are set for all the Pagy instances but can be overridden per instance by just passing them to +# Pagy.new|Pagy::Countless.new|Pagy::Calendar::*.new or any of the #pagy* controller methods + + +# Instance variables +# See https://ddnexus.github.io/pagy/docs/api/pagy#instance-variables +# Pagy::DEFAULT[:page] = 1 # default +# Pagy::DEFAULT[:items] = 20 # default +# Pagy::DEFAULT[:outset] = 0 # default + + +# Other Variables +# See https://ddnexus.github.io/pagy/docs/api/pagy#other-variables +# Pagy::DEFAULT[:size] = [1,4,4,1] # default +# Pagy::DEFAULT[:page_param] = :page # default +# The :params can be also set as a lambda e.g ->(params){ params.exclude('useless').merge!('custom' => 'useful') } +# Pagy::DEFAULT[:params] = {} # default +# Pagy::DEFAULT[:fragment] = '#fragment' # example +# Pagy::DEFAULT[:link_extra] = 'data-remote="true"' # example +# Pagy::DEFAULT[:i18n_key] = 'pagy.item_name' # default +# Pagy::DEFAULT[:cycle] = true # example +# Pagy::DEFAULT[:request_path] = "/foo" # example + + +# Extras +# See https://ddnexus.github.io/pagy/categories/extra + + +# Backend Extras + +# Arel extra: For better performance utilizing grouped ActiveRecord collections: +# See: https://ddnexus.github.io/pagy/docs/extras/arel +# require 'pagy/extras/arel' + +# Array extra: Paginate arrays efficiently, avoiding expensive array-wrapping and without overriding +# See https://ddnexus.github.io/pagy/docs/extras/array +# require 'pagy/extras/array' + +# Calendar extra: Add pagination filtering by calendar time unit (year, quarter, month, week, day) +# See https://ddnexus.github.io/pagy/docs/extras/calendar +# require 'pagy/extras/calendar' +# Default for each unit +# Pagy::Calendar::Year::DEFAULT[:order] = :asc # Time direction of pagination +# Pagy::Calendar::Year::DEFAULT[:format] = '%Y' # strftime format +# +# Pagy::Calendar::Quarter::DEFAULT[:order] = :asc # Time direction of pagination +# Pagy::Calendar::Quarter::DEFAULT[:format] = '%Y-Q%q' # strftime format +# +# Pagy::Calendar::Month::DEFAULT[:order] = :asc # Time direction of pagination +# Pagy::Calendar::Month::DEFAULT[:format] = '%Y-%m' # strftime format +# +# Pagy::Calendar::Week::DEFAULT[:order] = :asc # Time direction of pagination +# Pagy::Calendar::Week::DEFAULT[:format] = '%Y-%W' # strftime format +# +# Pagy::Calendar::Day::DEFAULT[:order] = :asc # Time direction of pagination +# Pagy::Calendar::Day::DEFAULT[:format] = '%Y-%m-%d' # strftime format +# +# Uncomment the following lines, if you need calendar localization without using the I18n extra +# module LocalizePagyCalendar +# def localize(time, opts) +# ::I18n.l(time, **opts) +# end +# end +# Pagy::Calendar.prepend LocalizePagyCalendar + +# Countless extra: Paginate without any count, saving one query per rendering +# See https://ddnexus.github.io/pagy/docs/extras/countless +# require 'pagy/extras/countless' +# Pagy::DEFAULT[:countless_minimal] = false # default (eager loading) + +# Elasticsearch Rails extra: Paginate `ElasticsearchRails::Results` objects +# See https://ddnexus.github.io/pagy/docs/extras/elasticsearch_rails +# Default :pagy_search method: change only if you use also +# the searchkick or meilisearch extra that defines the same +# Pagy::DEFAULT[:elasticsearch_rails_pagy_search] = :pagy_search +# Default original :search method called internally to do the actual search +# Pagy::DEFAULT[:elasticsearch_rails_search] = :search +# require 'pagy/extras/elasticsearch_rails' + +# Headers extra: http response headers (and other helpers) useful for API pagination +# See http://ddnexus.github.io/pagy/extras/headers +# require 'pagy/extras/headers' +# Pagy::DEFAULT[:headers] = { page: 'Current-Page', +# items: 'Page-Items', +# count: 'Total-Count', +# pages: 'Total-Pages' } # default + +# Meilisearch extra: Paginate `Meilisearch` result objects +# See https://ddnexus.github.io/pagy/docs/extras/meilisearch +# Default :pagy_search method: change only if you use also +# the elasticsearch_rails or searchkick extra that define the same method +# Pagy::DEFAULT[:meilisearch_pagy_search] = :pagy_search +# Default original :search method called internally to do the actual search +# Pagy::DEFAULT[:meilisearch_search] = :ms_search +# require 'pagy/extras/meilisearch' + +# Metadata extra: Provides the pagination metadata to Javascript frameworks like Vue.js, react.js, etc. +# See https://ddnexus.github.io/pagy/docs/extras/metadata +# you must require the frontend helpers internal extra (BEFORE the metadata extra) ONLY if you need also the :sequels +# require 'pagy/extras/frontend_helpers' +# require 'pagy/extras/metadata' +# For performance reasons, you should explicitly set ONLY the metadata you use in the frontend +# Pagy::DEFAULT[:metadata] = %i[scaffold_url page prev next last] # example + +# Searchkick extra: Paginate `Searchkick::Results` objects +# See https://ddnexus.github.io/pagy/docs/extras/searchkick +# Default :pagy_search method: change only if you use also +# the elasticsearch_rails or meilisearch extra that defines the same +# DEFAULT[:searchkick_pagy_search] = :pagy_search +# Default original :search method called internally to do the actual search +# Pagy::DEFAULT[:searchkick_search] = :search +# require 'pagy/extras/searchkick' +# uncomment if you are going to use Searchkick.pagy_search +# Searchkick.extend Pagy::Searchkick + + +# Frontend Extras + +# Bootstrap extra: Add nav, nav_js and combo_nav_js helpers and templates for Bootstrap pagination +# See https://ddnexus.github.io/pagy/docs/extras/bootstrap +# require 'pagy/extras/bootstrap' + +# Bulma extra: Add nav, nav_js and combo_nav_js helpers and templates for Bulma pagination +# See https://ddnexus.github.io/pagy/docs/extras/bulma +# require 'pagy/extras/bulma' + +# Foundation extra: Add nav, nav_js and combo_nav_js helpers and templates for Foundation pagination +# See https://ddnexus.github.io/pagy/docs/extras/foundation +# require 'pagy/extras/foundation' + +# Materialize extra: Add nav, nav_js and combo_nav_js helpers for Materialize pagination +# See https://ddnexus.github.io/pagy/docs/extras/materialize +# require 'pagy/extras/materialize' + +# Navs extra: Add nav_js and combo_nav_js javascript helpers +# Notice: the other frontend extras add their own framework-styled versions, +# so require this extra only if you need the unstyled version +# See https://ddnexus.github.io/pagy/docs/extras/navs +# require 'pagy/extras/navs' + +# Semantic extra: Add nav, nav_js and combo_nav_js helpers for Semantic UI pagination +# See https://ddnexus.github.io/pagy/docs/extras/semantic +# require 'pagy/extras/semantic' + +# UIkit extra: Add nav helper and templates for UIkit pagination +# See https://ddnexus.github.io/pagy/docs/extras/uikit +# require 'pagy/extras/uikit' + +# Multi size var used by the *_nav_js helpers +# See https://ddnexus.github.io/pagy/docs/extras/navs#steps +# Pagy::DEFAULT[:steps] = { 0 => [2,3,3,2], 540 => [3,5,5,3], 720 => [5,7,7,5] } # example + + +# Feature Extras + +# Gearbox extra: Automatically change the number of items per page depending on the page number +# See https://ddnexus.github.io/pagy/docs/extras/gearbox +# require 'pagy/extras/gearbox' +# set to false only if you want to make :gearbox_extra an opt-in variable +# Pagy::DEFAULT[:gearbox_extra] = false # default true +# Pagy::DEFAULT[:gearbox_items] = [15, 30, 60, 100] # default + +# Items extra: Allow the client to request a custom number of items per page with an optional selector UI +# See https://ddnexus.github.io/pagy/docs/extras/items +# require 'pagy/extras/items' +# set to false only if you want to make :items_extra an opt-in variable +# Pagy::DEFAULT[:items_extra] = false # default true +# Pagy::DEFAULT[:items_param] = :items # default +# Pagy::DEFAULT[:max_items] = 100 # default + +# Overflow extra: Allow for easy handling of overflowing pages +# See https://ddnexus.github.io/pagy/docs/extras/overflow +# require 'pagy/extras/overflow' +# Pagy::DEFAULT[:overflow] = :empty_page # default (other options: :last_page and :exception) + +# Support extra: Extra support for features like: incremental, infinite, auto-scroll pagination +# See https://ddnexus.github.io/pagy/docs/extras/support +# require 'pagy/extras/support' + +# Trim extra: Remove the page=1 param from links +# See https://ddnexus.github.io/pagy/docs/extras/trim +# require 'pagy/extras/trim' +# set to false only if you want to make :trim_extra an opt-in variable +# Pagy::DEFAULT[:trim_extra] = false # default true + +# Standalone extra: Use pagy in non Rack environment/gem +# See https://ddnexus.github.io/pagy/docs/extras/standalone +# require 'pagy/extras/standalone' +# Pagy::DEFAULT[:url] = 'http://www.example.com/subdir' # optional default + + +# Rails +# Enable the .js file required by the helpers that use javascript +# (pagy*_nav_js, pagy*_combo_nav_js, and pagy_items_selector_js) +# See https://ddnexus.github.io/pagy/docs/api/javascript + +# With the asset pipeline +# Sprockets need to look into the pagy javascripts dir, so add it to the assets paths +# Rails.application.config.assets.paths << Pagy.root.join('javascripts') + +# I18n + +# Pagy internal I18n: ~18x faster using ~10x less memory than the i18n gem +# See https://ddnexus.github.io/pagy/docs/api/i18n +# Notice: No need to configure anything in this section if your app uses only "en" +# or if you use the i18n extra below +# +# Examples: +# load the "de" built-in locale: +# Pagy::I18n.load(locale: 'de') +# +# load the "de" locale defined in the custom file at :filepath: +# Pagy::I18n.load(locale: 'de', filepath: 'path/to/pagy-de.yml') +# +# load the "de", "en" and "es" built-in locales: +# (the first passed :locale will be used also as the default_locale) +# Pagy::I18n.load({ locale: 'de' }, +# { locale: 'en' }, +# { locale: 'es' }) +# +# load the "en" built-in locale, a custom "es" locale, +# and a totally custom locale complete with a custom :pluralize proc: +# (the first passed :locale will be used also as the default_locale) +# Pagy::I18n.load({ locale: 'en' }, +# { locale: 'es', filepath: 'path/to/pagy-es.yml' }, +# { locale: 'xyz', # not built-in +# filepath: 'path/to/pagy-xyz.yml', +# pluralize: lambda{ |count| ... } ) + + +# I18n extra: uses the standard i18n gem which is ~18x slower using ~10x more memory +# than the default pagy internal i18n (see above) +# See https://ddnexus.github.io/pagy/docs/extras/i18n +# require 'pagy/extras/i18n' + +# Default i18n key +# Pagy::DEFAULT[:i18n_key] = 'pagy.item_name' # default + + +# When you are done setting your own default freeze it, so it will not get changed accidentally +Pagy::DEFAULT.freeze diff --git a/config/routes.rb b/config/routes.rb index cef51b7..8b4866b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ require 'sidekiq/web' Rails.application.routes.draw do - devise_for :users + devise_for :users, :controllers => { :confirmations => "users/confirmations" } get 'welcome', to: 'welcome#index' get 'check_your_email', to: 'welcome#check_your_email' @@ -43,7 +43,7 @@ Rails.application.routes.draw do namespace :admin do root to: 'dashboard#index' - get 'ldap_users', to: 'ldap_users#index' + resources 'users', param: 'address', only: ['index', 'show'], constraints: { address: /.*/ } get 'invitations', to: 'invitations#index' resources :donations get 'lightning', to: 'lightning#index' diff --git a/db/migrate/20230223115536_remove_ln_login_ciphertext_from_users.rb b/db/migrate/20230223115536_remove_ln_login_ciphertext_from_users.rb new file mode 100644 index 0000000..da1bf86 --- /dev/null +++ b/db/migrate/20230223115536_remove_ln_login_ciphertext_from_users.rb @@ -0,0 +1,5 @@ +class RemoveLnLoginCiphertextFromUsers < ActiveRecord::Migration[7.0] + def change + remove_column :users, :ln_login_ciphertext + end +end diff --git a/db/schema.rb b/db/schema.rb index 6b3cf56..f87e1b0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_02_17_084310) do +ActiveRecord::Schema[7.0].define(version: 2023_02_23_115536) do create_table "donations", force: :cascade do |t| t.integer "user_id" t.integer "amount_sats" diff --git a/db/seeds.rb b/db/seeds.rb index 98ae840..3671ad2 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -10,7 +10,7 @@ Sidekiq::Testing.inline! do ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true" - 5.times do |n| + 35.times do |n| username = Faker::Name.unique.first_name.downcase email = Faker::Internet.unique.email diff --git a/public/img/logos/icon_discourse.svg b/public/img/logos/icon_discourse.svg new file mode 100644 index 0000000..4cbb8c8 --- /dev/null +++ b/public/img/logos/icon_discourse.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/public/img/logos/icon_droneci.svg b/public/img/logos/icon_droneci.svg new file mode 100644 index 0000000..10f926a --- /dev/null +++ b/public/img/logos/icon_droneci.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/logos/icon_gitea.png b/public/img/logos/icon_gitea.png new file mode 100644 index 0000000..1538cd1 Binary files /dev/null and b/public/img/logos/icon_gitea.png differ diff --git a/public/img/logos/icon_lightning.svg b/public/img/logos/icon_lightning.svg new file mode 100644 index 0000000..406841e --- /dev/null +++ b/public/img/logos/icon_lightning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/logos/icon_mastodon.svg b/public/img/logos/icon_mastodon.svg new file mode 100644 index 0000000..7bd752a --- /dev/null +++ b/public/img/logos/icon_mastodon.svg @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/public/img/logos/icon_mediawiki.svg b/public/img/logos/icon_mediawiki.svg new file mode 100644 index 0000000..fdadd25 --- /dev/null +++ b/public/img/logos/icon_mediawiki.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/logos/icon_xmpp.svg b/public/img/logos/icon_xmpp.svg new file mode 100644 index 0000000..058c67a --- /dev/null +++ b/public/img/logos/icon_xmpp.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/jobs/create_lndhub_account_job_spec.rb b/spec/jobs/create_lndhub_account_job_spec.rb index aa2d179..51ffe2d 100644 --- a/spec/jobs/create_lndhub_account_job_spec.rb +++ b/spec/jobs/create_lndhub_account_job_spec.rb @@ -19,7 +19,7 @@ RSpec.describe CreateLndhubAccountJob, type: :job do .with { |req| req.body == '{}' } user.reload - expect(user.ln_login).to eq("abc123") + expect(user.ln_account).to eq("abc123") expect(user.ln_password).to eq("def456") end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ed135d9..b1724f1 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,7 +1,16 @@ require 'rails_helper' RSpec.describe User, type: :model do - let(:user) { create :user } + let(:user) { create :user, cn: "philipp" } + let(:dn) { "cn=philipp,ou=kosmos.org,cn=users,dc=kosmos,dc=org" } + + describe "#address" do + let(:user) { build :user, cn: "jimmy", ou: "kosmos.org" } + + it "returns the user address" do + expect(user.address).to eq("jimmy@kosmos.org") + end + end describe "#is_admin?" do it "returns true when admin flag is set in LDAP" do @@ -21,11 +30,75 @@ RSpec.describe User, type: :model do end end - describe "#address" do - let(:user) { build :user, cn: "jimmy", ou: "kosmos.org" } - - it "returns the user address" do - expect(user.address).to eq("jimmy@kosmos.org") + describe "#services_enabled" do + it "returns the entries from the LDAP service attribute" do + expect(user).to receive(:ldap_entry).and_return({ + uid: user.cn, ou: user.ou, mail: user.email, admin: nil, + service: ["discourse", "gitea", "wiki", "xmpp"] + }) + expect(user.services_enabled).to eq(["discourse", "gitea", "wiki", "xmpp"]) end end + + describe "#enable_service" do + before do + allow(user).to receive(:ldap_entry).and_return({ + uid: user.cn, ou: user.ou, mail: user.email, admin: nil, + service: ["discourse", "gitea"] + }) + allow(user).to receive(:dn).and_return(dn) + end + + it "adds the service to the LDAP entry" do + expect_any_instance_of(LdapService).to receive(:replace_attribute) + .with(dn, :service, ["discourse", "gitea", "wiki"]).and_return(true) + + user.enable_service(:wiki) + end + + it "adds multiple service to the LDAP entry" do + expect_any_instance_of(LdapService).to receive(:replace_attribute) + .with(dn, :service, ["discourse", "gitea", "wiki", "xmpp"]).and_return(true) + + user.enable_service([:wiki, :xmpp]) + end + end + + describe "#disable_service" do + before do + allow(user).to receive(:ldap_entry).and_return({ + uid: user.cn, ou: user.ou, mail: user.email, admin: nil, + service: ["discourse", "gitea", "xmpp"] + }) + allow(user).to receive(:dn).and_return(dn) + end + + it "removes the service from the LDAP entry" do + expect_any_instance_of(LdapService).to receive(:replace_attribute) + .with(dn, :service, ["discourse", "gitea"]).and_return(true) + + user.disable_service(:xmpp) + end + + it "removes multiple services from the LDAP entry" do + expect_any_instance_of(LdapService).to receive(:replace_attribute) + .with(dn, :service, ["discourse"]).and_return(true) + + user.disable_service([:xmpp, "gitea"]) + end + end + + describe "#disable_all_services" do + before do + allow(user).to receive(:dn).and_return(dn) + end + + it "removes all services from the LDAP entry" do + expect_any_instance_of(LdapService).to receive(:delete_attribute) + .with(dn, :service).and_return(true) + + user.disable_all_services + end + end + end diff --git a/tailwind.config.js b/tailwind.config.js index 20a464f..a86d1b2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,15 @@ module.exports = { './app/helpers/**/*.rb', './app/javascript/**/*.js' ], + safelist: [ + 'bg-gray-100', 'text-gray-800', + 'bg-red-100', 'text-red-800', + 'bg-yellow-100', 'text-yellow-800', + 'bg-green-100', 'text-green-800', + 'bg-blue-100', 'text-blue-800', + 'bg-purple-100', 'text-purple-800', + 'bg-pink-100', 'text-pink-800', + ], theme: { extend: { fontFamily: { @@ -16,5 +25,5 @@ module.exports = { }, plugins: [ require('@tailwindcss/forms') - ], + ] }