Implement ghostify (#2773)
This commit is contained in:
parent
7dd811a578
commit
486275cbbb
7 changed files with 113 additions and 24 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
58
app/services/moderator/delete_user.rb
Normal file
58
app/services/moderator/delete_user.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue