DEV: Add rake task to bulk delete posts (#31642)

Same as #31576, but in that one I added some support files that turned out to be unnecessary and broke the build. So this is the re-do. 🙏
This commit is contained in:
Ted Johansson
2025-03-05 09:31:23 +08:00
committed by GitHub
parent 503645dc34
commit 828fcb1ad3
5 changed files with 82 additions and 0 deletions

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require "highline/import"
class DestroyTask
def initialize(io = STDOUT)
@io = io
@ -46,6 +48,32 @@ class DestroyTask
categories.each { |c| @io.puts destroy_topics(c.slug, c.parent_category&.slug) }
end
def destroy_posts(post_ids, require_confirmation: true)
posts = Post.with_deleted.where(id: post_ids)
@io.puts "There are #{posts.count} posts to delete"
if posts.count < post_ids.size
@io.puts "Couldn't find the following posts:"
@io.puts " #{post_ids.map(&:to_i) - posts.pluck(:id)}"
end
if require_confirmation
confirm_destroy = ask("Are you sure? (Y/n)")
exit 1 if confirm_destroy.downcase != "y"
end
posts.find_each do |post|
@io.puts "Destroying post #{post.id}"
@io.puts PostDestroyer.new(
Discourse.system_user,
post,
context: I18n.t("staff_action_logs.cli_bulk_post_delete"),
force_destroy: true,
).destroy
end
end
def destroy_private_messages
Topic
.where(archetype: "private_message")

View File

@ -5560,6 +5560,7 @@ en:
revoked: Revoked
restored: Restored
bulk_user_delete: "deleted in a bulk delete operation"
cli_bulk_post_delete: "Bulk deleted from rake task"
reviewables:
already_handled: "Thanks, but we've already reviewed that post and determined it does not need to be flagged again."

View File

@ -50,3 +50,16 @@ task "destroy:categories" => :environment do |t, args|
puts "Going to delete these categories: #{categories}"
categories.each { |id| destroy_task.destroy_category(id, true) }
end
# Hard delete a list of posts by ID. Pass a comma
# separated list either as a rake argument or through
# STDIN, e.g. through a pipe.
#
# Example: rake destroy:posts[4,8,15,16,23,42]
# Example: cat post_ids.txt | rake destroy:posts
desc "Destroy a list of posts given by ID"
task "destroy:posts" => :environment do |_task, args|
post_ids = args.extras.empty? ? STDIN.read.strip.split(",") : args.extras
puts "Going to delete these posts: #{post_ids}"
DestroyTask.new.destroy_posts(post_ids)
end

View File

@ -67,6 +67,30 @@ RSpec.describe DestroyTask do
end
end
describe "#destroy_posts" do
let(:task) { DestroyTask.new(StringIO.new) }
let!(:t1) { Fabricate(:topic) }
let!(:t2) { Fabricate(:topic) }
let!(:p1) { Fabricate(:post, topic: t1) }
let!(:p2) { Fabricate(:post, topic: t1) }
let!(:p3) { Fabricate(:post, topic: t2) }
before { p2.trash! }
it "destroys posts listed and creates staff action logs" do
expect { task.destroy_posts([p2.id, p3.id], require_confirmation: false) }.to change {
Post.with_deleted.count
}.by(-2).and change { UserHistory.pluck(:action) }.from([]).to(
[
UserHistory.actions[:delete_post_permanently],
UserHistory.actions[:delete_topic_permanently],
],
)
end
end
describe "private messages" do
let!(:pm) { Fabricate(:private_message_post) }
let!(:pm2) { Fabricate(:private_message_post) }

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
describe "destroy:posts" do
it "accepts a list of post IDs piped through STDIN" do
destroy_task = instance_spy(DestroyTask)
DestroyTask.stubs(:new).returns(destroy_task)
STDIN.stubs(:read).returns("1,2,3\n")
capture_stdout do
invoke_rake_task("destroy:posts")
expect(destroy_task).to have_received(:destroy_posts).with(%w[1 2 3])
end
end
end