Skip to content

Commit

Permalink
soft delete users instead of destroying
Browse files Browse the repository at this point in the history
  • Loading branch information
mwvolo committed Oct 9, 2024
1 parent a44a05c commit a2aafd3
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 13 deletions.
14 changes: 8 additions & 6 deletions app/controllers/admin/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Admin
class UsersController < Admin::BaseController
layout 'admin', except: :js_search

before_action :get_user, only: [:edit, :update, :destroy, :become]
before_action :get_user, only: [:edit, :update, :become, :soft_delete]

# Used by dialog
def js_search
Expand Down Expand Up @@ -42,12 +42,14 @@ def update
end
end

def destroy
salesforce_ids = [@user.salesforce_contact_id, @user.salesforce_lead_id].collect(&:to_s)
security_log :user_deleted_by_admin, user_id: params[:id], uuid: @user.uuid, salesforce_ids: salesforce_ids
@user.destroy
def soft_delete
result = SoftDeleteUser.call(@user)

security_log :user_deleted_by_admin, user: @user, admin_id: @current_user.id

# redirect_to admin_users_path
flash[:alert] = "Authentications and PII removed from account."
redirect_to admin_users_path
flash[:alert] = "User account has been deleted"
end

def become
Expand Down
2 changes: 1 addition & 1 deletion app/models/application_user.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class ApplicationUser < ApplicationRecord
belongs_to :application, class_name: 'Doorkeeper::Application',
inverse_of: :application_users
belongs_to :user, inverse_of: :application_users
belongs_to :user, inverse_of: :application_users, optional: true

belongs_to :default_contact_info, class_name: 'ContactInfo'

Expand Down
6 changes: 5 additions & 1 deletion app/models/authentication.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
include UserSessionManagement

class Authentication < ApplicationRecord
belongs_to :user, inverse_of: :authentications
belongs_to :user, inverse_of: :authentications, optional: true

validates :provider, presence: true,
uniqueness: { scope: :user_id },
Expand All @@ -20,6 +22,8 @@ def display_name
protected

def check_not_last
return if user.is_deleted?

if user.present? && user.authentications.size <= 1 && user.activated?
throw(:abort)
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/contact_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def strip
end

def check_if_last_verified
if verified? and not user.contact_infos.verified.many? and not destroyed_by_association
if verified? and not user.contact_infos.verified.many? and not destroyed_by_association and not user.is_deleted?
errors.add(:user, :last_verified)
throw(:abort)
end
Expand Down
33 changes: 33 additions & 0 deletions app/routines/soft_delete_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class SoftDeleteUser

lev_routine

protected

def exec(user)
return if user.nil?

# Make sure object up to date, esp before dependent destroy stuff kicks in
user.reload

user.is_deleted = true
user.save!

user.external_ids.destroy_all
user.external_uuids.destroy_all
user.authentications.destroy_all
user.application_users.destroy_all
user.contact_infos.destroy_all
user.save!

user.reload
user.first_name = 'Deleted'
user.last_name = 'User'
user.save!

# security logs are read-only, but they contain PII so we force delete them for the user
user.security_logs.delete_all

end

end
10 changes: 8 additions & 2 deletions app/views/admin/users/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@
<%= form_for [:admin, @user], html: {id: 'admin-user-form', class: 'form-horizontal'} do |f| %>
<%= render "shared/error_messages", :target => @user %>

<% if @user.is_deleted? %>
<div class="alert alert-danger" role="alert">
This is a deleted user account. All authentications, external ids, and PII have been removed.
</div>
<% end %>

<div class="form-group">
<%= f.label :name, class: "col-sm-2 control-label" %>
<div class="col-sm-10">
Expand Down Expand Up @@ -401,8 +407,8 @@

<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<%= link_to('Destroy User Account',
admin_user_path(@user),
<%= link_to('De-identify User Account',
soft_delete_admin_user_path(@user),
class: 'btn btn-danger pull-right',
style: 'margin-top: -50px; padding-top: -50px;',
data: {
Expand Down
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,14 @@
put 'cron', to: 'base#cron'
get 'raise_exception/:type', to: 'base#raise_exception', as: 'raise_exception'

resources :users, only: [:index, :update, :edit, :destroy] do
resources :users, only: [:index, :update, :edit] do
post 'become', on: :member
get 'search', on: :collection
get 'js_search', on: :collection
get 'actions', on: :collection
put 'mark_users_updated', on: :collection
post 'force_update_lead', on: :member
delete 'soft_delete', on: :member
end

resource :reports, only: [:show]
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20241009175157_add_delete_flag_to_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddDeleteFlagToUser < ActiveRecord::Migration[5.2]
def change
add_column :users, :is_deleted, :boolean
end
end
4 changes: 3 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2024_08_08_171751) do
ActiveRecord::Schema.define(version: 2024_10_09_175157) do

# These are extensions that must be enabled in order to support this database
enable_extension "citext"
Expand Down Expand Up @@ -361,6 +361,7 @@
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "country", default: "United States", null: false
t.boolean "has_assignable_contacts"
t.index ["name", "city", "state"], name: "index_schools_on_name_and_city_and_state", opclass: :gist_trgm_ops, using: :gist
t.index ["salesforce_id"], name: "index_schools_on_salesforce_id", unique: true
t.index ["sheerid_school_name"], name: "index_schools_on_sheerid_school_name"
Expand Down Expand Up @@ -472,6 +473,7 @@
t.jsonb "books_used_details"
t.string "adopter_status"
t.jsonb "consent_preferences"
t.boolean "is_deleted"
t.index "lower((first_name)::text)", name: "index_users_on_first_name"
t.index "lower((last_name)::text)", name: "index_users_on_last_name"
t.index "lower((username)::text)", name: "index_users_on_username_case_insensitive"
Expand Down

0 comments on commit a2aafd3

Please sign in to comment.