From 486275cbbb662b06f763ff55ebe372659155f91e Mon Sep 17 00:00:00 2001
From: Jess Lee
Date: Tue, 14 May 2019 16:50:15 -0400
Subject: [PATCH] Implement ghostify (#2773)
---
app/controllers/internal/users_controller.rb | 7 ++-
app/models/user.rb | 2 +-
app/services/moderator/banish_user.rb | 13 +----
app/services/moderator/delete_user.rb | 58 +++++++++++++++++++
.../moderator/manage_activity_and_roles.rb | 3 +-
app/views/internal/users/edit.html.erb | 25 ++++++--
spec/requests/internal/users_spec.rb | 29 +++++++++-
7 files changed, 113 insertions(+), 24 deletions(-)
create mode 100644 app/services/moderator/delete_user.rb
diff --git a/app/controllers/internal/users_controller.rb b/app/controllers/internal/users_controller.rb
index db04f3f77..737430ba4 100644
--- a/app/controllers/internal/users_controller.rb
+++ b/app/controllers/internal/users_controller.rb
@@ -125,8 +125,8 @@ class Internal::UsersController < Internal::ApplicationController
def full_delete
@user = User.find(params[:id])
begin
- Moderator::BanishUser.call_full_delete(admin: current_user, user: @user)
- flash[:notice] = "@" + @user.username + " (email: " + @user.email + ", user_id: " + @user.id.to_s + ") has been fully deleted. If this is a GDPR delete, remember to delete them from Mailchimp and Google Analytics."
+ Moderator::DeleteUser.call_deletion(admin: current_user, user: @user, user_params: user_params)
+ flash[:notice] = "@" + @user.username + " (email: " + @user.email + ", user_id: " + @user.id.to_s + ") has been fully deleted. If requested, old content may have been ghostified. If this is a GDPR delete, delete them from Mailchimp & Google Analytics."
rescue StandardError => e
flash[:error] = e.message
end
@@ -161,6 +161,7 @@ class Internal::UsersController < Internal::ApplicationController
:add_credits,
:remove_credits,
:add_org_credits,
- :remove_org_credits)
+ :remove_org_credits,
+ :ghostify)
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index b089b0048..068bf6139 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,7 +1,7 @@
class User < ApplicationRecord
include CloudinaryHelper
- attr_accessor :scholar_email, :new_note, :quick_match, :mentorship_note, :note_for_current_role, :add_mentor, :add_mentee, :user_status, :toggle_mentorship, :pro, :merge_user_id, :add_credits, :remove_credits, :add_org_credits, :remove_org_credits
+ attr_accessor :scholar_email, :new_note, :quick_match, :mentorship_note, :note_for_current_role, :add_mentor, :add_mentee, :user_status, :toggle_mentorship, :pro, :merge_user_id, :add_credits, :remove_credits, :add_org_credits, :remove_org_credits, :ghostify
rolify
include AlgoliaSearch
diff --git a/app/services/moderator/banish_user.rb b/app/services/moderator/banish_user.rb
index 8adac1bfc..8bd6aeb98 100644
--- a/app/services/moderator/banish_user.rb
+++ b/app/services/moderator/banish_user.rb
@@ -11,22 +11,13 @@ module Moderator
new(user: user, admin: admin).banish
end
- def self.call_full_delete(admin:, user:)
- new(user: user, admin: admin).full_delete
- end
-
- def full_delete
- user.unsubscribe_from_newsletters
- delete_user_activity
- CacheBuster.new.bust("/#{user.old_username}")
- user.delete
- end
-
def banish
user.unsubscribe_from_newsletters if user.email?
remove_profile_info
handle_user_status("Ban", "spam account")
delete_user_activity
+ delete_comments
+ delete_articles
user.remove_from_algolia_index
reassign_and_bust_username
end
diff --git a/app/services/moderator/delete_user.rb b/app/services/moderator/delete_user.rb
new file mode 100644
index 000000000..237e5fdd1
--- /dev/null
+++ b/app/services/moderator/delete_user.rb
@@ -0,0 +1,58 @@
+module Moderator
+ class DeleteUser < ManageActivityAndRoles
+ attr_reader :user, :admin, :user_params
+
+ def initialize(admin:, user:, user_params:)
+ @user = user
+ @admin = admin
+ @user_params = user_params
+ end
+
+ def self.call_deletion(admin:, user:, user_params:)
+ if user_params[:ghostify] == "true"
+ new(user: user, admin: admin, user_params: user_params).ghostify
+ else
+ new(user: user, admin: admin, user_params: user_params).full_delete
+ end
+ end
+
+ def ghostify
+ @ghost = User.find_by(username: "ghost")
+ reassign_articles
+ reassign_comments
+ delete_non_content_activity_and_user
+ end
+
+ def full_delete
+ delete_comments
+ delete_articles
+ delete_non_content_activity_and_user
+ end
+
+ private
+
+ def delete_non_content_activity_and_user
+ delete_user_activity
+ user.unsubscribe_from_newsletters
+ CacheBuster.new.bust("/#{user.username}")
+ user.delete
+ end
+
+ def reassign_comments
+ return unless user.comments.any?
+
+ user.comments.find_each do |comment|
+ comment.update(user_id: @ghost.id)
+ end
+ end
+
+ def reassign_articles
+ return unless user.articles.any?
+
+ user.articles.find_each do |article|
+ path = "/#{@ghost.username}/#{article.slug}"
+ article.update_columns(user_id: @ghost.id, path: path)
+ end
+ end
+ end
+end
diff --git a/app/services/moderator/manage_activity_and_roles.rb b/app/services/moderator/manage_activity_and_roles.rb
index ee54b51c4..2a7fd64d4 100644
--- a/app/services/moderator/manage_activity_and_roles.rb
+++ b/app/services/moderator/manage_activity_and_roles.rb
@@ -44,12 +44,11 @@ module Moderator
user.reactions.delete_all
user.follows.delete_all
Follow.where(followable_id: user.id, followable_type: "User").delete_all
+ user.messages.delete_all
user.chat_channel_memberships.delete_all
user.mentions.delete_all
user.badge_achievements.delete_all
user.github_repos.delete_all
- delete_comments
- delete_articles
end
def remove_privileges
diff --git a/app/views/internal/users/edit.html.erb b/app/views/internal/users/edit.html.erb
index e37f52e1d..80812b66e 100644
--- a/app/views/internal/users/edit.html.erb
+++ b/app/views/internal/users/edit.html.erb
@@ -58,7 +58,7 @@
Do not do this lightly.
<%= form_for(@user, url: banish_internal_user_path(@user), html: { method: :post, onsubmit: "return confirm('Are you sure? This is extremely destructive and irreversible. Banishing will delete all articles and turn their username into @spam_###')" }) do %>
-
+
<% end %>
<% elsif current_user.has_role?(:super_admin) %>
This is not a new user. You are only allowed to take this action because you are a
@@ -67,7 +67,7 @@
This is extremely destructive. Banishing will delete all the user's existing content and change their username to @spam_###.
Do not do this lightly.
- <%= form_for(@user, url: banish_internal_user_path(@user), html: { method: :post, onsubmit: "return confirm('Are you sure? This is extremely destructive and irreversible.Banishing will delete all articles and turn their username into @spam_###')" }) do %>
+ <%= form_for(@user, url: banish_internal_user_path(@user), html: { method: :post, onsubmit: "return confirm('Are you sure? This is extremely destructive and irreversible. Banishing will delete all articles and turn their username into @spam_###')" }) do %>
<% end %>
<% else %>
@@ -75,6 +75,22 @@
<% end %>
+
+
Delete User & Turn Content Into Ghost
+ <% if current_user.has_role?(:super_admin) %>
+
This will
+ completely destroy the user and convert all of their articles & comments into a ghost author. This action is irreversible.
+
+
Do not do this lightly.
+ <%= form_for(@user, url: full_delete_internal_user_path(@user), html: { method: :post, onsubmit: "return confirm('Are you sure? 👻 This is extremely destructive and irreversible. The user will be deleted and their articles & comments will be converted to Ghost')" }) do |f| %>
+ <%= f.hidden_field :ghostify, value: true %>
+
+ <% end %>
+ <% else %>
+
Only super admins can fully delete users.
+ <% end %>
+
+
Fully Delete User
<% if current_user.has_role?(:super_admin) %>
@@ -82,8 +98,9 @@
completely destroy the user and all of their activity from our database. This action is irreversible.
Do not do this lightly.
- <%= form_for(@user, url: full_delete_internal_user_path(@user), html: { method: :post, onsubmit: "return confirm('Are you sure? This is extremely destructive and irreversible.')" }) do %>
-
+ <%= form_for(@user, url: full_delete_internal_user_path(@user), html: { method: :post, onsubmit: "return confirm('Are you sure? This is extremely destructive and irreversible.')" }) do |f| %>
+ <%= f.hidden_field :ghostify, value: false %>
+
<% end %>
<% else %>
Only super admins can fully delete users.
diff --git a/spec/requests/internal/users_spec.rb b/spec/requests/internal/users_spec.rb
index f24bd9a8b..18d138bce 100644
--- a/spec/requests/internal/users_spec.rb
+++ b/spec/requests/internal/users_spec.rb
@@ -1,13 +1,14 @@
require "rails_helper"
RSpec.describe "Internal::Users", type: :request do
- let!(:user) { create(:user, twitter_username: nil) }
+ let!(:user) { create(:user, twitter_username: nil, old_username: "username") }
let!(:user2) { create(:user, twitter_username: "Twitter") }
let(:user3) { create(:user) }
let(:super_admin) { create(:user, :super_admin) }
let(:article) { create(:article, user: user) }
let(:article2) { create(:article, user: user2) }
let(:badge) { create(:badge, title: "one-year-club") }
+ let(:ghost) { create(:user, username: "ghost", github_username: "Ghost") }
before do
sign_in super_admin
@@ -61,6 +62,12 @@ RSpec.describe "Internal::Users", type: :request do
Delayed::Worker.new(quiet: true).work_off
end
+ def call_ghost
+ ghost
+ post "/internal/users/#{user.id}/full_delete", params: { user: { ghostify: "true" } }
+ Delayed::Worker.new(quiet: true).work_off
+ end
+
context "when merging users" do
before do
full_profile
@@ -138,6 +145,22 @@ RSpec.describe "Internal::Users", type: :request do
end
end
+ context "when deleting user and converting content to ghost" do
+ it "raises a 'record not found' error after deletion" do
+ call_ghost
+ expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound)
+ end
+
+ it "reassigns comment and article content to ghost account" do
+ create(:article, user: user)
+ call_ghost
+ expect(ghost.articles.count).to eq(2)
+ expect(ghost.comments.count).to eq(1)
+ expect(ghost.comments.last.path).to include("ghost")
+ expect(ghost.articles.last.path).to include("ghost")
+ end
+ end
+
context "when deleting user" do
def create_mention
comment = create(
@@ -163,12 +186,12 @@ RSpec.describe "Internal::Users", type: :request do
end
it "raises a 'record not found' error after deletion" do
- post "/internal/users/#{user.id}/full_delete"
+ post "/internal/users/#{user.id}/full_delete", params: { user: { ghostify: "false" } }
expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound)
end
it "expect flash message" do
- post "/internal/users/#{user.id}/full_delete"
+ post "/internal/users/#{user.id}/full_delete", params: { user: { ghostify: "false" } }
expect(request.flash.notice).to include("fully deleted")
end
end