mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 02:51:10 +08:00
DAU/MAU reports previously calculated every requested date from the full user visit history, so dashboard requests became slower as that history grew. Persist the daily DAU and trailing-30-day MAU values and have the shared report read only those rollups. The scheduled job builds all existing history on its first successful run, then refreshes yesterday and today every three hours. Replacements are atomic, so a failed calculation leaves the previous report data intact.
179 lines
6.1 KiB
Ruby
Vendored
179 lines
6.1 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
require "highline"
|
|
require "io/console"
|
|
|
|
class DestroyTask
|
|
def initialize(io = STDOUT)
|
|
@io = io
|
|
@tty = IO.console
|
|
@hl = HighLine.new(@tty, @io)
|
|
end
|
|
|
|
def destroy_topics(category, parent_category = nil, delete_system_topics = false)
|
|
c = Category.find_by_slug(category, parent_category)
|
|
descriptive_slug = parent_category ? "#{parent_category}/#{category}" : category
|
|
return @io.puts "A category with the slug: #{descriptive_slug} could not be found" if c.nil?
|
|
if delete_system_topics
|
|
topics = Topic.where(category_id: c.id, pinned_at: nil)
|
|
else
|
|
topics = Topic.where(category_id: c.id, pinned_at: nil).where.not(user_id: -1)
|
|
end
|
|
@io.puts "There are #{topics.count} topics to delete in #{descriptive_slug} category"
|
|
topics.find_each do |topic|
|
|
@io.puts "Deleting #{topic.slug}..."
|
|
first_post = topic.ordered_posts.first
|
|
return @io.puts "Topic.ordered_posts.first was nil" if first_post.nil?
|
|
@io.puts PostDestroyer.new(
|
|
Discourse.system_user,
|
|
first_post,
|
|
context: I18n.t("staff_action_logs.cli_bulk_post_delete"),
|
|
).destroy
|
|
end
|
|
end
|
|
|
|
def destroy_topics_in_category(category_id, delete_system_topics = false)
|
|
c = Category.find(category_id)
|
|
return @io.puts "A category with the id: #{category_id} could not be found" if c.nil?
|
|
if delete_system_topics
|
|
topics = Topic.where(category_id: c.id, pinned_at: nil)
|
|
else
|
|
topics = Topic.where(category_id: c.id, pinned_at: nil).where.not(user_id: -1)
|
|
end
|
|
@io.puts "There are #{topics.count} topics to delete in #{c.slug} category"
|
|
topics.find_each do |topic|
|
|
first_post = topic.ordered_posts.first
|
|
return @io.puts "Topic.ordered_posts.first was nil for topic: #{topic.id}" if first_post.nil?
|
|
PostDestroyer.new(
|
|
Discourse.system_user,
|
|
first_post,
|
|
context: I18n.t("staff_action_logs.cli_bulk_post_delete"),
|
|
).destroy
|
|
end
|
|
topics = Topic.where(category_id: c.id, pinned_at: nil)
|
|
@io.puts "There are #{topics.count} topics that could not be deleted in #{c.slug} category"
|
|
end
|
|
|
|
def destroy_topics_all_categories
|
|
categories = Category.all
|
|
categories.each { |c| @io.puts destroy_topics(c.slug, c.parent_category&.slug) }
|
|
end
|
|
|
|
def destroy_posts(post_ids, require_confirmation: true)
|
|
if !SiteSetting.can_permanently_delete
|
|
@io.puts "The can_permanently_delete site setting needs to be enabled to destroy posts."
|
|
return
|
|
end
|
|
|
|
posts = Post.with_deleted.where(id: post_ids)
|
|
|
|
@io.puts(<<~NOTICE)
|
|
There are #{posts.count} posts to delete.
|
|
|
|
Note: If a post is the first post in a topic, all posts
|
|
in that topic will be deleted.
|
|
NOTICE
|
|
|
|
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 = @hl.ask("Are you sure? (Y/n)")
|
|
exit 1 if confirm_destroy.downcase != "y"
|
|
end
|
|
|
|
# Do first posts before the rest. The PostDestroyer will destroy
|
|
# all posts following a first post automatically, and we want to
|
|
# avoid duplicate audit trail entries by trying to delete them
|
|
# again. Note: Rails batching will order by pkey (post ID in this
|
|
# case) internally, and can not accept additional ordering options.
|
|
[posts.where(post_number: 1), posts].each do |scope|
|
|
scope.find_each(order: :desc) 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
|
|
end
|
|
|
|
def destroy_private_messages
|
|
Topic
|
|
.where(archetype: "private_message")
|
|
.find_each do |pm|
|
|
@io.puts "Destroying #{pm.slug} pm"
|
|
first_post = pm.ordered_posts.first
|
|
@io.puts PostDestroyer.new(
|
|
Discourse.system_user,
|
|
first_post,
|
|
context: I18n.t("staff_action_logs.cli_bulk_post_delete"),
|
|
).destroy
|
|
end
|
|
end
|
|
|
|
def destroy_category(category_id, destroy_system_topics = false)
|
|
c = Category.find_by_id(category_id)
|
|
return @io.puts "A category with the id: #{category_id} could not be found" if c.nil?
|
|
subcategories = Category.where(parent_category_id: c.id)
|
|
@io.puts "There are #{subcategories.count} subcategories to delete" if subcategories
|
|
subcategories.each { |s| category_topic_destroyer(s, destroy_system_topics) }
|
|
category_topic_destroyer(c, destroy_system_topics)
|
|
end
|
|
|
|
def destroy_groups
|
|
groups = Group.where(automatic: false)
|
|
groups.each do |group|
|
|
@io.puts "destroying group: #{group.id}"
|
|
@io.puts group.destroy
|
|
end
|
|
end
|
|
|
|
def destroy_users
|
|
User
|
|
.human_users
|
|
.where(admin: false)
|
|
.find_each do |user|
|
|
if UserDestroyer.new(Discourse.system_user).destroy(
|
|
user,
|
|
delete_posts: true,
|
|
context: "destroy task",
|
|
)
|
|
@io.puts "#{user.username} deleted"
|
|
else
|
|
@io.puts "#{user.username} not deleted"
|
|
end
|
|
rescue UserDestroyer::PostsExistError
|
|
raise Discourse::InvalidAccess.new(
|
|
"User #{user.username} has #{user.post_count} posts, so can't be deleted.",
|
|
)
|
|
rescue NoMethodError
|
|
@io.puts "#{user.username} could not be deleted"
|
|
rescue Discourse::InvalidAccess => e
|
|
@io.puts "#{user.username} #{e.message}"
|
|
end
|
|
end
|
|
|
|
def destroy_stats
|
|
ApplicationRequest.delete_all
|
|
IncomingLink.delete_all
|
|
UserVisit.delete_all
|
|
UserVisitDailyRollup.delete_all
|
|
UserProfileView.delete_all
|
|
UserProfile.update_all(views: 0)
|
|
PostAction.unscoped.delete_all
|
|
EmailLog.delete_all
|
|
end
|
|
|
|
private
|
|
|
|
def category_topic_destroyer(category, destroy_system_topics = false)
|
|
destroy_topics_in_category(category.id, destroy_system_topics)
|
|
@io.puts "Destroying #{category.slug} category"
|
|
category.destroy
|
|
end
|
|
end
|