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_domainmaster
parent
00862dcaff
commit
3c033c4352
@ -0,0 +1,36 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
module Admin |
||||
class AccountActionsController < BaseController |
||||
before_action :set_account |
||||
|
||||
def new |
||||
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true) |
||||
@warning_presets = AccountWarningPreset.all |
||||
end |
||||
|
||||
def create |
||||
account_action = Admin::AccountAction.new(resource_params) |
||||
account_action.target_account = @account |
||||
account_action.current_account = current_account |
||||
|
||||
account_action.save! |
||||
|
||||
if account_action.with_report? |
||||
redirect_to admin_report_path(account_action.report) |
||||
else |
||||
redirect_to admin_account_path(@account.id) |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_account |
||||
@account = Account.find(params[:account_id]) |
||||
end |
||||
|
||||
def resource_params |
||||
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification) |
||||
end |
||||
end |
||||
end |
@ -1,27 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
module Admin |
||||
class SilencesController < BaseController |
||||
before_action :set_account |
||||
|
||||
def create |
||||
authorize @account, :silence? |
||||
@account.update!(silenced: true) |
||||
log_action :silence, @account |
||||
redirect_to admin_accounts_path |
||||
end |
||||
|
||||
def destroy |
||||
authorize @account, :unsilence? |
||||
@account.update!(silenced: false) |
||||
log_action :unsilence, @account |
||||
redirect_to admin_accounts_path |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_account |
||||
@account = Account.find(params[:account_id]) |
||||
end |
||||
end |
||||
end |
@ -1,60 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
module Admin |
||||
class SuspensionsController < BaseController |
||||
before_action :set_account |
||||
|
||||
def new |
||||
@suspension = Form::AdminSuspensionConfirmation.new(report_id: params[:report_id]) |
||||
end |
||||
|
||||
def create |
||||
authorize @account, :suspend? |
||||
|
||||
@suspension = Form::AdminSuspensionConfirmation.new(suspension_params) |
||||
|
||||
if suspension_params[:acct] == @account.acct |
||||
resolve_report! if suspension_params[:report_id].present? |
||||
perform_suspend! |
||||
mark_reports_resolved! |
||||
redirect_to admin_accounts_path |
||||
else |
||||
flash.now[:alert] = I18n.t('admin.suspensions.bad_acct_msg') |
||||
render :new |
||||
end |
||||
end |
||||
|
||||
def destroy |
||||
authorize @account, :unsuspend? |
||||
@account.unsuspend! |
||||
log_action :unsuspend, @account |
||||
redirect_to admin_accounts_path |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_account |
||||
@account = Account.find(params[:account_id]) |
||||
end |
||||
|
||||
def suspension_params |
||||
params.require(:form_admin_suspension_confirmation).permit(:acct, :report_id) |
||||
end |
||||
|
||||
def resolve_report! |
||||
report = Report.find(suspension_params[:report_id]) |
||||
report.resolve!(current_account) |
||||
log_action :resolve, report |
||||
end |
||||
|
||||
def perform_suspend! |
||||
@account.suspend! |
||||
Admin::SuspensionWorker.perform_async(@account.id) |
||||
log_action :suspend, @account |
||||
end |
||||
|
||||
def mark_reports_resolved! |
||||
Report.where(target_account: @account).unresolved.update_all(action_taken: true, action_taken_by_account_id: current_account.id) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,58 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
module Admin |
||||
class WarningPresetsController < BaseController |
||||
before_action :set_warning_preset, except: [:index, :create] |
||||
|
||||
def index |
||||
authorize :account_warning_preset, :index? |
||||
|
||||
@warning_presets = AccountWarningPreset.all |
||||
@warning_preset = AccountWarningPreset.new |
||||
end |
||||
|
||||
def create |
||||
authorize :account_warning_preset, :create? |
||||
|
||||
@warning_preset = AccountWarningPreset.new(warning_preset_params) |
||||
|
||||
if @warning_preset.save |
||||
redirect_to admin_warning_presets_path |
||||
else |
||||
@warning_presets = AccountWarningPreset.all |
||||
render :index |
||||
end |
||||
end |
||||
|
||||
def edit |
||||
authorize @warning_preset, :update? |
||||
end |
||||
|
||||
def update |
||||
authorize @warning_preset, :update? |
||||
|
||||
if @warning_preset.update(warning_preset_params) |
||||
redirect_to admin_warning_presets_path |
||||
else |
||||
render :edit |
||||
end |
||||
end |
||||
|
||||
def destroy |
||||
authorize @warning_preset, :destroy? |
||||
|
||||
@warning_preset.destroy! |
||||
redirect_to admin_warning_presets_path |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_warning_preset |
||||
@warning_preset = AccountWarningPreset.find(params[:id]) |
||||
end |
||||
|
||||
def warning_preset_params |
||||
params.require(:account_warning_preset).permit(:text) |
||||
end |
||||
end |
||||
end |
After Width: | Height: | Size: 197 B |
After Width: | Height: | Size: 371 B |
@ -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 |
@ -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 |
@ -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 |
@ -1,7 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Form::AdminSuspensionConfirmation |
||||
include ActiveModel::Model |
||||
|
||||
attr_accessor :acct, :report_id |
||||
end |
@ -0,0 +1,19 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class AccountWarningPresetPolicy < ApplicationPolicy |
||||
def index? |
||||
staff? |
||||
end |
||||
|
||||
def create? |
||||
staff? |
||||
end |
||||
|
||||
def update? |
||||
staff? |
||||
end |
||||
|
||||
def destroy? |
||||
staff? |
||||
end |
||||
end |
@ -0,0 +1,26 @@ |
||||
- content_for :page_title do |
||||
= t('admin.account_actions.title', acct: @account.acct) |
||||
|
||||
= simple_form_for @account_action, url: admin_account_action_path(@account.id) do |f| |
||||
= f.input :report_id, as: :hidden |
||||
|
||||
.fields-group |
||||
= f.input :type, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { I18n.t("simple_form.labels.admin_account_action.types.#{type}")}, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.acct) |
||||
|
||||
- if @account.local? |
||||
%hr.spacer/ |
||||
|
||||
.fields-group |
||||
= f.input :send_email_notification, as: :boolean, wrapper: :with_label |
||||
|
||||
%hr.spacer/ |
||||
|
||||
- unless @warning_presets.empty? |
||||
.fields-group |
||||
= f.input :warning_preset_id, collection: @warning_presets, label_method: :text, wrapper: :with_block_label |
||||
|
||||
.fields-group |
||||
= f.input :text, as: :text, wrapper: :with_block_label, hint: t('simple_form.hints.admin_account_action.text_html', path: admin_warning_presets_path) |
||||
|
||||
.actions |
||||
= f.button :button, t('admin.account_actions.action'), type: :submit |
@ -0,0 +1,6 @@ |
||||
.speech-bubble.warning |
||||
.speech-bubble__bubble |
||||
= Formatter.instance.linkify(account_warning.text) |
||||
.speech-bubble__owner |
||||
= admin_account_link_to account_warning.account |
||||
%time.formatted{ datetime: account_warning.created_at.iso8601 }= l account_warning.created_at |
@ -1,25 +0,0 @@ |
||||
- content_for :page_title do |
||||
= t('admin.suspensions.title', acct: @account.acct) |
||||
|
||||
= simple_form_for @suspension, url: admin_account_suspension_path(@account.id), method: :post do |f| |
||||
%p.hint= t('admin.suspensions.warning_html') |
||||
|
||||
.fields-group |
||||
%ul |
||||
%li.negative-hint |
||||
= number_to_human @account.statuses_count, strip_insignificant_zeros: true |
||||
= t('accounts.posts', count: @account.statuses_count) |
||||
%li.negative-hint |
||||
= number_to_human @account.following_count, strip_insignificant_zeros: true |
||||
= t('accounts.following', count: @account.following_count) |
||||
%li.negative-hint |
||||
= number_to_human @account.followers_count, strip_insignificant_zeros: true |
||||
= t('accounts.followers', count: @account.followers_count) |
||||
|
||||
%p.hint= t('admin.suspensions.hint_html', value: content_tag(:code, @account.acct)) |
||||
|
||||
= f.input :acct |
||||
= f.input_field :report_id, as: :hidden |
||||
|
||||
.actions |
||||
= f.button :button, t('admin.suspensions.proceed'), type: :submit, class: 'negative' |
@ -0,0 +1,11 @@ |
||||
- content_for :page_title do |
||||
= t('admin.warning_presets.edit_preset') |
||||
|
||||
= simple_form_for @warning_preset, url: admin_warning_preset_path(@warning_preset) do |f| |
||||
= render 'shared/error_messages', object: @warning_preset |
||||
|
||||
.fields-group |
||||
= f.input :text, wrapper: :with_block_label |
||||
|
||||
.actions |
||||
= f.button :button, t('generic.save_changes'), type: :submit |
@ -0,0 +1,30 @@ |
||||
- content_for :page_title do |
||||
= t('admin.warning_presets.title') |
||||
|
||||
- if can? :create, :account_warning_preset |
||||
= simple_form_for @warning_preset, url: admin_warning_presets_path do |f| |
||||
= render 'shared/error_messages', object: @warning_preset |
||||
|
||||
.fields-group |
||||
= f.input :text, wrapper: :with_block_label |
||||
|
||||
.actions |
||||
= f.button :button, t('admin.warning_presets.add_new'), type: :submit |
||||
|
||||
%hr.spacer/ |
||||
|
||||
- unless @warning_presets.empty? |
||||
.table-wrapper |
||||
%table.table |
||||
%thead |
||||
%tr |
||||
%th= t('simple_form.labels.account_warning_preset.text') |
||||
%th |
||||
%tbody |
||||
- @warning_presets.each do |preset| |
||||
%tr |
||||
%td |
||||
= Formatter.instance.linkify(preset.text) |
||||
%td |
||||
= table_link_to 'pencil', t('admin.warning_presets.edit'), edit_admin_warning_preset_path(preset) |
||||
= table_link_to 'trash', t('admin.warning_presets.delete'), admin_warning_preset_path(preset), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } |
@ -0,0 +1,63 @@ |
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.email-body |
||||
.email-container |
||||
%table.content-section{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.content-cell.hero |
||||
.email-row |
||||
.col-6 |
||||
%table.column{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.column-cell.text-center.padded |
||||
%table.hero-icon.alert-icon{ align: 'center', cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td |
||||
= image_tag full_pack_url('icon_warning.png'), alt: '' |
||||
|
||||
%h1= t "user_mailer.warning.title.#{@warning.action}" |
||||
|
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.email-body |
||||
.email-container |
||||
%table.content-section{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.content-cell.content-start |
||||
.email-row |
||||
.col-6 |
||||
%table.column{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.column-cell.text-center |
||||
- unless @warning.none_action? |
||||
%p= t "user_mailer.warning.explanation.#{@warning.action}" |
||||
|
||||
- unless @warning.text.blank? |
||||
= Formatter.instance.linkify(@warning.text) |
||||
|
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.email-body |
||||
.email-container |
||||
%table.content-section{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.content-cell |
||||
%table.column{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.column-cell.button-cell |
||||
%table.button{ align: 'center', cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.button-primary |
||||
= link_to about_more_url do |
||||
%span= t 'user_mailer.warning.review_server_policies' |
@ -0,0 +1,9 @@ |
||||
<%= t "user_mailer.warning.title.#{@warning.action}" %> |
||||
|
||||
=== |
||||
|
||||
<% unless @warning.none_action? %> |
||||
<%= t "user_mailer.warning.explanation.#{@warning.action}" %> |
||||
|
||||
<% end %> |
||||
<%= @warning.text %> |