about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2018-12-22 20:02:09 +0100
committerGitHub <noreply@github.com>2018-12-22 20:02:09 +0100
commit3c033c4352f8b156887cd7157b4a89c23a545838 (patch)
treefa6317223a0104abea84a10e6234a0beef316001 /app/models
parent00862dcaff7cb918d29947accda1c01873a7ddeb (diff)
Add moderation warnings (#9519)
* Add moderation warnings

Replace individual routes for disabling, silencing, and suspending
a user, as well as the report update route, with a unified account
action controller that allows you to select an action (none,
disable, silence, suspend) as well as whether it should generate an
e-mail notification with optional custom text. That notification,
with the optional custom text, is saved as a warning.

Additionally, there are warning presets you can configure to save
time when performing the above.

* Use Account#local_username_and_domain
Diffstat (limited to 'app/models')
-rw-r--r--app/models/account.rb8
-rw-r--r--app/models/account_warning.rb23
-rw-r--r--app/models/account_warning_preset.rb15
-rw-r--r--app/models/admin/account_action.rb134
-rw-r--r--app/models/concerns/account_associations.rb2
-rw-r--r--app/models/form/admin_suspension_confirmation.rb7
6 files changed, 182 insertions, 7 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index 5a7a9c580..16ef6c187 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -155,6 +155,14 @@ class Account < ApplicationRecord
     ResolveAccountService.new.call(acct)
   end
 
+  def silence!
+    update!(silenced: true)
+  end
+
+  def unsilence!
+    update!(silenced: false)
+  end
+
   def suspend!
     transaction do
       user&.disable! if local?
diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb
new file mode 100644
index 000000000..157e6c04d
--- /dev/null
+++ b/app/models/account_warning.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: account_warnings
+#
+#  id                :bigint(8)        not null, primary key
+#  account_id        :bigint(8)
+#  target_account_id :bigint(8)
+#  action            :integer          default("none"), not null
+#  text              :text             default(""), not null
+#  created_at        :datetime         not null
+#  updated_at        :datetime         not null
+#
+
+class AccountWarning < ApplicationRecord
+  enum action: %i(none disable silence suspend), _suffix: :action
+
+  belongs_to :account, inverse_of: :account_warnings
+  belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings
+
+  scope :latest, -> { order(created_at: :desc) }
+  scope :custom, -> { where.not(text: '') }
+end
diff --git a/app/models/account_warning_preset.rb b/app/models/account_warning_preset.rb
new file mode 100644
index 000000000..ba8ceabb3
--- /dev/null
+++ b/app/models/account_warning_preset.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: account_warning_presets
+#
+#  id         :bigint(8)        not null, primary key
+#  text       :text             default(""), not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
+class AccountWarningPreset < ApplicationRecord
+  validates :text, presence: true
+end
diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb
new file mode 100644
index 000000000..84c3f880d
--- /dev/null
+++ b/app/models/admin/account_action.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+class Admin::AccountAction
+  include ActiveModel::Model
+  include AccountableConcern
+  include Authorization
+
+  TYPES = %w(
+    none
+    disable
+    silence
+    suspend
+  ).freeze
+
+  attr_accessor :target_account,
+                :current_account,
+                :type,
+                :text,
+                :report_id,
+                :warning_preset_id,
+                :send_email_notification
+
+  attr_reader :warning
+
+  def save!
+    ApplicationRecord.transaction do
+      process_action!
+      process_warning!
+    end
+
+    queue_email!
+    process_reports!
+  end
+
+  def report
+    @report ||= Report.find(report_id) if report_id.present?
+  end
+
+  def with_report?
+    !report.nil?
+  end
+
+  class << self
+    def types_for_account(account)
+      if account.local?
+        TYPES
+      else
+        TYPES - %w(none disable)
+      end
+    end
+  end
+
+  private
+
+  def process_action!
+    case type
+    when 'disable'
+      handle_disable!
+    when 'silence'
+      handle_silence!
+    when 'suspend'
+      handle_suspend!
+    end
+  end
+
+  def process_warning!
+    return unless warnable?
+
+    authorize(target_account, :warn?)
+
+    @warning = AccountWarning.create!(target_account: target_account,
+                                      account: current_account,
+                                      action: type,
+                                      text: text_for_warning)
+
+    # A log entry is only interesting if the warning contains
+    # custom text from someone. Otherwise it's just noise.
+    log_action(:create, warning) if warning.text.present?
+  end
+
+  def process_reports!
+    return if report_id.blank?
+
+    authorize(report, :update?)
+
+    if type == 'none'
+      log_action(:resolve, report)
+      report.resolve!(current_account)
+    else
+      Report.where(target_account: target_account).unresolved.update_all(action_taken: true, action_taken_by_account_id: current_account.id)
+    end
+  end
+
+  def handle_disable!
+    authorize(target_account.user, :disable?)
+    log_action(:disable, target_account.user)
+    target_account.user&.disable!
+  end
+
+  def handle_silence!
+    authorize(target_account, :silence?)
+    log_action(:silence, target_account)
+    target_account.silence!
+  end
+
+  def handle_suspend!
+    authorize(target_account, :suspend?)
+    log_action(:suspend, target_account)
+    target_account.suspend!
+    queue_suspension_worker!
+  end
+
+  def text_for_warning
+    [warning_preset&.text, text].compact.join("\n\n")
+  end
+
+  def queue_suspension_worker!
+    Admin::SuspensionWorker.perform_async(target_account.id)
+  end
+
+  def queue_email!
+    return unless warnable?
+
+    UserMailer.warning(target_account.user, warning).deliver_later!
+  end
+
+  def warnable?
+    send_email_notification && target_account.local?
+  end
+
+  def warning_preset
+    @warning_preset ||= AccountWarningPreset.find(warning_preset_id) if warning_preset_id.present?
+  end
+end
diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb
index ae50860ed..a894b5eed 100644
--- a/app/models/concerns/account_associations.rb
+++ b/app/models/concerns/account_associations.rb
@@ -39,6 +39,8 @@ module AccountAssociations
     # Moderation notes
     has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
     has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
+    has_many :account_warnings, dependent: :destroy, inverse_of: :account
+    has_many :targeted_account_warnings, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
 
     # Lists (that the account is on, not owned by the account)
     has_many :list_accounts, inverse_of: :account, dependent: :destroy
diff --git a/app/models/form/admin_suspension_confirmation.rb b/app/models/form/admin_suspension_confirmation.rb
deleted file mode 100644
index c34b5b30e..000000000
--- a/app/models/form/admin_suspension_confirmation.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class Form::AdminSuspensionConfirmation
-  include ActiveModel::Model
-
-  attr_accessor :acct, :report_id
-end