Implement ghostify (#2773)

This commit is contained in:
Jess Lee 2019-05-14 16:50:15 -04:00 committed by Mac Siri
parent 7dd811a578
commit 486275cbbb
7 changed files with 113 additions and 24 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -58,7 +58,7 @@
</p>
<p><strong>Do not do this lightly.</strong></p>
<%= 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 %>
<button class="btn btn-danger" style="font-size:2em;">Banish User for Spam</button>
<button class="btn btn-danger" style="font-size:2em;">🚫 Banish User for Spam 🚫</button>
<% end %>
<% elsif current_user.has_role?(:super_admin) %>
<p><strong>This is not a new user.</strong> You are only allowed to take this action because you are a
@ -67,7 +67,7 @@
<em>This is extremely destructive. Banishing will delete all the user's existing content and change their username to @spam_###.</em>
</p>
<p><strong>Do not do this lightly.</strong></p>
<%= 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 %>
<button class="btn btn-danger" style="font-size:2em;">Banish User for Spam!</button>
<% end %>
<% else %>
@ -75,6 +75,22 @@
<% end %>
</div>
<div class="row">
<h2>Delete User & Turn Content Into Ghost</h2>
<% if current_user.has_role?(:super_admin) %>
<p>This will
<strong>completely destroy the user</strong> and convert all of their articles & comments into a ghost author. This action is irreversible.
</p>
<p><strong>Do not do this lightly.</strong></p>
<%= 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 %>
<button class="btn btn-danger" style="font-size:2em;">👻 Fully Delete User & Ghostify 👻</button>
<% end %>
<% else %>
<p>Only super admins can fully delete users.</p>
<% end %>
</div>
<div class="row">
<h2>Fully Delete User</h2>
<% if current_user.has_role?(:super_admin) %>
@ -82,8 +98,9 @@
<strong>completely destroy the user</strong> and all of their activity from our database. This action is irreversible.
</p>
<p><strong>Do not do this lightly.</strong></p>
<%= 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 %>
<button class="btn btn-danger" style="font-size:2em;">Fully Delete User & All Activity</button>
<%= 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 %>
<button class="btn btn-danger" style="font-size:2em;">☠️ Fully Delete User & All Activity ☠️</button>
<% end %>
<% else %>
<p>Only super admins can fully delete users.</p>

View file

@ -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