From bf75d50cc35d578c410a117c42b32952a1fbab69 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 13:57:31 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20modificar=20el=20estado=20de=20moderaci?= =?UTF-8?q?=C3=B3n=20en=20masa=20#15328=20#15327?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/activity_pubs_controller.rb | 45 +++++++------------ .../actor_moderations_controller.rb | 23 +++++----- .../instance_moderations_controller.rb | 15 ++++--- .../activity_pub/instance_moderation_job.rb | 13 ++---- app/models/activity_pub.rb | 22 --------- app/models/actor_moderation.rb | 13 ------ app/models/concerns/aasm_events_concern.rb | 20 +++++++++ app/models/fediblock_state.rb | 12 ----- app/models/instance_moderation.rb | 30 ++++++------- config/locales/es.yml | 13 ++++++ 10 files changed, 88 insertions(+), 118 deletions(-) diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index 1efe2b89..edece8f8 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -27,7 +27,7 @@ class ActivityPubsController < ApplicationController authorize activity_pubs action = params[:activity_pub_action].to_sym - method = :"#{action}!" + method = :"#{action}_all!" may = :"may_#{action}?" redirect_to_moderation_queue! @@ -35,36 +35,25 @@ class ActivityPubsController < ApplicationController return unless ActivityPub.events.include? action # Crear una sola remote flag por autore - if action == :report - message = remote_flag_params(activity_pubs.first).dig(:remote_flag_attributes, :message) - - activity_pubs.distinct.pluck(:actor_id).each do |actor_id| - remote_flag = ActivityPub::RemoteFlag.find_or_initialize_by(actor_id: actor_id, site_id: site.id) - remote_flag.message = message - # Lo estamos actualizando, con lo que lo vamos a volver a enviar - remote_flag.requeue if remote_flag.persisted? - remote_flag.save - # XXX: Idealmente todas las ActivityPub que enviamos pueden - # cambiar de estado, pero chequeamos de todas formas. - remote_flag.activity_pubs << (activity_pubs.where(actor_id: actor_id).to_a.select { |a| a.public_send(may) }) - end - end - ActivityPub.transaction do - activity_pubs.find_each do |activity_pub| - next unless activity_pub.public_send(may) + if action == :report + message = remote_flag_params(activity_pubs.first).dig(:remote_flag_attributes, :message) - activity_pub.public_send(method) - - flash[:success] = I18n.t('activity_pubs.action_on_several.success') - rescue Exception => e - ExceptionNotifier.notify_exception(e, - data: { site: site.name, params: params.permit!.to_h, - activity_pub: activity_pub.id }) - - flash.delete(:success) - flash[:error] = I18n.t('activity_pubs.action_on_several.error') + activity_pubs.distinct.pluck(:actor_id).each do |actor_id| + remote_flag = ActivityPub::RemoteFlag.find_or_initialize_by(actor_id: actor_id, site_id: site.id) + remote_flag.message = message + # Lo estamos actualizando, con lo que lo vamos a volver a enviar + remote_flag.requeue if remote_flag.persisted? + remote_flag.save + # XXX: Idealmente todas las ActivityPub que enviamos pueden + # cambiar de estado, pero chequeamos de todas formas. + remote_flag.activity_pubs << (activity_pubs.where(actor_id: actor_id).to_a.select { |a| a.public_send(may) }) + end end + + message = activity_pubs.public_send(method) ? :success : :error + + flash[message] = I18n.t("activity_pubs.action_on_several.#{message}") end end diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 7c2c3d82..bc4a059b 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -37,7 +37,7 @@ class ActorModerationsController < ApplicationController authorize actor_moderations action = params[:actor_moderation_action].to_sym - method = :"#{action}!" + method = :"#{action}_all!" may = :"may_#{action}?" redirect_to_moderation_queue! @@ -45,20 +45,17 @@ class ActorModerationsController < ApplicationController return unless ActorModeration.events.include? action ActorModeration.transaction do - actor_moderations.find_each do |actor_moderation| - next unless actor_moderation.public_send(may) + if action == :report + actor_moderations.find_each do |actor_moderation| + next unless actor_moderation.public_send(may) - actor_moderation.update(actor_moderation_params(actor_moderation)) if action == :report - - actor_moderation.public_send(method) - - flash[:success] = I18n.t('actor_moderations.action_on_several.success') - rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params.permit!.to_h }) - - flash.delete(:success) - flash[:error] = I18n.t('actor_moderations.action_on_several.error') + actor_moderation.update(actor_moderation_params(actor_moderation)) + end end + + message = actor_moderation.public_send(method) ? :success : :error + + flash[message] = I18n.t("actor_moderations.action_on_several.#{message}") end end diff --git a/app/controllers/instance_moderations_controller.rb b/app/controllers/instance_moderations_controller.rb index 270f0588..06f5cfc1 100644 --- a/app/controllers/instance_moderations_controller.rb +++ b/app/controllers/instance_moderations_controller.rb @@ -10,6 +10,12 @@ class InstanceModerationsController < ApplicationController instance_moderation.public_send(:"#{event}!") if instance_moderation.public_send(:"may_#{event}?") + flash[:success] = I18n.t("instance_moderations.#{event}.success") + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params.permit!.to_h }) + + flash[:error] = I18n.t("instance_moderations.#{event}.error") + ensure redirect_to_moderation_queue! end end @@ -20,17 +26,16 @@ class InstanceModerationsController < ApplicationController authorize instance_moderations action = params[:instance_moderation_action].to_sym - method = :"#{action}!" - may = :"may_#{action}?" + method = :"#{action}_all!" redirect_to_moderation_queue! return unless InstanceModeration.events.include? action InstanceModeration.transaction do - instance_moderations.find_each do |instance_moderation| - instance_moderation.public_send(method) if instance_moderation.public_send(may) - end + message = instance_moderations.public_send(method) ? :success : :error + + flash[:message] = I18n.t("instance_moderations.action_on_several.#{message}") end end diff --git a/app/jobs/activity_pub/instance_moderation_job.rb b/app/jobs/activity_pub/instance_moderation_job.rb index 17def46e..e28be84e 100644 --- a/app/jobs/activity_pub/instance_moderation_job.rb +++ b/app/jobs/activity_pub/instance_moderation_job.rb @@ -14,7 +14,6 @@ class ActivityPub end instances = ActivityPub::Instance.where(hostname: hostnames) - success = true Site.transaction do # Crea todas las moderaciones de instancia con un estado por @@ -22,17 +21,11 @@ class ActivityPub instances.find_each do |instance| # Esto bloquea cada una individualmente en la Social Inbox, # idealmente son pocas instancias las que aparecen. - site.instance_moderations.find_or_create_by(instance: instance).tap do |instance_moderation| - instance_moderation.block! if instance_moderation.may_block? - # Notificar todos los errores sin detener la ejecución - rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, instance_moderation: instance_moderation.id }) - success = false - end + site.instance_moderations.find_or_create_by(instance: instance) end - end - success + site.instance_moderations.where(instance_id: instances.ids).block_all! + end end end end diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index fce340db..38bf3b3a 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -38,28 +38,6 @@ class ActivityPub < ApplicationRecord end end - # Permite todos los comentarios. No podemos hacer acciones en masa - # sobre comentarios en la Social Inbox, con lo que tenemos que - # comunicarnos individualmente. - def self.allow_all! - self.find_each do |activity_pub| - activity_pub.allow! if activity_pub.may_allow? - rescue Exception => e - notify_exception!(e, activity_pub) - end - end - - # Rechaza todos los comentarios. No podemos hacer acciones en masa - # sobre comentarios en la Social Inbox, con lo que tenemos que - # comunicarnos individualmente. - def self.reject_all! - self.find_each do |activity_pub| - activity_pub.reject! if activity_pub.may_reject? - rescue Exception => e - notify_exception!(e, activity_pub) - end - end - aasm do # Todavía no hay una decisión sobre el objeto state :paused, initial: true diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index a510f1eb..7cb8827d 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -14,19 +14,6 @@ class ActorModeration < ApplicationRecord accepts_nested_attributes_for :remote_flag - # Bloquea todes les Actores bloqueables - def self.block_all! - self.update_all(aasm_state: 'blocked', updated_at: Time.now) - end - - def self.pause_all! - self.update_all(aasm_state: 'paused', updated_at: Time.now) - end - - def self.remove_all! - self.update_all(aasm_state: 'removed', updated_at: Time.now) - end - aasm do state :paused, initial: true state :allowed diff --git a/app/models/concerns/aasm_events_concern.rb b/app/models/concerns/aasm_events_concern.rb index b189dbe4..967a61b3 100644 --- a/app/models/concerns/aasm_events_concern.rb +++ b/app/models/concerns/aasm_events_concern.rb @@ -28,6 +28,26 @@ module AasmEventsConcern aasm.states.map(&:name) - self::IGNORED_STATES end + # Define un método que cambia el estado para todos los objetos del + # scope actual. + # + # @return [Bool] Si hubo al menos un error, devuelve false. + self.events.each do |event| + define_singleton_method(:"#{event}_all!") do + success = true + + self.find_each do |object| + object.public_send(:"#{event}!") if object.public_send(:"may_#{event}?") + rescue Exception => e + success = false + + notify_exception! e, object + end + + success + end + end + # Envía notificación de errores # # @param exception [Exception] diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index e50abaef..dfdc4e34 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -39,12 +39,6 @@ class FediblockState < ApplicationRecord # Luego esta tarea crea las que falten e ignora las que ya se # bloquearon. ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames) - - # Bloquear a todes les Actores de las instancias bloqueadas para - # indicarle a le usuarie que les tiene que desbloquear - # manualmente. - actor_ids = ActivityPub::Actor.where(instance_id: instance_ids).ids - ActorModeration.where(actor_id: actor_ids).paused.block_all! end end @@ -66,12 +60,6 @@ class FediblockState < ApplicationRecord # bloqueadas por otros fediblocks. instance_ids = ActivityPub::Instance.where(hostname: unique_hostnames).ids site.instance_moderations.where(instance_id: instance_ids).pause_all! - - # Volver a pausar todes les actores de esta instancia que fueron - # bloqueades, a menos que hayan sido bloqueades por otro - # fediblock. - actor_ids = ActivityPub::Actor.where(instance_id: instance_ids).ids - ActorModeration.where(actor_id: actor_ids).blocked.pause_all! end end end diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index ef04b7ff..918c6ad0 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -11,26 +11,13 @@ class InstanceModeration < ApplicationRecord belongs_to :site belongs_to :instance, class_name: 'ActivityPub::Instance' - # Traer todas las instancias bloqueables, según la máquina de estados, - # todas las que no estén bloqueadas ya. - scope :may_block, -> { where.not(aasm_state: 'blocked') } - scope :may_pause, -> { where.not(aasm_state: 'paused') } - - # Bloquear instancias en masa - def self.block_all! - self.may_block.update_all(aasm_state: 'blocked', updated_at: Time.now) - end - - # Pausar instancias en masa - def self.pause_all! - self.may_pause.update_all(aasm_state: 'paused', updated_at: Time.now) - end - aasm do state :paused, initial: true state :allowed state :blocked + # Al volver la instancia a pausa no cambiamos el estado de + # moderación de actores pre-existente. event :pause do transitions from: %i[allowed blocked], to: :paused @@ -39,23 +26,36 @@ class InstanceModeration < ApplicationRecord end end + # Al permitir, también permitimos todes les actores que no hayan + # tenido acciones de moderación. event :allow do transitions from: %i[paused blocked], to: :allowed before do allow_remotely! + + site.actor_moderations.paused.where(actor_id: actor_ids).allow_all! end end + # Al bloquear, también bloqueamos a todes les actores que no hayan + # tenido acciones de moderación. event :block do transitions from: %i[paused allowed], to: :blocked before do block_remotely! + + site.actor_moderations.paused.where(actor_id: actor_ids).block_all! end end end + # @return [Array] + def actor_ids + ActivityPub::Actor.where(instance_id: self.instance_id).ids + end + # Elimina la instancia de todas las listas # # @return [Boolean] diff --git a/config/locales/es.yml b/config/locales/es.yml index 3df608f5..3cc6c49a 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -145,6 +145,19 @@ es: report: success: "Cuenta reportada a su instancia." error: "No se pudo reportar la cuenta. Hemos recibido el reporte y lo estaremos verificando." + instance_moderations: + action_on_several: + success: "Se ha modificado el estado de moderación de varias instancias. Podés encontrarlas usando los filtros en la sección Instancias." + error: "Hubo un error al modificar el estado de moderación de varias instancias. Hemos recibido el reporte y lo estaremos verificando." + pause: + success: "Instancia pausada. Todos los comentarios y cuentas de esta instancia necesitan ser aprobados manualmente. No se ha modificado el estado de moderación de cuentas y comentarios pre-existentes." + error: "No se pudo pausar la instancia. Hemos recibido el reporte y lo estaremos verificando." + allow: + success: "Instancia permitida. Todos los comentarios y cuentas pendientes de moderación fueron aprobados y los próximos serán aprobados inmediatamente." + error: "No se pudo permitir la instancia. Hemos recibido el reporte y lo estaremos verificando." + block: + success: "Instancia bloqueada. Todos los comentarios y cuentas serán rechazados inmediatamente. Todos los comentarios y cuentas pendientes de moderación fueron rechazados." + error: "No se pudo bloquear la instancia. Hemos recibido el reporte y lo estaremos verificando." fediblock_states: action_on_several: success: "Se habilitaron las listas de bloqueo, podés encontrar las instancias filtrando por Bloqueadas. Todas las cuentas de estas instancias pendientes de moderación han sido bloqueadas. Podés activarlas individualmente en la sección Cuentas."