diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 4030650..acbbd3f 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -2,7 +2,11 @@ class RegistrationsController < Devise::RegistrationsController
before_action :authenticate_user!, only: [:edit, :update, :destroy]
def destroy
- # TODO: Disallow/disable deletion for last admin account; update :edit view
+ if current_user.sole_admin?
+ redirect_back fallback_location: edit_user_registration_path,
+ alert: t(".sole_admin")
+ return
+ end
super
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 0de9460..0d478da 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -29,4 +29,11 @@ class User < ApplicationRecord
def at_least(status)
User.statuses[self.status] >= User.statuses[status]
end
+
+ # Returns true when this user is the only admin account in the system.
+ # Used to block actions that would leave the application without an admin
+ # (account deletion, status demotion).
+ def sole_admin?
+ admin? && !User.admin.where.not(id: id).exists?
+ end
end
diff --git a/app/views/users/registrations/edit.html.erb b/app/views/users/registrations/edit.html.erb
index 9e1907a..5861074 100644
--- a/app/views/users/registrations/edit.html.erb
+++ b/app/views/users/registrations/edit.html.erb
@@ -4,9 +4,8 @@
<% end %>
- <%#= TODO: Disallow/disable deletion for last admin account, image_button_to_if %>
- <%= image_button_to t('.delete'), 'account-remove-outline', user_registration_path,
- form_class: 'tools-area', method: :delete, data: {turbo: false},
+ <%= image_button_to_if !current_user.sole_admin?, t('.delete'), 'account-remove-outline',
+ user_registration_path, form_class: 'tools-area', method: :delete, data: {turbo: false},
onclick: {confirm: t('.confirm_delete')} %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 82ad2a3..8162d04 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -162,6 +162,9 @@ en:
New password:
leave blank to keep unchanged
%{password_length_hint_html}
+ registrations:
+ destroy:
+ sole_admin: You cannot delete the only admin account.
actions: Actions
setup:
new:
diff --git a/test/controllers/registrations_controller_test.rb b/test/controllers/registrations_controller_test.rb
new file mode 100644
index 0000000..edabb9d
--- /dev/null
+++ b/test/controllers/registrations_controller_test.rb
@@ -0,0 +1,18 @@
+require "test_helper"
+
+class RegistrationsControllerTest < ActionDispatch::IntegrationTest
+ test "sole admin cannot delete account" do
+ sign_in users(:admin)
+ delete user_registration_path
+ assert_redirected_to edit_user_registration_path
+ assert_equal t("registrations.destroy.sole_admin"), flash[:alert]
+ assert User.exists?(users(:admin).id)
+ end
+
+ test "non-admin can delete account" do
+ sign_in users(:alice)
+ assert_difference ->{ User.count }, -1 do
+ delete user_registration_path
+ end
+ end
+end
diff --git a/test/system/users_test.rb b/test/system/users_test.rb
index ad362cd..3628a66 100644
--- a/test/system/users_test.rb
+++ b/test/system/users_test.rb
@@ -149,7 +149,7 @@ class UsersTest < ApplicationSystemTestCase
end
test "delete profile" do
- user = sign_in
+ user = sign_in user: users.reject(&:admin?).select(&:confirmed?).sample
# TODO: remove condition after root_url changed to different path than
# profile in routes.rb
unless has_current_path?(edit_user_registration_path)
@@ -162,6 +162,14 @@ class UsersTest < ApplicationSystemTestCase
assert_text t("devise.registrations.destroyed")
end
+ test "sole admin cannot delete profile" do
+ sign_in user: users(:admin)
+ unless has_current_path?(edit_user_registration_path)
+ first(:link_or_button, users(:admin).email).click
+ end
+ assert find(:button, t("users.registrations.edit.delete"))[:disabled]
+ end
+
test "index forbidden for non admin" do
sign_in user: users.reject(&:admin?).select(&:confirmed?).sample
visit users_path