diff --git a/app/views/icons/_plus-circle.html.erb b/app/views/icons/_plus-circle.html.erb
index 4291ff0..14ae0bf 100644
--- a/app/views/icons/_plus-circle.html.erb
+++ b/app/views/icons/_plus-circle.html.erb
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/app/views/icons/_x-circle.html.erb b/app/views/icons/_x-circle.html.erb
index 94aad5e..c1bea72 100644
--- a/app/views/icons/_x-circle.html.erb
+++ b/app/views/icons/_x-circle.html.erb
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/app/views/notification_mailer/new_invitations_available.text.erb b/app/views/notification_mailer/new_invitations_available.text.erb
new file mode 100644
index 0000000..9cf8adc
--- /dev/null
+++ b/app/views/notification_mailer/new_invitations_available.text.erb
@@ -0,0 +1,11 @@
+Hi <%= @user.display_name.presence || @user.cn %>,
+
+New invitations have just been added to your Kosmos account, so you can invite more people to our cooperative services:
+
+<%= invitations_url %>
+
+Have a nice day!
+
+---
+
+Tip: if you want to invite someone you're meeting in person, log into your account panel on a mobile device and let people scan the invitation QR code from theirs.
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index bb264bf..d59cdd9 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -325,3 +325,10 @@ Devise.setup do |config|
# changed. Defaults to true, so a user is signed in automatically after changing a password.
# config.sign_in_after_change_password = true
end
+
+# https://github.com/heartcombo/devise/issues/5644
+class Devise::SecretKeyFinder
+ def find
+ @application.secret_key_base
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index 643b095..39e69f2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -73,9 +73,19 @@ Rails.application.routes.draw do
namespace :admin do
root to: 'dashboard#index'
- resources 'users', param: 'address', only: ['index', 'show'], constraints: { address: /.*/ }
+ resources 'users', param: 'username', only: ['index', 'show'] do
+ member do
+ post 'invitations', to: 'users#create_invitations'
+ delete 'invitations', to: 'users#delete_invitations'
+ end
+ end
+
+ # post 'users/:username/invitations', to: 'users#create_invitations'
+
get 'invitations', to: 'invitations#index'
+
resources :donations
+
get 'lightning', to: 'lightning#index'
namespace :app_catalog do
diff --git a/db/seeds.rb b/db/seeds.rb
index dd13a45..fa957a6 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -3,10 +3,10 @@ require 'sidekiq/testing'
ldap = LdapService.new
Sidekiq::Testing.inline! do
- CreateAccount.call(
+ CreateAccount.call(account: {
username: "admin", domain: "kosmos.org", email: "admin@example.com",
password: "admin is admin", confirmed: true
- )
+ })
ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true"
@@ -15,9 +15,9 @@ Sidekiq::Testing.inline! do
email = Faker::Internet.unique.email
next if username.length < 3
- CreateAccount.call(
+ CreateAccount.call(account: {
username: username, domain: "kosmos.org", email: email,
password: "user is user", confirmed: true
- )
+ })
end
end
diff --git a/spec/features/admin/users_spec.rb b/spec/features/admin/users_spec.rb
new file mode 100644
index 0000000..c804823
--- /dev/null
+++ b/spec/features/admin/users_spec.rb
@@ -0,0 +1,55 @@
+require "rails_helper"
+
+RSpec.describe "Admin: User management", type: :feature do
+ let(:admin) { create :user }
+ let(:user) { create :user, id: 2, cn: "alfred", email: "alfred@example.com" }
+
+ before do
+ user.save!
+
+ allow(Devise::LDAP::Adapter).to receive(:get_ldap_param)
+ .with(admin.cn, :admin).and_return(["true"])
+ allow(Devise::LDAP::Adapter).to receive(:get_ldap_param)
+ .with(user.cn, :admin).and_return(nil)
+ allow_any_instance_of(User).to receive(:ldap_entry)
+ .and_return({ uid: user.cn, mail: user.email, display_name: "Freddy" })
+ allow_any_instance_of(LdapManager::FetchAvatar).to receive(:call)
+ .and_return(nil)
+
+ login_as admin, :scope => :user
+ end
+
+ describe "User details page" do
+ before do
+ visit admin_user_path("alfred")
+ end
+
+ it "shows the user info" do
+ within "h1" do
+ expect(page).to have_content("User: alfred")
+ end
+ expect(page).to have_content("alfred@example.com")
+ end
+ end
+
+ scenario 'Add invitations to account' do
+ visit admin_user_path("alfred")
+ find("#add-invitations").click
+
+ select "5", :from => "amount"
+ uncheck "notify_user"
+ click_button "Add"
+
+ expect(user.invitations.count).to eq(5)
+ end
+
+ scenario 'Remove invitations from account' do
+ 3.times { Invitation.create(user: user) }
+ expect(user.invitations.count).to eq(3)
+
+ visit admin_user_path("alfred")
+ find("#remove-invitations").click
+
+ expect(user.invitations.count).to eq(0)
+ end
+end
diff --git a/spec/services/create_invitations_spec.rb b/spec/services/create_invitations_spec.rb
new file mode 100644
index 0000000..4e5de23
--- /dev/null
+++ b/spec/services/create_invitations_spec.rb
@@ -0,0 +1,40 @@
+require 'rails_helper'
+
+RSpec.describe CreateInvitations, type: :model do
+ include ActiveJob::TestHelper
+
+ let(:user) { create :user }
+
+ describe "#call" do
+ before do
+ CreateInvitations.call(user: user, amount: 5)
+ end
+
+ after(:each) { clear_enqueued_jobs }
+
+ it "creates the right amount of invitations for the given user" do
+ expect(user.invitations.count).to eq(5)
+ end
+
+ it "sends an email notification to the user" do
+ expect(enqueued_jobs.size).to eq(1)
+ expect(enqueued_jobs.first["job_class"]).to eq("ActionMailer::MailDeliveryJob")
+ args = enqueued_jobs.first['arguments']
+ expect(args[0]).to eq("NotificationMailer")
+ expect(args[1]).to eq("new_invitations_available")
+ expect(args[3]["params"]["user"]["_aj_globalid"]).to eq("gid://akkounts/User/1")
+ end
+ end
+
+ describe "#call with notification disabled" do
+ before do
+ CreateInvitations.call(user: user, amount: 3, notify: false)
+ end
+
+ after(:each) { clear_enqueued_jobs }
+
+ it "does not send an email notification to the user" do
+ expect(enqueued_jobs.size).to eq(0)
+ end
+ end
+end