Compare commits
16 Commits
4bf6985b87
...
3dbde86cdf
Author | SHA1 | Date | |
---|---|---|---|
3dbde86cdf | |||
0dcfefd66c | |||
c6a187b25a | |||
c99d8545c1 | |||
e8f912360b | |||
c94a0e34d1 | |||
04094efbdb | |||
71352d13d2 | |||
fff7527694 | |||
7a8ca0707a | |||
b657a25d4d | |||
e48132cf5f | |||
463bf34cdf | |||
f313686b13 | |||
0b4bc4ef5c | |||
393f85e45c |
1
Gemfile
1
Gemfile
@ -32,6 +32,7 @@ gem 'devise_ldap_authenticatable'
|
|||||||
gem 'net-ldap'
|
gem 'net-ldap'
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
|
gem 'aasm'
|
||||||
gem "image_processing", "~> 1.12.2"
|
gem "image_processing", "~> 1.12.2"
|
||||||
gem "rqrcode", "~> 2.0"
|
gem "rqrcode", "~> 2.0"
|
||||||
gem 'rails-settings-cached', '~> 2.8.3'
|
gem 'rails-settings-cached', '~> 2.8.3'
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
aasm (5.5.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
actioncable (8.0.2)
|
actioncable (8.0.2)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2)
|
||||||
@ -526,6 +528,7 @@ PLATFORMS
|
|||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
aasm
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
bcrypt (~> 3.1)
|
bcrypt (~> 3.1)
|
||||||
capybara
|
capybara
|
||||||
|
@ -128,6 +128,7 @@ command:
|
|||||||
|
|
||||||
### Front-end
|
### Front-end
|
||||||
|
|
||||||
|
* [Icons](https://feathericons.com)
|
||||||
* [Tailwind CSS](https://tailwindcss.com/)
|
* [Tailwind CSS](https://tailwindcss.com/)
|
||||||
* [Sass](https://sass-lang.com/documentation)
|
* [Sass](https://sass-lang.com/documentation)
|
||||||
* [Stimulus](https://stimulus.hotwired.dev/handbook/)
|
* [Stimulus](https://stimulus.hotwired.dev/handbook/)
|
||||||
|
@ -29,7 +29,7 @@ class SidenavLinkComponent < ViewComponent::Base
|
|||||||
|
|
||||||
def class_names_icon(path)
|
def class_names_icon(path)
|
||||||
if @active
|
if @active
|
||||||
"text-teal-500 group-hover:text-teal-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
|
"text-teal-600 group-hover:text-teal-600 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
|
||||||
elsif @disabled
|
elsif @disabled
|
||||||
"text-gray-300 group-hover:text-gray-300 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
|
"text-gray-300 group-hover:text-gray-300 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
|
||||||
else
|
else
|
||||||
|
@ -4,11 +4,22 @@ class Admin::DonationsController < Admin::BaseController
|
|||||||
|
|
||||||
# GET /donations
|
# GET /donations
|
||||||
def index
|
def index
|
||||||
@pagy, @donations = pagy(Donation.completed.order('paid_at desc'))
|
@username = params[:username].presence
|
||||||
|
|
||||||
|
pending_scope = Donation.incomplete.joins(:user).order('paid_at desc')
|
||||||
|
completed_scope = Donation.completed.joins(:user).order('paid_at desc')
|
||||||
|
|
||||||
|
if @username
|
||||||
|
pending_scope = pending_scope.where(users: { cn: @username })
|
||||||
|
completed_scope = completed_scope.where(users: { cn: @username })
|
||||||
|
end
|
||||||
|
|
||||||
|
@pending_donations = pending_scope
|
||||||
|
@pagy, @donations = pagy(completed_scope)
|
||||||
|
|
||||||
@stats = {
|
@stats = {
|
||||||
overall_sats: @donations.sum("amount_sats"),
|
overall_sats: completed_scope.sum("amount_sats"),
|
||||||
donor_count: Donation.completed.count(:user_id)
|
donor_count: completed_scope.distinct.count(:user_id)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,12 +1,28 @@
|
|||||||
class Admin::InvitationsController < Admin::BaseController
|
class Admin::InvitationsController < Admin::BaseController
|
||||||
|
before_action :set_current_section
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@current_section = :invitations
|
@username = params[:username].presence
|
||||||
@pagy, @invitations_used = pagy(Invitation.used.order('used_at desc'))
|
accepted_scope = Invitation.used.order('used_at desc')
|
||||||
|
unused_scope = Invitation.unused
|
||||||
|
|
||||||
|
if @username
|
||||||
|
accepted_scope = accepted_scope.joins(:user).where(users: { cn: @username })
|
||||||
|
unused_scope = unused_scope.joins(:user).where(users: { cn: @username })
|
||||||
|
end
|
||||||
|
|
||||||
|
@pagy, @invitations_used = pagy(accepted_scope)
|
||||||
|
|
||||||
@stats = {
|
@stats = {
|
||||||
available: Invitation.unused.count,
|
available: unused_scope.count,
|
||||||
accepted: @invitations_used.length,
|
accepted: accepted_scope.count,
|
||||||
users_with_referrals: Invitation.used.distinct.count(:user_id)
|
users_with_referrals: accepted_scope.distinct.count(:user_id)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_current_section
|
||||||
|
@current_section = :invitations
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
23
app/controllers/admin/settings/membership_controller.rb
Normal file
23
app/controllers/admin/settings/membership_controller.rb
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
class Admin::Settings::MembershipController < Admin::SettingsController
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
update_settings
|
||||||
|
|
||||||
|
redirect_to admin_settings_membership_path, flash: {
|
||||||
|
success: "Settings saved"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def setting_params
|
||||||
|
params.require(:setting).permit([
|
||||||
|
:member_status_contributor,
|
||||||
|
:member_status_sustainer,
|
||||||
|
:user_index_show_contributors,
|
||||||
|
:user_index_show_sustainers
|
||||||
|
])
|
||||||
|
end
|
||||||
|
end
|
@ -4,18 +4,30 @@ class Admin::UsersController < Admin::BaseController
|
|||||||
|
|
||||||
# GET /admin/users
|
# GET /admin/users
|
||||||
def index
|
def index
|
||||||
ldap = LdapService.new
|
ldap = LdapService.new
|
||||||
@ou = Setting.primary_domain
|
ou = Setting.primary_domain
|
||||||
@pagy, @users = pagy(User.where(ou: @ou).order(cn: :asc))
|
@show_contributors = Setting.user_index_show_contributors
|
||||||
|
@show_sustainers = Setting.user_index_show_sustainers
|
||||||
|
|
||||||
|
@contributors = ldap.search_users(:memberStatus, :contributor, :cn) if @show_contributors
|
||||||
|
@sustainers = ldap.search_users(:memberStatus, :sustainer, :cn) if @show_sustainers
|
||||||
|
@admins = ldap.search_users(:admin, true, :cn)
|
||||||
|
@pagy, @users = pagy(User.where(ou: ou).order(cn: :asc))
|
||||||
|
|
||||||
@stats = {
|
@stats = {
|
||||||
users_confirmed: User.where(ou: @ou).confirmed.count,
|
users_confirmed: User.where(ou: ou).confirmed.count,
|
||||||
users_pending: User.where(ou: @ou).pending.count
|
users_pending: User.where(ou: ou).pending.count
|
||||||
}
|
}
|
||||||
|
@stats[:users_contributing] = @contributors.size if @show_contributors
|
||||||
|
@stats[:users_paying] = @sustainers.size if @show_sustainers
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /admin/users/:username
|
# GET /admin/users/:username
|
||||||
def show
|
def show
|
||||||
|
@invitees = @user.invitees
|
||||||
|
@recent_invitees = @user.invitees.order(created_at: :desc).limit(5)
|
||||||
|
@more_invitees = (@invitees - @recent_invitees).count
|
||||||
|
|
||||||
if Setting.lndhub_admin_enabled?
|
if Setting.lndhub_admin_enabled?
|
||||||
@lndhub_user = @user.lndhub_user
|
@lndhub_user = @user.lndhub_user
|
||||||
end
|
end
|
||||||
|
@ -11,7 +11,7 @@ class Contributions::DonationsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
@current_section = :contributions
|
@current_section = :contributions
|
||||||
@donations_completed = current_user.donations.completed.order('paid_at desc')
|
@donations_completed = current_user.donations.completed.order('paid_at desc')
|
||||||
@donations_pending = current_user.donations.processing.order('created_at desc')
|
@donations_processing = current_user.donations.processing.order('created_at desc')
|
||||||
|
|
||||||
if Setting.lndhub_enabled?
|
if Setting.lndhub_enabled?
|
||||||
begin
|
begin
|
||||||
@ -81,14 +81,11 @@ class Contributions::DonationsController < ApplicationController
|
|||||||
|
|
||||||
case invoice["status"]
|
case invoice["status"]
|
||||||
when "Settled"
|
when "Settled"
|
||||||
@donation.paid_at = DateTime.now
|
@donation.complete!
|
||||||
@donation.payment_status = "settled"
|
|
||||||
@donation.save!
|
|
||||||
flash_message = { success: "Thank you!" }
|
flash_message = { success: "Thank you!" }
|
||||||
when "Processing"
|
when "Processing"
|
||||||
unless @donation.processing?
|
unless @donation.processing?
|
||||||
@donation.payment_status = "processing"
|
@donation.start_processing!
|
||||||
@donation.save!
|
|
||||||
flash_message = { success: "Thank you! We will send you an email when the payment is confirmed." }
|
flash_message = { success: "Thank you! We will send you an email when the payment is confirmed." }
|
||||||
BtcpayCheckDonationJob.set(wait: 20.seconds).perform_later(@donation)
|
BtcpayCheckDonationJob.set(wait: 20.seconds).perform_later(@donation)
|
||||||
end
|
end
|
||||||
|
@ -10,9 +10,7 @@ class BtcpayCheckDonationJob < ApplicationJob
|
|||||||
|
|
||||||
case invoice["status"]
|
case invoice["status"]
|
||||||
when "Settled"
|
when "Settled"
|
||||||
donation.paid_at = DateTime.now
|
donation.complete!
|
||||||
donation.payment_status = "settled"
|
|
||||||
donation.save!
|
|
||||||
|
|
||||||
NotificationMailer.with(user: donation.user)
|
NotificationMailer.with(user: donation.user)
|
||||||
.bitcoin_donation_confirmed
|
.bitcoin_donation_confirmed
|
||||||
|
18
app/models/concerns/settings/membership_settings.rb
Normal file
18
app/models/concerns/settings/membership_settings.rb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module Settings
|
||||||
|
module MembershipSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :member_status_contributor, type: :string,
|
||||||
|
default: "Contributor"
|
||||||
|
field :member_status_sustainer, type: :string,
|
||||||
|
default: "Sustainer"
|
||||||
|
|
||||||
|
# Admin panel
|
||||||
|
field :user_index_show_contributors, type: :boolean,
|
||||||
|
default: false
|
||||||
|
field :user_index_show_sustainers, type: :boolean,
|
||||||
|
default: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,22 +1,42 @@
|
|||||||
class Donation < ApplicationRecord
|
class Donation < ApplicationRecord
|
||||||
# Relations
|
include AASM
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
# Validations
|
|
||||||
validates_presence_of :user
|
validates_presence_of :user
|
||||||
validates_presence_of :donation_method,
|
validates_presence_of :donation_method,
|
||||||
inclusion: { in: %w[ custom btcpay lndhub ] }
|
inclusion: { in: %w[ custom btcpay lndhub ] }
|
||||||
validates_presence_of :payment_status, allow_nil: true,
|
validates_presence_of :payment_status, allow_nil: true,
|
||||||
inclusion: { in: %w[ processing settled ] }
|
inclusion: { in: %w[ pending processing settled ] }
|
||||||
validates_presence_of :paid_at, allow_nil: true
|
validates_presence_of :paid_at, allow_nil: true
|
||||||
validates_presence_of :amount_sats, allow_nil: true
|
validates_presence_of :amount_sats, allow_nil: true
|
||||||
validates_presence_of :fiat_amount, allow_nil: true
|
validates_presence_of :fiat_amount, allow_nil: true
|
||||||
validates_presence_of :fiat_currency, allow_nil: true,
|
validates_presence_of :fiat_currency, allow_nil: true,
|
||||||
inclusion: { in: %w[ EUR USD ] }
|
inclusion: { in: %w[ EUR USD ] }
|
||||||
|
|
||||||
#Scopes
|
scope :pending, -> { where(payment_status: "pending") }
|
||||||
scope :processing, -> { where(payment_status: "processing") }
|
scope :processing, -> { where(payment_status: "processing") }
|
||||||
scope :completed, -> { where(payment_status: "settled") }
|
scope :completed, -> { where(payment_status: "settled") }
|
||||||
|
scope :incomplete, -> { where.not(payment_status: "settled") }
|
||||||
|
|
||||||
|
aasm column: :payment_status do
|
||||||
|
state :pending, initial: true
|
||||||
|
state :processing
|
||||||
|
state :settled
|
||||||
|
|
||||||
|
event :start_processing do
|
||||||
|
transitions from: :pending, to: :processing
|
||||||
|
end
|
||||||
|
|
||||||
|
event :complete do
|
||||||
|
transitions from: :processing, to: :settled, after: [:set_paid_at, :set_sustainer_status]
|
||||||
|
transitions from: :pending, to: :settled, after: [:set_paid_at, :set_sustainer_status]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pending?
|
||||||
|
payment_status == "pending"
|
||||||
|
end
|
||||||
|
|
||||||
def processing?
|
def processing?
|
||||||
payment_status == "processing"
|
payment_status == "processing"
|
||||||
@ -25,4 +45,17 @@ class Donation < ApplicationRecord
|
|||||||
def completed?
|
def completed?
|
||||||
payment_status == "settled"
|
payment_status == "settled"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_paid_at
|
||||||
|
update paid_at: DateTime.now if paid_at.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_sustainer_status
|
||||||
|
user.add_member_status :sustainer
|
||||||
|
rescue => e
|
||||||
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||||
|
Rails.logger.error("Failed to set memberStatus: #{e.message}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -16,6 +16,7 @@ class Setting < RailsSettings::Base
|
|||||||
include Settings::LightningNetworkSettings
|
include Settings::LightningNetworkSettings
|
||||||
include Settings::MastodonSettings
|
include Settings::MastodonSettings
|
||||||
include Settings::MediaWikiSettings
|
include Settings::MediaWikiSettings
|
||||||
|
include Settings::MembershipSettings
|
||||||
include Settings::NostrSettings
|
include Settings::NostrSettings
|
||||||
include Settings::OpenCollectiveSettings
|
include Settings::OpenCollectiveSettings
|
||||||
include Settings::RemoteStorageSettings
|
include Settings::RemoteStorageSettings
|
||||||
|
@ -163,7 +163,21 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
def ldap_entry(reload: false)
|
def ldap_entry(reload: false)
|
||||||
return @ldap_entry if defined?(@ldap_entry) && !reload
|
return @ldap_entry if defined?(@ldap_entry) && !reload
|
||||||
@ldap_entry = ldap.fetch_users(uid: self.cn, ou: self.ou).first
|
@ldap_entry = ldap.fetch_users(cn: self.cn).first
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_to_ldap_array(attr_key, ldap_attr, value)
|
||||||
|
current_entries = ldap_entry[attr_key.to_sym] || []
|
||||||
|
new_entries = Array(value).map(&:to_s)
|
||||||
|
entries = (current_entries + new_entries).uniq.sort
|
||||||
|
ldap.replace_attribute(dn, ldap_attr.to_sym, entries)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_from_ldap_array(attr_key, ldap_attr, value)
|
||||||
|
current_entries = ldap_entry[attr_key.to_sym] || []
|
||||||
|
entries_to_remove = Array(value).map(&:to_s)
|
||||||
|
entries = (current_entries - entries_to_remove).uniq.sort
|
||||||
|
ldap.replace_attribute(dn, ldap_attr.to_sym, entries)
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_name
|
def display_name
|
||||||
@ -220,21 +234,39 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def enable_service(service)
|
def enable_service(service)
|
||||||
current_services = services_enabled
|
add_to_ldap_array :services_enabled, :serviceEnabled, service
|
||||||
new_services = Array(service).map(&:to_s)
|
ldap_entry(reload: true)[:services_enabled]
|
||||||
services = (current_services + new_services).uniq.sort
|
|
||||||
ldap.replace_attribute(dn, :serviceEnabled, services)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_service(service)
|
def disable_service(service)
|
||||||
current_services = services_enabled
|
remove_from_ldap_array :services_enabled, :serviceEnabled, service
|
||||||
disabled_services = Array(service).map(&:to_s)
|
ldap_entry(reload: true)[:services_enabled]
|
||||||
services = (current_services - disabled_services).uniq.sort
|
|
||||||
ldap.replace_attribute(dn, :serviceEnabled, services)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_all_services
|
def disable_all_services
|
||||||
ldap.delete_attribute(dn,:service)
|
ldap.delete_attribute(dn, :serviceEnabled)
|
||||||
|
end
|
||||||
|
|
||||||
|
def member_status
|
||||||
|
ldap_entry[:member_status] || []
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_member_status(status)
|
||||||
|
add_to_ldap_array :member_status, :memberStatus, status
|
||||||
|
ldap_entry(reload: true)[:member_status]
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_member_status(status)
|
||||||
|
remove_from_ldap_array :member_status, :memberStatus, status
|
||||||
|
ldap_entry(reload: true)[:member_status]
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_contributing_member?
|
||||||
|
member_status.map(&:to_sym).include?(:contributor)
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_paying_member?
|
||||||
|
member_status.map(&:to_sym).include?(:sustainer)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -50,19 +50,17 @@ class LdapService < ApplicationService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fetch_users(args={})
|
def fetch_users(args={})
|
||||||
if args[:ou]
|
|
||||||
treebase = "ou=#{args[:ou]},cn=users,#{ldap_suffix}"
|
|
||||||
else
|
|
||||||
treebase = ldap_config["base"]
|
|
||||||
end
|
|
||||||
|
|
||||||
attributes = %w[
|
attributes = %w[
|
||||||
dn cn uid mail displayName admin serviceEnabled
|
dn cn uid mail displayName admin serviceEnabled memberStatus
|
||||||
mailRoutingAddress mailpassword nostrKey pgpKey
|
mailRoutingAddress mailpassword nostrKey pgpKey
|
||||||
]
|
]
|
||||||
filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
|
filter = Net::LDAP::Filter.eq('objectClass', 'person') &
|
||||||
|
Net::LDAP::Filter.eq("cn", args[:cn] || "*")
|
||||||
|
|
||||||
entries = client.search(base: treebase, filter: filter, attributes: attributes)
|
entries = client.search(
|
||||||
|
base: ldap_config["base"], filter: filter,
|
||||||
|
attributes: attributes
|
||||||
|
)
|
||||||
entries.sort_by! { |e| e.cn[0] }
|
entries.sort_by! { |e| e.cn[0] }
|
||||||
entries = entries.collect do |e|
|
entries = entries.collect do |e|
|
||||||
{
|
{
|
||||||
@ -71,6 +69,7 @@ class LdapService < ApplicationService
|
|||||||
display_name: e.try(:displayName) ? e.displayName.first : nil,
|
display_name: e.try(:displayName) ? e.displayName.first : nil,
|
||||||
admin: e.try(:admin) ? 'admin' : nil,
|
admin: e.try(:admin) ? 'admin' : nil,
|
||||||
services_enabled: e.try(:serviceEnabled),
|
services_enabled: e.try(:serviceEnabled),
|
||||||
|
member_status: e.try(:memberStatus),
|
||||||
email_maildrop: e.try(:mailRoutingAddress),
|
email_maildrop: e.try(:mailRoutingAddress),
|
||||||
email_password: e.try(:mailpassword),
|
email_password: e.try(:mailpassword),
|
||||||
nostr_key: e.try(:nostrKey) ? e.nostrKey.first : nil,
|
nostr_key: e.try(:nostrKey) ? e.nostrKey.first : nil,
|
||||||
@ -79,10 +78,20 @@ class LdapService < ApplicationService
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def search_users(search_attr, value, return_attr)
|
||||||
|
filter = Net::LDAP::Filter.eq('objectClass', 'person') &
|
||||||
|
Net::LDAP::Filter.eq(search_attr.to_s, value.to_s) &
|
||||||
|
Net::LDAP::Filter.present('cn')
|
||||||
|
entries = client.search(
|
||||||
|
base: ldap_config["base"], filter: filter,
|
||||||
|
attributes: [return_attr]
|
||||||
|
)
|
||||||
|
entries.map { |entry| entry[return_attr].first }.compact
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_organizations
|
def fetch_organizations
|
||||||
attributes = %w{dn ou description}
|
attributes = %w{dn ou description}
|
||||||
filter = Net::LDAP::Filter.eq("objectClass", "organizationalUnit")
|
filter = Net::LDAP::Filter.eq("objectClass", "organizationalUnit")
|
||||||
# filter = Net::LDAP::Filter.eq("objectClass", "*")
|
|
||||||
treebase = "cn=users,#{ldap_suffix}"
|
treebase = "cn=users,#{ldap_suffix}"
|
||||||
|
|
||||||
entries = client.search(base: treebase, filter: filter, attributes: attributes)
|
entries = client.search(base: treebase, filter: filter, attributes: attributes)
|
||||||
|
11
app/views/admin/_username_search_form.html.erb
Normal file
11
app/views/admin/_username_search_form.html.erb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<%= form_with url: path, method: :get, local: true, class: "flex gap-1" do %>
|
||||||
|
<%= text_field_tag :username, @username, placeholder: 'Filter by username' %>
|
||||||
|
<%= button_tag type: 'submit', name: nil, title: "Filter", class: 'btn-md btn-icon btn-outline' do %>
|
||||||
|
<%= render partial: "icons/filter", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||||
|
<% end %>
|
||||||
|
<% if @username %>
|
||||||
|
<%= link_to path, title: "Remove filter", class: 'btn-md btn-icon btn-outline' do %>
|
||||||
|
<%= render partial: "icons/x", locals: { custom_class: "text-red-600 h-4 w-4 inline" } %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
34
app/views/admin/donations/_list.html.erb
Normal file
34
app/views/admin/donations/_list.html.erb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<table class="divided">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>User</th>
|
||||||
|
<th class="text-right">Sats</th>
|
||||||
|
<th class="text-right">Fiat Amount</th>
|
||||||
|
<th class="pl-2">Public name</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% donations.each do |donation| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= link_to donation.user.cn, admin_user_path(donation.user.cn), class: 'ks-text-link' %></td>
|
||||||
|
<td class="text-right"><% if donation.amount_sats.present? %><%= number_with_delimiter donation.amount_sats %><% end %></td>
|
||||||
|
<td class="text-right"><% if donation.fiat_amount.present? %><%= number_to_currency donation.fiat_amount.to_f / 100, unit: "" %> <%= donation.fiat_currency %><% end %></td>
|
||||||
|
<td class="pl-2"><%= donation.public_name %></td>
|
||||||
|
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d (%H:%M UTC)") : donation.created_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
|
||||||
|
<%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
|
||||||
|
<%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red',
|
||||||
|
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<% if defined?(pagy) %>
|
||||||
|
<div class="mt-8">
|
||||||
|
<%== pagy_nav pagy %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
@ -5,7 +5,7 @@
|
|||||||
<%= render QuickstatsContainerComponent.new do %>
|
<%= render QuickstatsContainerComponent.new do %>
|
||||||
<%= render QuickstatsItemComponent.new(
|
<%= render QuickstatsItemComponent.new(
|
||||||
type: :number,
|
type: :number,
|
||||||
title: 'Overall',
|
title: 'Received',
|
||||||
value: @stats[:overall_sats],
|
value: @stats[:overall_sats],
|
||||||
unit: 'sats'
|
unit: 'sats'
|
||||||
) %>
|
) %>
|
||||||
@ -19,41 +19,28 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<% if @donations.any? %>
|
<%= render partial: "admin/username_search_form",
|
||||||
<h3>Recent Donations</h3>
|
locals: { path: admin_donations_path } %>
|
||||||
<table class="divided mb-8">
|
</section>
|
||||||
<thead>
|
|
||||||
<tr>
|
<% if @pending_donations.present? %>
|
||||||
<th>User</th>
|
<section>
|
||||||
<th class="text-right">Sats</th>
|
<h3>Pending</h3>
|
||||||
<th class="text-right">Fiat Amount</th>
|
<%= render partial: "admin/donations/list", locals: {
|
||||||
<th class="pl-2">Public name</th>
|
donations: @pending_donations
|
||||||
<th>Date</th>
|
} %>
|
||||||
<th></th>
|
</section>
|
||||||
</tr>
|
<% end %>
|
||||||
</thead>
|
|
||||||
<tbody>
|
<section>
|
||||||
<% @donations.each do |donation| %>
|
<% if @donations.present? %>
|
||||||
<tr>
|
<h3>Received</h3>
|
||||||
<td><%= link_to donation.user.cn, admin_user_path(donation.user.cn), class: 'ks-text-link' %></td>
|
<%= render partial: "admin/donations/list", locals: {
|
||||||
<td class="text-right"><% if donation.amount_sats.present? %><%= number_with_delimiter donation.amount_sats %><% end %></td>
|
donations: @donations, pagy: @pagy
|
||||||
<td class="text-right"><% if donation.fiat_amount.present? %><%= number_to_currency donation.fiat_amount.to_f / 100, unit: "" %> <%= donation.fiat_currency %><% end %></td>
|
} %>
|
||||||
<td class="pl-2"><%= donation.public_name %></td>
|
|
||||||
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d (%H:%M UTC)") : "" %></td>
|
|
||||||
<td class="text-right">
|
|
||||||
<%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
|
|
||||||
<%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
|
|
||||||
<%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red',
|
|
||||||
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<%== pagy_nav @pagy %>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>
|
<p>
|
||||||
No donations yet.
|
No donations received yet.
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</section>
|
</section>
|
||||||
|
@ -25,7 +25,15 @@
|
|||||||
<td><%= @donation.public_name %></td>
|
<td><%= @donation.public_name %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Payment status</th>
|
||||||
|
<td><%= @donation.payment_status %></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Created at</th>
|
||||||
|
<td><%= @donation.created_at&.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Paid at</th>
|
||||||
<td><%= @donation.paid_at&.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
<td><%= @donation.paid_at&.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -21,9 +21,15 @@
|
|||||||
) %>
|
) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<%= render partial: "admin/username_search_form",
|
||||||
|
locals: { path: admin_invitations_path } %>
|
||||||
|
</section>
|
||||||
|
|
||||||
<% if @invitations_used.any? %>
|
<% if @invitations_used.any? %>
|
||||||
<section>
|
<section>
|
||||||
<h3>Recently Accepted</h3>
|
<h3>Accepted</h3>
|
||||||
<table class="divided mb-8">
|
<table class="divided mb-8">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
53
app/views/admin/settings/membership/show.html.erb
Normal file
53
app/views/admin/settings/membership/show.html.erb
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<%= render HeaderComponent.new(title: "Settings") %>
|
||||||
|
|
||||||
|
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
|
||||||
|
<%= form_for(Setting.new, url: admin_settings_membership_path, method: :put) do |f| %>
|
||||||
|
<section>
|
||||||
|
<h3>Membership</h3>
|
||||||
|
|
||||||
|
<% if @errors && @errors.any? %>
|
||||||
|
<%= render partial: "admin/settings/errors", locals: { errors: @errors } %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<ul role="list">
|
||||||
|
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||||
|
key: :member_status_contributor,
|
||||||
|
title: "Status name for contributing users",
|
||||||
|
description: "A contributing member of your organization/group"
|
||||||
|
) %>
|
||||||
|
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||||
|
key: :member_status_sustainer,
|
||||||
|
title: "Status name for paying users",
|
||||||
|
description: "A paying/donating member or customer"
|
||||||
|
) %>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Admin panel</h3>
|
||||||
|
|
||||||
|
<ul role="list">
|
||||||
|
<%= render FormElements::FieldsetToggleComponent.new(
|
||||||
|
form: f,
|
||||||
|
attribute: :user_index_show_contributors,
|
||||||
|
enabled: Setting.user_index_show_contributors?,
|
||||||
|
title: "Show #{Setting.member_status_contributor.downcase} status in user list",
|
||||||
|
description: "Can slow down page rendering with large user base"
|
||||||
|
) %>
|
||||||
|
<%= render FormElements::FieldsetToggleComponent.new(
|
||||||
|
form: f,
|
||||||
|
attribute: :user_index_show_sustainers,
|
||||||
|
enabled: Setting.user_index_show_sustainers?,
|
||||||
|
title: "Show #{Setting.member_status_sustainer.downcase} status in user list",
|
||||||
|
description: "Can slow down page rendering with large user base"
|
||||||
|
) %>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<p class="pt-6 border-t border-gray-200 text-right">
|
||||||
|
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
@ -13,6 +13,20 @@
|
|||||||
title: 'Pending',
|
title: 'Pending',
|
||||||
value: @stats[:users_pending],
|
value: @stats[:users_pending],
|
||||||
) %>
|
) %>
|
||||||
|
<% if @show_contributors %>
|
||||||
|
<%= render QuickstatsItemComponent.new(
|
||||||
|
type: :number,
|
||||||
|
title: Setting.member_status_contributor.pluralize,
|
||||||
|
value: @stats[:users_contributing],
|
||||||
|
) %>
|
||||||
|
<% end %>
|
||||||
|
<% if @show_sustainers %>
|
||||||
|
<%= render QuickstatsItemComponent.new(
|
||||||
|
type: :number,
|
||||||
|
title: Setting.member_status_sustainer.pluralize,
|
||||||
|
value: @stats[:users_paying],
|
||||||
|
) %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -29,8 +43,12 @@
|
|||||||
<% @users.each do |user| %>
|
<% @users.each do |user| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= link_to(user.cn, admin_user_path(user.cn), class: 'ks-text-link') %></td>
|
<td><%= link_to(user.cn, admin_user_path(user.cn), class: 'ks-text-link') %></td>
|
||||||
<td><%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %></td>
|
<td>
|
||||||
<td><%= user.is_admin? ? badge("admin", :red) : "" %></td>
|
<%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %>
|
||||||
|
<% if @show_contributors %><%= @contributors.include?(user.cn) ? badge("contributor", :green) : "" %><% end %>
|
||||||
|
<% if @show_sustainers %><%= @sustainers.include?(user.cn) ? badge("sustainer", :green) : "" %><% end %>
|
||||||
|
</td>
|
||||||
|
<td><%= @admins.include?(user.cn) ? badge("admin", :red) : "" %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -32,6 +32,30 @@
|
|||||||
<th>Roles</th>
|
<th>Roles</th>
|
||||||
<td><%= @user.is_admin? ? badge("admin", :red) : "—" %></td>
|
<td><%= @user.is_admin? ? badge("admin", :red) : "—" %></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td>
|
||||||
|
<% if @user.is_contributing_member? || @user.is_paying_member? %>
|
||||||
|
<%= @user.is_contributing_member? ? badge("contributor", :green) : "" %>
|
||||||
|
<%= @user.is_paying_member? ? badge("sustainer", :green) : "" %>
|
||||||
|
<% else %>
|
||||||
|
—
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Donations</th>
|
||||||
|
<td>
|
||||||
|
<% if @user.donations.any? %>
|
||||||
|
<%= link_to admin_donations_path(username: @user.cn), class: "ks-text-link" do %>
|
||||||
|
<%= @user.donations.completed.count %> for
|
||||||
|
<%= number_with_delimiter @user.donations.completed.sum("amount_sats") %> sats
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
—
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Invited by</th>
|
<th>Invited by</th>
|
||||||
<td>
|
<td>
|
||||||
@ -75,10 +99,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="align-top">Invited users</th>
|
<th class="align-top">Invited users</th>
|
||||||
<td class="align-top">
|
<td class="align-top">
|
||||||
<% if @user.invitees.length > 0 %>
|
<% if @invitees.any? %>
|
||||||
<ul class="mb-0">
|
<ul class="mb-0">
|
||||||
<% @user.invitees.order(cn: :asc).each do |invitee| %>
|
<% @recent_invitees.each do |invitee| %>
|
||||||
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.cn, admin_user_path(invitee.cn), class: 'ks-text-link' %></li>
|
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.cn, admin_user_path(invitee.cn), class: "ks-text-link" %></li>
|
||||||
|
<% end %>
|
||||||
|
<% if @more_invitees > 0 %>
|
||||||
|
<li>and <%= link_to "#{@more_invitees} more", admin_invitations_path(username: @user.cn), class: "ks-text-link" %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% else %>—<% end %>
|
<% else %>—<% end %>
|
||||||
@ -267,7 +294,9 @@
|
|||||||
) %>
|
) %>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
|
<% if @user.nostr_pubkey.present? %>
|
||||||
<%= link_to "Open profile", "https://njump.me/#{@user.nostr_pubkey_bech32}", class: "btn-sm btn-gray" %>
|
<%= link_to "Open profile", "https://njump.me/#{@user.nostr_pubkey_bech32}", class: "btn-sm btn-gray" %>
|
||||||
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -22,17 +22,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<% if @donations_pending.any? %>
|
<% if @donations_processing.any? %>
|
||||||
<section class="donation-list">
|
<section class="donation-list">
|
||||||
<h2>Pending</h2>
|
<h2>Pending</h2>
|
||||||
<%= render partial: "contributions/donations/list",
|
<%= render partial: "contributions/donations/list",
|
||||||
locals: { donations: @donations_pending } %>
|
locals: { donations: @donations_processing } %>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @donations_completed.any? %>
|
<% if @donations_completed.any? %>
|
||||||
<section class="donation-list">
|
<section class="donation-list">
|
||||||
<h2>Past contributions</h2>
|
<h2>Contributions</h2>
|
||||||
<%= render partial: "contributions/donations/list",
|
<%= render partial: "contributions/donations/list",
|
||||||
locals: { donations: @donations_completed } %>
|
locals: { donations: @donations_completed } %>
|
||||||
</section>
|
</section>
|
||||||
|
@ -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-filter"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-filter <%= custom_class %>"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon></svg>
|
||||||
|
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 311 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-server"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-server <%= custom_class %>"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg>
|
||||||
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 452 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></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-users <%= custom_class %>"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 421 B |
@ -3,7 +3,11 @@
|
|||||||
active: current_page?(admin_settings_registrations_path)
|
active: current_page?(admin_settings_registrations_path)
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
name: "Services", path: admin_settings_services_path, icon: "grid",
|
name: "Membership", path: admin_settings_membership_path, icon: "users",
|
||||||
|
active: current_page?(admin_settings_membership_path)
|
||||||
|
) %>
|
||||||
|
<%= render SidenavLinkComponent.new(
|
||||||
|
name: "Services", path: admin_settings_services_path, icon: "server",
|
||||||
active: controller_name == "services"
|
active: controller_name == "services"
|
||||||
) %>
|
) %>
|
||||||
<% if controller_name == "services" %>
|
<% if controller_name == "services" %>
|
||||||
|
@ -109,6 +109,7 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
namespace :settings do
|
namespace :settings do
|
||||||
resource 'registrations', only: ['show', 'update']
|
resource 'registrations', only: ['show', 'update']
|
||||||
|
resource 'membership', only: ['show', 'update'], controller: 'membership'
|
||||||
resources 'services', param: 'service', only: ['index', 'show', 'update']
|
resources 'services', param: 'service', only: ['index', 'show', 'update']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
class UpdatePaymentStatusToPending < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
Donation.where(payment_status: nil).update_all(payment_status: "pending")
|
||||||
|
Donation.where.not(payment_status: %w[pending processing settled]).update_all(payment_status: "pending")
|
||||||
|
end
|
||||||
|
end
|
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_05_17_105755) do
|
ActiveRecord::Schema[8.0].define(version: 2025_05_27_113805) do
|
||||||
create_table "active_storage_attachments", force: :cascade do |t|
|
create_table "active_storage_attachments", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
|
@ -21,7 +21,7 @@ namespace :ldap do
|
|||||||
|
|
||||||
desc "Add custom attributes to schema"
|
desc "Add custom attributes to schema"
|
||||||
task add_custom_attributes: :environment do |t, args|
|
task add_custom_attributes: :environment do |t, args|
|
||||||
%w[ admin service_enabled nostr_key pgp_key ].each do |name|
|
%w[ admin service_enabled member_status nostr_key pgp_key ].each do |name|
|
||||||
Rake::Task["ldap:modify_ldap_schema"].invoke(name, "add")
|
Rake::Task["ldap:modify_ldap_schema"].invoke(name, "add")
|
||||||
Rake::Task['ldap:modify_ldap_schema'].reenable
|
Rake::Task['ldap:modify_ldap_schema'].reenable
|
||||||
end
|
end
|
||||||
@ -29,7 +29,7 @@ namespace :ldap do
|
|||||||
|
|
||||||
desc "Delete custom attributes from schema"
|
desc "Delete custom attributes from schema"
|
||||||
task delete_custom_attributes: :environment do |t, args|
|
task delete_custom_attributes: :environment do |t, args|
|
||||||
%w[ admin service_enabled nostr_key pgp_key ].each do |name|
|
%w[ admin service_enabled member_status nostr_key pgp_key ].each do |name|
|
||||||
Rake::Task["ldap:modify_ldap_schema"].invoke(name, "delete")
|
Rake::Task["ldap:modify_ldap_schema"].invoke(name, "delete")
|
||||||
Rake::Task['ldap:modify_ldap_schema'].reenable
|
Rake::Task['ldap:modify_ldap_schema'].reenable
|
||||||
end
|
end
|
||||||
|
8
schemas/ldap/member_status.ldif
Normal file
8
schemas/ldap/member_status.ldif
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
dn: cn=schema
|
||||||
|
changetype: modify
|
||||||
|
add: attributeTypes
|
||||||
|
attributeTypes: ( 1.3.6.1.4.1.61554.1.1.2.1.3
|
||||||
|
NAME 'memberStatus'
|
||||||
|
DESC 'Current member/contributor status'
|
||||||
|
EQUALITY caseExactMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
@ -8,16 +8,18 @@ RSpec.describe BtcpayCheckDonationJob, type: :job do
|
|||||||
user.donations.create!(
|
user.donations.create!(
|
||||||
donation_method: "btcpay",
|
donation_method: "btcpay",
|
||||||
btcpay_invoice_id: "K4e31MhbLKmr3D7qoNYRd3",
|
btcpay_invoice_id: "K4e31MhbLKmr3D7qoNYRd3",
|
||||||
paid_at: nil, payment_status: "processing",
|
paid_at: nil,
|
||||||
fiat_amount: 120, fiat_currency: "USD"
|
payment_status: "processing",
|
||||||
|
fiat_amount: 120,
|
||||||
|
fiat_currency: "USD"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
||||||
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
uid: user.cn, ou: user.ou, mail: user.email, admin: nil, display_name: nil
|
||||||
display_name: nil
|
|
||||||
})
|
})
|
||||||
|
allow_any_instance_of(User).to receive(:add_member_status)
|
||||||
end
|
end
|
||||||
|
|
||||||
after(:each) do
|
after(:each) do
|
||||||
@ -65,15 +67,20 @@ RSpec.describe BtcpayCheckDonationJob, type: :job do
|
|||||||
it "notifies the user via email" do
|
it "notifies the user via email" do
|
||||||
perform_enqueued_jobs(only: described_class) { job }
|
perform_enqueued_jobs(only: described_class) { job }
|
||||||
expect(enqueued_jobs.size).to eq(1)
|
expect(enqueued_jobs.size).to eq(1)
|
||||||
job = enqueued_jobs.select{|j| j['job_class'] == "ActionMailer::MailDeliveryJob"}.first
|
job = enqueued_jobs.select { |j| j['job_class'] == "ActionMailer::MailDeliveryJob" }.first
|
||||||
expect(job['arguments'][0]).to eq('NotificationMailer')
|
expect(job['arguments'][0]).to eq('NotificationMailer')
|
||||||
expect(job['arguments'][1]).to eq('bitcoin_donation_confirmed')
|
expect(job['arguments'][1]).to eq('bitcoin_donation_confirmed')
|
||||||
expect(job['arguments'][3]['params']['user']['_aj_globalid']).to eq('gid://akkounts/User/1')
|
expect(job['arguments'][3]['params']['user']['_aj_globalid']).to eq(user.to_global_id.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not enqueue itself again" do
|
it "does not enqueue itself again" do
|
||||||
expect_any_instance_of(described_class).not_to receive(:re_enqueue_job)
|
expect_any_instance_of(described_class).not_to receive(:re_enqueue_job)
|
||||||
perform_enqueued_jobs(only: described_class) { job }
|
perform_enqueued_jobs(only: described_class) { job }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "updates the user's member status" do
|
||||||
|
expect_any_instance_of(User).to receive(:add_member_status).with(:sustainer)
|
||||||
|
perform_enqueued_jobs(only: described_class) { job }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -154,7 +154,7 @@ RSpec.describe User, type: :model do
|
|||||||
|
|
||||||
it "removes all services from the LDAP entry" do
|
it "removes all services from the LDAP entry" do
|
||||||
expect_any_instance_of(LdapService).to receive(:delete_attribute)
|
expect_any_instance_of(LdapService).to receive(:delete_attribute)
|
||||||
.with(dn, :service).and_return(true)
|
.with(dn, :serviceEnabled).and_return(true)
|
||||||
|
|
||||||
user.disable_all_services
|
user.disable_all_services
|
||||||
end
|
end
|
||||||
|
@ -177,7 +177,7 @@ RSpec.describe "Donations", type: :request do
|
|||||||
.to_return(status: 200, headers: {}, body: invoice)
|
.to_return(status: 200, headers: {}, body: invoice)
|
||||||
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/MCkDbf2cUgBuuisUCgnRnb/payment-methods")
|
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/MCkDbf2cUgBuuisUCgnRnb/payment-methods")
|
||||||
.to_return(status: 200, headers: {}, body: payments)
|
.to_return(status: 200, headers: {}, body: payments)
|
||||||
|
allow(user).to receive(:add_member_status).with(:sustainer).and_return(["sustainer"])
|
||||||
get confirm_btcpay_contributions_donation_path(subject)
|
get confirm_btcpay_contributions_donation_path(subject)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -185,11 +185,16 @@ RSpec.describe "Donations", type: :request do
|
|||||||
subject.reload
|
subject.reload
|
||||||
expect(subject.paid_at).not_to be_nil
|
expect(subject.paid_at).not_to be_nil
|
||||||
expect(subject.amount_sats).to eq(2061)
|
expect(subject.amount_sats).to eq(2061)
|
||||||
|
expect(subject.payment_status).to eq("settled")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "redirects to the donations index" do
|
it "redirects to the donations index" do
|
||||||
expect(response).to redirect_to(contributions_donations_url)
|
expect(response).to redirect_to(contributions_donations_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "updates the user's member status" do
|
||||||
|
expect(user).to have_received(:add_member_status).with(:sustainer)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "amount in sats" do
|
describe "amount in sats" do
|
||||||
|
Loading…
x
Reference in New Issue
Block a user