* Add database columns for adding notes to domain blocks/restrctions * Add admin UI to set private and public comments when blocking a domain * Add text for private and public comments on domain blocks * Show domain block comments in admin UI * Add comments to the domain block undo page * Make UnblockDomainService more robust regarding upgraded domain blocks * Allow editing domain blocks * Rename button from “undo domain block” to “view domain block” in account admin UI * Change test to unsilence silenced users from upgraded blocksmaster^2
@@ -2,13 +2,17 @@ | |||||
module Admin | module Admin | ||||
class DomainBlocksController < BaseController | class DomainBlocksController < BaseController | ||||
before_action :set_domain_block, only: [:show, :destroy] | |||||
before_action :set_domain_block, only: [:show, :destroy, :edit, :update] | |||||
def new | def new | ||||
authorize :domain_block, :create? | authorize :domain_block, :create? | ||||
@domain_block = DomainBlock.new(domain: params[:_domain]) | @domain_block = DomainBlock.new(domain: params[:_domain]) | ||||
end | end | ||||
def edit | |||||
authorize :domain_block, :create? | |||||
end | |||||
def create | def create | ||||
authorize :domain_block, :create? | authorize :domain_block, :create? | ||||
@@ -35,6 +39,22 @@ module Admin | |||||
end | end | ||||
end | end | ||||
def update | |||||
authorize :domain_block, :create? | |||||
@domain_block.update(update_params) | |||||
severity_changed = @domain_block.severity_changed? | |||||
if @domain_block.save | |||||
DomainBlockWorker.perform_async(@domain_block.id, severity_changed) | |||||
log_action :create, @domain_block | |||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') | |||||
else | |||||
render :edit | |||||
end | |||||
end | |||||
def show | def show | ||||
authorize @domain_block, :show? | authorize @domain_block, :show? | ||||
end | end | ||||
@@ -52,8 +72,12 @@ module Admin | |||||
@domain_block = DomainBlock.find(params[:id]) | @domain_block = DomainBlock.find(params[:id]) | ||||
end | end | ||||
def update_params | |||||
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment) | |||||
end | |||||
def resource_params | def resource_params | ||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports) | |||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment) | |||||
end | end | ||||
end | end | ||||
end | end |
@@ -21,6 +21,8 @@ module Admin | |||||
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count | @blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count | ||||
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url) | @available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url) | ||||
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size) | @media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size) | ||||
@private_comment = @domain_block&.private_comment | |||||
@public_comment = @domain_block&.public_comment | |||||
end | end | ||||
private | private | ||||
@@ -3,13 +3,15 @@ | |||||
# | # | ||||
# Table name: domain_blocks | # Table name: domain_blocks | ||||
# | # | ||||
# id :bigint(8) not null, primary key | |||||
# domain :string default(""), not null | |||||
# created_at :datetime not null | |||||
# updated_at :datetime not null | |||||
# severity :integer default("silence") | |||||
# reject_media :boolean default(FALSE), not null | |||||
# reject_reports :boolean default(FALSE), not null | |||||
# id :bigint(8) not null, primary key | |||||
# domain :string default(""), not null | |||||
# created_at :datetime not null | |||||
# updated_at :datetime not null | |||||
# severity :integer default("silence") | |||||
# reject_media :boolean default(FALSE), not null | |||||
# reject_reports :boolean default(FALSE), not null | |||||
# private_comment :text | |||||
# public_comment :text | |||||
# | # | ||||
class DomainBlock < ApplicationRecord | class DomainBlock < ApplicationRecord | ||||
@@ -3,13 +3,22 @@ | |||||
class BlockDomainService < BaseService | class BlockDomainService < BaseService | ||||
attr_reader :domain_block | attr_reader :domain_block | ||||
def call(domain_block) | |||||
def call(domain_block, update = false) | |||||
@domain_block = domain_block | @domain_block = domain_block | ||||
process_domain_block! | process_domain_block! | ||||
process_retroactive_updates! if update | |||||
end | end | ||||
private | private | ||||
def process_retroactive_updates! | |||||
# If the domain block severity has been changed, undo the appropriate limitations | |||||
scope = Account.by_domain_and_subdomains(domain_block.domain) | |||||
scope.where(silenced_at: domain_block.created_at).in_batches.update_all(silenced_at: nil) unless domain_block.silence? | |||||
scope.where(suspended_at: domain_block.created_at).in_batches.update_all(suspended_at: nil) unless domain_block.suspend? | |||||
end | |||||
def process_domain_block! | def process_domain_block! | ||||
clear_media! if domain_block.reject_media? | clear_media! if domain_block.reject_media? | ||||
@@ -10,24 +10,9 @@ class UnblockDomainService < BaseService | |||||
end | end | ||||
def process_retroactive_updates | def process_retroactive_updates | ||||
blocked_accounts.in_batches.update_all(update_options) unless domain_block.noop? | |||||
end | |||||
def blocked_accounts | |||||
scope = Account.by_domain_and_subdomains(domain_block.domain) | scope = Account.by_domain_and_subdomains(domain_block.domain) | ||||
if domain_block.silence? | |||||
scope.where(silenced_at: @domain_block.created_at) | |||||
else | |||||
scope.where(suspended_at: @domain_block.created_at) | |||||
end | |||||
end | |||||
def update_options | |||||
{ domain_block_impact => nil } | |||||
end | |||||
def domain_block_impact | |||||
domain_block.silence? ? :silenced_at : :suspended_at | |||||
scope.where(silenced_at: domain_block.created_at).in_batches.update_all(silenced_at: nil) unless domain_block.noop? | |||||
scope.where(suspended_at: domain_block.created_at).in_batches.update_all(suspended_at: nil) if domain_block.suspend? | |||||
end | end | ||||
end | end |
@@ -174,7 +174,7 @@ | |||||
- unless @account.local? | - unless @account.local? | ||||
- if DomainBlock.where(domain: @account.domain).exists? | - if DomainBlock.where(domain: @account.domain).exists? | ||||
= link_to t('admin.domain_blocks.undo'), admin_instance_path(@account.domain), class: 'button' | |||||
= link_to t('admin.domain_blocks.view'), admin_instance_path(@account.domain), class: 'button' | |||||
- else | - else | ||||
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive' | = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive' | ||||
@@ -0,0 +1,30 @@ | |||||
- content_for :header_tags do | |||||
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous' | |||||
- content_for :page_title do | |||||
= t('admin.domain_blocks.edit') | |||||
= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :put do |f| | |||||
= render 'shared/error_messages', object: @domain_block | |||||
.fields-row | |||||
.fields-row__column.fields-row__column-6.fields-group | |||||
= f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), hint: t('admin.domain_blocks.new.hint'), required: true, readonly: true, disabled: true | |||||
.fields-row__column.fields-row__column-6.fields-group | |||||
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| t("admin.domain_blocks.new.severity.#{type}") }, hint: t('admin.domain_blocks.new.severity.desc_html') | |||||
.fields-group | |||||
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint') | |||||
.fields-group | |||||
= f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint') | |||||
.field-group | |||||
= f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6 | |||||
.field-group | |||||
= f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), rows: 6 | |||||
.actions | |||||
= f.button :button, t('generic.save_changes'), type: :submit |
@@ -20,5 +20,11 @@ | |||||
.fields-group | .fields-group | ||||
= f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint') | = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint') | ||||
.field-group | |||||
= f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6 | |||||
.field-group | |||||
= f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), rows: 6 | |||||
.actions | .actions | ||||
= f.button :button, t('.create'), type: :submit | = f.button :button, t('.create'), type: :submit |
@@ -1,6 +1,18 @@ | |||||
- content_for :page_title do | - content_for :page_title do | ||||
= t('admin.domain_blocks.show.title', domain: @domain_block.domain) | = t('admin.domain_blocks.show.title', domain: @domain_block.domain) | ||||
- if @domain_block.private_comment.present? | |||||
.speech-bubble | |||||
.speech-bubble__bubble | |||||
= simple_format(h(@domain_block.private_comment)) | |||||
.speech-bubble__owner= t 'admin.instances.private_comment' | |||||
- if @domain_block.public_comment.present? | |||||
.speech-bubble | |||||
.speech-bubble__bubble | |||||
= simple_format(h(@domain_block.public_comment)) | |||||
.speech-bubble__owner= t 'admin.instances.public_comment' | |||||
= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f| | = simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f| | ||||
- unless (@domain_block.noop?) | - unless (@domain_block.noop?) | ||||
@@ -31,6 +31,18 @@ | |||||
= fa_icon 'times' | = fa_icon 'times' | ||||
.dashboard__counters__label= t 'admin.instances.delivery_available' | .dashboard__counters__label= t 'admin.instances.delivery_available' | ||||
- if @private_comment.present? | |||||
.speech-bubble | |||||
.speech-bubble__bubble | |||||
= simple_format(h(@private_comment)) | |||||
.speech-bubble__owner= t 'admin.instances.private_comment' | |||||
- if @public_comment.present? | |||||
.speech-bubble | |||||
.speech-bubble__bubble | |||||
= simple_format(h(@public_comment)) | |||||
.speech-bubble__owner= t 'admin.instances.public_comment' | |||||
%hr.spacer/ | %hr.spacer/ | ||||
%div{ style: 'overflow: hidden' } | %div{ style: 'overflow: hidden' } | ||||
@@ -41,6 +53,7 @@ | |||||
- if @domain_allow | - if @domain_allow | ||||
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } | = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } | ||||
- elsif @domain_block | - elsif @domain_block | ||||
= link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@domain_block), class: 'button' | |||||
= link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button' | = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button' | ||||
- else | - else | ||||
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button' | = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button' |
@@ -3,8 +3,8 @@ | |||||
class DomainBlockWorker | class DomainBlockWorker | ||||
include Sidekiq::Worker | include Sidekiq::Worker | ||||
def perform(domain_block_id) | |||||
BlockDomainService.new.call(DomainBlock.find(domain_block_id)) | |||||
def perform(domain_block_id, update = false) | |||||
BlockDomainService.new.call(DomainBlock.find(domain_block_id), update) | |||||
rescue ActiveRecord::RecordNotFound | rescue ActiveRecord::RecordNotFound | ||||
true | true | ||||
end | end | ||||
@@ -284,6 +284,7 @@ en: | |||||
created_msg: Domain block is now being processed | created_msg: Domain block is now being processed | ||||
destroyed_msg: Domain block has been undone | destroyed_msg: Domain block has been undone | ||||
domain: Domain | domain: Domain | ||||
edit: Edit domain block | |||||
existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first. | existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first. | ||||
new: | new: | ||||
create: Create block | create: Create block | ||||
@@ -294,6 +295,10 @@ en: | |||||
silence: Silence | silence: Silence | ||||
suspend: Suspend | suspend: Suspend | ||||
title: New domain block | title: New domain block | ||||
private_comment: Private comment | |||||
private_comment_hint: Comment about this domain limitation for internal use by the moderators. | |||||
public_comment: Public comment | |||||
public_comment_hint: Comment about this domain limitation for the general public, if advertising the list of domain limitations is enabled. | |||||
reject_media: Reject media files | reject_media: Reject media files | ||||
reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions | reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions | ||||
reject_reports: Reject reports | reject_reports: Reject reports | ||||
@@ -313,6 +318,7 @@ en: | |||||
title: Undo domain block for %{domain} | title: Undo domain block for %{domain} | ||||
undo: Undo | undo: Undo | ||||
undo: Undo domain block | undo: Undo domain block | ||||
view: View domain block | |||||
email_domain_blocks: | email_domain_blocks: | ||||
add_new: Add new | add_new: Add new | ||||
created_msg: Successfully added e-mail domain to blacklist | created_msg: Successfully added e-mail domain to blacklist | ||||
@@ -336,6 +342,8 @@ en: | |||||
all: All | all: All | ||||
limited: Limited | limited: Limited | ||||
title: Moderation | title: Moderation | ||||
private_comment: Private comment | |||||
public_comment: Public comment | |||||
title: Federation | title: Federation | ||||
total_blocked_by_us: Blocked by us | total_blocked_by_us: Blocked by us | ||||
total_followed_by_them: Followed by them | total_followed_by_them: Followed by them | ||||
@@ -155,7 +155,11 @@ Rails.application.routes.draw do | |||||
get '/dashboard', to: 'dashboard#index' | get '/dashboard', to: 'dashboard#index' | ||||
resources :domain_allows, only: [:new, :create, :show, :destroy] | resources :domain_allows, only: [:new, :create, :show, :destroy] | ||||
resources :domain_blocks, only: [:new, :create, :show, :destroy] | |||||
resources :domain_blocks, only: [:new, :create, :show, :destroy, :update] do | |||||
member do | |||||
get :edit | |||||
end | |||||
end | |||||
resources :email_domain_blocks, only: [:index, :new, :create, :destroy] | resources :email_domain_blocks, only: [:index, :new, :create, :destroy] | ||||
resources :action_logs, only: [:index] | resources :action_logs, only: [:index] | ||||
resources :warning_presets, except: [:new] | resources :warning_presets, except: [:new] | ||||
@@ -0,0 +1,7 @@ | |||||
class AddCommentsToDomainBlocks < ActiveRecord::Migration[5.2] | |||||
def change | |||||
add_column :domain_blocks, :private_comment, :text | |||||
add_column :domain_blocks, :public_comment, :text | |||||
end | |||||
end | |||||
@@ -10,7 +10,7 @@ | |||||
# | # | ||||
# It's strongly recommended that you check this file into your version control system. | # It's strongly recommended that you check this file into your version control system. | ||||
ActiveRecord::Schema.define(version: 2019_08_05_123746) do | |||||
ActiveRecord::Schema.define(version: 2019_08_07_135426) do | |||||
# These are extensions that must be enabled in order to support this database | # These are extensions that must be enabled in order to support this database | ||||
enable_extension "plpgsql" | enable_extension "plpgsql" | ||||
@@ -259,6 +259,8 @@ ActiveRecord::Schema.define(version: 2019_08_05_123746) do | |||||
t.integer "severity", default: 0 | t.integer "severity", default: 0 | ||||
t.boolean "reject_media", default: false, null: false | t.boolean "reject_media", default: false, null: false | ||||
t.boolean "reject_reports", default: false, null: false | t.boolean "reject_reports", default: false, null: false | ||||
t.text "private_comment" | |||||
t.text "public_comment" | |||||
t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true | t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true | ||||
end | end | ||||
@@ -31,7 +31,7 @@ describe UnblockDomainService, type: :service do | |||||
subject.call(@domain_block) | subject.call(@domain_block) | ||||
expect_deleted_domain_block | expect_deleted_domain_block | ||||
expect(@suspended.reload.suspended?).to be false | expect(@suspended.reload.suspended?).to be false | ||||
expect(@silenced.reload.silenced?).to be true | |||||
expect(@silenced.reload.silenced?).to be false | |||||
expect(@independently_suspended.reload.suspended?).to be true | expect(@independently_suspended.reload.suspended?).to be true | ||||
expect(@independently_silenced.reload.silenced?).to be true | expect(@independently_silenced.reload.silenced?).to be true | ||||
end | end | ||||
@@ -14,7 +14,7 @@ describe DomainBlockWorker do | |||||
result = subject.perform(domain_block.id) | result = subject.perform(domain_block.id) | ||||
expect(result).to be_nil | expect(result).to be_nil | ||||
expect(service).to have_received(:call).with(domain_block) | |||||
expect(service).to have_received(:call).with(domain_block, false) | |||||
end | end | ||||
it 'calls domain block service for relevant domain block' do | it 'calls domain block service for relevant domain block' do | ||||