From 471b82969078e24db1b1243349f57fe6438bc5f2 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 7 Feb 2024 16:24:52 -0300 Subject: [PATCH 001/297] feat: actualizar cliente de DP --- Gemfile | 2 +- Gemfile.lock | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 466ec079..80051e93 100644 --- a/Gemfile +++ b/Gemfile @@ -37,7 +37,7 @@ gem 'commonmarker' gem 'devise' gem 'devise-i18n' gem 'devise_invitable' -gem 'distributed-press-api-client', '~> 0.3.0rc0' +gem 'distributed-press-api-client', '~> 0.4.0rc0' gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n' gem 'exception_notification' gem 'fast_blank' diff --git a/Gemfile.lock b/Gemfile.lock index 78563c84..90bf6d98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -157,11 +157,12 @@ GEM devise_invitable (2.0.8) actionmailer (>= 5.0) devise (>= 4.6) - distributed-press-api-client (0.3.0rc0) + distributed-press-api-client (0.4.0rc0) addressable (~> 2.3, >= 2.3.0) climate_control dry-schema httparty (~> 0.18) + httparty-cache json (~> 2.1, >= 2.1.0) jwt (~> 2.6.0) dotenv (2.8.1) @@ -259,6 +260,8 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) + httparty-cache (0.0.1) + httparty (~> 0.18) i18n (1.14.1) concurrent-ruby (~> 1.0) icalendar (2.8.0) @@ -600,7 +603,7 @@ DEPENDENCIES devise devise-i18n devise_invitable - distributed-press-api-client (~> 0.3.0rc0) + distributed-press-api-client (~> 0.4.0rc0) dotenv-rails down ed25519 From 6841945b398cd04a38b859715fcaedb26e51bf81 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 16 Feb 2024 14:52:37 -0300 Subject: [PATCH 002/297] feat: dependencias --- Gemfile | 5 ++++- Gemfile.lock | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 80051e93..3cf01934 100644 --- a/Gemfile +++ b/Gemfile @@ -37,7 +37,9 @@ gem 'commonmarker' gem 'devise' gem 'devise-i18n' gem 'devise_invitable' -gem 'distributed-press-api-client', '~> 0.4.0rc0' +gem 'redis-client' +gem 'hiredis-client' +gem 'distributed-press-api-client', '~> 0.4.0rc2' gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n' gem 'exception_notification' gem 'fast_blank' @@ -65,6 +67,7 @@ gem 'redis', '~> 4.0', require: %w[redis redis/connection/hiredis] gem 'redis-rails' gem 'rollups', git: 'https://github.com/fauno/rollup.git', branch: 'update' gem 'rubyzip' +gem 'ruby-brs' gem 'rugged', '1.5.0.1' gem 'git_clone_url' gem 'concurrent-ruby-ext' diff --git a/Gemfile.lock b/Gemfile.lock index 90bf6d98..5b8ca619 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -88,6 +88,7 @@ GEM zeitwerk (~> 2.3) addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) + adsp (1.0.10) ast (2.4.2) autoprefixer-rails (10.4.13.0) execjs (~> 2) @@ -157,12 +158,12 @@ GEM devise_invitable (2.0.8) actionmailer (>= 5.0) devise (>= 4.6) - distributed-press-api-client (0.4.0rc0) + distributed-press-api-client (0.4.0rc2) addressable (~> 2.3, >= 2.3.0) climate_control dry-schema httparty (~> 0.18) - httparty-cache + httparty-cache (~> 0.0.4) json (~> 2.1, >= 2.1.0) jwt (~> 2.6.0) dotenv (2.8.1) @@ -256,11 +257,13 @@ GEM heapy (0.2.0) thor hiredis (0.6.3-x86_64-linux-musl) + hiredis-client (0.14.1-x86_64-linux-musl) + redis-client (= 0.14.1) http_parser.rb (0.8.0-x86_64-linux-musl) httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - httparty-cache (0.0.1) + httparty-cache (0.0.4) httparty (~> 0.18) i18n (1.14.1) concurrent-ruby (~> 1.0) @@ -450,6 +453,8 @@ GEM redis-activesupport (5.3.0) activesupport (>= 3, < 8) redis-store (>= 1.3, < 2) + redis-client (0.14.1) + connection_pool redis-rack (2.1.4) rack (>= 2.0.8, < 3) redis-store (>= 1.2, < 2) @@ -487,6 +492,8 @@ GEM activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) + ruby-brs (1.3.3-x86_64-linux-musl) + adsp (~> 1.0) ruby-filemagic (0.7.3-x86_64-linux-musl) ruby-progressbar (1.13.0) ruby-statistics (3.0.2) @@ -603,7 +610,7 @@ DEPENDENCIES devise devise-i18n devise_invitable - distributed-press-api-client (~> 0.4.0rc0) + distributed-press-api-client (~> 0.4.0rc2) dotenv-rails down ed25519 @@ -619,6 +626,7 @@ DEPENDENCIES haml-lint hamlit-rails hiredis + hiredis-client httparty icalendar image_processing @@ -652,10 +660,12 @@ DEPENDENCIES rails-i18n rails_warden redis (~> 4.0) + redis-client redis-rails rgl rollups! rubocop-rails + ruby-brs rubyzip rugged (= 1.5.0.1) safe_yaml From cdf0685c67721e0f4686e205379a00883506800f Mon Sep 17 00:00:00 2001 From: f Date: Fri, 16 Feb 2024 14:53:05 -0300 Subject: [PATCH 003/297] feat: asociar rol con deploy nos permite acceder al token --- app/models/deploy.rb | 2 ++ app/models/rol.rb | 1 + .../20240216170202_add_rol_to_deploys.rb | 18 ++++++++++++++++++ db/structure.sql | 6 ++++-- 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20240216170202_add_rol_to_deploys.rb diff --git a/app/models/deploy.rb b/app/models/deploy.rb index 1f087eb3..8f28f214 100644 --- a/app/models/deploy.rb +++ b/app/models/deploy.rb @@ -10,6 +10,8 @@ require 'open3' # :attributes`. class Deploy < ApplicationRecord belongs_to :site + belongs_to :rol + has_many :build_stats, dependent: :destroy DEPENDENCIES = [] diff --git a/app/models/rol.rb b/app/models/rol.rb index 37332400..c9a92515 100644 --- a/app/models/rol.rb +++ b/app/models/rol.rb @@ -11,6 +11,7 @@ class Rol < ApplicationRecord belongs_to :usuarie belongs_to :site + has_many :deploys validates_inclusion_of :rol, in: ROLES diff --git a/db/migrate/20240216170202_add_rol_to_deploys.rb b/db/migrate/20240216170202_add_rol_to_deploys.rb new file mode 100644 index 00000000..5f629432 --- /dev/null +++ b/db/migrate/20240216170202_add_rol_to_deploys.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Establece una relación entre roles y deploys +class AddRolToDeploys < ActiveRecord::Migration[6.1] + def up + add_column :deploys, :rol_id, :integer, index: true + + Deploy.find_each do |deploy| + rol_id = deploy.site.roles.find_by(rol: 'usuarie', temporal: false).id + + deploy.update_column(:rol_id, rol_id) if rol_id + end + end + + def down + remove_column :deploys, :rol_id + end +end diff --git a/db/structure.sql b/db/structure.sql index cb085f63..dede286d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -759,7 +759,8 @@ CREATE TABLE public.deploys ( updated_at timestamp without time zone NOT NULL, site_id integer, type character varying, - "values" text + "values" text, + rol_id integer ); @@ -2318,6 +2319,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230731195050'), ('20230829204127'), ('20230921155401'), -('20230927153926'); +('20230927153926'), +('20240216170202'); From 6d0ea6fa5d864ae9969d43fa2fef5caf54888548 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 16 Feb 2024 14:54:50 -0300 Subject: [PATCH 004/297] feat: acceder a la social inbox desde el sitio --- app/models/site/social_distributed_press.rb | 11 ++++- app/models/social_inbox.rb | 52 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 app/models/social_inbox.rb diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index 3be6404e..1193ca76 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'distributed_press/v1/social/client' + class Site # Agrega soporte para Social Distributed Press en los sitios module SocialDistributedPress @@ -10,13 +12,20 @@ class Site before_save :generate_private_key_pem!, unless: :private_key_pem? + # @return [SocialInbox] + def social_inbox + @social_inbox ||= SocialInbox.new(site: self) + end + private # Genera la llave privada y la almacena # # @return [nil] def generate_private_key_pem! - self.private_key_pem ||= ::DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: 2048).private_key.export + self.private_key_pem ||= DistributedPress::V1::Social::Client.new( + public_key_url: nil, + key_size: 2048).private_key.export end end end diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb new file mode 100644 index 00000000..47c3457c --- /dev/null +++ b/app/models/social_inbox.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'distributed_press/v1/social/client' + +# Gestiona la Social Inbox de un sitio +class SocialInbox + # @return [Site] + attr_reader :site + + # @param :site [Site] + def initialize(site:) + @site = site + end + + # @return [String] + def actor + @actor ||= + begin + user = site.config.dig('activity_pub', 'username') + user ||= hostname.split('.', 2).first + + "@#{user}@#{hostname}" + end + end + + # @return [DistributedPress::V1::Social::Client] + def client + @client ||= DistributedPress::V1::Social::Client.new( + url: site.config.dig('activity_pub', 'url'), + public_key_url: public_key_url, + private_key_pem: site.private_key_pem, + logger: Rails.logger, + cache_store: :redis + ) + end + + # @return [String] + def public_key_url + @public_key_url ||= URI("https://#{hostname}").tap do |uri| + uri.path = '/about.jsonld' + uri.fragment = 'main-key' + end.to_s + end + + def hostname + @hostname ||= + begin + host = site.config.dig('activity_pub', 'hostname') + host ||= site.hostname + end + end +end From b792cb2d43a6ac145bb8a3afea6f0ccb1841eab5 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 16 Feb 2024 15:59:37 -0300 Subject: [PATCH 005/297] feat: poder reutilizar webhooks --- .../api/v1/concerns/webhook_concern.rb | 71 +++++++++++++++++++ app/controllers/api/v1/webhooks_controller.rb | 56 +-------------- config/routes.rb | 4 +- 3 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 app/controllers/api/v1/concerns/webhook_concern.rb diff --git a/app/controllers/api/v1/concerns/webhook_concern.rb b/app/controllers/api/v1/concerns/webhook_concern.rb new file mode 100644 index 00000000..59d48b28 --- /dev/null +++ b/app/controllers/api/v1/concerns/webhook_concern.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Api + module V1 + # Helpers para webhooks + module WebhookConcern + extend ActiveSupport::Concern + + included do + # Responde con forbidden si falla la validación del token + rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer + + private + + # Valida el token que envía la plataforma en el webhook + # + # @return [String] + def token + @token ||= + begin + _headers = request.headers + _token ||= _headers['X-Gitlab-Token'].presence + _token ||= token_from_signature(_headers['X-Gitea-Signature'].presence) + _token ||= token_from_signature(_headers['X-Hub-Signature-256'].presence, 'sha256=') + _token + ensure + raise ActiveRecord::RecordNotFound, 'Proveedor no soportado' if _token.blank? + end + end + + # Valida token a partir de firma + # + # @param signature [String,nil] + # @param prepend [String] + # @return [String, nil] + def token_from_signature(signature, prepend = '') + return if signature.nil? + + payload = request.raw_post + + site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token| + new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload) + + ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s) + end + end + + # Encuentra el sitio a partir de la URL + # + # @return [Site] + def site + @site ||= Site.find_by_name!(params[:site_id]) + end + + # Encuentra le usuarie + # + # @return [Site] + def usuarie + @usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie + end + + # Respuesta de error a plataformas + def platforms_answer(exception) + ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }) + + head :forbidden + end + end + end + end +end diff --git a/app/controllers/api/v1/webhooks_controller.rb b/app/controllers/api/v1/webhooks_controller.rb index 6e7b7022..f64fa93c 100644 --- a/app/controllers/api/v1/webhooks_controller.rb +++ b/app/controllers/api/v1/webhooks_controller.rb @@ -4,8 +4,7 @@ module Api module V1 # Recibe webhooks y lanza un PullJob class WebhooksController < BaseController - # responde con forbidden si falla la validación del token - rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer + include WebhookConcern # Trae los cambios a partir de un post de Webhooks: # (Gitlab, Github, Gitea, etc) @@ -19,59 +18,6 @@ module Api GitPullJob.perform_later(site, usuarie, message) head :ok end - - private - - # encuentra el sitio a partir de la url - def site - @site ||= Site.find_by_name!(params[:site_id]) - end - - # valida el token que envía la plataforma del webhook - # - # @return [String] - def token - @token ||= - begin - # Gitlab - if request.headers['X-Gitlab-Token'].present? - request.headers['X-Gitlab-Token'] - # Github - elsif request.headers['X-Hub-Signature-256'].present? - token_from_signature(request.headers['X-Hub-Signature-256'], 'sha256=') - # Gitea - elsif request.headers['X-Gitea-Signature'].present? - token_from_signature(request.headers['X-Gitea-Signature']) - else - raise ActiveRecord::RecordNotFound, 'proveedor no soportado' - end - end - end - - # valida token a partir de firma de webhook - # - # @return [String, Boolean] - def token_from_signature(signature, prepend = '') - payload = request.body.read - site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token| - new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload) - ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s) - end.tap do |t| - raise ActiveRecord::RecordNotFound, 'token no encontrado' if t.nil? - end - end - - # encuentra le usuarie - def usuarie - @usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie - end - - # respuesta de error a plataformas - def platforms_answer(exception) - ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }) - - head :forbidden - end end end end diff --git a/config/routes.rb b/config/routes.rb index 635be07a..8186b64e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,7 +18,9 @@ Rails.application.routes.draw do get :'contact/cookie', to: 'invitades#contact_cookie' post :'contact/:form', to: 'contact#receive', as: :contact - post :'webhooks/pull', to: 'webhooks#pull' + namespace :webhooks do + post :pull, to: 'webhooks#pull' + end end end end From b24f49fe2611791eb377a56d4f7e9cdbeafafbd4 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 16 Feb 2024 16:01:17 -0300 Subject: [PATCH 006/297] feat: crear webhooks en la social inbox #15109 --- .../api/v1/social_inbox_controller.rb | 22 +++++++++++ app/models/deploy_social_distributed_press.rb | 39 +++++++++++++++++++ app/models/social_inbox.rb | 6 +++ config/application.rb | 3 ++ config/routes.rb | 6 +++ 5 files changed, 76 insertions(+) create mode 100644 app/controllers/api/v1/social_inbox_controller.rb diff --git a/app/controllers/api/v1/social_inbox_controller.rb b/app/controllers/api/v1/social_inbox_controller.rb new file mode 100644 index 00000000..3881b6bc --- /dev/null +++ b/app/controllers/api/v1/social_inbox_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Api + module V1 + # Recibe webhooks de la Social Inbox + class SocialInboxController < BaseController + include WebhookConcern + + def moderationqueued + head :accepted + end + + def onapproved + head :accepted + end + + def onrejected + head :accepted + end + end + end +end diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index db555ab7..f5c38a22 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -7,6 +7,8 @@ class DeploySocialDistributedPress < Deploy # Solo luego de publicar remotamente DEPENDENCIES = %i[deploy_distributed_press deploy_rsync deploy_full_rsync] + after_save :create_hooks! + # Envía las notificaciones def deploy(output: false) with_tempfile(site.private_key_pem) do |file| @@ -52,4 +54,41 @@ class DeploySocialDistributedPress < Deploy def flags_for_build(**args) "--key #{Shellwords.escape args[:private_key].path}" end + + private + + # Obtiene el hostname de la API de Sutty + # + # @return [String] + def api_hostname + Rails.application.routes.default_url_options[:host].sub('panel', 'api') + end + + # Crea los hooks en la Social Inbox para que nos avise de actividades + # nuevas + # + # @return [nil] + def create_hooks! + hook_client = site.social_inbox.hook + + hook_client.class::EVENTS.each do |event| + event_url = :"v1_site_webhooks_social_inbox_#{event}_url" + + webhook = DistributedPress::V1::Social::Schemas::Webhook.new.call({ + method: 'POST', + url: Rails.application.routes.url_helpers.public_send(event_url, site_id: site.name, host: api_hostname), + headers: { + 'X-Social-Inbox': rol.token + } + }) + + raise ArgumentError, webhook.errors.messages if webhook.failure? + + response = hook_client.put(event: event, hook: webhook) + + raise ArgumentError, response.parsed_body unless response.ok? + rescue ArgumentError => e + ExceptionNotifier.notify_exception(e, data: { site_id: site.name, usuarie_id: rol.usuarie_id }) + end + end end diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 47c3457c..8aa5b504 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'distributed_press/v1/social/client' +require 'distributed_press/v1/social/hook' # Gestiona la Social Inbox de un sitio class SocialInbox @@ -34,6 +35,11 @@ class SocialInbox ) end + # @return [DistributedPress::V1::Social::Hook] + def hook + @hook ||= DistributedPress::V1::Social::Hook.new(client: client, actor: actor) + end + # @return [String] def public_key_url @public_key_url ||= URI("https://#{hostname}").tap do |uri| diff --git a/config/application.rb b/config/application.rb index 529e341a..73a7a884 100644 --- a/config/application.rb +++ b/config/application.rb @@ -2,6 +2,9 @@ require_relative 'boot' +require 'redis-client' +require 'hiredis-client' +require 'brs' require 'rails' # Pick the frameworks you want: require 'active_model/railtie' diff --git a/config/routes.rb b/config/routes.rb index 8186b64e..f6f081f7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,12 @@ Rails.application.routes.draw do namespace :webhooks do post :pull, to: 'webhooks#pull' + + namespace :social_inbox do + post :moderationqueued, to: 'social_inbox#moderationqueued' + post :onapproved, to: 'social_inbox#onapproved' + post :onrejected, to: 'social_inbox#onrejected' + end end end end From b4117d7c348b63b0af2efa86bbb3b79d582ec1ad Mon Sep 17 00:00:00 2001 From: f Date: Sat, 17 Feb 2024 14:11:17 -0300 Subject: [PATCH 007/297] chore: rubocop --- .../api/v1/concerns/webhook_concern.rb | 13 ++++++------ app/models/deploy.rb | 8 ++++---- app/models/deploy_social_distributed_press.rb | 20 +++++++++++-------- app/models/site/social_distributed_press.rb | 3 ++- app/models/social_inbox.rb | 5 +---- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/app/controllers/api/v1/concerns/webhook_concern.rb b/app/controllers/api/v1/concerns/webhook_concern.rb index 59d48b28..9960e550 100644 --- a/app/controllers/api/v1/concerns/webhook_concern.rb +++ b/app/controllers/api/v1/concerns/webhook_concern.rb @@ -18,13 +18,14 @@ module Api def token @token ||= begin - _headers = request.headers - _token ||= _headers['X-Gitlab-Token'].presence - _token ||= token_from_signature(_headers['X-Gitea-Signature'].presence) - _token ||= token_from_signature(_headers['X-Hub-Signature-256'].presence, 'sha256=') - _token + header = request.headers + token = header['X-Social-Inbox'].presence + token ||= header['X-Gitlab-Token'].presence + token ||= token_from_signature(header['X-Gitea-Signature'].presence) + token ||= token_from_signature(header['X-Hub-Signature-256'].presence, 'sha256=') + token ensure - raise ActiveRecord::RecordNotFound, 'Proveedor no soportado' if _token.blank? + raise ActiveRecord::RecordNotFound, 'Proveedor no soportado' if token.blank? end end diff --git a/app/models/deploy.rb b/app/models/deploy.rb index 8f28f214..77646034 100644 --- a/app/models/deploy.rb +++ b/app/models/deploy.rb @@ -14,8 +14,8 @@ class Deploy < ApplicationRecord has_many :build_stats, dependent: :destroy - DEPENDENCIES = [] - SOFT_DEPENDENCIES = [] + DEPENDENCIES = [].freeze + SOFT_DEPENDENCIES = [].freeze def deploy(**) raise NotImplementedError @@ -74,7 +74,7 @@ class Deploy < ApplicationRecord 'HOME' => home_dir, 'PATH' => paths.join(':'), 'JEKYLL_ENV' => Rails.env, - 'LANG' => ENV['LANG'], + 'LANG' => ENV.fetch('LANG', nil) }) end @@ -139,7 +139,7 @@ class Deploy < ApplicationRecord # provisto con el archivo como parámetro # # @param :content [String] - def with_tempfile(content, &block) + def with_tempfile(content) Tempfile.create(SecureRandom.hex) do |file| file.write content.to_s file.rewind diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index f5c38a22..fc0e01d5 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -5,7 +5,7 @@ require 'distributed_press/v1/social/client' # Publicar novedades al Fediverso class DeploySocialDistributedPress < Deploy # Solo luego de publicar remotamente - DEPENDENCIES = %i[deploy_distributed_press deploy_rsync deploy_full_rsync] + DEPENDENCIES = %i[deploy_distributed_press deploy_rsync deploy_full_rsync].freeze after_save :create_hooks! @@ -70,17 +70,21 @@ class DeploySocialDistributedPress < Deploy # @return [nil] def create_hooks! hook_client = site.social_inbox.hook + webhook_class = DistributedPress::V1::Social::Schemas::Webhook hook_client.class::EVENTS.each do |event| event_url = :"v1_site_webhooks_social_inbox_#{event}_url" - webhook = DistributedPress::V1::Social::Schemas::Webhook.new.call({ - method: 'POST', - url: Rails.application.routes.url_helpers.public_send(event_url, site_id: site.name, host: api_hostname), - headers: { - 'X-Social-Inbox': rol.token - } - }) + webhook = + webhook_class.new.call({ + method: 'POST', + url: Rails.application.routes.url_helpers.public_send( + event_url, site_id: site.name, host: api_hostname + ), + headers: { + 'X-Social-Inbox': rol.token + } + }) raise ArgumentError, webhook.errors.messages if webhook.failure? diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index 1193ca76..d3ebf579 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -25,7 +25,8 @@ class Site def generate_private_key_pem! self.private_key_pem ||= DistributedPress::V1::Social::Client.new( public_key_url: nil, - key_size: 2048).private_key.export + key_size: 2048 + ).private_key.export end end end diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 8aa5b504..24f749be 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -50,9 +50,6 @@ class SocialInbox def hostname @hostname ||= - begin - host = site.config.dig('activity_pub', 'hostname') - host ||= site.hostname - end + site.config.dig('activity_pub', 'hostname') || site.hostname end end From 27c0ca655eef45d61523f577dc9cc34cee0d0afe Mon Sep 17 00:00:00 2001 From: f Date: Tue, 20 Feb 2024 14:44:52 -0300 Subject: [PATCH 008/297] feat: modelo de datos de activitypub #15109 --- app/models/activity_pub.rb | 33 ++++ app/models/activity_pub/activity.rb | 24 +++ app/models/activity_pub/activity/create.rb | 3 + app/models/activity_pub/activity/delete.rb | 3 + app/models/activity_pub/activity/flag.rb | 3 + app/models/activity_pub/activity/follow.rb | 7 + app/models/activity_pub/activity/generic.rb | 3 + app/models/activity_pub/activity/undo.rb | 8 + app/models/activity_pub/activity/update.rb | 3 + app/models/activity_pub/actor.rb | 14 ++ .../activity_pub/concerns/json_ld_concern.rb | 32 ++++ app/models/activity_pub/instance.rb | 21 +++ app/models/activity_pub/object.rb | 11 ++ app/models/activity_pub/object/application.rb | 6 + app/models/activity_pub/object/article.rb | 6 + app/models/activity_pub/object/generic.rb | 4 + app/models/activity_pub/object/note.rb | 6 + .../activity_pub/object/organization.rb | 6 + app/models/activity_pub/object/person.rb | 6 + ...19153919_create_activity_pub_activities.rb | 16 ++ ...240219175839_create_activity_pub_actors.rb | 12 ++ .../20240219204011_create_activity_pubs.rb | 18 ++ ...40219204224_create_activity_pub_objects.rb | 17 ++ ...220161414_create_activity_pub_instances.rb | 13 ++ db/structure.sql | 161 +++++++++++++++++- 25 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 app/models/activity_pub.rb create mode 100644 app/models/activity_pub/activity.rb create mode 100644 app/models/activity_pub/activity/create.rb create mode 100644 app/models/activity_pub/activity/delete.rb create mode 100644 app/models/activity_pub/activity/flag.rb create mode 100644 app/models/activity_pub/activity/follow.rb create mode 100644 app/models/activity_pub/activity/generic.rb create mode 100644 app/models/activity_pub/activity/undo.rb create mode 100644 app/models/activity_pub/activity/update.rb create mode 100644 app/models/activity_pub/actor.rb create mode 100644 app/models/activity_pub/concerns/json_ld_concern.rb create mode 100644 app/models/activity_pub/instance.rb create mode 100644 app/models/activity_pub/object.rb create mode 100644 app/models/activity_pub/object/application.rb create mode 100644 app/models/activity_pub/object/article.rb create mode 100644 app/models/activity_pub/object/generic.rb create mode 100644 app/models/activity_pub/object/note.rb create mode 100644 app/models/activity_pub/object/organization.rb create mode 100644 app/models/activity_pub/object/person.rb create mode 100644 db/migrate/20240219153919_create_activity_pub_activities.rb create mode 100644 db/migrate/20240219175839_create_activity_pub_actors.rb create mode 100644 db/migrate/20240219204011_create_activity_pubs.rb create mode 100644 db/migrate/20240219204224_create_activity_pub_objects.rb create mode 100644 db/migrate/20240220161414_create_activity_pub_instances.rb diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb new file mode 100644 index 00000000..c0474b89 --- /dev/null +++ b/app/models/activity_pub.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# = ActivityPub = +# +# El registro de actividades recibidas y su estado. Cuando recibimos +# una actividad, puede estar destinada a varies actores dentro de Sutty, +# con lo que generamos una cola para cada une. +# +# @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions} +class ActivityPub < ApplicationRecord + include AASM + + belongs_to :site + belongs_to :object, polymorphic: true + has_many :activities + + validates :site_id, presence: true + validates :object_id, presence: true + validates :aasm_state, presence: true, inclusion: { in: %w[paused approved rejected reported deleted] } + + aasm do + # Todavía no hay una decisión sobre el objeto + state :paused, initial: true + # Le usuarie aprobó el objeto + state :approved + # Le usuarie rechazó el objeto + state :rejected + # Le usuarie reportó el objeto + state :reported + # Le actore eliminó el objeto + state :deleted + end +end diff --git a/app/models/activity_pub/activity.rb b/app/models/activity_pub/activity.rb new file mode 100644 index 00000000..4a88c1f3 --- /dev/null +++ b/app/models/activity_pub/activity.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# = Activity = +# +# Lleva un registro de las actividades que nos piden hacer remotamente. +# +# Las actividades pueden tener distintos destinataries (sitios/actores). +# +# @todo Obtener el contenido del objeto dinámicamente si no existe +# localmente, por ejemplo cuando la actividad crea un objeto pero lo +# envía como referencia en lugar de anidarlo. +# +# @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions} +class ActivityPub::Activity < ApplicationRecord + include ActivityPub::Concerns::JsonLdConcern + + belongs_to :activity_pub + has_one :object, through: :activity_pub + + validates :activity_pub_id, presence: true + + # Siempre en orden descendiente para saber el último estado + default_scope -> { order(created_at: :desc) } +end diff --git a/app/models/activity_pub/activity/create.rb b/app/models/activity_pub/activity/create.rb new file mode 100644 index 00000000..3dcba5c2 --- /dev/null +++ b/app/models/activity_pub/activity/create.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +class ActivityPub::Activity::Create < ActivityPub::Activity; end diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb new file mode 100644 index 00000000..f3684a0f --- /dev/null +++ b/app/models/activity_pub/activity/delete.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +class ActivityPub::Activity::Delete < ActivityPub::Activity; end diff --git a/app/models/activity_pub/activity/flag.rb b/app/models/activity_pub/activity/flag.rb new file mode 100644 index 00000000..2911911e --- /dev/null +++ b/app/models/activity_pub/activity/flag.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +class ActivityPub::Activity::Flag < ActivityPub::Activity; end diff --git a/app/models/activity_pub/activity/follow.rb b/app/models/activity_pub/activity/follow.rb new file mode 100644 index 00000000..c22dfd51 --- /dev/null +++ b/app/models/activity_pub/activity/follow.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# = Follow = +# +# Una actividad de seguimiento se refiere siempre a une actore (el +# sitio) y proviene de otre actore. +class ActivityPub::Activity::Follow < ActivityPub::Activity; end diff --git a/app/models/activity_pub/activity/generic.rb b/app/models/activity_pub/activity/generic.rb new file mode 100644 index 00000000..8bf76471 --- /dev/null +++ b/app/models/activity_pub/activity/generic.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +class ActivityPub::Activity::Generic < ActivityPub::Activity; end diff --git a/app/models/activity_pub/activity/undo.rb b/app/models/activity_pub/activity/undo.rb new file mode 100644 index 00000000..a4915394 --- /dev/null +++ b/app/models/activity_pub/activity/undo.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# = Undo = +# +# Deshace una actividad, dependiendo de la actividad a la que se +# refiere. +class ActivityPub::Activity::Undo < ActivityPub::Activity +end diff --git a/app/models/activity_pub/activity/update.rb b/app/models/activity_pub/activity/update.rb new file mode 100644 index 00000000..8089cdcf --- /dev/null +++ b/app/models/activity_pub/activity/update.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +class ActivityPub::Activity::Update < ActivityPub::Activity; end diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb new file mode 100644 index 00000000..f29c382a --- /dev/null +++ b/app/models/activity_pub/actor.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# = Actor = +# +# Actor es la entidad que realiza acciones en ActivityPub +# +# @todo Obtener el perfil dinámicamente +class ActivityPub::Actor < ApplicationRecord + include ActivityPub::Concerns::JsonLdConcern + + belongs_to :instance + has_many :activity_pubs, as: :object + has_many :objects +end diff --git a/app/models/activity_pub/concerns/json_ld_concern.rb b/app/models/activity_pub/concerns/json_ld_concern.rb new file mode 100644 index 00000000..b0899606 --- /dev/null +++ b/app/models/activity_pub/concerns/json_ld_concern.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class ActivityPub + module Concerns + module JsonLdConcern + extend ActiveSupport::Concern + + included do + validates :uri, presence: true, uniqueness: true + + # Cuando asignamos contenido, obtener la URI si no lo hicimos ya + before_save :uri_from_content!, unless: :uri? + + # Obtiene un tipo de actividad a partir del tipo informado + # + # @param object [Hash] + # @return [Activity] + def self.type_from(object) + "#{self.class.name}::#{object[:type].presence || 'Generic'}".constantize + rescue NameError + self.class::Generic + end + + private + + def uri_from_content! + self.uri = content[:id] + end + end + end + end +end diff --git a/app/models/activity_pub/instance.rb b/app/models/activity_pub/instance.rb new file mode 100644 index 00000000..fe4a777b --- /dev/null +++ b/app/models/activity_pub/instance.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# = Instance = +# +# Representa cada instancia del fediverso que interactúa con la Social +# Inbox. +class ActivityPub::Instance < ApplicationRecord + include AASM + + validates :aasm_state, presence: true, inclusion: { in: %w[paused allowed blocked] } + validates :hostname, uniqueness: true, hostname: true + + has_many :activity_pubs + has_many :actors + + aasm do + state :paused, initial: true + state :allowed + state :blocked + end +end diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb new file mode 100644 index 00000000..519749ef --- /dev/null +++ b/app/models/activity_pub/object.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# Almacena objetos de ActivityPub, como Note, Article, etc. +class ActivityPub::Object < ApplicationRecord + include ActivityPub::Concerns::JsonLdConcern + + belongs_to :actor + has_many :activity_pubs, as: :object + + validates :actor_id, presence: true +end diff --git a/app/models/activity_pub/object/application.rb b/app/models/activity_pub/object/application.rb new file mode 100644 index 00000000..e8a6f97c --- /dev/null +++ b/app/models/activity_pub/object/application.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# = Application = +# +# Una aplicación o instancia +class ActivityPub::Object::Application < ActivityPub::Object; end diff --git a/app/models/activity_pub/object/article.rb b/app/models/activity_pub/object/article.rb new file mode 100644 index 00000000..ad1a6131 --- /dev/null +++ b/app/models/activity_pub/object/article.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# = Article = +# +# Representa artículos +class ActivityPub::Object::Article < ActivityPub::Object; end diff --git a/app/models/activity_pub/object/generic.rb b/app/models/activity_pub/object/generic.rb new file mode 100644 index 00000000..f345e7a9 --- /dev/null +++ b/app/models/activity_pub/object/generic.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# = Generic = +class ActivityPub::Object::Generic < ActivityPub::Object; end diff --git a/app/models/activity_pub/object/note.rb b/app/models/activity_pub/object/note.rb new file mode 100644 index 00000000..0f84c747 --- /dev/null +++ b/app/models/activity_pub/object/note.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# = Note = +# +# Representa notas, el tipo más común de objeto del Fediverso. +class ActivityPub::Object::Note < ActivityPub::Object; end diff --git a/app/models/activity_pub/object/organization.rb b/app/models/activity_pub/object/organization.rb new file mode 100644 index 00000000..a5327d10 --- /dev/null +++ b/app/models/activity_pub/object/organization.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# = Organization = +# +# Una organización +class ActivityPub::Object::Organization < ActivityPub::Object; end diff --git a/app/models/activity_pub/object/person.rb b/app/models/activity_pub/object/person.rb new file mode 100644 index 00000000..98a1568d --- /dev/null +++ b/app/models/activity_pub/object/person.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# = Person = +# +# Una persona, el perfil de une actore +class ActivityPub::Object::Person < ActivityPub::Object; end diff --git a/db/migrate/20240219153919_create_activity_pub_activities.rb b/db/migrate/20240219153919_create_activity_pub_activities.rb new file mode 100644 index 00000000..555656ad --- /dev/null +++ b/db/migrate/20240219153919_create_activity_pub_activities.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Actividades. Se asocian a un objeto y a una cola de moderación +class CreateActivityPubActivities < ActiveRecord::Migration[6.1] + def change + create_table :activity_pub_activities, id: :uuid do |t| + t.timestamps + + t.uuid :activity_pub_id, index: true, null: false + + t.string :type, null: false + t.string :uri, null: false + t.jsonb :content, default: {} + end + end +end diff --git a/db/migrate/20240219175839_create_activity_pub_actors.rb b/db/migrate/20240219175839_create_activity_pub_actors.rb new file mode 100644 index 00000000..656b3f63 --- /dev/null +++ b/db/migrate/20240219175839_create_activity_pub_actors.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Almacena actores de ActivityPub y los relaciona con actividades +class CreateActivityPubActors < ActiveRecord::Migration[6.1] + def change + create_table :activity_pub_actors, id: :uuid do |t| + t.timestamps + t.uuid :instance_id, index: true, null: false + t.string :uri, index: true, unique: true, null: false + end + end +end diff --git a/db/migrate/20240219204011_create_activity_pubs.rb b/db/migrate/20240219204011_create_activity_pubs.rb new file mode 100644 index 00000000..cf797fc8 --- /dev/null +++ b/db/migrate/20240219204011_create_activity_pubs.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Registro de actividades. +class CreateActivityPubs < ActiveRecord::Migration[6.1] + def change + create_table :activity_pubs, id: :uuid do |t| + t.timestamps + + t.bigint :site_id, null: false + t.uuid :object_id, null: false + t.string :object_type, null: false + + t.string :aasm_state, null: false + + t.index %i[site_id object_id object_type], unique: true + end + end +end diff --git a/db/migrate/20240219204224_create_activity_pub_objects.rb b/db/migrate/20240219204224_create_activity_pub_objects.rb new file mode 100644 index 00000000..865589ab --- /dev/null +++ b/db/migrate/20240219204224_create_activity_pub_objects.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Almacena objetos de ActivityPub. Los objetos pueden estar compartidos +# por toda la instancia. +class CreateActivityPubObjects < ActiveRecord::Migration[6.1] + def change + create_table :activity_pub_objects, id: :uuid do |t| + t.timestamps + + t.uuid :actor_id, index: true, null: false + + t.string :type, null: false + t.string :uri, null: false, unique: true + t.jsonb :content, default: {} + end + end +end diff --git a/db/migrate/20240220161414_create_activity_pub_instances.rb b/db/migrate/20240220161414_create_activity_pub_instances.rb new file mode 100644 index 00000000..feb9351d --- /dev/null +++ b/db/migrate/20240220161414_create_activity_pub_instances.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Almacena las instancias +class CreateActivityPubInstances < ActiveRecord::Migration[6.1] + def change + create_table :activity_pub_instances, id: :uuid do |t| + t.timestamps + t.string :hostname, index: true, unique: true, null: false + t.string :aasm_state, null: false + t.jsonb :content, default: {} + end + end +end diff --git a/db/structure.sql b/db/structure.sql index dede286d..723c9e99 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -473,6 +473,78 @@ CREATE SEQUENCE public.active_storage_variant_records_id_seq ALTER SEQUENCE public.active_storage_variant_records_id_seq OWNED BY public.active_storage_variant_records.id; +-- +-- Name: activity_pub_activities; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pub_activities ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + activity_pub_id uuid NOT NULL, + type character varying NOT NULL, + uri character varying NOT NULL, + content jsonb DEFAULT '{}'::jsonb +); + + +-- +-- Name: activity_pub_actors; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pub_actors ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + instance_id uuid NOT NULL, + uri character varying NOT NULL +); + + +-- +-- Name: activity_pub_instances; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pub_instances ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + hostname character varying NOT NULL, + aasm_state character varying NOT NULL, + content jsonb DEFAULT '{}'::jsonb +); + + +-- +-- Name: activity_pub_objects; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pub_objects ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + actor_id uuid NOT NULL, + type character varying NOT NULL, + uri character varying NOT NULL, + content jsonb DEFAULT '{}'::jsonb +); + + +-- +-- Name: activity_pubs; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pubs ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + site_id bigint NOT NULL, + object_id uuid NOT NULL, + object_type character varying NOT NULL, + aasm_state character varying NOT NULL +); + + -- -- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: - -- @@ -1565,6 +1637,46 @@ ALTER TABLE ONLY public.active_storage_variant_records ADD CONSTRAINT active_storage_variant_records_pkey PRIMARY KEY (id); +-- +-- Name: activity_pub_activities activity_pub_activities_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pub_activities + ADD CONSTRAINT activity_pub_activities_pkey PRIMARY KEY (id); + + +-- +-- Name: activity_pub_actors activity_pub_actors_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pub_actors + ADD CONSTRAINT activity_pub_actors_pkey PRIMARY KEY (id); + + +-- +-- Name: activity_pub_instances activity_pub_instances_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pub_instances + ADD CONSTRAINT activity_pub_instances_pkey PRIMARY KEY (id); + + +-- +-- Name: activity_pub_objects activity_pub_objects_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pub_objects + ADD CONSTRAINT activity_pub_objects_pkey PRIMARY KEY (id); + + +-- +-- Name: activity_pubs activity_pubs_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pubs + ADD CONSTRAINT activity_pubs_pkey PRIMARY KEY (id); + + -- -- Name: blazer_audits blazer_audits_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1865,6 +1977,48 @@ CREATE UNIQUE INDEX index_active_storage_blobs_on_key_and_service_name ON public CREATE UNIQUE INDEX index_active_storage_variant_records_uniqueness ON public.active_storage_variant_records USING btree (blob_id, variation_digest); +-- +-- Name: index_activity_pub_activities_on_activity_pub_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_activities_on_activity_pub_id ON public.activity_pub_activities USING btree (activity_pub_id); + + +-- +-- Name: index_activity_pub_actors_on_instance_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_actors_on_instance_id ON public.activity_pub_actors USING btree (instance_id); + + +-- +-- Name: index_activity_pub_actors_on_uri; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_actors_on_uri ON public.activity_pub_actors USING btree (uri); + + +-- +-- Name: index_activity_pub_instances_on_hostname; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_instances_on_hostname ON public.activity_pub_instances USING btree (hostname); + + +-- +-- Name: index_activity_pub_objects_on_actor_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_objects_on_actor_id ON public.activity_pub_objects USING btree (actor_id); + + +-- +-- Name: index_activity_pubs_on_site_id_and_object_id_and_object_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_activity_pubs_on_site_id_and_object_id_and_object_type ON public.activity_pubs USING btree (site_id, object_id, object_type); + + -- -- Name: index_blazer_audits_on_query_id; Type: INDEX; Schema: public; Owner: - -- @@ -2320,6 +2474,11 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230829204127'), ('20230921155401'), ('20230927153926'), -('20240216170202'); +('20240216170202'), +('20240219153919'), +('20240219175839'), +('20240219204011'), +('20240219204224'), +('20240220161414'); From 8921de81aef45c3d822144fc1f2e4bbb38fe0f3e Mon Sep 17 00:00:00 2001 From: f Date: Tue, 20 Feb 2024 17:13:42 -0300 Subject: [PATCH 009/297] feat: rutas --- .../api/v1/concerns/webhook_concern.rb | 72 ------------------ .../api/v1/social_inbox_controller.rb | 22 ------ .../v1/webhooks/concerns/webhook_concern.rb | 76 +++++++++++++++++++ .../api/v1/webhooks/pull_controller.rb | 25 ++++++ .../v1/webhooks/social_inbox_controller.rb | 39 ++++++++++ app/controllers/api/v1/webhooks_controller.rb | 23 ------ config/routes.rb | 4 +- 7 files changed, 142 insertions(+), 119 deletions(-) delete mode 100644 app/controllers/api/v1/concerns/webhook_concern.rb delete mode 100644 app/controllers/api/v1/social_inbox_controller.rb create mode 100644 app/controllers/api/v1/webhooks/concerns/webhook_concern.rb create mode 100644 app/controllers/api/v1/webhooks/pull_controller.rb create mode 100644 app/controllers/api/v1/webhooks/social_inbox_controller.rb delete mode 100644 app/controllers/api/v1/webhooks_controller.rb diff --git a/app/controllers/api/v1/concerns/webhook_concern.rb b/app/controllers/api/v1/concerns/webhook_concern.rb deleted file mode 100644 index 9960e550..00000000 --- a/app/controllers/api/v1/concerns/webhook_concern.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -module Api - module V1 - # Helpers para webhooks - module WebhookConcern - extend ActiveSupport::Concern - - included do - # Responde con forbidden si falla la validación del token - rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer - - private - - # Valida el token que envía la plataforma en el webhook - # - # @return [String] - def token - @token ||= - begin - header = request.headers - token = header['X-Social-Inbox'].presence - token ||= header['X-Gitlab-Token'].presence - token ||= token_from_signature(header['X-Gitea-Signature'].presence) - token ||= token_from_signature(header['X-Hub-Signature-256'].presence, 'sha256=') - token - ensure - raise ActiveRecord::RecordNotFound, 'Proveedor no soportado' if token.blank? - end - end - - # Valida token a partir de firma - # - # @param signature [String,nil] - # @param prepend [String] - # @return [String, nil] - def token_from_signature(signature, prepend = '') - return if signature.nil? - - payload = request.raw_post - - site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token| - new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload) - - ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s) - end - end - - # Encuentra el sitio a partir de la URL - # - # @return [Site] - def site - @site ||= Site.find_by_name!(params[:site_id]) - end - - # Encuentra le usuarie - # - # @return [Site] - def usuarie - @usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie - end - - # Respuesta de error a plataformas - def platforms_answer(exception) - ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }) - - head :forbidden - end - end - end - end -end diff --git a/app/controllers/api/v1/social_inbox_controller.rb b/app/controllers/api/v1/social_inbox_controller.rb deleted file mode 100644 index 3881b6bc..00000000 --- a/app/controllers/api/v1/social_inbox_controller.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Api - module V1 - # Recibe webhooks de la Social Inbox - class SocialInboxController < BaseController - include WebhookConcern - - def moderationqueued - head :accepted - end - - def onapproved - head :accepted - end - - def onrejected - head :accepted - end - end - end -end diff --git a/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb b/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb new file mode 100644 index 00000000..a546a55c --- /dev/null +++ b/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module Api + module V1 + module Webhooks + module Concerns + # Helpers para webhooks + module WebhookConcern + extend ActiveSupport::Concern + + included do + # Responde con forbidden si falla la validación del token + rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer + + private + + # Valida el token que envía la plataforma en el webhook + # + # @return [String] + def token + @token ||= + begin + header = request.headers + token = header['X-Social-Inbox'].presence + token ||= header['X-Gitlab-Token'].presence + token ||= token_from_signature(header['X-Gitea-Signature'].presence) + token ||= token_from_signature(header['X-Hub-Signature-256'].presence, 'sha256=') + token + ensure + raise ActiveRecord::RecordNotFound, 'Proveedor no soportado' if token.blank? + end + end + + # Valida token a partir de firma + # + # @param signature [String,nil] + # @param prepend [String] + # @return [String, nil] + def token_from_signature(signature, prepend = '') + return if signature.nil? + + payload = request.raw_post + + site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token| + new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload) + + ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s) + end + end + + # Encuentra el sitio a partir de la URL + # + # @return [Site] + def site + @site ||= Site.find_by_name!(params[:site_id]) + end + + # Encuentra le usuarie + # + # @return [Site] + def usuarie + @usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie + end + + # Respuesta de error a plataformas + def platforms_answer(exception) + ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }) + + head :forbidden + end + end + end + end + end + end +end diff --git a/app/controllers/api/v1/webhooks/pull_controller.rb b/app/controllers/api/v1/webhooks/pull_controller.rb new file mode 100644 index 00000000..5f0b703b --- /dev/null +++ b/app/controllers/api/v1/webhooks/pull_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Api + module V1 + module Webhooks + # Recibe webhooks y lanza un PullJob + class PullController < BaseController + include WebhookConcern + + # Trae los cambios a partir de un post de Webhooks: + # (Gitlab, Github, Gitea, etc) + # + # @return [nil] + def pull + message = I18n.with_locale(site.default_locale) do + I18n.t('webhooks.pull.message') + end + + GitPullJob.perform_later(site, usuarie, message) + head :ok + end + end + end + end +end diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb new file mode 100644 index 00000000..bc604156 --- /dev/null +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Api + module V1 + module Webhooks + # Recibe webhooks de la Social Inbox + # + # @see {https://www.w3.org/TR/activitypub/} + class SocialInboxController < BaseController + include Api::V1::Webhooks::Concerns::WebhookConcern + + # Cuando una actividad ingresa en la cola de moderación, la + # recibimos por acá + # + # Vamos a recibir Create, Update, Delete, Follow, Undo y obtener + # el objeto dentro de cada una para guardar un estado asociado + # al sitio. + # + # El objeto del estado puede ser un objeto o une actore, + # dependiendo de la actividad. + def moderationqueued + head :accepted + end + + # Cuando aprobamos una actividad, recibimos la confirmación y + # cambiamos el estado + def onapproved + head :accepted + end + + # Cuando rechazamos una actividad, recibimos la confirmación y + # cambiamos el estado + def onrejected + head :accepted + end + end + end + end +end diff --git a/app/controllers/api/v1/webhooks_controller.rb b/app/controllers/api/v1/webhooks_controller.rb deleted file mode 100644 index f64fa93c..00000000 --- a/app/controllers/api/v1/webhooks_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Api - module V1 - # Recibe webhooks y lanza un PullJob - class WebhooksController < BaseController - include WebhookConcern - - # Trae los cambios a partir de un post de Webhooks: - # (Gitlab, Github, Gitea, etc) - # - # @return [nil] - def pull - message = I18n.with_locale(site.default_locale) do - I18n.t('webhooks.pull.message') - end - - GitPullJob.perform_later(site, usuarie, message) - head :ok - end - end - end -end diff --git a/config/routes.rb b/config/routes.rb index f6f081f7..88376dde 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,9 +19,9 @@ Rails.application.routes.draw do post :'contact/:form', to: 'contact#receive', as: :contact namespace :webhooks do - post :pull, to: 'webhooks#pull' + post :pull, to: 'pull#pull' - namespace :social_inbox do + scope :social_inbox do post :moderationqueued, to: 'social_inbox#moderationqueued' post :onapproved, to: 'social_inbox#onapproved' post :onrejected, to: 'social_inbox#onrejected' From 9fa43353144d01154dca75cf88bd27a4c879dc03 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 20 Feb 2024 17:15:43 -0300 Subject: [PATCH 010/297] feat: recibir y almacenar actividades --- Gemfile | 3 + Gemfile.lock | 237 ++++++++++-------- .../v1/webhooks/social_inbox_controller.rb | 105 ++++++++ app/models/activity_pub/activity.rb | 5 + .../activity_pub/concerns/json_ld_concern.rb | 6 +- app/models/site/social_distributed_press.rb | 2 + app/models/social_inbox.rb | 19 +- 7 files changed, 262 insertions(+), 115 deletions(-) diff --git a/Gemfile b/Gemfile index 3cf01934..8e955e8f 100644 --- a/Gemfile +++ b/Gemfile @@ -80,6 +80,9 @@ gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git' gem 'kaminari' gem 'device_detector' +gem 'after_commit_everywhere' +gem 'aasm' + # database gem 'hairtrigger' gem 'pg' diff --git a/Gemfile.lock b/Gemfile.lock index 5b8ca619..50745140 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -27,74 +27,80 @@ GIT GEM remote: https://17.3.alpine.gems.sutty.nl/ specs: - actioncable (6.1.7.3) - actionpack (= 6.1.7.3) - activesupport (= 6.1.7.3) + aasm (5.5.0) + concurrent-ruby (~> 1.0) + actioncable (6.1.7.4) + actionpack (= 6.1.7.4) + activesupport (= 6.1.7.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.3) - actionpack (= 6.1.7.3) - activejob (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + actionmailbox (6.1.7.4) + actionpack (= 6.1.7.4) + activejob (= 6.1.7.4) + activerecord (= 6.1.7.4) + activestorage (= 6.1.7.4) + activesupport (= 6.1.7.4) mail (>= 2.7.1) - actionmailer (6.1.7.3) - actionpack (= 6.1.7.3) - actionview (= 6.1.7.3) - activejob (= 6.1.7.3) - activesupport (= 6.1.7.3) + actionmailer (6.1.7.4) + actionpack (= 6.1.7.4) + actionview (= 6.1.7.4) + activejob (= 6.1.7.4) + activesupport (= 6.1.7.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.3) - actionview (= 6.1.7.3) - activesupport (= 6.1.7.3) + actionpack (6.1.7.4) + actionview (= 6.1.7.4) + activesupport (= 6.1.7.4) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.3) - actionpack (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + actiontext (6.1.7.4) + actionpack (= 6.1.7.4) + activerecord (= 6.1.7.4) + activestorage (= 6.1.7.4) + activesupport (= 6.1.7.4) nokogiri (>= 1.8.5) - actionview (6.1.7.3) - activesupport (= 6.1.7.3) + actionview (6.1.7.4) + activesupport (= 6.1.7.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.7.3) - activesupport (= 6.1.7.3) + activejob (6.1.7.4) + activesupport (= 6.1.7.4) globalid (>= 0.3.6) - activemodel (6.1.7.3) - activesupport (= 6.1.7.3) - activerecord (6.1.7.3) - activemodel (= 6.1.7.3) - activesupport (= 6.1.7.3) - activestorage (6.1.7.3) - actionpack (= 6.1.7.3) - activejob (= 6.1.7.3) - activerecord (= 6.1.7.3) - activesupport (= 6.1.7.3) + activemodel (6.1.7.4) + activesupport (= 6.1.7.4) + activerecord (6.1.7.4) + activemodel (= 6.1.7.4) + activesupport (= 6.1.7.4) + activestorage (6.1.7.4) + actionpack (= 6.1.7.4) + activejob (= 6.1.7.4) + activerecord (= 6.1.7.4) + activesupport (= 6.1.7.4) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.3) + activesupport (6.1.7.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.4) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) adsp (1.0.10) + after_commit_everywhere (1.4.0) + activerecord (>= 4.2) + activesupport ast (2.4.2) autoprefixer-rails (10.4.13.0) execjs (~> 2) - bcrypt (3.1.19-x86_64-linux-musl) + bcrypt (3.1.20-x86_64-linux-musl) bcrypt_pbkdf (1.1.0-x86_64-linux-musl) benchmark-ips (2.12.0) + bigdecimal (3.1.1) bindex (0.8.1-x86_64-linux-musl) blazer (2.6.5) activerecord (>= 5) @@ -105,7 +111,8 @@ GEM autoprefixer-rails (>= 9.1.0) popper_js (>= 1.16.1, < 2) sassc-rails (>= 2.0.0) - brakeman (5.4.1) + brakeman (6.1.1) + racc builder (3.2.4) bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) @@ -125,6 +132,7 @@ GEM concurrent-ruby (1.2.2) concurrent-ruby-ext (1.2.2-x86_64-linux-musl) concurrent-ruby (= 1.2.2) + connection_pool (2.4.1) crass (1.0.6) database_cleaner (2.0.2) database_cleaner-active_record (>= 2, < 3) @@ -132,7 +140,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.3-x86_64-linux-musl) + date (3.3.4-x86_64-linux-musl) dead_end (4.0.0) derailed_benchmarks (2.1.2) benchmark-ips (~> 2) @@ -146,8 +154,8 @@ GEM rake (> 10, < 14) ruby-statistics (>= 2.1) thor (>= 0.19, < 2) - device_detector (1.1.1) - devise (4.9.2) + device_detector (1.1.2) + devise (4.9.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -155,7 +163,7 @@ GEM warden (~> 1.2.3) devise-i18n (1.11.0) devise (>= 4.9.0) - devise_invitable (2.0.8) + devise_invitable (2.0.9) actionmailer (>= 5.0) devise (>= 4.6) distributed-press-api-client (0.4.0rc2) @@ -172,10 +180,10 @@ GEM railties (>= 3.2) down (5.4.1) addressable (~> 2.8) - dry-configurable (1.0.1) + dry-configurable (1.1.0) dry-core (~> 1.0, < 2) zeitwerk (~> 2.6) - dry-core (1.0.0) + dry-core (1.0.1) concurrent-ruby (~> 1.0) zeitwerk (~> 2.6) dry-inflector (1.0.0) @@ -184,7 +192,7 @@ GEM concurrent-ruby (~> 1.0) dry-core (~> 1.0, < 2) zeitwerk (~> 2.6) - dry-schema (1.13.1) + dry-schema (1.13.3) concurrent-ruby (~> 1.0) dry-configurable (~> 1.0, >= 1.0.1) dry-core (~> 1.0, < 2) @@ -192,7 +200,8 @@ GEM dry-logic (>= 1.4, < 2) dry-types (>= 1.7, < 2) zeitwerk (~> 2.6) - dry-types (1.7.1) + dry-types (1.7.2) + bigdecimal (~> 3.0) concurrent-ruby (~> 1.0) dry-core (~> 1.0) dry-inflector (~> 1.0) @@ -225,25 +234,25 @@ GEM ffi (~> 1.0) git_clone_url (2.0.0) uri-ssh_git (>= 2.0) - globalid (1.1.0) - activesupport (>= 5.0) + globalid (1.2.1) + activesupport (>= 6.1) groupdate (6.2.1) activesupport (>= 5.2) hairtrigger (1.0.0) activerecord (>= 6.0, < 8) ruby2ruby (~> 2.4) ruby_parser (~> 3.10) - haml (6.1.2-x86_64-linux-musl) + haml (6.3.0) temple (>= 0.8.2) thor tilt haml-lint (0.999.999) haml_lint - haml_lint (0.45.0) - haml (>= 4.0, < 6.2) + haml_lint (0.53.0) + haml (>= 5.0) parallel (~> 1.10) rainbow - rubocop (>= 0.50.0) + rubocop (>= 1.0) sysexits (~> 1.1) hamlit (3.0.3-x86_64-linux-musl) temple (>= 0.8.2) @@ -296,7 +305,7 @@ GEM terminal-table (~> 2.0) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) - jekyll-images (0.4.1) + jekyll-images (0.4.4) jekyll (~> 4) ruby-filemagic (~> 0.7) ruby-vips (~> 2) @@ -306,7 +315,7 @@ GEM sassc (> 2.0.1, < 3.0) jekyll-watch (2.2.1) listen (~> 3.0) - json (2.6.3-x86_64-linux-musl) + json (2.7.1-x86_64-linux-musl) jwt (2.6.0) kaminari (1.2.2) activesupport (>= 4.1.0) @@ -335,12 +344,12 @@ GEM loaf (0.10.0) railties (>= 3.2) lockbox (1.2.0) - lograge (0.12.0) + lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.21.3) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -354,36 +363,37 @@ GEM method_source (1.0.0) mini_histogram (0.3.1) mini_magick (4.12.0) - mini_mime (1.1.2) - mini_portile2 (2.8.2) - minitest (5.18.0) + mini_mime (1.1.5) + mini_portile2 (2.8.5) + minitest (5.21.1) mobility (1.2.9) i18n (>= 0.6.10, < 2) request_store (~> 1.0) multi_xml (0.6.0) - net-imap (0.3.4) + net-imap (0.4.9) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol - net-ssh (7.1.0) + net-ssh (7.2.1) netaddr (2.0.6) - nio4r (2.5.9-x86_64-linux-musl) - nokogiri (1.15.4-x86_64-linux-musl) + nio4r (2.7.0-x86_64-linux-musl) + nokogiri (1.16.0-x86_64-linux-musl) mini_portile2 (~> 2.8.2) racc (~> 1.4) orm_adapter (0.5.0) pairing_heap (3.0.1) - parallel (1.23.0) - parser (3.2.2.1) + parallel (1.24.0) + parser (3.2.2.3) ast (~> 2.4.1) + racc pathutil (0.16.2) forwardable-extended (~> 2.6) - pg (1.5.3-x86_64-linux-musl) + pg (1.5.4-x86_64-linux-musl) pg_search (2.3.6) activerecord (>= 5.2) activesupport (>= 5.2) @@ -393,55 +403,57 @@ GEM pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (5.0.3) - puma (6.3.1-x86_64-linux-musl) + public_suffix (5.0.4) + puma (6.4.2-x86_64-linux-musl) nio4r (~> 2.0) - pundit (2.3.0) + pundit (2.3.1) activesupport (>= 3.0.0) que (2.2.1) - racc (1.7.1-x86_64-linux-musl) - rack (2.2.7) + racc (1.7.3-x86_64-linux-musl) + rack (2.2.8) rack-cors (2.0.1) rack (>= 2.0.0) rack-mini-profiler (3.1.0) rack (>= 1.2.0) - rack-proxy (0.7.6) + rack-proxy (0.7.7) rack rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.3) - actioncable (= 6.1.7.3) - actionmailbox (= 6.1.7.3) - actionmailer (= 6.1.7.3) - actionpack (= 6.1.7.3) - actiontext (= 6.1.7.3) - actionview (= 6.1.7.3) - activejob (= 6.1.7.3) - activemodel (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + rails (6.1.7.4) + actioncable (= 6.1.7.4) + actionmailbox (= 6.1.7.4) + actionmailer (= 6.1.7.4) + actionpack (= 6.1.7.4) + actiontext (= 6.1.7.4) + actionview (= 6.1.7.4) + activejob (= 6.1.7.4) + activemodel (= 6.1.7.4) + activerecord (= 6.1.7.4) + activestorage (= 6.1.7.4) + activesupport (= 6.1.7.4) bundler (>= 1.15.0) - railties (= 6.1.7.3) + railties (= 6.1.7.4) sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) - rails-i18n (7.0.7) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + rails-i18n (7.0.8) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) rails_warden (0.6.0) warden (>= 1.2.0) - railties (6.1.7.3) - actionpack (= 6.1.7.3) - activesupport (= 6.1.7.3) + railties (6.1.7.4) + actionpack (= 6.1.7.4) + activesupport (= 6.1.7.4) method_source rake (>= 12.2) thor (~> 1.0) rainbow (3.1.1) - rake (13.0.6) + rake (13.1.0) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) @@ -464,13 +476,13 @@ GEM redis-store (>= 1.2, < 2) redis-store (1.9.2) redis (>= 4, < 6) - regexp_parser (2.8.0) + regexp_parser (2.9.0) request_store (1.5.1) rack (>= 1.4) - responders (3.1.0) + responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.2.5) + rexml (3.2.6) rgl (0.6.3) pairing_heap (>= 0.3.0) rexml (~> 3.2, >= 3.2.4) @@ -486,18 +498,19 @@ GEM rubocop-ast (>= 1.24.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.28.1) + rubocop-ast (1.30.0) parser (>= 3.2.1.0) - rubocop-rails (2.19.1) + rubocop-rails (2.23.1) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-brs (1.3.3-x86_64-linux-musl) adsp (~> 1.0) ruby-filemagic (0.7.3-x86_64-linux-musl) ruby-progressbar (1.13.0) ruby-statistics (3.0.2) - ruby-vips (2.1.4) + ruby-vips (2.2.0) ffi (~> 1.12) ruby2ruby (2.5.0) ruby_parser (~> 3.1) @@ -530,14 +543,14 @@ GEM spring-watcher-listen (2.1.0) listen (>= 2.7, < 4.0) spring (>= 4) - sprockets (4.2.0) + sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.6.3-x86_64-linux-musl) + sqlite3 (1.7.0-x86_64-linux-musl) mini_portile2 (~> 2.8.0) stackprof (0.2.25-x86_64-linux-musl) stream (0.5.5) @@ -546,13 +559,13 @@ GEM jekyll (~> 4) symbol-fstring (1.0.2-x86_64-linux-musl) sysexits (1.2.0) - temple (0.10.1) + temple (0.10.3) terminal-table (2.0.0) unicode-display_width (~> 1.1, >= 1.1.1) thor (1.3.0) - tilt (2.1.0) + tilt (2.3.0) timecop (0.9.6) - timeout (0.3.2) + timeout (0.4.1) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) @@ -562,7 +575,7 @@ GEM execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext - unf_ext (0.0.8.2-x86_64-linux-musl) + unf_ext (0.0.9-x86_64-linux-musl) unicode-display_width (1.8.0) uri-ssh_git (2.0.0) validates_hostname (1.0.13) @@ -588,12 +601,14 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.34) - zeitwerk (2.6.8) + zeitwerk (2.6.12) PLATFORMS x86_64-linux-musl DEPENDENCIES + aasm + after_commit_everywhere bcrypt (~> 3.1.7) bcrypt_pbkdf blazer diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index bc604156..c8c695c1 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -19,6 +19,16 @@ module Api # El objeto del estado puede ser un objeto o une actore, # dependiendo de la actividad. def moderationqueued + # Devuelve un error si el token no es válido + usuarie.present? + + ActivityPub.transaction do + # Crea todos los registros necesarios y actualiza el estado + activity.update_activity_pub_state! + end + rescue ActiveRecord::RecordInvalid => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, usuarie: usuarie.email, activity: original_activity }) + ensure head :accepted end @@ -33,6 +43,101 @@ module Api def onrejected head :accepted end + + private + + # Si el objeto ya viene incorporado en la actividad o lo tenemos + # que traer remotamente. + # + # @return [Bool] + def object_embedded? + @object_embedded ||= original_activity[:object].is_a?(Hash) + end + + # Encuentra la URI del objeto o falla si no la encuentra. + # + # @return [String] + def object_uri + @object_uri ||= + begin + case original_activity[:object] + when String then original_activity[:object] + when Hash then original_activity.dig(:object, :id) + end + end + ensure + raise ActiveRecord::RecordNotFound, 'object id missing' unless @object_uri + end + + # Atajo a la instancia + # + # @return [ActivityPub::Instance] + def instance + actor.instance + end + + # Genera un objeto a partir de la actividad. Si el objeto ya + # existe, actualiza su contenido. + # + # @return [ActivityPub::Object] + def object + @object ||= ActivityPub::Object.type_from(original_object).find_or_initialize_by(actor: actor, uri: object_uri).tap do |o| + o.content = original_object if object_embedded? + o.save! + end + end + + # Genera el seguimiento del estado del objeto con respecto al + # sitio. + # + # @return [ActivityPub] + def activity_pub + @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, object: object) + end + + # Crea la actividad y la vincula con el estado + # + # @return [ActivityPub::Activity] + def activity + @activity ||= ActivityPub::Activity.type_from(original_activity).new(uri: original_activity[:id], activity_pub: activity_pub).tap do |a| + a.content = original_activity.dup + a.content[:object] = object.uri + a.save! + end + end + + # Actor, si no hay instancia, la crea en el momento + # + # @return [Actor] + def actor + @actor ||= ActivityPub::Actor.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| + next if a.instance + + a.instance = ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) + a.save! + end + end + + # Descubre la actividad recibida, generando un error si la + # actividad no está dirigida a nosotres. + # + # @todo Validar formato + # @return [Hash] + def original_activity + @original_activity ||= FastJsonparser.parse(request.raw_post).tap do |activity| + raise '@context missing' unless activity[:@context].presence + raise 'id missing' unless activity[:id].presence + raise 'object missing' unless activity[:object].presence + raise 'not for us' unless [activity[:to]].flatten.include?(site.social_inbox.actor_id) + rescue RuntimeError => e + raise ActiveRecord::RecordNotFound, e.message + end + end + + # @return [Hash,String] + def original_object + @original_object ||= original_activity[:object].dup + end end end end diff --git a/app/models/activity_pub/activity.rb b/app/models/activity_pub/activity.rb index 4a88c1f3..a1f734e0 100644 --- a/app/models/activity_pub/activity.rb +++ b/app/models/activity_pub/activity.rb @@ -21,4 +21,9 @@ class ActivityPub::Activity < ApplicationRecord # Siempre en orden descendiente para saber el último estado default_scope -> { order(created_at: :desc) } + + # Cambia la máquina de estados según el tipo de actividad + def update_activity_pub_state! + nil + end end diff --git a/app/models/activity_pub/concerns/json_ld_concern.rb b/app/models/activity_pub/concerns/json_ld_concern.rb index b0899606..bc30330c 100644 --- a/app/models/activity_pub/concerns/json_ld_concern.rb +++ b/app/models/activity_pub/concerns/json_ld_concern.rb @@ -16,9 +16,11 @@ class ActivityPub # @param object [Hash] # @return [Activity] def self.type_from(object) - "#{self.class.name}::#{object[:type].presence || 'Generic'}".constantize + raise NameError unless object.is_a?(Hash) + + "#{model_name.name}::#{object[:type].presence || 'Generic'}".constantize rescue NameError - self.class::Generic + model_name.name.constantize::Generic end private diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index d3ebf579..c3abe06e 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -10,6 +10,8 @@ class Site included do encrypts :private_key_pem + has_many :activity_pubs + before_save :generate_private_key_pem!, unless: :private_key_pem? # @return [SocialInbox] diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 24f749be..78362a10 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -24,6 +24,12 @@ class SocialInbox end end + def actor_id + @actor_id ||= generate_uri do |uri| + uri.path = '/about.jsonld' + end + end + # @return [DistributedPress::V1::Social::Client] def client @client ||= DistributedPress::V1::Social::Client.new( @@ -42,14 +48,23 @@ class SocialInbox # @return [String] def public_key_url - @public_key_url ||= URI("https://#{hostname}").tap do |uri| + @public_key_url ||= generate_uri do |uri| uri.path = '/about.jsonld' uri.fragment = 'main-key' - end.to_s + end end def hostname @hostname ||= site.config.dig('activity_pub', 'hostname') || site.hostname end + + # Genera una URI dentro de este sitio + # + # @return [String] + def generate_uri(&block) + @public_key_url ||= URI("https://#{hostname}").tap do |uri| + yield uri + end.to_s + end end From 88e93e3b5bab7c8fff75062bfc4788f4b33e2453 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 20 Feb 2024 17:15:57 -0300 Subject: [PATCH 011/297] feat: al eliminar una actividad, vaciar su objeto --- app/models/activity_pub.rb | 11 +++++++++++ app/models/activity_pub/activity/delete.rb | 8 +++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index c0474b89..c15998ea 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -29,5 +29,16 @@ class ActivityPub < ApplicationRecord state :reported # Le actore eliminó el objeto state :deleted + + # Recibir una acción de eliminación, eliminar el contenido de la + # base de datos. Esto elimina el contenido para todos los sitios + # porque estamos respetando lo que pidió le actore. + event :delete do + transitions to: :deleted + + after do + object.update(object: {}) + end + end end end diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index f3684a0f..1730d49d 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -1,3 +1,9 @@ # frozen_string_literal: true -class ActivityPub::Activity::Delete < ActivityPub::Activity; end +class ActivityPub::Activity::Delete < ActivityPub::Activity + # Si estamos eliminando el objeto, tenemos que vaciar su contenido y + # cambiar el estado a borrado + def update_activity_pub_state! + activity_pub.deleted! + end +end From 8ff556c9ed5f4f585c1148488bed5930d2db6d91 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 20 Feb 2024 17:16:08 -0300 Subject: [PATCH 012/297] fix: al actualizar un objeto, pausar la actividad --- app/models/activity_pub/activity/update.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/activity_pub/activity/update.rb b/app/models/activity_pub/activity/update.rb index 8089cdcf..34703938 100644 --- a/app/models/activity_pub/activity/update.rb +++ b/app/models/activity_pub/activity/update.rb @@ -1,3 +1,9 @@ # frozen_string_literal: true -class ActivityPub::Activity::Update < ActivityPub::Activity; end +class ActivityPub::Activity::Update < ActivityPub::Activity + # Si estamos actualizando el objeto, tenemos que devolverlo a estado + # de moderación + def update_activity_pub_state! + activity_pub.paused! + end +end From 9a479a157b6d865cf4233c9737bf26d750934e99 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 20 Feb 2024 17:18:15 -0300 Subject: [PATCH 013/297] feat: volver a pausar un objeto aprobado cuando se lo actualiza --- app/models/activity_pub.rb | 6 ++++++ app/models/activity_pub/activity/update.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index c15998ea..2a127433 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -40,5 +40,11 @@ class ActivityPub < ApplicationRecord object.update(object: {}) end end + + # Si un objeto previamente aprobado fue actualizado, volvemos a + # pausarlo. + event :pause do + transitions from: :approved, to: :paused + end end end diff --git a/app/models/activity_pub/activity/update.rb b/app/models/activity_pub/activity/update.rb index 34703938..e9203ba5 100644 --- a/app/models/activity_pub/activity/update.rb +++ b/app/models/activity_pub/activity/update.rb @@ -4,6 +4,6 @@ class ActivityPub::Activity::Update < ActivityPub::Activity # Si estamos actualizando el objeto, tenemos que devolverlo a estado # de moderación def update_activity_pub_state! - activity_pub.paused! + activity_pub.pause! if activity_pub.approved? end end From fc7a524a85189bd1cdfe2c86cffa938faa5962aa Mon Sep 17 00:00:00 2001 From: f Date: Tue, 20 Feb 2024 17:18:39 -0300 Subject: [PATCH 014/297] fix: aasm --- config/application.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/application.rb b/config/application.rb index 73a7a884..27a21cc6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -2,6 +2,7 @@ require_relative 'boot' +require 'aasm' require 'redis-client' require 'hiredis-client' require 'brs' From 051d8c6d5624486fb85f9f625eec6bf8ec854789 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 12:30:01 -0300 Subject: [PATCH 015/297] =?UTF-8?q?feat:=20obtener=20el=20contenido=20del?= =?UTF-8?q?=20objeto=20m=C3=A1s=20adelante?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/v1/webhooks/social_inbox_controller.rb | 9 +++++++-- app/jobs/activity_pub/fetch_job.rb | 18 ++++++++++++++++++ app/models/social_inbox.rb | 6 ++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 app/jobs/activity_pub/fetch_job.rb diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index c8c695c1..e51b89dd 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -77,12 +77,17 @@ module Api end # Genera un objeto a partir de la actividad. Si el objeto ya - # existe, actualiza su contenido. + # existe, actualiza su contenido. Si el objeto no viene + # incorporado, obtenemos el contenido más tarde. # # @return [ActivityPub::Object] def object @object ||= ActivityPub::Object.type_from(original_object).find_or_initialize_by(actor: actor, uri: object_uri).tap do |o| - o.content = original_object if object_embedded? + if object_embedded? + o.content = original_object + else + ActivityPub::FetchJob.perform_later(site: site, object: object) + end o.save! end end diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb new file mode 100644 index 00000000..526cdafb --- /dev/null +++ b/app/jobs/activity_pub/fetch_job.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Obtiene o actualiza el contenido de un objeto, usando las credenciales +# del sitio. +# +# XXX: Esto usa las credenciales del sitio para volver el objeto +# disponible para todo el CMS. Asumimos que el objeto devuelto es el +# mismo para todo el mundo y las credenciales solo son para +# autenticación. +class ActivityPub::FetchJob < ApplicationJob + def perform(site:, object:) + ActivityPub::Object.transaction do + response = site.social_inbox.dereferencer.get(uri: object.uri) + + object.update(content: FastJsonparser.parse(response.body)) if response.ok? + end + end +end diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 78362a10..624ee571 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -2,6 +2,7 @@ require 'distributed_press/v1/social/client' require 'distributed_press/v1/social/hook' +require 'distributed_press/v1/social/dereferencer' # Gestiona la Social Inbox de un sitio class SocialInbox @@ -41,6 +42,11 @@ class SocialInbox ) end + # @return [DistributedPress::V1::Social::Dereferencer] + def dereferencer + @dereferencer ||= DistributedPress::V1::Social::Dereferencer.new(client: client) + end + # @return [DistributedPress::V1::Social::Hook] def hook @hook ||= DistributedPress::V1::Social::Hook.new(client: client, actor: actor) From fb4401fd537cf0071ce41963334ec919a70ada63 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 12:32:39 -0300 Subject: [PATCH 016/297] feat: no actualizar si no es necesario MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cuando la respuesta viene desde la caché, no es es necesario modificar el objeto. --- app/jobs/activity_pub/fetch_job.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 526cdafb..097a8d32 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -12,7 +12,11 @@ class ActivityPub::FetchJob < ApplicationJob ActivityPub::Object.transaction do response = site.social_inbox.dereferencer.get(uri: object.uri) - object.update(content: FastJsonparser.parse(response.body)) if response.ok? + # @todo Fallar cuando la respuesta no funcione? + return unless response.ok? + return unless response.miss? + + object.update(content: FastJsonparser.parse(response.body)) end end end From 091d5ac41d5a0bfc1ff30ae4e28a6b61c74a963c Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 12:46:38 -0300 Subject: [PATCH 017/297] chore: rubocop --- .../v1/webhooks/social_inbox_controller.rb | 18 ++++++++------- app/jobs/activity_pub/fetch_job.rb | 18 ++++++++------- app/models/activity_pub/activity.rb | 22 ++++++++++--------- app/models/activity_pub/activity/create.rb | 6 ++++- app/models/activity_pub/activity/delete.rb | 14 +++++++----- app/models/activity_pub/activity/flag.rb | 6 ++++- app/models/activity_pub/activity/follow.rb | 6 ++++- app/models/activity_pub/activity/generic.rb | 6 ++++- app/models/activity_pub/activity/undo.rb | 6 ++++- app/models/activity_pub/activity/update.rb | 14 +++++++----- app/models/activity_pub/actor.rb | 12 +++++----- app/models/activity_pub/instance.rb | 22 ++++++++++--------- app/models/activity_pub/object.rb | 12 +++++----- app/models/activity_pub/object/application.rb | 6 ++++- app/models/activity_pub/object/article.rb | 6 ++++- app/models/activity_pub/object/generic.rb | 6 ++++- app/models/activity_pub/object/note.rb | 6 ++++- .../activity_pub/object/organization.rb | 6 ++++- app/models/activity_pub/object/person.rb | 6 ++++- app/models/social_inbox.rb | 4 +--- 20 files changed, 132 insertions(+), 70 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index e51b89dd..3341b33d 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -27,7 +27,9 @@ module Api activity.update_activity_pub_state! end rescue ActiveRecord::RecordInvalid => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, usuarie: usuarie.email, activity: original_activity }) + ExceptionNotifier.notify_exception(e, + data: { site: site.name, usuarie: usuarie.email, + activity: original_activity }) ensure head :accepted end @@ -59,11 +61,9 @@ module Api # @return [String] def object_uri @object_uri ||= - begin - case original_activity[:object] - when String then original_activity[:object] - when Hash then original_activity.dig(:object, :id) - end + case original_activity[:object] + when String then original_activity[:object] + when Hash then original_activity.dig(:object, :id) end ensure raise ActiveRecord::RecordNotFound, 'object id missing' unless @object_uri @@ -82,7 +82,8 @@ module Api # # @return [ActivityPub::Object] def object - @object ||= ActivityPub::Object.type_from(original_object).find_or_initialize_by(actor: actor, uri: object_uri).tap do |o| + @object ||= ActivityPub::Object.type_from(original_object).find_or_initialize_by(actor: actor, + uri: object_uri).tap do |o| if object_embedded? o.content = original_object else @@ -104,7 +105,8 @@ module Api # # @return [ActivityPub::Activity] def activity - @activity ||= ActivityPub::Activity.type_from(original_activity).new(uri: original_activity[:id], activity_pub: activity_pub).tap do |a| + @activity ||= ActivityPub::Activity.type_from(original_activity).new(uri: original_activity[:id], + activity_pub: activity_pub).tap do |a| a.content = original_activity.dup a.content[:object] = object.uri a.save! diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 097a8d32..ff14c795 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -7,16 +7,18 @@ # disponible para todo el CMS. Asumimos que el objeto devuelto es el # mismo para todo el mundo y las credenciales solo son para # autenticación. -class ActivityPub::FetchJob < ApplicationJob - def perform(site:, object:) - ActivityPub::Object.transaction do - response = site.social_inbox.dereferencer.get(uri: object.uri) +class ActivityPub + class FetchJob < ApplicationJob + def perform(site:, object:) + ActivityPub::Object.transaction do + response = site.social_inbox.dereferencer.get(uri: object.uri) - # @todo Fallar cuando la respuesta no funcione? - return unless response.ok? - return unless response.miss? + # @todo Fallar cuando la respuesta no funcione? + return unless response.ok? + return unless response.miss? - object.update(content: FastJsonparser.parse(response.body)) + object.update(content: FastJsonparser.parse(response.body)) + end end end end diff --git a/app/models/activity_pub/activity.rb b/app/models/activity_pub/activity.rb index a1f734e0..5ee3d2d1 100644 --- a/app/models/activity_pub/activity.rb +++ b/app/models/activity_pub/activity.rb @@ -11,19 +11,21 @@ # envía como referencia en lugar de anidarlo. # # @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions} -class ActivityPub::Activity < ApplicationRecord - include ActivityPub::Concerns::JsonLdConcern +class ActivityPub + class Activity < ApplicationRecord + include ActivityPub::Concerns::JsonLdConcern - belongs_to :activity_pub - has_one :object, through: :activity_pub + belongs_to :activity_pub + has_one :object, through: :activity_pub - validates :activity_pub_id, presence: true + validates :activity_pub_id, presence: true - # Siempre en orden descendiente para saber el último estado - default_scope -> { order(created_at: :desc) } + # Siempre en orden descendiente para saber el último estado + default_scope -> { order(created_at: :desc) } - # Cambia la máquina de estados según el tipo de actividad - def update_activity_pub_state! - nil + # Cambia la máquina de estados según el tipo de actividad + def update_activity_pub_state! + nil + end end end diff --git a/app/models/activity_pub/activity/create.rb b/app/models/activity_pub/activity/create.rb index 3dcba5c2..4acafaf2 100644 --- a/app/models/activity_pub/activity/create.rb +++ b/app/models/activity_pub/activity/create.rb @@ -1,3 +1,7 @@ # frozen_string_literal: true -class ActivityPub::Activity::Create < ActivityPub::Activity; end +class ActivityPub + module Activity + class Create < ActivityPub::Activity; end + end +end diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 1730d49d..2973f730 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -1,9 +1,13 @@ # frozen_string_literal: true -class ActivityPub::Activity::Delete < ActivityPub::Activity - # Si estamos eliminando el objeto, tenemos que vaciar su contenido y - # cambiar el estado a borrado - def update_activity_pub_state! - activity_pub.deleted! +class ActivityPub + module Activity + class Delete < ActivityPub::Activity + # Si estamos eliminando el objeto, tenemos que vaciar su contenido y + # cambiar el estado a borrado + def update_activity_pub_state! + activity_pub.deleted! + end + end end end diff --git a/app/models/activity_pub/activity/flag.rb b/app/models/activity_pub/activity/flag.rb index 2911911e..27bbe266 100644 --- a/app/models/activity_pub/activity/flag.rb +++ b/app/models/activity_pub/activity/flag.rb @@ -1,3 +1,7 @@ # frozen_string_literal: true -class ActivityPub::Activity::Flag < ActivityPub::Activity; end +class ActivityPub + module Activity + class Flag < ActivityPub::Activity; end + end +end diff --git a/app/models/activity_pub/activity/follow.rb b/app/models/activity_pub/activity/follow.rb index c22dfd51..9e32b67d 100644 --- a/app/models/activity_pub/activity/follow.rb +++ b/app/models/activity_pub/activity/follow.rb @@ -4,4 +4,8 @@ # # Una actividad de seguimiento se refiere siempre a une actore (el # sitio) y proviene de otre actore. -class ActivityPub::Activity::Follow < ActivityPub::Activity; end +class ActivityPub + module Activity + class Follow < ActivityPub::Activity; end + end +end diff --git a/app/models/activity_pub/activity/generic.rb b/app/models/activity_pub/activity/generic.rb index 8bf76471..89c26247 100644 --- a/app/models/activity_pub/activity/generic.rb +++ b/app/models/activity_pub/activity/generic.rb @@ -1,3 +1,7 @@ # frozen_string_literal: true -class ActivityPub::Activity::Generic < ActivityPub::Activity; end +class ActivityPub + module Activity + class Generic < ActivityPub::Activity; end + end +end diff --git a/app/models/activity_pub/activity/undo.rb b/app/models/activity_pub/activity/undo.rb index a4915394..98233af9 100644 --- a/app/models/activity_pub/activity/undo.rb +++ b/app/models/activity_pub/activity/undo.rb @@ -4,5 +4,9 @@ # # Deshace una actividad, dependiendo de la actividad a la que se # refiere. -class ActivityPub::Activity::Undo < ActivityPub::Activity +class ActivityPub + module Activity + class Undo < ActivityPub::Activity + end + end end diff --git a/app/models/activity_pub/activity/update.rb b/app/models/activity_pub/activity/update.rb index e9203ba5..50622b93 100644 --- a/app/models/activity_pub/activity/update.rb +++ b/app/models/activity_pub/activity/update.rb @@ -1,9 +1,13 @@ # frozen_string_literal: true -class ActivityPub::Activity::Update < ActivityPub::Activity - # Si estamos actualizando el objeto, tenemos que devolverlo a estado - # de moderación - def update_activity_pub_state! - activity_pub.pause! if activity_pub.approved? +class ActivityPub + module Activity + class Update < ActivityPub::Activity + # Si estamos actualizando el objeto, tenemos que devolverlo a estado + # de moderación + def update_activity_pub_state! + activity_pub.pause! if activity_pub.approved? + end + end end end diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index f29c382a..7be69602 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -5,10 +5,12 @@ # Actor es la entidad que realiza acciones en ActivityPub # # @todo Obtener el perfil dinámicamente -class ActivityPub::Actor < ApplicationRecord - include ActivityPub::Concerns::JsonLdConcern +class ActivityPub + class Actor < ApplicationRecord + include ActivityPub::Concerns::JsonLdConcern - belongs_to :instance - has_many :activity_pubs, as: :object - has_many :objects + belongs_to :instance + has_many :activity_pubs, as: :object + has_many :objects + end end diff --git a/app/models/activity_pub/instance.rb b/app/models/activity_pub/instance.rb index fe4a777b..b13b8676 100644 --- a/app/models/activity_pub/instance.rb +++ b/app/models/activity_pub/instance.rb @@ -4,18 +4,20 @@ # # Representa cada instancia del fediverso que interactúa con la Social # Inbox. -class ActivityPub::Instance < ApplicationRecord - include AASM +class ActivityPub + class Instance < ApplicationRecord + include AASM - validates :aasm_state, presence: true, inclusion: { in: %w[paused allowed blocked] } - validates :hostname, uniqueness: true, hostname: true + validates :aasm_state, presence: true, inclusion: { in: %w[paused allowed blocked] } + validates :hostname, uniqueness: true, hostname: true - has_many :activity_pubs - has_many :actors + has_many :activity_pubs + has_many :actors - aasm do - state :paused, initial: true - state :allowed - state :blocked + aasm do + state :paused, initial: true + state :allowed + state :blocked + end end end diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index 519749ef..49a06772 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true # Almacena objetos de ActivityPub, como Note, Article, etc. -class ActivityPub::Object < ApplicationRecord - include ActivityPub::Concerns::JsonLdConcern +class ActivityPub + class Object < ApplicationRecord + include ActivityPub::Concerns::JsonLdConcern - belongs_to :actor - has_many :activity_pubs, as: :object + belongs_to :actor + has_many :activity_pubs, as: :object - validates :actor_id, presence: true + validates :actor_id, presence: true + end end diff --git a/app/models/activity_pub/object/application.rb b/app/models/activity_pub/object/application.rb index e8a6f97c..e8d8fa0e 100644 --- a/app/models/activity_pub/object/application.rb +++ b/app/models/activity_pub/object/application.rb @@ -3,4 +3,8 @@ # = Application = # # Una aplicación o instancia -class ActivityPub::Object::Application < ActivityPub::Object; end +class ActivityPub + module Object + class Application < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/article.rb b/app/models/activity_pub/object/article.rb index ad1a6131..69fe371c 100644 --- a/app/models/activity_pub/object/article.rb +++ b/app/models/activity_pub/object/article.rb @@ -3,4 +3,8 @@ # = Article = # # Representa artículos -class ActivityPub::Object::Article < ActivityPub::Object; end +class ActivityPub + module Object + class Article < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/generic.rb b/app/models/activity_pub/object/generic.rb index f345e7a9..c16b25c6 100644 --- a/app/models/activity_pub/object/generic.rb +++ b/app/models/activity_pub/object/generic.rb @@ -1,4 +1,8 @@ # frozen_string_literal: true # = Generic = -class ActivityPub::Object::Generic < ActivityPub::Object; end +class ActivityPub + module Object + class Generic < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/note.rb b/app/models/activity_pub/object/note.rb index 0f84c747..06b969ab 100644 --- a/app/models/activity_pub/object/note.rb +++ b/app/models/activity_pub/object/note.rb @@ -3,4 +3,8 @@ # = Note = # # Representa notas, el tipo más común de objeto del Fediverso. -class ActivityPub::Object::Note < ActivityPub::Object; end +class ActivityPub + module Object + class Note < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/organization.rb b/app/models/activity_pub/object/organization.rb index a5327d10..31e5887d 100644 --- a/app/models/activity_pub/object/organization.rb +++ b/app/models/activity_pub/object/organization.rb @@ -3,4 +3,8 @@ # = Organization = # # Una organización -class ActivityPub::Object::Organization < ActivityPub::Object; end +class ActivityPub + module Object + class Organization < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/person.rb b/app/models/activity_pub/object/person.rb index 98a1568d..fd01e515 100644 --- a/app/models/activity_pub/object/person.rb +++ b/app/models/activity_pub/object/person.rb @@ -3,4 +3,8 @@ # = Person = # # Una persona, el perfil de une actore -class ActivityPub::Object::Person < ActivityPub::Object; end +class ActivityPub + module Object + class Person < ActivityPub::Object; end + end +end diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 624ee571..45b8afd8 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -69,8 +69,6 @@ class SocialInbox # # @return [String] def generate_uri(&block) - @public_key_url ||= URI("https://#{hostname}").tap do |uri| - yield uri - end.to_s + @public_key_url ||= URI("https://#{hostname}").tap(&block).to_s end end From 7b8730c34c2ca41e5f3f25bd77c15cd3cf1e572a Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 13:04:15 -0300 Subject: [PATCH 018/297] feat: las actividades se aprueban cuando las confirma la SI --- .../api/v1/webhooks/social_inbox_controller.rb | 6 +++++- app/models/activity_pub.rb | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 3341b33d..76c7ec40 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -35,8 +35,12 @@ module Api end # Cuando aprobamos una actividad, recibimos la confirmación y - # cambiamos el estado + # cambiamos el estado. def onapproved + ActivityPub.transaction do + activity_pub.approve! if activity_pub.waiting? + end + head :accepted end diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 2a127433..71d67722 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -21,6 +21,8 @@ class ActivityPub < ApplicationRecord aasm do # Todavía no hay una decisión sobre el objeto state :paused, initial: true + # Estamos esperando respuesta desde la Social Inbox + state :waiting # Le usuarie aprobó el objeto state :approved # Le usuarie rechazó el objeto @@ -44,7 +46,12 @@ class ActivityPub < ApplicationRecord # Si un objeto previamente aprobado fue actualizado, volvemos a # pausarlo. event :pause do - transitions from: :approved, to: :paused + transitions from: %i[waiting approved], to: :paused + end + + # La actividad se aprueba + event :approve do + transitions from: :waiting, to: :approved end end end From e733c45b63cfd901e82dd9565585b2cb2bcbea3b Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 13:06:06 -0300 Subject: [PATCH 019/297] fix: las actividades se pueden rechazar --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 4 ++++ app/models/activity_pub.rb | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 76c7ec40..20028708 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -47,6 +47,10 @@ module Api # Cuando rechazamos una actividad, recibimos la confirmación y # cambiamos el estado def onrejected + ActivityPub.transaction do + activity_pub.reject! if activity_pub.waiting? + end + head :accepted end diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 71d67722..b8e8eded 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -53,5 +53,10 @@ class ActivityPub < ApplicationRecord event :approve do transitions from: :waiting, to: :approved end + + # La actividad fue rechazada + event :reject do + transitions from: :waiting, to: :rejected + end end end From a9bdabf409076494b97d237631d47e1bbe64e592 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 13:07:27 -0300 Subject: [PATCH 020/297] =?UTF-8?q?fix:=20se=20puede=20volver=20a=20pausar?= =?UTF-8?q?=20despu=C3=A9s=20de=20rechazarla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index b8e8eded..1c225226 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -46,7 +46,7 @@ class ActivityPub < ApplicationRecord # Si un objeto previamente aprobado fue actualizado, volvemos a # pausarlo. event :pause do - transitions from: %i[waiting approved], to: :paused + transitions from: %i[waiting approved rejected], to: :paused end # La actividad se aprueba From b477487b659e01e522d621584cb9b94fba676eb7 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 13:13:24 -0300 Subject: [PATCH 021/297] =?UTF-8?q?fixup!=20feat:=20obtener=20el=20conteni?= =?UTF-8?q?do=20del=20objeto=20m=C3=A1s=20adelante?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 20028708..532cca5e 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -95,8 +95,9 @@ module Api if object_embedded? o.content = original_object else - ActivityPub::FetchJob.perform_later(site: site, object: object) + ActivityPub::FetchJob.perform_later(site: site, object: o) end + o.save! end end From 732012e14b8d54db01e274db2d9f53a223523898 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 13:13:34 -0300 Subject: [PATCH 022/297] docs: orden en que se crean los registros --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 532cca5e..51322990 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -24,6 +24,12 @@ module Api ActivityPub.transaction do # Crea todos los registros necesarios y actualiza el estado + # + # 1. Actor + # 2. Instance + # 3. Object + # 4. ActivityPub + # 5. Activity activity.update_activity_pub_state! end rescue ActiveRecord::RecordInvalid => e From 34e63ff2dca083306ada0023098a7937b373f19f Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 14:07:11 -0300 Subject: [PATCH 023/297] =?UTF-8?q?feat:=20actualizar=20el=20tipo=20tambi?= =?UTF-8?q?=C3=A9n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/fetch_job.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index ff14c795..e5950c86 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -17,7 +17,9 @@ class ActivityPub return unless response.ok? return unless response.miss? - object.update(content: FastJsonparser.parse(response.body)) + content = FastJsonparser.parse(response.body) + + object.update(content: content, type: ActivityPub::Object.type_from(content).name) end end end From 03c0b71b5f36f14af1f8a9dbab1f9a97fb706d2f Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 15:42:56 -0300 Subject: [PATCH 024/297] fix: no es necesario vincular actores con objetos --- .../api/v1/webhooks/social_inbox_controller.rb | 3 +-- app/models/activity_pub/actor.rb | 1 - app/models/activity_pub/object.rb | 2 -- .../20240221184007_remove_actor_from_objects.rb | 12 ++++++++++++ 4 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20240221184007_remove_actor_from_objects.rb diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 51322990..c15a757d 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -96,8 +96,7 @@ module Api # # @return [ActivityPub::Object] def object - @object ||= ActivityPub::Object.type_from(original_object).find_or_initialize_by(actor: actor, - uri: object_uri).tap do |o| + @object ||= ActivityPub::Object.type_from(original_object).find_or_initialize_by(uri: object_uri).tap do |o| if object_embedded? o.content = original_object else diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index 7be69602..e79a596a 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -11,6 +11,5 @@ class ActivityPub belongs_to :instance has_many :activity_pubs, as: :object - has_many :objects end end diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index 49a06772..ec759e3e 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -7,7 +7,5 @@ class ActivityPub belongs_to :actor has_many :activity_pubs, as: :object - - validates :actor_id, presence: true end end diff --git a/db/migrate/20240221184007_remove_actor_from_objects.rb b/db/migrate/20240221184007_remove_actor_from_objects.rb new file mode 100644 index 00000000..6ee5822c --- /dev/null +++ b/db/migrate/20240221184007_remove_actor_from_objects.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# No es necesario vincular actores con objetos, porque la forma en que +# lo estábamos haciendo no se refiere a le actore del objeto, sino de +# acciones distintas sobre el mismo objeto, generado por une actore. +# +# Y ese valor ya lo podemos obtener desde attributedTo +class RemoveActorFromObjects < ActiveRecord::Migration[6.1] + def change + remove_column :activity_pub_objects, :actor_id, :uuid, index: true + end +end From 0443cb0fc3cce6d7e6eff11b97b9f5b5914939c1 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 15:43:10 -0300 Subject: [PATCH 025/297] fix: responder con forbidden cuando los registros no sean validos --- app/controllers/api/v1/webhooks/concerns/webhook_concern.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb b/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb index a546a55c..b94c91f6 100644 --- a/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb +++ b/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb @@ -11,6 +11,7 @@ module Api included do # Responde con forbidden si falla la validación del token rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer + rescue_from ActiveRecord::RecordInvalid, with: :platforms_answer private From 5dbff20e2bf43df6833106c1f5f790a2399b38bc Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 15:44:04 -0300 Subject: [PATCH 026/297] fixup! chore: rubocop --- app/models/activity_pub/activity/create.rb | 2 +- app/models/activity_pub/activity/delete.rb | 2 +- app/models/activity_pub/activity/flag.rb | 2 +- app/models/activity_pub/activity/follow.rb | 2 +- app/models/activity_pub/activity/generic.rb | 2 +- app/models/activity_pub/activity/undo.rb | 2 +- app/models/activity_pub/activity/update.rb | 2 +- app/models/activity_pub/object/application.rb | 2 +- app/models/activity_pub/object/article.rb | 2 +- app/models/activity_pub/object/generic.rb | 2 +- app/models/activity_pub/object/note.rb | 2 +- app/models/activity_pub/object/organization.rb | 2 +- app/models/activity_pub/object/person.rb | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/models/activity_pub/activity/create.rb b/app/models/activity_pub/activity/create.rb index 4acafaf2..9cd32559 100644 --- a/app/models/activity_pub/activity/create.rb +++ b/app/models/activity_pub/activity/create.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ActivityPub - module Activity + class Activity class Create < ActivityPub::Activity; end end end diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 2973f730..7080375e 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ActivityPub - module Activity + class Activity class Delete < ActivityPub::Activity # Si estamos eliminando el objeto, tenemos que vaciar su contenido y # cambiar el estado a borrado diff --git a/app/models/activity_pub/activity/flag.rb b/app/models/activity_pub/activity/flag.rb index 27bbe266..ffbc374b 100644 --- a/app/models/activity_pub/activity/flag.rb +++ b/app/models/activity_pub/activity/flag.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ActivityPub - module Activity + class Activity class Flag < ActivityPub::Activity; end end end diff --git a/app/models/activity_pub/activity/follow.rb b/app/models/activity_pub/activity/follow.rb index 9e32b67d..e383490a 100644 --- a/app/models/activity_pub/activity/follow.rb +++ b/app/models/activity_pub/activity/follow.rb @@ -5,7 +5,7 @@ # Una actividad de seguimiento se refiere siempre a une actore (el # sitio) y proviene de otre actore. class ActivityPub - module Activity + class Activity class Follow < ActivityPub::Activity; end end end diff --git a/app/models/activity_pub/activity/generic.rb b/app/models/activity_pub/activity/generic.rb index 89c26247..95fff3eb 100644 --- a/app/models/activity_pub/activity/generic.rb +++ b/app/models/activity_pub/activity/generic.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ActivityPub - module Activity + class Activity class Generic < ActivityPub::Activity; end end end diff --git a/app/models/activity_pub/activity/undo.rb b/app/models/activity_pub/activity/undo.rb index 98233af9..41fb5e51 100644 --- a/app/models/activity_pub/activity/undo.rb +++ b/app/models/activity_pub/activity/undo.rb @@ -5,7 +5,7 @@ # Deshace una actividad, dependiendo de la actividad a la que se # refiere. class ActivityPub - module Activity + class Activity class Undo < ActivityPub::Activity end end diff --git a/app/models/activity_pub/activity/update.rb b/app/models/activity_pub/activity/update.rb index 50622b93..19c95b68 100644 --- a/app/models/activity_pub/activity/update.rb +++ b/app/models/activity_pub/activity/update.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ActivityPub - module Activity + class Activity class Update < ActivityPub::Activity # Si estamos actualizando el objeto, tenemos que devolverlo a estado # de moderación diff --git a/app/models/activity_pub/object/application.rb b/app/models/activity_pub/object/application.rb index e8d8fa0e..99ac935c 100644 --- a/app/models/activity_pub/object/application.rb +++ b/app/models/activity_pub/object/application.rb @@ -4,7 +4,7 @@ # # Una aplicación o instancia class ActivityPub - module Object + class Object class Application < ActivityPub::Object; end end end diff --git a/app/models/activity_pub/object/article.rb b/app/models/activity_pub/object/article.rb index 69fe371c..126ba3f1 100644 --- a/app/models/activity_pub/object/article.rb +++ b/app/models/activity_pub/object/article.rb @@ -4,7 +4,7 @@ # # Representa artículos class ActivityPub - module Object + class Object class Article < ActivityPub::Object; end end end diff --git a/app/models/activity_pub/object/generic.rb b/app/models/activity_pub/object/generic.rb index c16b25c6..3e5ff719 100644 --- a/app/models/activity_pub/object/generic.rb +++ b/app/models/activity_pub/object/generic.rb @@ -2,7 +2,7 @@ # = Generic = class ActivityPub - module Object + class Object class Generic < ActivityPub::Object; end end end diff --git a/app/models/activity_pub/object/note.rb b/app/models/activity_pub/object/note.rb index 06b969ab..ca113c15 100644 --- a/app/models/activity_pub/object/note.rb +++ b/app/models/activity_pub/object/note.rb @@ -4,7 +4,7 @@ # # Representa notas, el tipo más común de objeto del Fediverso. class ActivityPub - module Object + class Object class Note < ActivityPub::Object; end end end diff --git a/app/models/activity_pub/object/organization.rb b/app/models/activity_pub/object/organization.rb index 31e5887d..e3385232 100644 --- a/app/models/activity_pub/object/organization.rb +++ b/app/models/activity_pub/object/organization.rb @@ -4,7 +4,7 @@ # # Una organización class ActivityPub - module Object + class Object class Organization < ActivityPub::Object; end end end diff --git a/app/models/activity_pub/object/person.rb b/app/models/activity_pub/object/person.rb index fd01e515..a6a85d43 100644 --- a/app/models/activity_pub/object/person.rb +++ b/app/models/activity_pub/object/person.rb @@ -4,7 +4,7 @@ # # Una persona, el perfil de une actore class ActivityPub - module Object + class Object class Person < ActivityPub::Object; end end end From ddaee30da715981c9871728706450050ecbfd091 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 15:44:16 -0300 Subject: [PATCH 027/297] fix: primero guardar el objeto antes de obtener su contenido --- .../api/v1/webhooks/social_inbox_controller.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index c15a757d..24865c92 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -97,13 +97,13 @@ module Api # @return [ActivityPub::Object] def object @object ||= ActivityPub::Object.type_from(original_object).find_or_initialize_by(uri: object_uri).tap do |o| - if object_embedded? - o.content = original_object - else - ActivityPub::FetchJob.perform_later(site: site, object: o) - end + o.content = original_object if object_embedded? o.save! + + # XXX: el objeto necesita ser guardado antes de poder + # procesarlo + ActivityPub::FetchJob.perform_later(site: site, object: o) unless object_embedded? end end From d98f893a7153d5fe546c6db5632356733e2ec4eb Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 15:44:29 -0300 Subject: [PATCH 028/297] fix: algunas actividades no tienen destinatarie --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 24865c92..fb7ede50 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -149,7 +149,6 @@ module Api raise '@context missing' unless activity[:@context].presence raise 'id missing' unless activity[:id].presence raise 'object missing' unless activity[:object].presence - raise 'not for us' unless [activity[:to]].flatten.include?(site.social_inbox.actor_id) rescue RuntimeError => e raise ActiveRecord::RecordNotFound, e.message end From 39bbc3a2bd26c7240a933e4769e5c642cea7fff7 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 15:44:41 -0300 Subject: [PATCH 029/297] =?UTF-8?q?fix:=20deleted=20es=20un=20m=C3=A9todo?= =?UTF-8?q?=20reservado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 1c225226..42ed3f61 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -30,13 +30,13 @@ class ActivityPub < ApplicationRecord # Le usuarie reportó el objeto state :reported # Le actore eliminó el objeto - state :deleted + state :removed # Recibir una acción de eliminación, eliminar el contenido de la # base de datos. Esto elimina el contenido para todos los sitios # porque estamos respetando lo que pidió le actore. - event :delete do - transitions to: :deleted + event :remove do + transitions to: :removed after do object.update(object: {}) From f517889992e9f72fb517ab9c79afed20c80e2769 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 15:45:15 -0300 Subject: [PATCH 030/297] fixup! fix: no es necesario vincular actores con objetos --- db/structure.sql | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/db/structure.sql b/db/structure.sql index 723c9e99..ee99e791 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -523,7 +523,6 @@ CREATE TABLE public.activity_pub_objects ( id uuid DEFAULT gen_random_uuid() NOT NULL, created_at timestamp(6) without time zone NOT NULL, updated_at timestamp(6) without time zone NOT NULL, - actor_id uuid NOT NULL, type character varying NOT NULL, uri character varying NOT NULL, content jsonb DEFAULT '{}'::jsonb @@ -2005,13 +2004,6 @@ CREATE INDEX index_activity_pub_actors_on_uri ON public.activity_pub_actors USIN CREATE INDEX index_activity_pub_instances_on_hostname ON public.activity_pub_instances USING btree (hostname); --- --- Name: index_activity_pub_objects_on_actor_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_activity_pub_objects_on_actor_id ON public.activity_pub_objects USING btree (actor_id); - - -- -- Name: index_activity_pubs_on_site_id_and_object_id_and_object_type; Type: INDEX; Schema: public; Owner: - -- @@ -2479,6 +2471,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240219175839'), ('20240219204011'), ('20240219204224'), -('20240220161414'); +('20240220161414'), +('20240221184007'); From f8f0eb9d3c341467ec25b9b48d242d29c36b3401 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 16:42:02 -0300 Subject: [PATCH 031/297] fix: no buscar csrf --- app/controllers/api/v1/webhooks/concerns/webhook_concern.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb b/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb index b94c91f6..aef2dd83 100644 --- a/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb +++ b/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb @@ -9,6 +9,8 @@ module Api extend ActiveSupport::Concern included do + skip_before_action :verify_authenticity_token + # Responde con forbidden si falla la validación del token rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer rescue_from ActiveRecord::RecordInvalid, with: :platforms_answer From a4133c60018772af78b5bb5813050b4d1fbf5b34 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 16:42:14 -0300 Subject: [PATCH 032/297] fix: asegurarse que todos los registros existan --- .../api/v1/webhooks/social_inbox_controller.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index fb7ede50..e6b80c4b 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -24,12 +24,10 @@ module Api ActivityPub.transaction do # Crea todos los registros necesarios y actualiza el estado - # - # 1. Actor - # 2. Instance - # 3. Object - # 4. ActivityPub - # 5. Activity + actor.present? + instance.present? + object.present? + activity_pub.present? activity.update_activity_pub_state! end rescue ActiveRecord::RecordInvalid => e From 3292d7ebe3fc8f82fe817bc9ccd5d0be0074a171 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 16:42:28 -0300 Subject: [PATCH 033/297] fix: vaciar el contenido antes de cambiar de estado --- app/models/activity_pub.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 42ed3f61..9445717f 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -38,8 +38,8 @@ class ActivityPub < ApplicationRecord event :remove do transitions to: :removed - after do - object.update(object: {}) + before do + object.update(content: {}) end end From a13f42c22f39f00835a6a5862e750a00a61be62f Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 16:43:31 -0300 Subject: [PATCH 034/297] fixup! fixup! fix: no es necesario vincular actores con objetos --- app/models/activity_pub/object.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index ec759e3e..898d5375 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -5,7 +5,6 @@ class ActivityPub class Object < ApplicationRecord include ActivityPub::Concerns::JsonLdConcern - belongs_to :actor has_many :activity_pubs, as: :object end end From b562b006bc716200898524a027ac67efb24d9080 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 17:03:59 -0300 Subject: [PATCH 035/297] =?UTF-8?q?fix:=20versi=C3=B3n=20requerida=20por?= =?UTF-8?q?=20aasm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 8e955e8f..676b3673 100644 --- a/Gemfile +++ b/Gemfile @@ -80,7 +80,7 @@ gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git' gem 'kaminari' gem 'device_detector' -gem 'after_commit_everywhere' +gem 'after_commit_everywhere', '~> 1.0' gem 'aasm' # database diff --git a/Gemfile.lock b/Gemfile.lock index 50745140..7aac4c12 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -608,7 +608,7 @@ PLATFORMS DEPENDENCIES aasm - after_commit_everywhere + after_commit_everywhere (~> 1.0) bcrypt (~> 3.1.7) bcrypt_pbkdf blazer From e53f31f359617b90cd77921c4e67f2521daed337 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 17:04:39 -0300 Subject: [PATCH 036/297] =?UTF-8?q?fixup!=20fix:=20deleted=20es=20un=20m?= =?UTF-8?q?=C3=A9todo=20reservado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 9445717f..902924b6 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -16,7 +16,7 @@ class ActivityPub < ApplicationRecord validates :site_id, presence: true validates :object_id, presence: true - validates :aasm_state, presence: true, inclusion: { in: %w[paused approved rejected reported deleted] } + validates :aasm_state, presence: true, inclusion: { in: %w[paused approved rejected reported removed] } aasm do # Todavía no hay una decisión sobre el objeto From fc7c2e5b7455b8e91d526a366ac0f57bce8c6c2e Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 17:05:17 -0300 Subject: [PATCH 037/297] fix: undo auto-cancela la actividad --- app/models/activity_pub.rb | 4 +++- app/models/activity_pub/activity/undo.rb | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 902924b6..7e5bcabe 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -38,8 +38,10 @@ class ActivityPub < ApplicationRecord event :remove do transitions to: :removed + # @todo Es posible que haya un ActivityPub::FetchJob pendiente + # cuando estamos haciendo esto, que rellene el objeto después. before do - object.update(content: {}) + object.update(content: {}) unless object.content.empty? end end diff --git a/app/models/activity_pub/activity/undo.rb b/app/models/activity_pub/activity/undo.rb index 41fb5e51..4b6a5a4c 100644 --- a/app/models/activity_pub/activity/undo.rb +++ b/app/models/activity_pub/activity/undo.rb @@ -7,6 +7,18 @@ class ActivityPub class Activity class Undo < ActivityPub::Activity + # Una actividad de deshacer tiene anidada como objeto la actividad + # a deshacer. Para respetar la voluntad de le actore remote, + # tendríamos que eliminar cualquier actividad pendiente sobre el + # objeto. + # + # Sin embargo, estas acciones nunca deberían llegar a nuestra + # Inbox. + # + # @see {https://github.com/hyphacoop/social.distributed.press/issues/43} + def update_activity_pub_state! + activity_pub.remove! + end end end end From dca266714d520cfb0fbd36460c7a898573fac33a Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 17:08:11 -0300 Subject: [PATCH 038/297] fix: no completar el contenido si el objeto tiene actividades canceladas --- app/jobs/activity_pub/fetch_job.rb | 2 ++ app/models/activity_pub.rb | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index e5950c86..ec8c29f7 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -11,6 +11,8 @@ class ActivityPub class FetchJob < ApplicationJob def perform(site:, object:) ActivityPub::Object.transaction do + return if object.activity_pubs.where(aasm_state: 'removed').count.positive? + response = site.social_inbox.dereferencer.get(uri: object.uri) # @todo Fallar cuando la respuesta no funcione? diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 7e5bcabe..df8e5c5c 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -38,8 +38,6 @@ class ActivityPub < ApplicationRecord event :remove do transitions to: :removed - # @todo Es posible que haya un ActivityPub::FetchJob pendiente - # cuando estamos haciendo esto, que rellene el objeto después. before do object.update(content: {}) unless object.content.empty? end From 2ae2b8e9e87b254cc041bda6c060a7b93d171592 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 17:09:15 -0300 Subject: [PATCH 039/297] =?UTF-8?q?fix:=20las=20actividades=20de=20borrado?= =?UTF-8?q?=20eliminan=20el=20contenido=20tambi=C3=A9n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub/activity/delete.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 7080375e..351dd3cb 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -4,9 +4,9 @@ class ActivityPub class Activity class Delete < ActivityPub::Activity # Si estamos eliminando el objeto, tenemos que vaciar su contenido y - # cambiar el estado a borrado + # cambiar el estado a borrado. def update_activity_pub_state! - activity_pub.deleted! + activity_pub.remove! end end end From 7a936c114314ae4fbcc5827cc1aa019dad02bd2e Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 17:22:31 -0300 Subject: [PATCH 040/297] =?UTF-8?q?fix:=20no=20actualizar=20si=20ya=20esta?= =?UTF-8?q?ba=20cacheado=20y=20el=20contenido=20exist=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/fetch_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index ec8c29f7..b6c45026 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -17,7 +17,7 @@ class ActivityPub # @todo Fallar cuando la respuesta no funcione? return unless response.ok? - return unless response.miss? + return if response.miss? && object.content.present? content = FastJsonparser.parse(response.body) From e62d6c6c1d5e03d3b4b6c9c47feefc5d2b186833 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 17:50:06 -0300 Subject: [PATCH 041/297] fix: asignar un tipo por defecto para el objeto --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index e6b80c4b..c71c4922 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -94,7 +94,10 @@ module Api # # @return [ActivityPub::Object] def object - @object ||= ActivityPub::Object.type_from(original_object).find_or_initialize_by(uri: object_uri).tap do |o| + @object ||= ActivityPub::Object.find_or_initialize_by(uri: object_uri).tap do |o| + # XXX: Si el objeto es una actividad, esto siempre va a ser + # Generic + o.type ||= 'ActivityPub::Object::Generic' o.content = original_object if object_embedded? o.save! From 6b12e5141cd1f4004b53573d39d934f2c205d24b Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Feb 2024 17:54:18 -0300 Subject: [PATCH 042/297] feat: al deshacer una actividad resolvemos la pendiente --- app/models/activity_pub/activity/undo.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/activity_pub/activity/undo.rb b/app/models/activity_pub/activity/undo.rb index 4b6a5a4c..18fbff5e 100644 --- a/app/models/activity_pub/activity/undo.rb +++ b/app/models/activity_pub/activity/undo.rb @@ -17,7 +17,10 @@ class ActivityPub # # @see {https://github.com/hyphacoop/social.distributed.press/issues/43} def update_activity_pub_state! - activity_pub.remove! + ActivityPub.transaction do + ActivityPub::Activity.find_by(uri: content['object'])&.activity_pub&.remove! + activity_pub.remove! + end end end end From 476ab951bbd4f8d0f560ffdcb3ba709e1b5ba258 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 13:31:31 -0300 Subject: [PATCH 043/297] fixup! feat: rutas --- app/controllers/api/v1/webhooks/pull_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/pull_controller.rb b/app/controllers/api/v1/webhooks/pull_controller.rb index 5f0b703b..93256fc7 100644 --- a/app/controllers/api/v1/webhooks/pull_controller.rb +++ b/app/controllers/api/v1/webhooks/pull_controller.rb @@ -5,7 +5,7 @@ module Api module Webhooks # Recibe webhooks y lanza un PullJob class PullController < BaseController - include WebhookConcern + include Api::V1::Webhooks::Concerns::WebhookConcern # Trae los cambios a partir de un post de Webhooks: # (Gitlab, Github, Gitea, etc) From 1404d2006d4566508c9eaa242de0940c4af61836 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 13:32:30 -0300 Subject: [PATCH 044/297] fixup! feat: asociar rol con deploy nos permite acceder al token --- db/migrate/20240216170202_add_rol_to_deploys.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20240216170202_add_rol_to_deploys.rb b/db/migrate/20240216170202_add_rol_to_deploys.rb index 5f629432..93c4553d 100644 --- a/db/migrate/20240216170202_add_rol_to_deploys.rb +++ b/db/migrate/20240216170202_add_rol_to_deploys.rb @@ -6,7 +6,7 @@ class AddRolToDeploys < ActiveRecord::Migration[6.1] add_column :deploys, :rol_id, :integer, index: true Deploy.find_each do |deploy| - rol_id = deploy.site.roles.find_by(rol: 'usuarie', temporal: false).id + rol_id = deploy.site.roles.find_by(rol: 'usuarie', temporal: false)&.id deploy.update_column(:rol_id, rol_id) if rol_id end From c415c79c5c91eaca7ebf615a46fc33df88efa8df Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 17:46:58 -0300 Subject: [PATCH 045/297] =?UTF-8?q?fix:=20por=20alguna=20raz=C3=B3n=20usam?= =?UTF-8?q?os=20REDIS=5FSERVER?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/social_inbox.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 45b8afd8..b0fde523 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -38,7 +38,7 @@ class SocialInbox public_key_url: public_key_url, private_key_pem: site.private_key_pem, logger: Rails.logger, - cache_store: :redis + cache_store: HTTParty::Cache::Store::Redis.new(redis_url: ENV['REDIS_SERVER']) ) end From b0e07d5d023baeddd7d63ef21cc690b9c140c31e Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 17:56:12 -0300 Subject: [PATCH 046/297] fix: require --- app/models/social_inbox.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index b0fde523..036aa02b 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -3,6 +3,7 @@ require 'distributed_press/v1/social/client' require 'distributed_press/v1/social/hook' require 'distributed_press/v1/social/dereferencer' +require 'httparty/cache/store/redis' # Gestiona la Social Inbox de un sitio class SocialInbox From 1022d57fd3e2ee56de3c56353d26ef6a5627346e Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 17:58:51 -0300 Subject: [PATCH 047/297] fix: los scopes en las rutas son raros --- app/models/deploy_social_distributed_press.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index fc0e01d5..29670d3b 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -73,7 +73,7 @@ class DeploySocialDistributedPress < Deploy webhook_class = DistributedPress::V1::Social::Schemas::Webhook hook_client.class::EVENTS.each do |event| - event_url = :"v1_site_webhooks_social_inbox_#{event}_url" + event_url = :"v1_site_webhooks_#{event}_url" webhook = webhook_class.new.call({ From f50e2d430d4ca57eb743ec5fe226a064e8d34495 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 18:01:09 -0300 Subject: [PATCH 048/297] fix: dump --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7aac4c12..8ece3064 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -272,7 +272,7 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - httparty-cache (0.0.4) + httparty-cache (0.0.5) httparty (~> 0.18) i18n (1.14.1) concurrent-ruby (~> 1.0) From c23d931dd43e1ca248a71df495b39c6830a8a718 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 18:04:56 -0300 Subject: [PATCH 049/297] =?UTF-8?q?fix:=20usar=20el=20m=C3=A9todo=20correc?= =?UTF-8?q?to?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/deploy_social_distributed_press.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index 29670d3b..7f761e46 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -90,7 +90,7 @@ class DeploySocialDistributedPress < Deploy response = hook_client.put(event: event, hook: webhook) - raise ArgumentError, response.parsed_body unless response.ok? + raise ArgumentError, response.body unless response.ok? rescue ArgumentError => e ExceptionNotifier.notify_exception(e, data: { site_id: site.name, usuarie_id: rol.usuarie_id }) end From c02a9762068284586f5c636dc98694613774b21e Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 18:08:48 -0300 Subject: [PATCH 050/297] feat: poder acceder a la inbox --- app/models/social_inbox.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 036aa02b..21b0ca45 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -2,6 +2,7 @@ require 'distributed_press/v1/social/client' require 'distributed_press/v1/social/hook' +require 'distributed_press/v1/social/inbox' require 'distributed_press/v1/social/dereferencer' require 'httparty/cache/store/redis' @@ -43,6 +44,11 @@ class SocialInbox ) end + # @return [DistributedPress::V1::Social::Inbox] + def inbox + @inbox ||= DistributedPress::V1::Social::Inbox.new(client: client, actor: actor) + end + # @return [DistributedPress::V1::Social::Dereferencer] def dereferencer @dereferencer ||= DistributedPress::V1::Social::Dereferencer.new(client: client) From 9b401f8c120f9cf074779c8c2bea724d21fec854 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 18:13:53 -0300 Subject: [PATCH 051/297] fix: el hostname puede estar en varios lados --- app/models/social_inbox.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 21b0ca45..2f5e7eca 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -67,9 +67,12 @@ class SocialInbox end end + # El hostname puede estar en varios lados... + # + # @return [String] def hostname @hostname ||= - site.config.dig('activity_pub', 'hostname') || site.hostname + site.config.dig('activity_pub', 'hostname') || site.config['hostname'] || site.hostname end # Genera una URI dentro de este sitio From 3a382390b1e82f78df79b6910cfd6d4ecdee8c1c Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 18:42:20 -0300 Subject: [PATCH 052/297] fix: la social inbox no confirma acciones! --- .../v1/webhooks/social_inbox_controller.rb | 24 ++++++++++++++----- app/models/activity_pub.rb | 8 +++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index c71c4922..1ffc1596 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -38,21 +38,33 @@ module Api head :accepted end - # Cuando aprobamos una actividad, recibimos la confirmación y - # cambiamos el estado. + # Cuando la Social Inbox acepta una actividad, la recibimos + # igual y la guardamos por si cambiamos de idea. + # + # @todo DRY def onapproved ActivityPub.transaction do - activity_pub.approve! if activity_pub.waiting? + actor.present? + instance.present? + object.present? + activity.present? + activity_pub.approve! end head :accepted end - # Cuando rechazamos una actividad, recibimos la confirmación y - # cambiamos el estado + # Cuando la Social Inbox rechaza una actividad, la recibimos + # igual y la guardamos por si cambiamos de idea. + # + # @todo DRY def onrejected ActivityPub.transaction do - activity_pub.reject! if activity_pub.waiting? + actor.present? + instance.present? + object.present? + activity.present? + activity_pub.reject! end head :accepted diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index df8e5c5c..07754b87 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -21,8 +21,6 @@ class ActivityPub < ApplicationRecord aasm do # Todavía no hay una decisión sobre el objeto state :paused, initial: true - # Estamos esperando respuesta desde la Social Inbox - state :waiting # Le usuarie aprobó el objeto state :approved # Le usuarie rechazó el objeto @@ -46,17 +44,17 @@ class ActivityPub < ApplicationRecord # Si un objeto previamente aprobado fue actualizado, volvemos a # pausarlo. event :pause do - transitions from: %i[waiting approved rejected], to: :paused + transitions from: %i[approved rejected], to: :paused end # La actividad se aprueba event :approve do - transitions from: :waiting, to: :approved + transitions from: %i[paused rejected], to: :approved end # La actividad fue rechazada event :reject do - transitions from: :waiting, to: :rejected + transitions from: %i[paused approved], to: :rejected end end end From fd1d2382fa481ec45888bb036bc8aac27e3d50b4 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Feb 2024 18:42:36 -0300 Subject: [PATCH 053/297] feat: luego de rechazar podemos reportar --- app/models/activity_pub.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 07754b87..217c15a1 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -56,5 +56,10 @@ class ActivityPub < ApplicationRecord event :reject do transitions from: %i[paused approved], to: :rejected end + + # Solo podemos reportarla luego de rechazarla + event :report do + transitions from: :rejected, to: :reported + end end end From 836e5d5935e675d46adf507353a4a6de4332f4bf Mon Sep 17 00:00:00 2001 From: f Date: Fri, 23 Feb 2024 12:26:33 -0300 Subject: [PATCH 054/297] fix: las actividades pueden existir previamente --- .../api/v1/webhooks/social_inbox_controller.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 1ffc1596..8b8ba35f 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -132,12 +132,14 @@ module Api # # @return [ActivityPub::Activity] def activity - @activity ||= ActivityPub::Activity.type_from(original_activity).new(uri: original_activity[:id], - activity_pub: activity_pub).tap do |a| - a.content = original_activity.dup - a.content[:object] = object.uri - a.save! - end + @activity ||= + ActivityPub::Activity + .type_from(original_activity) + .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub).tap do |a| + a.content = original_activity.dup + a.content[:object] = object.uri + a.save! + end end # Actor, si no hay instancia, la crea en el momento From a51cb4dbddb0a984e2546edc807c4a7e897321e2 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 23 Feb 2024 12:36:03 -0300 Subject: [PATCH 055/297] =?UTF-8?q?fix:=20copiar=20el=20contexto=20desde?= =?UTF-8?q?=20la=20acci=C3=B3n=20al=20objeto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 8b8ba35f..02c91813 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -171,7 +171,9 @@ module Api # @return [Hash,String] def original_object - @original_object ||= original_activity[:object].dup + @original_object ||= original_activity[:object].dup.tap do |o| + o[:@context] = original_activity[:@context].dup + end end end end From a4062c5f545a7f2bac4c5b909d83eaca037ea3ec Mon Sep 17 00:00:00 2001 From: f Date: Fri, 23 Feb 2024 12:52:01 -0300 Subject: [PATCH 056/297] feat: eliminar un objeto elimina todas las actividades pendientes --- app/models/activity_pub/activity/delete.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 351dd3cb..9e7daa16 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -3,10 +3,19 @@ class ActivityPub class Activity class Delete < ActivityPub::Activity - # Si estamos eliminando el objeto, tenemos que vaciar su contenido y - # cambiar el estado a borrado. + # Los Delete se refieren a objetos. Al eliminar un objeto, + # cancelamos todas las actividades que tienen relacionadas. + # + # XXX: La actividad tiene una firma, pero la implementación no + # está recomendada + # + # @see {https://docs.joinmastodon.org/spec/security/#ld} def update_activity_pub_state! - activity_pub.remove! + ActivityPub.transaction do + ActivityPub::Object.find_by(uri: ActivityPub.uri_from_object(content['object']))&.activity_pubs&.find_each(&:remove!) + + activity_pub.remove! + end end end end From 2c8dbc885c38d25037df28b1a99a1ce0991ff5fd Mon Sep 17 00:00:00 2001 From: f Date: Fri, 23 Feb 2024 12:53:56 -0300 Subject: [PATCH 057/297] fix: evitar advertencias --- app/controllers/application_controller.rb | 2 +- app/models/concerns/tienda.rb | 2 +- app/models/site.rb | 2 +- app/models/site/api.rb | 2 +- app/models/site/social_distributed_press.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2746ab10..3201b909 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,7 +3,7 @@ # Forma de ingreso a Sutty class ApplicationController < ActionController::Base include ExceptionHandler - include Pundit + include Pundit::Authorization protect_from_forgery with: :null_session, prepend: true diff --git a/app/models/concerns/tienda.rb b/app/models/concerns/tienda.rb index cd09358e..86174c9a 100644 --- a/app/models/concerns/tienda.rb +++ b/app/models/concerns/tienda.rb @@ -5,7 +5,7 @@ module Tienda extend ActiveSupport::Concern included do - encrypts :tienda_api_key + has_encrypted :tienda_api_key def tienda? tienda_api_key.present? && tienda_url.present? diff --git a/app/models/site.rb b/app/models/site.rb index dd250e3d..a9cb3652 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -17,7 +17,7 @@ class Site < ApplicationRecord # tiene acceso pero los datos se guardan cifrados en el sitio. Esto # protege información privada en repositorios públicos, pero no la # protege de acceso al panel de Sutty! - encrypts :private_key + has_encrypted :private_key validates :name, uniqueness: true, hostname: { allow_root_label: true diff --git a/app/models/site/api.rb b/app/models/site/api.rb index 73f8e710..6c6f0ece 100644 --- a/app/models/site/api.rb +++ b/app/models/site/api.rb @@ -5,7 +5,7 @@ class Site extend ActiveSupport::Concern included do - encrypts :api_key + has_encrypted :api_key before_save :add_api_key_if_missing! # Genera mensajes secretos que podemos usar para la API de cada diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index c3abe06e..7ebdcb36 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -8,7 +8,7 @@ class Site extend ActiveSupport::Concern included do - encrypts :private_key_pem + has_encrypted :private_key_pem has_many :activity_pubs From e3aabf4b91e485ddfa90293fc71d1e41145fea7f Mon Sep 17 00:00:00 2001 From: f Date: Fri, 23 Feb 2024 12:54:31 -0300 Subject: [PATCH 058/297] feat: poder obtener el id desde un objeto --- app/models/activity_pub.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 217c15a1..e99fa1f4 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -18,6 +18,16 @@ class ActivityPub < ApplicationRecord validates :object_id, presence: true validates :aasm_state, presence: true, inclusion: { in: %w[paused approved rejected reported removed] } + # Encuentra la URI de un objeto + # + # @return [String, nil] + def self.uri_from_object(object) + case object + when String then object + when Hash then object['id'] + end + end + aasm do # Todavía no hay una decisión sobre el objeto state :paused, initial: true From 77137f311cba52a54b0a028f18d7f009f011c9cb Mon Sep 17 00:00:00 2001 From: f Date: Fri, 23 Feb 2024 12:56:39 -0300 Subject: [PATCH 059/297] docs: recordar que tenemos que validar mejor --- app/models/activity_pub/activity/delete.rb | 2 ++ app/models/activity_pub/activity/undo.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 9e7daa16..f6ff6536 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -9,6 +9,8 @@ class ActivityPub # XXX: La actividad tiene una firma, pero la implementación no # está recomendada # + # @todo Validar que le Actor corresponda con los objetos. Esto ya + # lo haría la Social Inbox por nosotres. # @see {https://docs.joinmastodon.org/spec/security/#ld} def update_activity_pub_state! ActivityPub.transaction do diff --git a/app/models/activity_pub/activity/undo.rb b/app/models/activity_pub/activity/undo.rb index 18fbff5e..ae78a0d3 100644 --- a/app/models/activity_pub/activity/undo.rb +++ b/app/models/activity_pub/activity/undo.rb @@ -15,6 +15,8 @@ class ActivityPub # Sin embargo, estas acciones nunca deberían llegar a nuestra # Inbox. # + # @todo Validar que le Actor corresponda con los objetos. Esto ya + # lo haría la Social Inbox por nosotres. # @see {https://github.com/hyphacoop/social.distributed.press/issues/43} def update_activity_pub_state! ActivityPub.transaction do From f83642797922b32f316398e276533095180b2e35 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 24 Feb 2024 11:09:57 -0300 Subject: [PATCH 060/297] =?UTF-8?q?feat:=20obtener=20informaci=C3=B3n=20so?= =?UTF-8?q?bre=20la=20instancia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v1/webhooks/social_inbox_controller.rb | 3 +++ app/jobs/activity_pub/instance_fetch_job.rb | 22 +++++++++++++++++++ app/models/social_inbox.rb | 8 +++---- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 app/jobs/activity_pub/instance_fetch_job.rb diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 02c91813..97ca58b5 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -150,6 +150,9 @@ module Api next if a.instance a.instance = ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) + + ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) + a.save! end end diff --git a/app/jobs/activity_pub/instance_fetch_job.rb b/app/jobs/activity_pub/instance_fetch_job.rb new file mode 100644 index 00000000..a5c07162 --- /dev/null +++ b/app/jobs/activity_pub/instance_fetch_job.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ActivityPub + # Obtiene o actualiza los datos de una instancia. + class InstanceFetchJob < ApplicationJob + def perform(site:, instance:) + %w[/api/v2/instance /api/v1/instance].each do |api| + uri = SocialInbox.generate_uri(instance.hostname) do |u| + u.path = api + end + + response = site.social_inbox.dereferencer.get(uri: uri) + + next unless response.ok? + + instance.update(content: response.parsed_response) + + break + end + end + end +end diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 2f5e7eca..03612779 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -28,7 +28,7 @@ class SocialInbox end def actor_id - @actor_id ||= generate_uri do |uri| + @actor_id ||= SocialInbox.generate_uri(hostname) do |uri| uri.path = '/about.jsonld' end end @@ -61,7 +61,7 @@ class SocialInbox # @return [String] def public_key_url - @public_key_url ||= generate_uri do |uri| + @public_key_url ||= SocialInbox.generate_uri(hostname) do |uri| uri.path = '/about.jsonld' uri.fragment = 'main-key' end @@ -78,7 +78,7 @@ class SocialInbox # Genera una URI dentro de este sitio # # @return [String] - def generate_uri(&block) - @public_key_url ||= URI("https://#{hostname}").tap(&block).to_s + def self.generate_uri(hostname, &block) + URI("https://#{hostname}").tap(&block).to_s end end From 9e54d6311c0fbd9a7dfc34ab5b37460cf97b1021 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 24 Feb 2024 12:46:55 -0300 Subject: [PATCH 061/297] =?UTF-8?q?feat:=20obtener=20informaci=C3=B3n=20de?= =?UTF-8?q?=20le=20actore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v1/webhooks/social_inbox_controller.rb | 2 ++ app/jobs/activity_pub/actor_fetch_job.rb | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 app/jobs/activity_pub/actor_fetch_job.rb diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 97ca58b5..a5951716 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -154,6 +154,8 @@ module Api ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) a.save! + + ActivityPub::ActorFetchJob.perform_later(site: site, actor: a) end end diff --git a/app/jobs/activity_pub/actor_fetch_job.rb b/app/jobs/activity_pub/actor_fetch_job.rb new file mode 100644 index 00000000..1c6f1735 --- /dev/null +++ b/app/jobs/activity_pub/actor_fetch_job.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Obtiene o actualiza el contenido de un objeto, usando las credenciales +# del sitio. +# +# XXX: Esto usa las credenciales del sitio para volver el objeto +# disponible para todo el CMS. Asumimos que el objeto devuelto es el +# mismo para todo el mundo y las credenciales solo son para +# autenticación. +class ActivityPub + class ActorFetchJob < ApplicationJob + def perform(site:, actor:) + ActivityPub::Actor.transaction do + response = site.social_inbox.dereferencer.get(uri: actor.uri) + + # @todo Fallar cuando la respuesta no funcione? + return unless response.ok? + return if response.miss? && actor.content.present? + + actor.update(content: FastJsonparser.parse(response.body)) + end + end + end +end From a76c652f98e217a451a8b489586c3dc7500b8ef0 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 24 Feb 2024 12:47:11 -0300 Subject: [PATCH 062/297] =?UTF-8?q?fix:=20permitir=20guardar=20le=20actore?= =?UTF-8?q?=20si=20la=20instancia=20ya=20exist=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/v1/webhooks/social_inbox_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index a5951716..a1ff9677 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -147,11 +147,11 @@ module Api # @return [Actor] def actor @actor ||= ActivityPub::Actor.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| - next if a.instance + unless a.instance + a.instance = ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) - a.instance = ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) - - ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) + ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) + end a.save! From 380d484c00aa2b53d171c0c85bc21160482679d0 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 24 Feb 2024 13:04:52 -0300 Subject: [PATCH 063/297] feat: poder encontrar instancias a partir de actividades --- app/controllers/moderation_queue_controller.rb | 6 ++++++ app/models/activity_pub/activity.rb | 1 + app/models/activity_pub/actor.rb | 1 + app/models/activity_pub/instance.rb | 4 ++++ app/models/activity_pub/object.rb | 7 +++++++ app/views/moderation_queue/_instance.haml | 18 +++++++----------- app/views/moderation_queue/_instances.haml | 2 +- .../20240223170317_add_actor_to_activities.rb | 18 ++++++++++++++++++ db/structure.sql | 6 ++++-- 9 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 db/migrate/20240223170317_add_actor_to_activities.rb diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index eec0c70f..5f94e1f7 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -5,6 +5,12 @@ class ModerationQueueController < ApplicationController # Cola de moderación viendo todo el sitio def index dummy_data + + # @todo cambiar el estado por query + @activity_pubs = site.activity_pubs.where(aasm_state: 'paused') + @activities = ActivityPub::Activity.where(activity_pub_id: @activity_pubs.pluck(:id)) + @actors = ActivityPub::Actor.where(id: @activities.unscoped.distinct.pluck(:actor_id)) + @instances = ActivityPub::Instance.where(id: @actors.distinct.pluck(:instance_id)) end # Perfil remoto de usuarie diff --git a/app/models/activity_pub/activity.rb b/app/models/activity_pub/activity.rb index 5ee3d2d1..a220b831 100644 --- a/app/models/activity_pub/activity.rb +++ b/app/models/activity_pub/activity.rb @@ -16,6 +16,7 @@ class ActivityPub include ActivityPub::Concerns::JsonLdConcern belongs_to :activity_pub + belongs_to :actor has_one :object, through: :activity_pub validates :activity_pub_id, presence: true diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index e79a596a..7a858a7e 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -11,5 +11,6 @@ class ActivityPub belongs_to :instance has_many :activity_pubs, as: :object + has_many :activities end end diff --git a/app/models/activity_pub/instance.rb b/app/models/activity_pub/instance.rb index b13b8676..17bf183d 100644 --- a/app/models/activity_pub/instance.rb +++ b/app/models/activity_pub/instance.rb @@ -19,5 +19,9 @@ class ActivityPub state :allowed state :blocked end + + def uri + @uri ||= "https://#{hostname}/" + end end end diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index 898d5375..c196160f 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -6,5 +6,12 @@ class ActivityPub include ActivityPub::Concerns::JsonLdConcern has_many :activity_pubs, as: :object + + # Encontrar le Actor por su relación con el objeto + # + # @return [ActivityPub::Actor,nil] + def actor + ActivityPub::Actor.find_by(uri: content['actor']) + end end end diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index cff8a957..958a0199 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -1,16 +1,12 @@ -- host = instance['domain'] -- host ||= instance['uri'] -- hosthttps = "https://#{host}" - .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: host + = render 'components/checkbox', id: instance.hostname .col-11 - %h4 - %a{ href: hosthttps }= instance['title'] - %p= instance['description'].html_safe - %p + %h4 + %a{ href: instance.uri }= instance.content['title'] + %p= instance.content['description'].html_safe + %p %span= t('.users') %span - = instance.dig('usage', 'users', 'active_month') - = instance.dig('stats', 'user_count') + = instance.content.dig('usage', 'users', 'active_month') + = instance.content.dig('stats', 'user_count') diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 1accf60d..e836d7e0 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -7,7 +7,7 @@ -# Botones moderación .d-flex.pb-4 - = render 'components/instances_btn_box' + = render 'components/instances_btn_box', instance: instance %hr %h3.mt-5= t('moderation_queue.instances.title') diff --git a/db/migrate/20240223170317_add_actor_to_activities.rb b/db/migrate/20240223170317_add_actor_to_activities.rb new file mode 100644 index 00000000..a546cd94 --- /dev/null +++ b/db/migrate/20240223170317_add_actor_to_activities.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Relaciona Actor con Activity +class AddActorToActivities < ActiveRecord::Migration[6.1] + def up + add_column :activity_pub_activities, :actor_id, :uuid, index: true + + ActivityPub::Activity.find_each do |activity| + actor = ActivityPub::Actor.find_by(uri: activity.content['actor']) + + activity.update(actor: actor) if actor.present? + end + end + + def down + remove_column :activity_pub_activities, :actor_id, :uuid, index: true + end +end diff --git a/db/structure.sql b/db/structure.sql index ee99e791..97e9ba36 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -484,7 +484,8 @@ CREATE TABLE public.activity_pub_activities ( activity_pub_id uuid NOT NULL, type character varying NOT NULL, uri character varying NOT NULL, - content jsonb DEFAULT '{}'::jsonb + content jsonb DEFAULT '{}'::jsonb, + actor_id uuid ); @@ -2472,6 +2473,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240219204011'), ('20240219204224'), ('20240220161414'), -('20240221184007'); +('20240221184007'), +('20240223170317'); From 64cef8a13e5532ad670378dd8dffd04ef9135dbf Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 10:39:00 -0300 Subject: [PATCH 064/297] feat: relacionar actividades con instancias de origen --- app/controllers/moderation_queue_controller.rb | 6 ++---- app/models/activity_pub.rb | 1 + ...226133022_add_instance_id_to_activity_pubs.rb | 16 ++++++++++++++++ db/structure.sql | 6 ++++-- 4 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20240226133022_add_instance_id_to_activity_pubs.rb diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index 5f94e1f7..8920c717 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -7,10 +7,8 @@ class ModerationQueueController < ApplicationController dummy_data # @todo cambiar el estado por query - @activity_pubs = site.activity_pubs.where(aasm_state: 'paused') - @activities = ActivityPub::Activity.where(activity_pub_id: @activity_pubs.pluck(:id)) - @actors = ActivityPub::Actor.where(id: @activities.unscoped.distinct.pluck(:actor_id)) - @instances = ActivityPub::Instance.where(id: @actors.distinct.pluck(:instance_id)) + @activity_pubs = site.activity_pubs + @instances = ActivityPub::Instance.where(id: @activity_pubs.distinct.pluck(:instance_id)) end # Perfil remoto de usuarie diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index e99fa1f4..1afeee96 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -10,6 +10,7 @@ class ActivityPub < ApplicationRecord include AASM + belongs_to :instance belongs_to :site belongs_to :object, polymorphic: true has_many :activities diff --git a/db/migrate/20240226133022_add_instance_id_to_activity_pubs.rb b/db/migrate/20240226133022_add_instance_id_to_activity_pubs.rb new file mode 100644 index 00000000..710aacef --- /dev/null +++ b/db/migrate/20240226133022_add_instance_id_to_activity_pubs.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Relaciona instancias con sus actividades +class AddInstanceIdToActivityPubs < ActiveRecord::Migration[6.1] + def up + add_column :activity_pubs, :instance_id, :uuid, index: true + + ActivityPub.all.find_each do |activity_pub| + activity_pub.update(instance: activity_pub&.object&.actor&.instance) + end + end + + def down + remove_column :activity_pubs, :instance_id, :uuid, index: true + end +end diff --git a/db/structure.sql b/db/structure.sql index 97e9ba36..4fb11e5a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -541,7 +541,8 @@ CREATE TABLE public.activity_pubs ( site_id bigint NOT NULL, object_id uuid NOT NULL, object_type character varying NOT NULL, - aasm_state character varying NOT NULL + aasm_state character varying NOT NULL, + instance_id uuid ); @@ -2474,6 +2475,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240219204224'), ('20240220161414'), ('20240221184007'), -('20240223170317'); +('20240223170317'), +('20240226133022'); From dc11e6efc76a72a6d934f8eee13718b206531206 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 12:27:56 -0300 Subject: [PATCH 065/297] =?UTF-8?q?feat:=20cada=20sitio=20tiene=20un=20est?= =?UTF-8?q?ado=20de=20moderaci=C3=B3n=20para=20la=20instancia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v1/webhooks/social_inbox_controller.rb | 5 +- .../instance_moderations_controller.rb | 35 +++++++++ .../moderation_queue_controller.rb | 2 +- app/models/activity_pub/instance.rb | 1 + app/models/instance_moderation.rb | 27 +++++++ app/models/site/social_distributed_press.rb | 1 + app/policies/instance_moderation_policy.rb | 16 +++++ app/views/components/_btn_base.haml | 6 +- app/views/components/_instances_btn_box.haml | 6 +- app/views/moderation_queue/_instances.haml | 6 +- app/views/moderation_queue/index.haml | 2 +- config/routes.rb | 6 ++ ...240226134335_create_instance_moderation.rb | 26 +++++++ db/structure.sql | 72 ++++++++++++++++++- 14 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 app/controllers/instance_moderations_controller.rb create mode 100644 app/models/instance_moderation.rb create mode 100644 app/policies/instance_moderation_policy.rb create mode 100644 db/migrate/20240226134335_create_instance_moderation.rb diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index a1ff9677..12545915 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -142,7 +142,8 @@ module Api end end - # Actor, si no hay instancia, la crea en el momento + # Actor, si no hay instancia, la crea en el momento, junto con + # su estado de moderación. # # @return [Actor] def actor @@ -150,6 +151,8 @@ module Api unless a.instance a.instance = ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) + site.instance_moderations.find_or_create_by(instance: a.instance) + ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) end diff --git a/app/controllers/instance_moderations_controller.rb b/app/controllers/instance_moderations_controller.rb new file mode 100644 index 00000000..55f3c51b --- /dev/null +++ b/app/controllers/instance_moderations_controller.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# Actualiza la relación entre un sitio y una instancia +class InstanceModerationsController < ApplicationController + before_action :authorize_policy + + def pause + instance_moderation.pause! + + redirect_to site_moderation_queue_path + end + + def allow + instance_moderation.allow! + + redirect_to site_moderation_queue_path + end + + def block + instance_moderation.block! + + redirect_to site_moderation_queue_path + end + + private + + # @return [InstanceModeration] + def instance_moderation + @instance_moderation ||= site.instance_moderations.find(params[:instance_moderation_id]) + end + + def authorize_policy + authorize instance_moderation + end +end diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index 8920c717..d2123234 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -8,7 +8,7 @@ class ModerationQueueController < ApplicationController # @todo cambiar el estado por query @activity_pubs = site.activity_pubs - @instances = ActivityPub::Instance.where(id: @activity_pubs.distinct.pluck(:instance_id)) + @instance_moderations = site.instance_moderations end # Perfil remoto de usuarie diff --git a/app/models/activity_pub/instance.rb b/app/models/activity_pub/instance.rb index 17bf183d..627ccb10 100644 --- a/app/models/activity_pub/instance.rb +++ b/app/models/activity_pub/instance.rb @@ -13,6 +13,7 @@ class ActivityPub has_many :activity_pubs has_many :actors + has_many :instance_moderations aasm do state :paused, initial: true diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb new file mode 100644 index 00000000..3c092958 --- /dev/null +++ b/app/models/instance_moderation.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# Mantiene el registro de relaciones entre sitios e instancias +class InstanceModeration < ApplicationRecord + include AASM + + belongs_to :site + belongs_to :instance, class_name: 'ActivityPub::Instance' + + aasm do + state :paused, initial: true + state :allowed + state :blocked + + event :pause do + transitions from: %i[allowed blocked], to: :paused + end + + event :allow do + transitions from: %i[paused blocked], to: :allowed + end + + event :block do + transitions from: %i[paused allowed], to: :blocked + end + end +end diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index 7ebdcb36..73b284bf 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -11,6 +11,7 @@ class Site has_encrypted :private_key_pem has_many :activity_pubs + has_many :instance_moderations before_save :generate_private_key_pem!, unless: :private_key_pem? diff --git a/app/policies/instance_moderation_policy.rb b/app/policies/instance_moderation_policy.rb new file mode 100644 index 00000000..6b3157e8 --- /dev/null +++ b/app/policies/instance_moderation_policy.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Solo les usuaries pueden moderar instancias +InstanceModerationPolicy = Struct.new(:usuarie, :instance_moderation) do + def pause? + instance_moderation.site.usuarie? usuarie + end + + def allow? + pause? + end + + def block? + pause? + end +end diff --git a/app/views/components/_btn_base.haml b/app/views/components/_btn_base.haml index 7fa507ca..677b88f1 100644 --- a/app/views/components/_btn_base.haml +++ b/app/views/components/_btn_base.haml @@ -1,3 +1,7 @@ -# Componente Botón general Moderación -%button.btn{ href: href, class: local_assigns[:class] }= text +- local_assigns[:method] ||= 'patch' +- local_assigns[:class] = "btn #{local_assigns[:class]}" + +-# @todo path es obligatorio += button_to text, local_assigns[:path], **local_assigns diff --git a/app/views/components/_instances_btn_box.haml b/app/views/components/_instances_btn_box.haml index 854262c0..74cad4a4 100644 --- a/app/views/components/_instances_btn_box.haml +++ b/app/views/components/_instances_btn_box.haml @@ -1,6 +1,6 @@ -# Componente botonera de moderación de Instancias - btn_class = 'btn btn-secondary' -= render 'components/btn_base', text: t('.text_check'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_allow'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_deny'), class: btn_class, href: '' \ No newline at end of file += render 'components/btn_base', path: site_instance_moderation_pause_path(instance_moderation_id: instance_moderation), text: t('.text_check'), class: btn_class, disabled: !instance_moderation.may_pause? += render 'components/btn_base', path: site_instance_moderation_allow_path(instance_moderation_id: instance_moderation), text: t('.text_allow'), class: btn_class, disabled: !instance_moderation.may_allow? += render 'components/btn_base', path: site_instance_moderation_block_path(instance_moderation_id: instance_moderation), text: t('.text_deny'), class: btn_class, disabled: !instance_moderation.may_block? diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index e836d7e0..318cddef 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -1,13 +1,13 @@ -# Filtros = render 'components/instances_filters' -- @instances.each do |instance| +- instance_moderations.each do |instance_moderation| %hr - = render 'moderation_queue/instance', instance: instance + = render 'moderation_queue/instance', instance: instance_moderation.instance -# Botones moderación .d-flex.pb-4 - = render 'components/instances_btn_box', instance: instance + = render 'components/instances_btn_box', site: site, instance_moderation: instance_moderation %hr %h3.mt-5= t('moderation_queue.instances.title') diff --git a/app/views/moderation_queue/index.haml b/app/views/moderation_queue/index.haml index ab98ee30..0c937758 100644 --- a/app/views/moderation_queue/index.haml +++ b/app/views/moderation_queue/index.haml @@ -5,7 +5,7 @@ .col - summary = t('.instances') = render 'layouts/details', summary: summary do - = render 'moderation_queue/instances', site: @site, post: @post, moderation_queue: @moderation_queue + = render 'moderation_queue/instances', site: @site, instance_moderations: @instance_moderations %hr - summary = t('.accounts') = render 'layouts/details', summary: summary do diff --git a/config/routes.rb b/config/routes.rb index ddc29994..b6250902 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -62,6 +62,12 @@ Rails.application.routes.draw do get 'remote_profile', to: 'moderation_queue#remote_profile' get 'instances', to: 'moderation_queue#instances' + resources :instance_moderations, only: [] do + patch :pause, to: 'instance_moderations#pause' + patch :allow, to: 'instance_moderations#allow' + patch :block, to: 'instance_moderations#block' + end + # Gestionar artículos según idioma nested do scope '/(:locale)', constraint: /[a-z]{2}(-[A-Z]{2})?/ do diff --git a/db/migrate/20240226134335_create_instance_moderation.rb b/db/migrate/20240226134335_create_instance_moderation.rb new file mode 100644 index 00000000..8b08e14e --- /dev/null +++ b/db/migrate/20240226134335_create_instance_moderation.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Como la instancia es única para todo el panel, necesitamos llevar +# registro de su relación con cada sitio por separado. +class CreateInstanceModeration < ActiveRecord::Migration[6.1] + def up + create_table :instance_moderations do |t| + t.timestamps + + t.belongs_to :site + t.uuid :instance_id, index: true + + t.string :aasm_state, null: false, default: 'paused' + + t.index %i[site_id instance_id], unique: true + end + + ActivityPub.all.find_each do |activity_pub| + InstanceModeration.find_or_create_by(site: activity_pub.site, instance: activity_pub.instance) + end + end + + def down + drop_table :instance_moderations + end +end diff --git a/db/structure.sql b/db/structure.sql index 4fb11e5a..c3896060 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -950,6 +950,39 @@ CREATE TABLE public.indexed_posts ( ); +-- +-- Name: instance_moderations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.instance_moderations ( + id bigint NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + site_id bigint, + instance_id uuid, + aasm_state character varying DEFAULT 'paused'::character varying NOT NULL +); + + +-- +-- Name: instance_moderations_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.instance_moderations_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: instance_moderations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.instance_moderations_id_seq OWNED BY public.instance_moderations.id; + + -- -- Name: licencias; Type: TABLE; Schema: public; Owner: - -- @@ -1514,6 +1547,13 @@ ALTER TABLE ONLY public.designs ALTER COLUMN id SET DEFAULT nextval('public.desi ALTER TABLE ONLY public.distributed_press_publishers ALTER COLUMN id SET DEFAULT nextval('public.distributed_press_publishers_id_seq'::regclass); +-- +-- Name: instance_moderations id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.instance_moderations ALTER COLUMN id SET DEFAULT nextval('public.instance_moderations_id_seq'::regclass); + + -- -- Name: licencias id; Type: DEFAULT; Schema: public; Owner: - -- @@ -1774,6 +1814,14 @@ ALTER TABLE ONLY public.indexed_posts ADD CONSTRAINT indexed_posts_pkey PRIMARY KEY (id); +-- +-- Name: instance_moderations instance_moderations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.instance_moderations + ADD CONSTRAINT instance_moderations_pkey PRIMARY KEY (id); + + -- -- Name: licencias licencias_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2139,6 +2187,27 @@ CREATE INDEX index_indexed_posts_on_locale ON public.indexed_posts USING btree ( CREATE INDEX index_indexed_posts_on_site_id ON public.indexed_posts USING btree (site_id); +-- +-- Name: index_instance_moderations_on_instance_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_instance_moderations_on_instance_id ON public.instance_moderations USING btree (instance_id); + + +-- +-- Name: index_instance_moderations_on_site_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_instance_moderations_on_site_id ON public.instance_moderations USING btree (site_id); + + +-- +-- Name: index_instance_moderations_on_site_id_and_instance_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_instance_moderations_on_site_id_and_instance_id ON public.instance_moderations USING btree (site_id, instance_id); + + -- -- Name: index_licencias_on_name; Type: INDEX; Schema: public; Owner: - -- @@ -2476,6 +2545,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240220161414'), ('20240221184007'), ('20240223170317'), -('20240226133022'); +('20240226133022'), +('20240226134335'); From ffdcb5fae8b37187963182323076ddf048e149c9 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 16:12:56 -0300 Subject: [PATCH 066/297] =?UTF-8?q?feat:=20poder=20cambiar=20el=20estado?= =?UTF-8?q?=20de=20moderaci=C3=B3n=20a=20instancias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub/instance.rb | 6 ++++++ app/models/instance_moderation.rb | 30 ++++++++++++++++++++++++++--- app/models/social_inbox.rb | 12 ++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/app/models/activity_pub/instance.rb b/app/models/activity_pub/instance.rb index 627ccb10..42cd2695 100644 --- a/app/models/activity_pub/instance.rb +++ b/app/models/activity_pub/instance.rb @@ -15,12 +15,18 @@ class ActivityPub has_many :actors has_many :instance_moderations + # XXX: Mantenemos esto por si queremos bloquear una instancia a + # nivel general aasm do state :paused, initial: true state :allowed state :blocked end + def list_name + "@*@#{hostname}" + end + def uri @uri ||= "https://#{hostname}/" end diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 3c092958..17b38fb7 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -13,15 +13,39 @@ class InstanceModeration < ApplicationRecord state :blocked event :pause do - transitions from: %i[allowed blocked], to: :paused + transitions from: %i[allowed blocked], to: :paused, guard: :pause_remotely! end event :allow do - transitions from: %i[paused blocked], to: :allowed + transitions from: %i[paused blocked], to: :allowed, guard: :allow_remotely! end event :block do - transitions from: %i[paused allowed], to: :blocked + transitions from: %i[paused allowed], to: :blocked, guard: :block_remotely! end end + + # Elimina la instancia de todas las listas + # + # @return [Boolean] + def pause_remotely! + site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && + site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? + end + + # Deja de permitir la instancia + # + # @return [Boolean] + def block_remotely! + site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? && + site.social_inbox.blocklist.post(list: [instance.list_name]).ok? + end + + # Permite la instancia + # + # @return [Boolean] + def allow_remotely! + site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && + site.social_inbox.allowlist.post(list: [instance.list_name]).ok? + end end diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 03612779..6677a320 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true require 'distributed_press/v1/social/client' +require 'distributed_press/v1/social/allowlist' +require 'distributed_press/v1/social/blocklist' require 'distributed_press/v1/social/hook' require 'distributed_press/v1/social/inbox' require 'distributed_press/v1/social/dereferencer' @@ -59,6 +61,16 @@ class SocialInbox @hook ||= DistributedPress::V1::Social::Hook.new(client: client, actor: actor) end + # @return [DistributedPress::V1::Social::Allowlist] + def allowlist + @allowlist ||= DistributedPress::V1::Social::Allowlist.new(client: client, actor: actor) + end + + # @return [DistributedPress::V1::Social::Blocklist] + def blocklist + @blocklist ||= DistributedPress::V1::Social::Blocklist.new(client: client, actor: actor) + end + # @return [String] def public_key_url @public_key_url ||= SocialInbox.generate_uri(hostname) do |uri| From a1b5e385b0ff97a079d60fb689fa05757002269e Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 16:50:03 -0300 Subject: [PATCH 067/297] fix: los guards se ejecutan todo el tiempo --- app/models/instance_moderation.rb | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 17b38fb7..735ec387 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -13,15 +13,27 @@ class InstanceModeration < ApplicationRecord state :blocked event :pause do - transitions from: %i[allowed blocked], to: :paused, guard: :pause_remotely! + transitions from: %i[allowed blocked], to: :paused + + before do + pause_remotely! + end end event :allow do - transitions from: %i[paused blocked], to: :allowed, guard: :allow_remotely! + transitions from: %i[paused blocked], to: :allowed + + before do + allow_remotely! + end end event :block do - transitions from: %i[paused allowed], to: :blocked, guard: :block_remotely! + transitions from: %i[paused allowed], to: :blocked + + before do + block_remotely! + end end end @@ -29,7 +41,8 @@ class InstanceModeration < ApplicationRecord # # @return [Boolean] def pause_remotely! - site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && + raise AASM::InvalidTransition unless + site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? end @@ -37,7 +50,8 @@ class InstanceModeration < ApplicationRecord # # @return [Boolean] def block_remotely! - site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? && + raise AASM::InvalidTransition unless + site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? && site.social_inbox.blocklist.post(list: [instance.list_name]).ok? end @@ -45,7 +59,8 @@ class InstanceModeration < ApplicationRecord # # @return [Boolean] def allow_remotely! - site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && + raise AASM::InvalidTransition unless + site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && site.social_inbox.allowlist.post(list: [instance.list_name]).ok? end end From cc7175ab997f2febd906022b9af6a69df7d7e057 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 16:51:33 -0300 Subject: [PATCH 068/297] feat: poder filtrar por estado de la instancia --- app/controllers/moderation_queue_controller.rb | 2 +- app/views/components/_instances_filters.haml | 2 +- app/views/components/_instances_show_submenu.haml | 5 +++-- app/views/moderation_queue/_instances.haml | 3 +++ config/locales/en.yml | 5 +++++ config/locales/es.yml | 6 ++++-- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index d2123234..8c030460 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -8,7 +8,7 @@ class ModerationQueueController < ApplicationController # @todo cambiar el estado por query @activity_pubs = site.activity_pubs - @instance_moderations = site.instance_moderations + @instance_moderations = rubanok_process(site.instance_moderations, with: InstanceModerationProcessor) end # Perfil remoto de usuarie diff --git a/app/views/components/_instances_filters.haml b/app/views/components/_instances_filters.haml index 213bb7c0..eac20d38 100644 --- a/app/views/components/_instances_filters.haml +++ b/app/views/components/_instances_filters.haml @@ -3,4 +3,4 @@ = render 'components/instances_checked_submenu' = render 'components/dropdown', text: t('.text_show') do - = render 'components/comments_show_submenu' + = render 'components/instances_show_submenu' diff --git a/app/views/components/_instances_show_submenu.haml b/app/views/components/_instances_show_submenu.haml index 1074cc3f..56206735 100644 --- a/app/views/components/_instances_show_submenu.haml +++ b/app/views/components/_instances_show_submenu.haml @@ -1,2 +1,3 @@ -= render 'components/dropdown_item', text: t('.submenu_allow'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_reject'), path: '/' \ No newline at end of file += render 'components/dropdown_item', text: t('.submenu_paused'), path: site_moderation_queue_path(state: 'paused') += render 'components/dropdown_item', text: t('.submenu_allowed'), path: site_moderation_queue_path(state: 'allowed') += render 'components/dropdown_item', text: t('.submenu_blocked'), path: site_moderation_queue_path(state: 'blocked') diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 2303a061..4c55c439 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -5,6 +5,9 @@ -# Filtros = render 'components/instances_filters' +- if instance_moderations.count.zero? + %h3= t('moderation_queue.nothing') + - instance_moderations.each do |instance_moderation| %hr = render 'moderation_queue/instance', instance: instance_moderation.instance diff --git a/config/locales/en.yml b/config/locales/en.yml index 1b4f2d86..188b85c4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -60,6 +60,10 @@ en: instances_show_submenu: submenu_allow: Allow submenu_reject: Reject + instances_show_submenu: + submenu_paused: Moderated + submenu_allowed: Allowed + submenu_blocked: Blocked comments_filters: text_show: Show text_checked: With selected @@ -102,6 +106,7 @@ en: text_deny: Block text_report: Report moderation_queue: + nothing: "There's nothing for this filter" index: title: Moderation instances: Instances diff --git a/config/locales/es.yml b/config/locales/es.yml index e277f76b..d3651cb7 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -58,8 +58,9 @@ es: submenu_allow: Permitir todo submenu_reject: Rechazado instances_show_submenu: - submenu_allow: Permitido - submenu_reject: Rechazado + submenu_paused: Pausadas + submenu_allowed: Permitidas + submenu_blocked: Bloqueadas comments_filters: text_show: Ver text_checked: Con los marcados @@ -102,6 +103,7 @@ es: text_deny: Bloquear text_report: Reportar moderation_queue: + nothing: 'No hay nada para este filtro' index: title: Actividades de moderación instances: Instancias From e78f71f25a5d6c1190b5efc4a8ede116e4b12800 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 16:52:02 -0300 Subject: [PATCH 069/297] fix: acciones --- app/views/components/_instances_checked_submenu.haml | 4 ++-- app/views/moderation_queue/_instances.haml | 5 +++-- config/locales/en.yml | 8 +++----- config/locales/es.yml | 5 +++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/views/components/_instances_checked_submenu.haml b/app/views/components/_instances_checked_submenu.haml index f0b76185..9da642a7 100644 --- a/app/views/components/_instances_checked_submenu.haml +++ b/app/views/components/_instances_checked_submenu.haml @@ -1,3 +1,3 @@ -= render 'components/dropdown_item', text: t('.submenu_case'), path: '/' += render 'components/dropdown_item', text: t('.submenu_pause'), path: '/' = render 'components/dropdown_item', text: t('.submenu_allow'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_reject'), path: '/' \ No newline at end of file += render 'components/dropdown_item', text: t('.submenu_block'), path: '/' diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 4c55c439..20501370 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -1,6 +1,7 @@ -.row.no-gutters.pt-2 +.row.no-gutters.pt-2 .col-1.d-flex.align-items-center - = render 'components/checkbox', id: moderation_queue.first['id'] + = render 'components/checkbox', id: 'all' do + %span.sr-only= t('moderation_queue.everything') .col-11 -# Filtros = render 'components/instances_filters' diff --git a/config/locales/en.yml b/config/locales/en.yml index 188b85c4..9e40c433 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -54,12 +54,9 @@ en: text_show: Show text_checked: With selected instances_checked_submenu: - submenu_case: Check case by case - submenu_allow: Allow everything - submenu_reject: Reject - instances_show_submenu: + submenu_pause: Moderate submenu_allow: Allow - submenu_reject: Reject + submenu_block: Block instances_show_submenu: submenu_paused: Moderated submenu_allowed: Allowed @@ -106,6 +103,7 @@ en: text_deny: Block text_report: Report moderation_queue: + everything: 'Select all' nothing: "There's nothing for this filter" index: title: Moderation diff --git a/config/locales/es.yml b/config/locales/es.yml index d3651cb7..af75105b 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -54,9 +54,9 @@ es: text_show: Ver text_checked: Con los marcados instances_checked_submenu: - submenu_case: Moderar caso por caso + submenu_pause: Moderar caso por caso submenu_allow: Permitir todo - submenu_reject: Rechazado + submenu_block: Rechazar todo instances_show_submenu: submenu_paused: Pausadas submenu_allowed: Permitidas @@ -103,6 +103,7 @@ es: text_deny: Bloquear text_report: Reportar moderation_queue: + everything: 'Seleccionar todo' nothing: 'No hay nada para este filtro' index: title: Actividades de moderación From 761d68604b9bbb1ce2a597d0717bd4121762f4e2 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 17:03:18 -0300 Subject: [PATCH 070/297] feat: poder seleccionar todas las instancias --- .../controllers/select_all_controller.js | 11 +++++++ app/views/components/_checkbox.haml | 2 +- app/views/components/_select_all.haml | 4 +++ app/views/moderation_queue/_instance.haml | 2 +- app/views/moderation_queue/_instances.haml | 33 ++++++++++--------- 5 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 app/javascript/controllers/select_all_controller.js create mode 100644 app/views/components/_select_all.haml diff --git a/app/javascript/controllers/select_all_controller.js b/app/javascript/controllers/select_all_controller.js new file mode 100644 index 00000000..7aca0f59 --- /dev/null +++ b/app/javascript/controllers/select_all_controller.js @@ -0,0 +1,11 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ["toggle", "input"]; + + toggle(event = undefined) { + this.inputTargets.forEach(input => { + input.checked = this.toggleTarget.checked; + }); + } +} diff --git a/app/views/components/_checkbox.haml b/app/views/components/_checkbox.haml index 27f9a776..1932df90 100644 --- a/app/views/components/_checkbox.haml +++ b/app/views/components/_checkbox.haml @@ -1,4 +1,4 @@ -# Componente Checkbox .custom-control.custom-checkbox - %input.custom-control-input{ type: 'checkbox', id: id, name: id, class: local_assigns[:class] } + %input.custom-control-input{ type: 'checkbox', id: id, name: id, **local_assigns } %label.custom-control-label{ for: id }= yield diff --git a/app/views/components/_select_all.haml b/app/views/components/_select_all.haml new file mode 100644 index 00000000..2603dfd3 --- /dev/null +++ b/app/views/components/_select_all.haml @@ -0,0 +1,4 @@ +-# + @param id [String] += render 'components/checkbox', id: id, data: { action: 'select-all#toggle', target: 'select-all.toggle' } do + %span.sr-only= t('.label') diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index 958a0199..327ea892 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -1,6 +1,6 @@ .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: instance.hostname + = render 'components/checkbox', id: instance.hostname, data: { target: 'select-all.input' } .col-11 %h4 %a{ href: instance.uri }= instance.content['title'] diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 20501370..1540ae53 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -1,24 +1,25 @@ -.row.no-gutters.pt-2 +.row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center - = render 'components/checkbox', id: 'all' do - %span.sr-only= t('moderation_queue.everything') + = render 'components/select_all', id: 'instances' .col-11 -# Filtros = render 'components/instances_filters' -- if instance_moderations.count.zero? - %h3= t('moderation_queue.nothing') + .col-12 + - if instance_moderations.count.zero? + %h3= t('moderation_queue.nothing') -- instance_moderations.each do |instance_moderation| - %hr - = render 'moderation_queue/instance', instance: instance_moderation.instance + - instance_moderations.each do |instance_moderation| + %hr + = render 'moderation_queue/instance', instance: instance_moderation.instance - -# Botones moderación - .d-flex.pb-4 - = render 'components/instances_btn_box', site: site, instance_moderation: instance_moderation + -# Botones moderación + .d-flex.pb-4 + = render 'components/instances_btn_box', site: site, instance_moderation: instance_moderation -%hr -%h3.mt-5= t('moderation_queue.instances.title') -%lead= t('moderation_queue.instances.description') -= render 'components/block_lists', blocklists: @blocklists -= render 'moderation_queue/block_instances_textarea' + %hr + .col-12 + %h3.mt-5= t('moderation_queue.instances.title') + %lead= t('moderation_queue.instances.description') + = render 'components/block_lists', blocklists: @blocklists + = render 'moderation_queue/block_instances_textarea' From 9b2efaafac38cf7a52dacb63a15f5265d38c1990 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 17:37:34 -0300 Subject: [PATCH 071/297] =?UTF-8?q?feat:=20hacer=20una=20acci=C3=B3n=20con?= =?UTF-8?q?=20las=20seleccionadas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../instance_moderations_controller.rb | 23 ++++++++++- app/policies/instance_moderation_policy.rb | 6 +++ app/views/components/_checkbox.haml | 3 +- app/views/components/_dropdown_button.haml | 4 ++ .../_instances_checked_submenu.haml | 6 +-- app/views/moderation_queue/_instance.haml | 2 +- app/views/moderation_queue/_instances.haml | 38 ++++++++++--------- config/routes.rb | 2 + 8 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 app/views/components/_dropdown_button.haml diff --git a/app/controllers/instance_moderations_controller.rb b/app/controllers/instance_moderations_controller.rb index 55f3c51b..67a6be1d 100644 --- a/app/controllers/instance_moderations_controller.rb +++ b/app/controllers/instance_moderations_controller.rb @@ -2,7 +2,7 @@ # Actualiza la relación entre un sitio y una instancia class InstanceModerationsController < ApplicationController - before_action :authorize_policy + before_action :authorize_policy, except: %i[action_on_several] def pause instance_moderation.pause! @@ -22,6 +22,27 @@ class InstanceModerationsController < ApplicationController redirect_to site_moderation_queue_path end + def action_on_several + instance_moderations = site.instance_moderations.where(id: params[:instance_moderation]) + + authorize instance_moderations + + action = params[:instance_moderation_action].to_sym + method = :"#{action}!" + + InstanceModeration.transaction do + instance_moderations.find_each do |instance_moderation| + events = instance_moderation.aasm.events.map(&:name) + + next unless events.include? action + + instance_moderation.public_send(method) + end + end + + redirect_to site_moderation_queue_path + end + private # @return [InstanceModeration] diff --git a/app/policies/instance_moderation_policy.rb b/app/policies/instance_moderation_policy.rb index 6b3157e8..c07455b3 100644 --- a/app/policies/instance_moderation_policy.rb +++ b/app/policies/instance_moderation_policy.rb @@ -13,4 +13,10 @@ InstanceModerationPolicy = Struct.new(:usuarie, :instance_moderation) do def block? pause? end + + # En este paso tenemos varias instancias por moderar pero todas son + # del mismo sitio. + def action_on_several? + instance_moderation.first.site.usuarie? usuarie + end end diff --git a/app/views/components/_checkbox.haml b/app/views/components/_checkbox.haml index 1932df90..596ff074 100644 --- a/app/views/components/_checkbox.haml +++ b/app/views/components/_checkbox.haml @@ -1,4 +1,5 @@ -# Componente Checkbox +- local_assigns[:name] ||= id .custom-control.custom-checkbox - %input.custom-control-input{ type: 'checkbox', id: id, name: id, **local_assigns } + %input.custom-control-input{ type: 'checkbox', id: id, **local_assigns } %label.custom-control-label{ for: id }= yield diff --git a/app/views/components/_dropdown_button.haml b/app/views/components/_dropdown_button.haml new file mode 100644 index 00000000..8b0c4684 --- /dev/null +++ b/app/views/components/_dropdown_button.haml @@ -0,0 +1,4 @@ +-# + @param name [String] + @param value [String] +%button.dropdown-item{type: 'submit', data: { target: 'dropdown.item' }, name: name, value: value }= text diff --git a/app/views/components/_instances_checked_submenu.haml b/app/views/components/_instances_checked_submenu.haml index 9da642a7..c3573ead 100644 --- a/app/views/components/_instances_checked_submenu.haml +++ b/app/views/components/_instances_checked_submenu.haml @@ -1,3 +1,3 @@ -= render 'components/dropdown_item', text: t('.submenu_pause'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_allow'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_block'), path: '/' += render 'components/dropdown_button', text: t('.submenu_pause'), name: 'instance_moderation_action', value: 'pause' += render 'components/dropdown_button', text: t('.submenu_allow'), name: 'instance_moderation_action', value: 'allow' += render 'components/dropdown_button', text: t('.submenu_block'), name: 'instance_moderation_action', value: 'block' diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index 327ea892..e7f74b7e 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -1,6 +1,6 @@ .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: instance.hostname, data: { target: 'select-all.input' } + = render 'components/checkbox', id: instance.hostname, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 %a{ href: instance.uri }= instance.content['title'] diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 1540ae53..129e1d41 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -1,24 +1,26 @@ -.row.no-gutters.pt-2{ data: { controller: 'select-all' } } - .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'instances' - .col-11 - -# Filtros - = render 'components/instances_filters' +%section + %form{ action: site_instance_moderations_action_on_several_path, method: :post } + .row.no-gutters.pt-2{ data: { controller: 'select-all' } } + .col-1.d-flex.align-items-center + = render 'components/select_all', id: 'instances' + .col-11 + -# Filtros + = render 'components/instances_filters' - .col-12 - - if instance_moderations.count.zero? - %h3= t('moderation_queue.nothing') + .col-12 + - if instance_moderations.count.zero? + %h3= t('moderation_queue.nothing') + + - instance_moderations.each do |instance_moderation| + %hr + = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance + + -# Botones moderación + .d-flex.pb-4 + = render 'components/instances_btn_box', site: site, instance_moderation: instance_moderation - - instance_moderations.each do |instance_moderation| %hr - = render 'moderation_queue/instance', instance: instance_moderation.instance - - -# Botones moderación - .d-flex.pb-4 - = render 'components/instances_btn_box', site: site, instance_moderation: instance_moderation - - %hr - .col-12 + %div %h3.mt-5= t('moderation_queue.instances.title') %lead= t('moderation_queue.instances.description') = render 'components/block_lists', blocklists: @blocklists diff --git a/config/routes.rb b/config/routes.rb index b6250902..d9e2aca7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -68,6 +68,8 @@ Rails.application.routes.draw do patch :block, to: 'instance_moderations#block' end + patch :instance_moderations_action_on_several, to: 'instance_moderations#action_on_several' + # Gestionar artículos según idioma nested do scope '/(:locale)', constraint: /[a-z]{2}(-[A-Z]{2})?/ do From ca4b3360cde74d1f325861e98d5d056980b11800 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 26 Feb 2024 17:38:00 -0300 Subject: [PATCH 072/297] fixup! feat: poder seleccionar todas las instancias --- app/processors/instance_moderation_processor.rb | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 app/processors/instance_moderation_processor.rb diff --git a/app/processors/instance_moderation_processor.rb b/app/processors/instance_moderation_processor.rb new file mode 100644 index 00000000..414901d6 --- /dev/null +++ b/app/processors/instance_moderation_processor.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Gestiona los filtros de InstanceModeration +class InstanceModerationProcessor < Rubanok::Processor + map :state, activate_always: true do |state: 'paused'| + raw.where(aasm_state: state) + end +end From f53ab113396b3e06544b39adf19b9012a865502a Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 12:29:42 -0300 Subject: [PATCH 073/297] fixup! feat: poder seleccionar todas las instancias --- Gemfile | 1 + Gemfile.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index 676b3673..d720a2d6 100644 --- a/Gemfile +++ b/Gemfile @@ -79,6 +79,7 @@ gem 'webpacker' gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git' gem 'kaminari' gem 'device_detector' +gem 'rubanok' gem 'after_commit_everywhere', '~> 1.0' gem 'aasm' diff --git a/Gemfile.lock b/Gemfile.lock index 8ece3064..72a30af1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -488,6 +488,7 @@ GEM rexml (~> 3.2, >= 3.2.4) stream (~> 0.5.3) rouge (3.30.0) + rubanok (0.5.0) rubocop (1.42.0) json (~> 2.3) parallel (~> 1.10) @@ -679,6 +680,7 @@ DEPENDENCIES redis-rails rgl rollups! + rubanok rubocop-rails ruby-brs rubyzip From df6c04888244afd876a786834892b671baa1bb54 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 12:32:09 -0300 Subject: [PATCH 074/297] feat: fediblock --- app/models/activity_pub/fediblock.rb | 77 +++++++++++++++++++ app/models/activity_pub/instance.rb | 6 ++ .../20240227134845_create_fediblocks.rb | 26 +++++++ db/seeds.rb | 6 ++ db/seeds/activity_pub/fediblocks.yml | 16 ++++ db/structure.sql | 27 ++++++- 6 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 app/models/activity_pub/fediblock.rb create mode 100644 db/migrate/20240227134845_create_fediblocks.rb create mode 100644 db/seeds/activity_pub/fediblocks.yml diff --git a/app/models/activity_pub/fediblock.rb b/app/models/activity_pub/fediblock.rb new file mode 100644 index 00000000..8d024f56 --- /dev/null +++ b/app/models/activity_pub/fediblock.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'httparty' + +# Listas de bloqueo y sus URLs de descarga +class ActivityPub + class Fediblock < ApplicationRecord + class Client + include ::HTTParty + + # @param url [String] + # @return [HTTParty::Response] + def get(url) + self.class.get(url, parser: csv_parser) + end + + # Procesa el CSV + # + # @return [Proc] + def csv_parser + @csv_parser ||= + begin + require 'csv' + + proc do |body, _| + CSV.parse(body, headers: true) + end + end + end + end + + class FediblockDownloadError < ::StandardError; end + + validates_presence_of :title, :url, :download_url, :format + validates_inclusion_of :format, in: %w[mastodon fediblock] + + HOSTNAME_HEADERS = { + 'mastodon' => '#domain', + 'fediblock' => 'domain' + } + + def client + @client ||= Client.new + end + + # Descarga la lista y crea las instancias con el estado necesario + def process! + response = client.get(download_url) + + raise FediblockDownloadError unless response.ok? + + Fediblock.transaction do + csv = response.parsed_response + process_csv! csv + + update(instances: csv.map { |r| r[hostname_header] }) + end + end + + private + + def hostname_header + HOSTNAME_HEADERS[format] + end + + # Crea o encuentra instancias que ya existían y las bloquea + # + # @param csv [CSV::Table] + def process_csv!(csv) + csv.each do |row| + ActivityPub::Instance.find_or_create_by(hostname: row[hostname_header]).tap do |i| + i.block! if i.may_block? + end + end + end + end +end diff --git a/app/models/activity_pub/instance.rb b/app/models/activity_pub/instance.rb index 42cd2695..749d98ac 100644 --- a/app/models/activity_pub/instance.rb +++ b/app/models/activity_pub/instance.rb @@ -21,6 +21,12 @@ class ActivityPub state :paused, initial: true state :allowed state :blocked + + # Al pasar una instancia a bloqueo, quiere decir que todos los + # sitios adoptan esta lista + event :block do + transitions from: %i[paused allowed], to: :blocked + end end def list_name diff --git a/db/migrate/20240227134845_create_fediblocks.rb b/db/migrate/20240227134845_create_fediblocks.rb new file mode 100644 index 00000000..03f65f7c --- /dev/null +++ b/db/migrate/20240227134845_create_fediblocks.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Las fediblocks son listas descargables de instancias bloqueadas. El +# formato hace una recomendación sobre suspensión o desfederación, pero +# nosotres bloqueamos todo. +class CreateFediblocks < ActiveRecord::Migration[6.1] + def up + create_table :activity_pub_fediblocks, id: :uuid do |t| + t.timestamps + + t.string :title, null: false + t.string :url, null: false + t.string :download_url, null: false + t.string :format, null: false + t.jsonb :instances, default: [] + end + + YAML.safe_load(File.read('db/seeds/activity_pub/fediblocks.yml')).each do |fediblock| + ActivityPub::Fediblock.create(**fediblock).process! + end + end + + def down + drop_table :activity_pub_fediblocks + end +end diff --git a/db/seeds.rb b/db/seeds.rb index b9ef96a1..8e8c291f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -27,3 +27,9 @@ if PrivacyPolicy.count.zero? PrivacyPolicy.new(**pp).save! end end + +YAML.safe_load(File.read('db/seeds/activity_pub/fediblocks.yml')).each do |fediblock| + ActivityPub::Fediblock.find_or_create_by(id: fediblock['id']).tap do |f| + f.update(**fediblock) + end +end diff --git a/db/seeds/activity_pub/fediblocks.yml b/db/seeds/activity_pub/fediblocks.yml new file mode 100644 index 00000000..c977f9bf --- /dev/null +++ b/db/seeds/activity_pub/fediblocks.yml @@ -0,0 +1,16 @@ +--- +- title: "Gardenfence" + url: "https://gardenfence.github.io/" + download_url: "https://github.com/gardenfence/blocklist/raw/main/gardenfence-fediblocksync.csv" + format: "fediblock" + id: "9046789a-5de8-4b16-beed-796060f8f3cc" +- title: "Oliphant Tier 0" + url: "https://writer.oliphant.social/oliphant/the-oliphant-social-blocklist" + download_url: "https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/mastodon/tier0.csv" + format: "mastodon" + id: "fc1efcb8-7e68-4a76-ae9e-0c447752b12b" +- title: "The Bad Space (90%)" + url: "https://tweaking.thebad.space/exports" + download_url: "https://tweaking.thebad.space/exports/mastodon/90" + format: "fediblock" + id: "5dd6705a-c28f-4912-9456-07b0d4983108" diff --git a/db/structure.sql b/db/structure.sql index c3896060..241039d1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -502,6 +502,22 @@ CREATE TABLE public.activity_pub_actors ( ); +-- +-- Name: activity_pub_fediblocks; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pub_fediblocks ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + title character varying NOT NULL, + url character varying NOT NULL, + download_url character varying NOT NULL, + format character varying NOT NULL, + instances jsonb DEFAULT '[]'::jsonb +); + + -- -- Name: activity_pub_instances; Type: TABLE; Schema: public; Owner: - -- @@ -1694,6 +1710,14 @@ ALTER TABLE ONLY public.activity_pub_actors ADD CONSTRAINT activity_pub_actors_pkey PRIMARY KEY (id); +-- +-- Name: activity_pub_fediblocks activity_pub_fediblocks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pub_fediblocks + ADD CONSTRAINT activity_pub_fediblocks_pkey PRIMARY KEY (id); + + -- -- Name: activity_pub_instances activity_pub_instances_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2546,6 +2570,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240221184007'), ('20240223170317'), ('20240226133022'), -('20240226134335'); +('20240226134335'), +('20240227134845'); From 8654228edcd3d7b24562f0f62565662b99100373 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 13:25:40 -0300 Subject: [PATCH 075/297] =?UTF-8?q?feat:=20actualizar=20los=20fediblocks?= =?UTF-8?q?=20todos=20los=20d=C3=ADas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Procfile | 1 + app/jobs/activity_pub/fediblock_fetch_job.rb | 14 ++++++++++++++ lib/tasks/activity_pub.rake | 8 ++++++++ monit.conf | 5 +++++ 4 files changed, 28 insertions(+) create mode 100644 app/jobs/activity_pub/fediblock_fetch_job.rb create mode 100644 lib/tasks/activity_pub.rake diff --git a/Procfile b/Procfile index eab8a502..a74f613b 100644 --- a/Procfile +++ b/Procfile @@ -10,3 +10,4 @@ cleanup: bundle exec rake cleanup:everything emergency_cleanup: bundle exec rake cleanup:everything BEFORE=7 stats: bundle exec rake stats:process_all que: daemonize -c /srv/ -p /srv/tmp/que.pid -u rails /usr/local/bin/syslogize bundle exec que +fediblock: bundle exec rails activity_pub:fediblocks diff --git a/app/jobs/activity_pub/fediblock_fetch_job.rb b/app/jobs/activity_pub/fediblock_fetch_job.rb new file mode 100644 index 00000000..d6cb4b83 --- /dev/null +++ b/app/jobs/activity_pub/fediblock_fetch_job.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class ActivityPub + # Se encarga de mantener las listas de bloqueo actualizadas + class FediblockFetchJob < ApplicationJob + def perform + ActivityPub::Fediblock.find_each do |fediblock| + fediblock.process! + rescue ActivityPub::Fediblock::FediblockDownloadError => e + ExceptionNotifier.notify_exception(e, data: { fediblock: fediblock.title }) + end + end + end +end diff --git a/lib/tasks/activity_pub.rake b/lib/tasks/activity_pub.rake new file mode 100644 index 00000000..08c0f980 --- /dev/null +++ b/lib/tasks/activity_pub.rake @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +namespace :activity_pub do + desc 'Update Fediblocks' + task fediblocks: :environment do |_, args| + ActivityPub::FediblockFetchJob.perform_later + end +end diff --git a/monit.conf b/monit.conf index accd0e28..2b7e50a8 100644 --- a/monit.conf +++ b/monit.conf @@ -9,6 +9,11 @@ check program distributed_press_tokens_renew every "0 3 * * *" if status != 0 then alert +check program fediblocks + with path "/usr/bin/foreman run -f /srv/Procfile -d /srv fediblocks" as uid "rails" gid "www-data" + every "0 7 * * *" + if status != 0 then alert + check program access_logs with path "/srv/http/bin/access_logs" as uid "app" and gid "www-data" every "0 0 * * *" From ebdd95151e61bc90d646a7564c289ed67d326981 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 13:43:46 -0300 Subject: [PATCH 076/297] feat: relacionar sitios con fediblocks --- app/models/fediblock_state.rb | 72 +++++++++++++++++++ app/models/site/social_distributed_press.rb | 1 + .../20240227142019_create_fediblock_states.rb | 29 ++++++++ db/structure.sql | 46 +++++++++++- 4 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 app/models/fediblock_state.rb create mode 100644 db/migrate/20240227142019_create_fediblock_states.rb diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb new file mode 100644 index 00000000..8045e23e --- /dev/null +++ b/app/models/fediblock_state.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +# Relación entre Fediblocks y Sites +class FediblockState < ApplicationRecord + include AASM + + belongs_to :site + belongs_to :fediblock, class_name: 'ActivityPub::Fediblock' + + # El efecto secundario de esta máquina de estados es modificar el + # estado de moderación de cada instancia en el sitio. Nos salteamos + # los hooks de los eventos individuales. + aasm do + # Aunque queramos las listas habilitadas por defecto, tenemos que + # habilitarlas luego de crearlas para poder generar la lista de + # bloqueo en la Social Inbox. + state :disabled, initial: true + state :enabled + + event :enable do + transitions from: :disabled, to: :enabled + + before do + enable_remotely! + end + end + + # Al deshabilitar, las listas pasan a modo pausa. + # + # @todo No cambiar el estado si se habían habilitado manualmente, + # pero esto implica que tenemos que encontrar las que sí y quitarlas + # de list_names + event :disable do + transitions from: :enabled, to: :disabled + + before do + disable_remotely! + end + end + end + + private + + # Obtiene todos los IDs de instancias para poder obtener el estado de + # moderación en el sitio. + # + # @return [Array] + def instance_ids + ActivityPub::Instance.where(hostname: fediblock.instances).pluck(:id) + end + + # @return [Array] + def list_names + @list_names ||= fediblock.instances.map do |instance| + "@*@#{instance}" + end + end + + # Al deshabilitar, las instancias pasan a ser analizadas caso por caso + def disable_remotely! + raise AASM::InvalidTransition unless + site.social_inbox.blocklist.delete(list: list_names).ok? && + site.social_inbox.allowlist.delete(list: list_names).ok? + end + + # Al habilitar, se bloquean todas las instancias de la lista + def enable_remotely! + raise AASM::InvalidTransition unless + site.social_inbox.blocklist.post(list: list_names).ok? && + site.social_inbox.allowlist.delete(list: list_names).ok? + end +end diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index 73b284bf..e0d2d4f4 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -12,6 +12,7 @@ class Site has_many :activity_pubs has_many :instance_moderations + has_many :fediblock_states before_save :generate_private_key_pem!, unless: :private_key_pem? diff --git a/db/migrate/20240227142019_create_fediblock_states.rb b/db/migrate/20240227142019_create_fediblock_states.rb new file mode 100644 index 00000000..c99cf63d --- /dev/null +++ b/db/migrate/20240227142019_create_fediblock_states.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# La relación entre sitios y fediblocks +class CreateFediblockStates < ActiveRecord::Migration[6.1] + def up + create_table :fediblock_states, id: :uuid do |t| + t.timestamps + + t.belongs_to :site + t.uuid :fediblock_id, index: true + t.string :aasm_state + + t.index %i[site_id fediblock_id], unique: true + end + + # Todas las listas están activas por defecto + DeploySocialDistributedPress.find_each do |deploy| + ActivityPub::Fediblock.find_each do |fediblock| + FediblockState.create(site: deploy.site, fediblock: fediblock, aasm_state: 'disabled').tap do |f| + f.enable! + end + end + end + end + + def down + drop_table :fediblock_states + end +end diff --git a/db/structure.sql b/db/structure.sql index 241039d1..ac897695 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -944,6 +944,20 @@ CREATE SEQUENCE public.distributed_press_publishers_id_seq ALTER SEQUENCE public.distributed_press_publishers_id_seq OWNED BY public.distributed_press_publishers.id; +-- +-- Name: fediblock_states; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.fediblock_states ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + site_id bigint, + fediblock_id uuid, + aasm_state character varying +); + + -- -- Name: indexed_posts; Type: TABLE; Schema: public; Owner: - -- @@ -1830,6 +1844,14 @@ ALTER TABLE ONLY public.distributed_press_publishers ADD CONSTRAINT distributed_press_publishers_pkey PRIMARY KEY (id); +-- +-- Name: fediblock_states fediblock_states_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.fediblock_states + ADD CONSTRAINT fediblock_states_pkey PRIMARY KEY (id); + + -- -- Name: indexed_posts indexed_posts_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2176,6 +2198,27 @@ CREATE UNIQUE INDEX index_designs_on_gem ON public.designs USING btree (gem); CREATE UNIQUE INDEX index_designs_on_name ON public.designs USING btree (name); +-- +-- Name: index_fediblock_states_on_fediblock_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_fediblock_states_on_fediblock_id ON public.fediblock_states USING btree (fediblock_id); + + +-- +-- Name: index_fediblock_states_on_site_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_fediblock_states_on_site_id ON public.fediblock_states USING btree (site_id); + + +-- +-- Name: index_fediblock_states_on_site_id_and_fediblock_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_fediblock_states_on_site_id_and_fediblock_id ON public.fediblock_states USING btree (site_id, fediblock_id); + + -- -- Name: index_indexed_posts_on_front_matter; Type: INDEX; Schema: public; Owner: - -- @@ -2571,6 +2614,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240223170317'), ('20240226133022'), ('20240226134335'), -('20240227134845'); +('20240227134845'), +('20240227142019'); From ec328d31b9e988c85d0b2c58c04f76e8dccee0a5 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 14:23:57 -0300 Subject: [PATCH 077/297] feat: cargar las fediblocks en la pantalla --- app/views/components/_block_list.haml | 6 +++--- app/views/components/_block_lists.haml | 4 ++-- app/views/moderation_queue/_instances.haml | 2 +- app/views/moderation_queue/index.haml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/components/_block_list.haml b/app/views/components/_block_list.haml index 5bc0e130..c70b40f8 100644 --- a/app/views/components/_block_list.haml +++ b/app/views/components/_block_list.haml @@ -2,7 +2,7 @@ .card.mt-3.mb-3 .card-body .d-flex.flex-row - = render 'components/checkbox', id: blocklist['id'] do - %span.h4= blocklist["title"] + = render 'components/checkbox', id: state.id, checked: state.enabled? do + %span.h4= blocklist.title %p - %a{ href: blocklist['link'] }= blocklist['title'] + %a{ href: blocklist.url }= t('.more') diff --git a/app/views/components/_block_lists.haml b/app/views/components/_block_lists.haml index 1e9cd76f..b6dc0afa 100644 --- a/app/views/components/_block_lists.haml +++ b/app/views/components/_block_lists.haml @@ -1,2 +1,2 @@ -- @blocklists.each do |blocklist| - = render 'components/block_list', blocklist: blocklist +- blocklists.each do |blocklist| + = render 'components/block_list', blocklist: blocklist.fediblock, state: blocklist diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 6303d07c..a08c8b10 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -19,5 +19,5 @@ %div %h3.mt-5= t('moderation_queue.instances.title') %lead= t('moderation_queue.instances.description') - = render 'components/block_lists', blocklists: @blocklists + = render 'components/block_lists', blocklists: fediblock_states = render 'moderation_queue/block_instances_textarea' diff --git a/app/views/moderation_queue/index.haml b/app/views/moderation_queue/index.haml index 0c937758..0fb1c968 100644 --- a/app/views/moderation_queue/index.haml +++ b/app/views/moderation_queue/index.haml @@ -5,7 +5,7 @@ .col - summary = t('.instances') = render 'layouts/details', summary: summary do - = render 'moderation_queue/instances', site: @site, instance_moderations: @instance_moderations + = render 'moderation_queue/instances', site: @site, instance_moderations: @instance_moderations, fediblock_states: @site.fediblock_states %hr - summary = t('.accounts') = render 'layouts/details', summary: summary do From 4bc11c700755d211967ef1b71e5e6828fc0f9e94 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 16:39:02 -0300 Subject: [PATCH 078/297] feat: gestionar listas de bloqueo desde el panel --- .../fediblock_states_controller.rb | 40 +++++++++++++++++++ .../activity_pub/instance_moderation_job.rb | 29 ++++++++++++++ app/models/site/social_distributed_press.rb | 1 + app/views/components/_block_list.haml | 14 ++++--- .../_block_instances_textarea.haml | 6 +-- app/views/moderation_queue/_instances.haml | 9 ++++- config/locales/en.yml | 2 + config/locales/es.yml | 2 + config/routes.rb | 1 + 9 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 app/controllers/fediblock_states_controller.rb create mode 100644 app/jobs/activity_pub/instance_moderation_job.rb diff --git a/app/controllers/fediblock_states_controller.rb b/app/controllers/fediblock_states_controller.rb new file mode 100644 index 00000000..c116b75a --- /dev/null +++ b/app/controllers/fediblock_states_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# Estado de las listas de bloqueo en cada sitio +class FediblockStatesController < ApplicationController + # Realiza cambios en las listas de bloqueo + def action_on_several + if fediblock_states_ids.present? + # Encontrar todas y deshabilitar las que no se enviaron + site.fediblock_states.all.find_each do |fediblock_state| + if fediblock_states_ids.include? fediblock_state.id + fediblock_state.enable! if fediblock_state.may_enable? + elsif fediblock_state.may_disable? + fediblock_state.disable! + end + end + end + + # Bloquear otras instancias + if custom_blocklist.present? + ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: custom_blocklist) + end + + redirect_to site_moderation_queue_path + end + + private + + def fediblock_states_ids + params[:fediblock_states_ids] || [] + end + + # La lista de hostnames + def custom_blocklist + @custom_blocklist ||= fediblocks_states_params[:custom_blocklist].split("\n").map(&:strip).select(&:present?) + end + + def fediblocks_states_params + @fediblocks_states_params ||= params.permit(:custom_blocklist, fediblock_states_ids: []) + end +end diff --git a/app/jobs/activity_pub/instance_moderation_job.rb b/app/jobs/activity_pub/instance_moderation_job.rb new file mode 100644 index 00000000..a9b0bea0 --- /dev/null +++ b/app/jobs/activity_pub/instance_moderation_job.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class ActivityPub + # Bloquea varias instancias de una sola vez + class InstanceModerationJob < ApplicationJob + # @param :site [Site] + # @param :hostnames [Array] + def perform(site:, hostnames:) + # Crear las instancias que no existan todavía + hostnames.each do |hostname| + ActivityPub::Instance.find_or_create_by(hostname: hostname) + end + + instances = ActivityPub::Instance.where(hostname: hostnames) + + Site.transaction do + # Crea todas las moderaciones de instancia con un estado por + # defecto si no existen + 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? + end + end + end + end + end +end diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index e0d2d4f4..fdb37bca 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -13,6 +13,7 @@ class Site has_many :activity_pubs has_many :instance_moderations has_many :fediblock_states + has_many :instances, through: :instance_moderations before_save :generate_private_key_pem!, unless: :private_key_pem? diff --git a/app/views/components/_block_list.haml b/app/views/components/_block_list.haml index f972642a..79481363 100644 --- a/app/views/components/_block_list.haml +++ b/app/views/components/_block_list.haml @@ -1,8 +1,12 @@ -# Componente Listas de bloqueo de Instancias +- know_more = t('.know_more') .card.mt-3.mb-3 .card-body - .d-flex.flex-row - = render 'components/checkbox', id: state.id, checked: state.enabled? do - %span.h4= blocklist.title - %p.mb-0 - %a{ href: blocklist.url }= t('.more') + = render 'components/checkbox', id: state.id, name: 'fediblock_states_ids[]', value: state.id, checked: state.enabled? do + %span.h4.mb-0= blocklist.title + + %dl.mb-0 + %dt.d-inline= t('.instances_blocked') + %dd.d-inline.font-weight-normal= blocklist.instances.count + %p.mb-0.font-weight-normal + %a{ href: blocklist.url }= know_more diff --git a/app/views/moderation_queue/_block_instances_textarea.haml b/app/views/moderation_queue/_block_instances_textarea.haml index 9b388a0d..9729d4de 100644 --- a/app/views/moderation_queue/_block_instances_textarea.haml +++ b/app/views/moderation_queue/_block_instances_textarea.haml @@ -1,5 +1,3 @@ .form-group - = label_tag "custom_blocklist", t('moderation_queue.instances.custom_block') - = text_area_tag "custom_blocklist", nil, class: 'form-control' - %button.btn.btn-secondary.mt-3{ type: 'submit' }= t('moderation_queue.instances.submit') - + = label_tag 'custom_blocklist', t('moderation_queue.instances.custom_block') + = text_area_tag 'custom_blocklist', nil, class: 'form-control' diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index a08c8b10..d2ebb5a7 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -19,5 +19,10 @@ %div %h3.mt-5= t('moderation_queue.instances.title') %lead= t('moderation_queue.instances.description') - = render 'components/block_lists', blocklists: fediblock_states - = render 'moderation_queue/block_instances_textarea' + + = form_tag site_fediblock_states_action_on_several_path, method: :patch do + = render 'components/block_lists', blocklists: fediblock_states + = render 'moderation_queue/block_instances_textarea' + + .form-group + %button.btn.btn-secondary.mt-3{ type: 'submit' }= t('moderation_queue.instances.submit') diff --git a/config/locales/en.yml b/config/locales/en.yml index 9581285f..717c9ece 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -50,6 +50,8 @@ en: pm: pm format: '%-I:%M %p' components: + block_list: + know_more: Know more instances_filters: text_show: Show text_checked: With selected diff --git a/config/locales/es.yml b/config/locales/es.yml index 9b1f1937..87111523 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -50,6 +50,8 @@ es: pm: pm format: '%-H:%M' components: + block_list: + know_more: Saber más (en inglés) instances_filters: text_show: Ver text_checked: Con los marcados diff --git a/config/routes.rb b/config/routes.rb index d9e2aca7..d70f8339 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,7 @@ Rails.application.routes.draw do end patch :instance_moderations_action_on_several, to: 'instance_moderations#action_on_several' + patch :fediblock_states_action_on_several, to: 'fediblock_states#action_on_several' # Gestionar artículos según idioma nested do From d5f48c60073e8245e1f1cf1cbd8c558da60cdcfc Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 16:40:08 -0300 Subject: [PATCH 079/297] feat: al actualizar las blocklists, bloquear las instancias en los sitios que las tengan habilitadas --- app/jobs/activity_pub/fediblock_fetch_job.rb | 11 ++++++++- .../activity_pub/fediblock_updated_job.rb | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 app/jobs/activity_pub/fediblock_updated_job.rb diff --git a/app/jobs/activity_pub/fediblock_fetch_job.rb b/app/jobs/activity_pub/fediblock_fetch_job.rb index d6cb4b83..6730cbf0 100644 --- a/app/jobs/activity_pub/fediblock_fetch_job.rb +++ b/app/jobs/activity_pub/fediblock_fetch_job.rb @@ -1,11 +1,20 @@ # frozen_string_literal: true class ActivityPub - # Se encarga de mantener las listas de bloqueo actualizadas + # Se encarga de mantener las listas de bloqueo actualizadas. Luego de + # actualizar el listado de instancias, bloquea las instancias en cada + # sitio que tenga el fediblock habilitado. class FediblockFetchJob < ApplicationJob def perform ActivityPub::Fediblock.find_each do |fediblock| fediblock.process! + + instances_added = fediblock.instances - fediblock.instances_was + + # No hacer nada si no cambió con respecto a la versión anterior + next if instances_added.empty? + + ActivityPub::FediblockUpdatedJob.perform_later(fediblock: fediblock, hostnames: instances_added) rescue ActivityPub::Fediblock::FediblockDownloadError => e ExceptionNotifier.notify_exception(e, data: { fediblock: fediblock.title }) end diff --git a/app/jobs/activity_pub/fediblock_updated_job.rb b/app/jobs/activity_pub/fediblock_updated_job.rb new file mode 100644 index 00000000..007fa25e --- /dev/null +++ b/app/jobs/activity_pub/fediblock_updated_job.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Se encarga de mantener sincronizadas las listas de instancias +# de los fediblocks con los sitios que las tengan activadas. +class ActivityPub + class FediblockUpdatedJob < ApplicationJob + # @param :fediblock [ActivityPub::Fediblock] + # @param :instances [Array] + def perform(fediblock:, hostnames:) + instances = ActivityPub::Instance.where(hostname: instances) + + # Todos los sitios con la Social Inbox habilitada + Site.where(id: DeploySocialDistributedPress.pluck(:site_id)).find_each do |site| + # Crea el estado si no existía + fediblock_state = site.fediblock_states.find_or_create_by(fediblock: fediblock) + + # No hace nada con los deshabilitados + next unless fediblock_state.enabled? + + ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: instances) + end + end + end +end From 34ead9ea4a40957e4f6251c691dcc5d53e258488 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 16:53:48 -0300 Subject: [PATCH 080/297] feat: establecer prioridades para las tareas --- app/jobs/activity_pub/actor_fetch_job.rb | 2 ++ app/jobs/activity_pub/fediblock_fetch_job.rb | 2 ++ app/jobs/activity_pub/fediblock_updated_job.rb | 2 ++ app/jobs/activity_pub/fetch_job.rb | 2 ++ app/jobs/activity_pub/instance_fetch_job.rb | 2 ++ app/jobs/activity_pub/instance_moderation_job.rb | 2 ++ 6 files changed, 12 insertions(+) diff --git a/app/jobs/activity_pub/actor_fetch_job.rb b/app/jobs/activity_pub/actor_fetch_job.rb index 1c6f1735..71107151 100644 --- a/app/jobs/activity_pub/actor_fetch_job.rb +++ b/app/jobs/activity_pub/actor_fetch_job.rb @@ -9,6 +9,8 @@ # autenticación. class ActivityPub class ActorFetchJob < ApplicationJob + self.priority = 50 + def perform(site:, actor:) ActivityPub::Actor.transaction do response = site.social_inbox.dereferencer.get(uri: actor.uri) diff --git a/app/jobs/activity_pub/fediblock_fetch_job.rb b/app/jobs/activity_pub/fediblock_fetch_job.rb index 6730cbf0..aa748bc6 100644 --- a/app/jobs/activity_pub/fediblock_fetch_job.rb +++ b/app/jobs/activity_pub/fediblock_fetch_job.rb @@ -5,6 +5,8 @@ class ActivityPub # actualizar el listado de instancias, bloquea las instancias en cada # sitio que tenga el fediblock habilitado. class FediblockFetchJob < ApplicationJob + self.priority = 50 + def perform ActivityPub::Fediblock.find_each do |fediblock| fediblock.process! diff --git a/app/jobs/activity_pub/fediblock_updated_job.rb b/app/jobs/activity_pub/fediblock_updated_job.rb index 007fa25e..b4a56609 100644 --- a/app/jobs/activity_pub/fediblock_updated_job.rb +++ b/app/jobs/activity_pub/fediblock_updated_job.rb @@ -4,6 +4,8 @@ # de los fediblocks con los sitios que las tengan activadas. class ActivityPub class FediblockUpdatedJob < ApplicationJob + self.priority = 50 + # @param :fediblock [ActivityPub::Fediblock] # @param :instances [Array] def perform(fediblock:, hostnames:) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index b6c45026..e3fef993 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -9,6 +9,8 @@ # autenticación. class ActivityPub class FetchJob < ApplicationJob + self.priority = 50 + def perform(site:, object:) ActivityPub::Object.transaction do return if object.activity_pubs.where(aasm_state: 'removed').count.positive? diff --git a/app/jobs/activity_pub/instance_fetch_job.rb b/app/jobs/activity_pub/instance_fetch_job.rb index a5c07162..0ceb1a8a 100644 --- a/app/jobs/activity_pub/instance_fetch_job.rb +++ b/app/jobs/activity_pub/instance_fetch_job.rb @@ -3,6 +3,8 @@ class ActivityPub # Obtiene o actualiza los datos de una instancia. class InstanceFetchJob < ApplicationJob + self.priority = 100 + def perform(site:, instance:) %w[/api/v2/instance /api/v1/instance].each do |api| uri = SocialInbox.generate_uri(instance.hostname) do |u| diff --git a/app/jobs/activity_pub/instance_moderation_job.rb b/app/jobs/activity_pub/instance_moderation_job.rb index a9b0bea0..b205e68f 100644 --- a/app/jobs/activity_pub/instance_moderation_job.rb +++ b/app/jobs/activity_pub/instance_moderation_job.rb @@ -3,6 +3,8 @@ class ActivityPub # Bloquea varias instancias de una sola vez class InstanceModerationJob < ApplicationJob + self.priority = 50 + # @param :site [Site] # @param :hostnames [Array] def perform(site:, hostnames:) From 342521c897d77243e63fcbb565651eba7ae0f80d Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Feb 2024 16:54:09 -0300 Subject: [PATCH 081/297] fix: guardar los datos de la instancia --- app/jobs/activity_pub/instance_fetch_job.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/jobs/activity_pub/instance_fetch_job.rb b/app/jobs/activity_pub/instance_fetch_job.rb index 0ceb1a8a..ce202092 100644 --- a/app/jobs/activity_pub/instance_fetch_job.rb +++ b/app/jobs/activity_pub/instance_fetch_job.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true class ActivityPub - # Obtiene o actualiza los datos de una instancia. + # Obtiene o actualiza los datos de una instancia. Usamos un cliente + # de ActivityPub porque la instancia podría estar en federación + # limitada. class InstanceFetchJob < ApplicationJob self.priority = 100 @@ -14,8 +16,10 @@ class ActivityPub response = site.social_inbox.dereferencer.get(uri: uri) next unless response.ok? + # @todo Validate schema + next unless response.parsed_response.is_a?(DistributedPress::V1::Social::ReferencedObject) - instance.update(content: response.parsed_response) + instance.update(content: response.parsed_response.object) break end From aae04f3739c1cd07a4518922d8ff2cbac66180ac Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 11:54:41 -0300 Subject: [PATCH 082/297] feat: al activar el fediverso, activar las listas de bloqueo --- app/models/deploy_social_distributed_press.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index 7f761e46..9f968f36 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -8,6 +8,7 @@ class DeploySocialDistributedPress < Deploy DEPENDENCIES = %i[deploy_distributed_press deploy_rsync deploy_full_rsync].freeze after_save :create_hooks! + after_create :enable_fediblocks! # Envía las notificaciones def deploy(output: false) @@ -95,4 +96,16 @@ class DeploySocialDistributedPress < Deploy ExceptionNotifier.notify_exception(e, data: { site_id: site.name, usuarie_id: rol.usuarie_id }) end end + + # Habilita todos los fediblocks disponibles. + # + # @todo Hacer que algunos sean opcionales + # @todo Mover a un Job + def enable_fediblocks! + ActivityPub::Fediblock.find_each do |fediblock| + site.fediblock_states.find_or_create_by(fediblock: fediblock).tap do |state| + state.enable! if state.may_enable? + end + end + end end From dc82b8cef2ee014898128a8916b75c5358d9646d Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 12:05:09 -0300 Subject: [PATCH 083/297] feat: asignar rol a deploys al crear o modificar el sitio --- app/services/site_service.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 5c37cfe3..a3649bc5 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -26,6 +26,8 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do # que no haya estados intermedios. site.locales = [usuarie.lang] + I18n.available_locales + add_role_to_deploys! + site.save && site.config.write && commit_config(action: :create) && @@ -43,7 +45,10 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do # Actualiza el sitio y guarda los cambios en la configuración def update I18n.with_locale(usuarie&.lang&.to_sym || I18n.default_locale) do - site.update(params) && + site.assign_attributes(params) + add_role_to_deploys! + + site.save && site.config.write && commit_config(action: :update) && site.reset.nil? && @@ -224,6 +229,17 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do private + # Asignar un rol a cada deploy si no lo tenía ya + def add_role_to_deploys! + site.deploys.each do |deploy| + deploy.rol ||= current_role + end + end + + def current_role + @current_role ||= usuarie.rol_for_site(site) + end + def with_all_locales(&block) site.locales.map do |locale| next unless I18n.available_locales.include? locale From d5cf0fffe09183cf9aea111bfaff60d6e7d6bfcb Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 12:09:16 -0300 Subject: [PATCH 084/297] feat: no mostrar comentarios a invitades --- app/views/posts/edit.haml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/views/posts/edit.haml b/app/views/posts/edit.haml index 2e46590e..e7e0260d 100644 --- a/app/views/posts/edit.haml +++ b/app/views/posts/edit.haml @@ -1,8 +1,9 @@ .row.justify-content-center .col-md-8 - - summary = t('posts.edit.post') - = render 'layouts/details', summary: summary do + - if policy(@site).edit? + = render 'layouts/details', summary: t('posts.edit.post') do + = render 'posts/form', site: @site, post: @post + = render 'layouts/details', summary: t('posts.edit.moderation_queue') do + = render 'posts/moderation_queue', site: @site, post: @post, moderation_queue: @moderation_queue + - else = render 'posts/form', site: @site, post: @post - - summary = t('posts.edit.moderation_queue') - = render 'layouts/details', summary: summary do - = render 'posts/moderation_queue', site: @site, post: @post, moderation_queue: @moderation_queue From f1de4c80735b106fe4b7951ece4367bb20522bac Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 12:32:59 -0300 Subject: [PATCH 085/297] fix: ignorar instancias que ya no existen --- app/jobs/activity_pub/instance_fetch_job.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/jobs/activity_pub/instance_fetch_job.rb b/app/jobs/activity_pub/instance_fetch_job.rb index ce202092..9c562f7d 100644 --- a/app/jobs/activity_pub/instance_fetch_job.rb +++ b/app/jobs/activity_pub/instance_fetch_job.rb @@ -21,6 +21,16 @@ class ActivityPub instance.update(content: response.parsed_response.object) + break + rescue BRS::BaseError, + Errno::ECONNREFUSED, + HTTParty::Error, + JSON::JSONError, + Net::OpenTimeout, + OpenSSL::OpenSSLError, + SocketError, + Errno::ENETUNREACH => e + ExceptionNotifier.notify_exception(e, data: { instance: uri }) break end end From ee10e170ec6b8c6f939173ee647f874d4770bc3b Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 15:01:36 -0300 Subject: [PATCH 086/297] =?UTF-8?q?fix:=20el=20rol=20todav=C3=ADa=20no=20e?= =?UTF-8?q?st=C3=A1=20guardado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/site_service.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/site_service.rb b/app/services/site_service.rb index a3649bc5..6b57fc8b 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -13,7 +13,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do def create self.site = Site.new params - add_role temporal: false, rol: 'usuarie' + role = add_role temporal: false, rol: 'usuarie' site.deploys.build type: 'DeployLocal' # Los sitios de testing no se sincronizan sync_nodes unless site.name.end_with? '.testing' @@ -26,7 +26,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do # que no haya estados intermedios. site.locales = [usuarie.lang] + I18n.available_locales - add_role_to_deploys! + add_role_to_deploys! role site.save && site.config.write && @@ -230,9 +230,9 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do private # Asignar un rol a cada deploy si no lo tenía ya - def add_role_to_deploys! + def add_role_to_deploys!(role = current_role) site.deploys.each do |deploy| - deploy.rol ||= current_role + deploy.rol ||= role end end From 670d6063e56922b3a8f8debdbd45fd823c34e38a Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 15:04:52 -0300 Subject: [PATCH 087/297] fix: generar un rol dentro del sitio --- app/services/site_service.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 6b57fc8b..dabce349 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -13,7 +13,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do def create self.site = Site.new params - role = add_role temporal: false, rol: 'usuarie' + role = site.roles.build(usuarie: usuarie, temporal: false, rol: 'usuarie') site.deploys.build type: 'DeployLocal' # Los sitios de testing no se sincronizan sync_nodes unless site.name.end_with? '.testing' @@ -106,11 +106,6 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do GitPushJob.perform_later(site) end - def add_role(temporal: true, rol: 'invitade') - site.roles << Rol.new(site: site, usuarie: usuarie, - temporal: temporal, rol: rol) - end - # Crea la licencia del sitio para cada locale disponible en el sitio # # @return [Boolean] From 7521f598a64df25daac30501029d21171d48f7b1 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 15:45:58 -0300 Subject: [PATCH 088/297] fix: poder deseleccionar todos los fediblocks --- app/controllers/fediblock_states_controller.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/controllers/fediblock_states_controller.rb b/app/controllers/fediblock_states_controller.rb index c116b75a..6d9737c3 100644 --- a/app/controllers/fediblock_states_controller.rb +++ b/app/controllers/fediblock_states_controller.rb @@ -4,14 +4,12 @@ class FediblockStatesController < ApplicationController # Realiza cambios en las listas de bloqueo def action_on_several - if fediblock_states_ids.present? - # Encontrar todas y deshabilitar las que no se enviaron - site.fediblock_states.all.find_each do |fediblock_state| - if fediblock_states_ids.include? fediblock_state.id - fediblock_state.enable! if fediblock_state.may_enable? - elsif fediblock_state.may_disable? - fediblock_state.disable! - end + # Encontrar todas y deshabilitar las que no se enviaron + site.fediblock_states.all.find_each do |fediblock_state| + if fediblock_states_ids.include? fediblock_state.id + fediblock_state.enable! if fediblock_state.may_enable? + elsif fediblock_state.may_disable? + fediblock_state.disable! end end From 15800e1096f01707def8a9c845407b38a9e8d545 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 15:47:40 -0300 Subject: [PATCH 089/297] BREAKING CHANGE: renombrar instancias por hostnames --- app/jobs/activity_pub/fediblock_fetch_job.rb | 6 +++--- app/jobs/activity_pub/fediblock_updated_job.rb | 9 ++++++--- app/models/activity_pub/fediblock.rb | 7 ++++++- app/views/components/_block_list.haml | 2 +- ...0228171335_rename_fediblock_instances_to_hostnames.rb | 9 +++++++++ db/structure.sql | 5 +++-- 6 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20240228171335_rename_fediblock_instances_to_hostnames.rb diff --git a/app/jobs/activity_pub/fediblock_fetch_job.rb b/app/jobs/activity_pub/fediblock_fetch_job.rb index aa748bc6..3d12f4cd 100644 --- a/app/jobs/activity_pub/fediblock_fetch_job.rb +++ b/app/jobs/activity_pub/fediblock_fetch_job.rb @@ -11,12 +11,12 @@ class ActivityPub ActivityPub::Fediblock.find_each do |fediblock| fediblock.process! - instances_added = fediblock.instances - fediblock.instances_was + hostnames_added = fediblock.hostnames - fediblock.hostnames_was # No hacer nada si no cambió con respecto a la versión anterior - next if instances_added.empty? + next if hostnames_added.empty? - ActivityPub::FediblockUpdatedJob.perform_later(fediblock: fediblock, hostnames: instances_added) + ActivityPub::FediblockUpdatedJob.perform_later(fediblock: fediblock, hostnames: hostnames_added) rescue ActivityPub::Fediblock::FediblockDownloadError => e ExceptionNotifier.notify_exception(e, data: { fediblock: fediblock.title }) end diff --git a/app/jobs/activity_pub/fediblock_updated_job.rb b/app/jobs/activity_pub/fediblock_updated_job.rb index b4a56609..1bb47517 100644 --- a/app/jobs/activity_pub/fediblock_updated_job.rb +++ b/app/jobs/activity_pub/fediblock_updated_job.rb @@ -2,14 +2,17 @@ # Se encarga de mantener sincronizadas las listas de instancias # de los fediblocks con los sitios que las tengan activadas. +# +# También va a asociar las listas con todos los sitios que tengan la +# Social Inbox habilitada. class ActivityPub class FediblockUpdatedJob < ApplicationJob self.priority = 50 # @param :fediblock [ActivityPub::Fediblock] - # @param :instances [Array] + # @param :hostnames [Array] def perform(fediblock:, hostnames:) - instances = ActivityPub::Instance.where(hostname: instances) + instances = ActivityPub::Instance.where(hostname: hostnames) # Todos los sitios con la Social Inbox habilitada Site.where(id: DeploySocialDistributedPress.pluck(:site_id)).find_each do |site| @@ -19,7 +22,7 @@ class ActivityPub # No hace nada con los deshabilitados next unless fediblock_state.enabled? - ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: instances) + ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: hostnames) end end end diff --git a/app/models/activity_pub/fediblock.rb b/app/models/activity_pub/fediblock.rb index 8d024f56..4abcb80f 100644 --- a/app/models/activity_pub/fediblock.rb +++ b/app/models/activity_pub/fediblock.rb @@ -43,6 +43,11 @@ class ActivityPub @client ||= Client.new end + # Todas las instancias de este fediblock + def instances + ActivityPub::Instance.where(hostname: hostnames) + end + # Descarga la lista y crea las instancias con el estado necesario def process! response = client.get(download_url) @@ -53,7 +58,7 @@ class ActivityPub csv = response.parsed_response process_csv! csv - update(instances: csv.map { |r| r[hostname_header] }) + update(hostnames: csv.map { |r| r[hostname_header] }) end end diff --git a/app/views/components/_block_list.haml b/app/views/components/_block_list.haml index 79481363..75825bb0 100644 --- a/app/views/components/_block_list.haml +++ b/app/views/components/_block_list.haml @@ -7,6 +7,6 @@ %dl.mb-0 %dt.d-inline= t('.instances_blocked') - %dd.d-inline.font-weight-normal= blocklist.instances.count + %dd.d-inline.font-weight-normal= blocklist.hostnames.count %p.mb-0.font-weight-normal %a{ href: blocklist.url }= know_more diff --git a/db/migrate/20240228171335_rename_fediblock_instances_to_hostnames.rb b/db/migrate/20240228171335_rename_fediblock_instances_to_hostnames.rb new file mode 100644 index 00000000..bad343f2 --- /dev/null +++ b/db/migrate/20240228171335_rename_fediblock_instances_to_hostnames.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Cambia el nombre de la columna para que podamos obtener todas las +# instancias de un fediblock +class RenameFediblockInstancesToHostnames < ActiveRecord::Migration[6.1] + def change + rename_column :activity_pub_fediblocks, :instances, :hostnames + end +end diff --git a/db/structure.sql b/db/structure.sql index ac897695..740f5d87 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -514,7 +514,7 @@ CREATE TABLE public.activity_pub_fediblocks ( url character varying NOT NULL, download_url character varying NOT NULL, format character varying NOT NULL, - instances jsonb DEFAULT '[]'::jsonb + hostnames jsonb DEFAULT '[]'::jsonb ); @@ -2615,6 +2615,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240226133022'), ('20240226134335'), ('20240227134845'), -('20240227142019'); +('20240227142019'), +('20240228171335'); From 1ee41f2d5e9de7dc78f79c7afcb574ff2fa79648 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 16:04:37 -0300 Subject: [PATCH 090/297] feat: al activar o desactivar un fediblock, vincular con todas las instancias --- app/models/fediblock_state.rb | 28 +++++++++++++++++++++------- app/models/instance_moderation.rb | 15 +++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index 8045e23e..13e31cb2 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -1,6 +1,12 @@ # frozen_string_literal: true -# Relación entre Fediblocks y Sites +# Relación entre Fediblocks y Sites. +# +# Cuando se habilita un Fediblock, tenemos que asociar todas sus +# instancias con el sitio y bloquearlas. Cuando se deshabilita, la +# relación ya está creada y se va actualizando. +# +# @see ActivityPub::FediblockUpdatedJob class FediblockState < ApplicationRecord include AASM @@ -22,6 +28,15 @@ class FediblockState < ApplicationRecord before do enable_remotely! + + # Al actualizar el estado en masa garantizamos que las + # instancias que ya existen queden sincronizadas con el bloqueo + # en masa que acabamos de hacer. + instance_moderations.block_all! + + # Luego esta tarea crea las que falten e ignora las que ya se + # bloquearon. + ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: fediblock.hostnames) end end @@ -35,18 +50,17 @@ class FediblockState < ApplicationRecord before do disable_remotely! + + instance_moderations.pause_all! end end end private - # Obtiene todos los IDs de instancias para poder obtener el estado de - # moderación en el sitio. - # - # @return [Array] - def instance_ids - ActivityPub::Instance.where(hostname: fediblock.instances).pluck(:id) + # Todas las instancias de moderación de este sitio + def instance_moderations + site.instance_moderations.where(instance_id: fediblock.instances.pluck(:id)) end # @return [Array] diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 735ec387..d9f74527 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -7,6 +7,21 @@ 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 From 43d190ba7426ee8cf85e88a6a88b8fd2cebc94b4 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 16:13:51 -0300 Subject: [PATCH 091/297] feat: mostrar la cantidad de instancias --- app/models/instance_moderation.rb | 3 +++ app/views/components/_instances_filters.haml | 2 +- app/views/components/_instances_show_submenu.haml | 6 +++--- app/views/moderation_queue/_instances.haml | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index d9f74527..10b5e8e0 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -11,6 +11,9 @@ class InstanceModeration < ApplicationRecord # todas las que no estén bloqueadas ya. scope :may_block, -> { where.not(aasm_state: 'blocked') } scope :may_pause, -> { where.not(aasm_state: 'paused') } + scope :paused, -> { where(aasm_state: 'paused') } + scope :blocked, -> { where(aasm_state: 'blocked') } + scope :allowed, -> { where(aasm_state: 'allowed') } # Bloquear instancias en masa def self.block_all! diff --git a/app/views/components/_instances_filters.haml b/app/views/components/_instances_filters.haml index eac20d38..fe40ced3 100644 --- a/app/views/components/_instances_filters.haml +++ b/app/views/components/_instances_filters.haml @@ -3,4 +3,4 @@ = render 'components/instances_checked_submenu' = render 'components/dropdown', text: t('.text_show') do - = render 'components/instances_show_submenu' + = render 'components/instances_show_submenu', site: site diff --git a/app/views/components/_instances_show_submenu.haml b/app/views/components/_instances_show_submenu.haml index 56206735..811d65c7 100644 --- a/app/views/components/_instances_show_submenu.haml +++ b/app/views/components/_instances_show_submenu.haml @@ -1,3 +1,3 @@ -= render 'components/dropdown_item', text: t('.submenu_paused'), path: site_moderation_queue_path(state: 'paused') -= render 'components/dropdown_item', text: t('.submenu_allowed'), path: site_moderation_queue_path(state: 'allowed') -= render 'components/dropdown_item', text: t('.submenu_blocked'), path: site_moderation_queue_path(state: 'blocked') += render 'components/dropdown_item', text: t('.submenu_paused', count: site.instance_moderations.paused.count), path: site_moderation_queue_path(state: 'paused') += render 'components/dropdown_item', text: t('.submenu_allowed', count: site.instance_moderations.allowed.count), path: site_moderation_queue_path(state: 'allowed') += render 'components/dropdown_item', text: t('.submenu_blocked', count: site.instance_moderations.blocked.count), path: site_moderation_queue_path(state: 'blocked') diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index d2ebb5a7..65f4350a 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -5,7 +5,7 @@ = render 'components/select_all', id: 'instances' .col-11 -# Filtros - = render 'components/instances_filters' + = render 'components/instances_filters', site: site .col-12 - if instance_moderations.count.zero? From 259d1c1e90d86d12c86aedf5aefdeb3bdad1cbbc Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 16:23:18 -0300 Subject: [PATCH 092/297] =?UTF-8?q?feat:=20optimizar=20acceso=20a=20inform?= =?UTF-8?q?aci=C3=B3n=20y=20ordenar=20por=20hostname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/processors/instance_moderation_processor.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/processors/instance_moderation_processor.rb b/app/processors/instance_moderation_processor.rb index 414901d6..908beaf7 100644 --- a/app/processors/instance_moderation_processor.rb +++ b/app/processors/instance_moderation_processor.rb @@ -2,6 +2,10 @@ # Gestiona los filtros de InstanceModeration class InstanceModerationProcessor < Rubanok::Processor + prepare do + raw.includes(:instance).order('activity_pub_instances.hostname') + end + map :state, activate_always: true do |state: 'paused'| raw.where(aasm_state: state) end From f9d02cc63eeb38d2b4e8c27e69521a463720eec4 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 16:32:03 -0300 Subject: [PATCH 093/297] =?UTF-8?q?feat:=20recordar=20el=20filtro=20que=20?= =?UTF-8?q?est=C3=A1bamos=20usando?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../instance_moderations_controller.rb | 21 +++++++++---------- .../moderation_queue_controller.rb | 2 ++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/controllers/instance_moderations_controller.rb b/app/controllers/instance_moderations_controller.rb index 67a6be1d..d25e1450 100644 --- a/app/controllers/instance_moderations_controller.rb +++ b/app/controllers/instance_moderations_controller.rb @@ -3,23 +3,18 @@ # Actualiza la relación entre un sitio y una instancia class InstanceModerationsController < ApplicationController before_action :authorize_policy, except: %i[action_on_several] + around_action :redirect_to_moderation_queue! def pause - instance_moderation.pause! - - redirect_to site_moderation_queue_path + instance_moderation.pause! if instance_moderation.may_pause? end def allow - instance_moderation.allow! - - redirect_to site_moderation_queue_path + instance_moderation.allow! if instance_moderation.may_allow? end def block - instance_moderation.block! - - redirect_to site_moderation_queue_path + instance_moderation.block! if instance_moderation.may_block? end def action_on_several @@ -39,12 +34,16 @@ class InstanceModerationsController < ApplicationController instance_moderation.public_send(method) end end - - redirect_to site_moderation_queue_path end private + def redirect_to_moderation_queue!(&action) + redirect_back fallback_location: site_moderation_queue_path, state: session[:moderation_queue_filtered_by_state] + + yield + end + # @return [InstanceModeration] def instance_moderation @instance_moderation ||= site.instance_moderations.find(params[:instance_moderation_id]) diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index 8c030460..fd7d2acb 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -6,6 +6,8 @@ class ModerationQueueController < ApplicationController def index dummy_data + session[:moderation_queue_filtered_by_state] = params[:state] + # @todo cambiar el estado por query @activity_pubs = site.activity_pubs @instance_moderations = rubanok_process(site.instance_moderations, with: InstanceModerationProcessor) From 448564aa46739bdb72e4d226775993ee6ea55271 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 16:36:50 -0300 Subject: [PATCH 094/297] =?UTF-8?q?fix:=20hacer=20la=20tarea=20sincr=C3=B3?= =?UTF-8?q?nicamente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/fediblock_state.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index 13e31cb2..a160c43a 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -36,7 +36,7 @@ class FediblockState < ApplicationRecord # Luego esta tarea crea las que falten e ignora las que ya se # bloquearon. - ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: fediblock.hostnames) + ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames) end end From 17705f66ad2f0c1a66469393f009e2081ebd50c6 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 16:42:18 -0300 Subject: [PATCH 095/297] feat: tarjeta cacheada de instancia --- app/views/moderation_queue/_instance.haml | 16 +++++++++------- app/views/moderation_queue/_instances.haml | 5 +++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index ec76c6e6..73655e1b 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -1,15 +1,17 @@ +- usuaries = instance.content.dig('usage', 'users', 'active_month') +- usuaries ||= instance.content.dig('stats', 'user_count') + .row.no-gutters.pt-2 .col-1 = render 'components/checkbox', id: instance.hostname, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 - %a{ href: instance.uri }= instance.content['title'] - %p= instance.content['description'].html_safe - %dl - %dt.d-inline= t('.users') - %dd.d-inline - = instance.content.dig('usage', 'users', 'active_month') - = instance.content.dig('stats', 'user_count') + %a{ href: instance.uri }= sanitize(instance.content['title']) || instance.hostname + %p= sanitize instance.content['description'] + - if usuaries.present? + %dl + %dt.d-inline= t('.users') + %dd.d-inline= sanitize usuaries.to_s -# Botones moderación .d-flex.pb-4 diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 65f4350a..a45da354 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -12,8 +12,9 @@ %h3= t('moderation_queue.nothing') - instance_moderations.each do |instance_moderation| - %hr - = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance + - cache [instance_moderation.aasm_state, instance_moderation.instance] do + %hr + = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance %hr %div From dd20c6ce84881d96704f24a516e9fc2561e535c7 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 16:42:39 -0300 Subject: [PATCH 096/297] fixup! feat: mostrar la cantidad de instancias --- config/locales/en.yml | 6 +++--- config/locales/es.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 717c9ece..c6a7d936 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -60,9 +60,9 @@ en: submenu_allow: Allow submenu_block: Block instances_show_submenu: - submenu_paused: Moderated - submenu_allowed: Allowed - submenu_blocked: Blocked + submenu_paused: "Moderated (%{count})" + submenu_allowed: "Allowed (%{count})" + submenu_blocked: "Blocked (%{count})" comments_filters: text_show: Show text_checked: With selected diff --git a/config/locales/es.yml b/config/locales/es.yml index 87111523..b3105220 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -60,9 +60,9 @@ es: submenu_allow: Permitir todo submenu_block: Rechazar todo instances_show_submenu: - submenu_paused: Pausadas - submenu_allowed: Permitidas - submenu_blocked: Bloqueadas + submenu_paused: "Pausadas (%{count})" + submenu_allowed: "Permitidas (%{count})" + submenu_blocked: "Bloqueadas (%{count})" comments_filters: text_show: Ver text_checked: Con los marcados From ef9169c89135259c98d5117b9af4f20179f2b110 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 17:12:33 -0300 Subject: [PATCH 097/297] =?UTF-8?q?fix:=20traducci=C3=B3n=20faltante?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/components/_block_list.haml | 3 ++- config/locales/en.yml | 1 + config/locales/es.yml | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/components/_block_list.haml b/app/views/components/_block_list.haml index 75825bb0..27e44cac 100644 --- a/app/views/components/_block_list.haml +++ b/app/views/components/_block_list.haml @@ -1,12 +1,13 @@ -# Componente Listas de bloqueo de Instancias - know_more = t('.know_more') +- instances_blocked = t('.instances_blocked') .card.mt-3.mb-3 .card-body = render 'components/checkbox', id: state.id, name: 'fediblock_states_ids[]', value: state.id, checked: state.enabled? do %span.h4.mb-0= blocklist.title %dl.mb-0 - %dt.d-inline= t('.instances_blocked') + %dt.d-inline= instances_blocked %dd.d-inline.font-weight-normal= blocklist.hostnames.count %p.mb-0.font-weight-normal %a{ href: blocklist.url }= know_more diff --git a/config/locales/en.yml b/config/locales/en.yml index c6a7d936..aaa5a451 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -52,6 +52,7 @@ en: components: block_list: know_more: Know more + instances_blocked: Instances blocked instances_filters: text_show: Show text_checked: With selected diff --git a/config/locales/es.yml b/config/locales/es.yml index b3105220..8fe01b83 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -52,6 +52,7 @@ es: components: block_list: know_more: Saber más (en inglés) + instances_blocked: Instancias bloqueadas instances_filters: text_show: Ver text_checked: Con los marcados From eafe8bcdd577d817a791552ffb774241bfd34422 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 17:26:22 -0300 Subject: [PATCH 098/297] feat: poder seleccionar todas las cuentas --- app/views/moderation_queue/_account.haml | 4 ++-- app/views/moderation_queue/_accounts.haml | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index 2c929402..27ea30d1 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -1,6 +1,6 @@ .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: profile['id'] + = render 'components/checkbox', id: profile['id'], name: 'actor[]', value: profile['id'], data: { target: 'select-all.input' } .col-11 %h4 %a{href: profile['id']}= profile['preferredUsername'] @@ -8,4 +8,4 @@ -# Botones de Moderación .d-flex.pb-4 - = render 'components/profiles_btn_box' \ No newline at end of file + = render 'components/profiles_btn_box' diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index 2d8e1420..c88a2de6 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -1,12 +1,10 @@ -.row.no-gutters.pt-2 +.row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center - = render 'components/checkbox', id: moderation_queue.first['id'] + = render 'components/select_all', id: 'actors' .col-11 -# Filtros = render 'components/profiles_filters' - -- @moderation_queue.map{ |c| c['attributedTo'] }.uniq.each do |remote_profile| - %hr - = render 'account', profile: remote_profile - - + .col-12 + - @moderation_queue.map{ |c| c['attributedTo'] }.uniq.each do |remote_profile| + %hr + = render 'account', profile: remote_profile From f524724c0ffe37830ef305f3ca6fa66f38b2bfbf Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 17:34:34 -0300 Subject: [PATCH 099/297] feat: moderar actores --- app/models/activity_pub/actor.rb | 1 + app/models/actor_moderation.rb | 32 +++++++++++++++ app/models/site/social_distributed_press.rb | 1 + ...20240228202830_create_actor_moderations.rb | 14 +++++++ db/structure.sql | 39 ++++++++++++++++++- 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 app/models/actor_moderation.rb create mode 100644 db/migrate/20240228202830_create_actor_moderations.rb diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index 7a858a7e..92750946 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -10,6 +10,7 @@ class ActivityPub include ActivityPub::Concerns::JsonLdConcern belongs_to :instance + has_many :actor_moderation has_many :activity_pubs, as: :object has_many :activities end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb new file mode 100644 index 00000000..69fbf3bd --- /dev/null +++ b/app/models/actor_moderation.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Mantiene la relación entre Site y Actor +class ActorModeration < ApplicationRecord + include AASM + + belongs_to :site + belongs_to :actor, class_name: 'ActivityPub::Actor' + + aasm do + state :paused, initial: true + state :allowed + state :blocked + state :reported + + event :pause do + transitions from: %i[allowed blocked reported], to: :paused + end + + event :allowed do + transitions from: %i[paused blocked reported], to: :allowed + end + + event :blocked do + transitions from: %i[paused allowed], to: :blocked + end + + event :reported do + transitions from: %i[blocked], to: :reported + end + end +end diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index fdb37bca..e916bf3e 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -12,6 +12,7 @@ class Site has_many :activity_pubs has_many :instance_moderations + has_many :actor_moderations has_many :fediblock_states has_many :instances, through: :instance_moderations diff --git a/db/migrate/20240228202830_create_actor_moderations.rb b/db/migrate/20240228202830_create_actor_moderations.rb new file mode 100644 index 00000000..01460eae --- /dev/null +++ b/db/migrate/20240228202830_create_actor_moderations.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Relación entre Actor y Site +class CreateActorModerations < ActiveRecord::Migration[6.1] + def change + create_table :actor_moderations, id: :uuid do |t| + t.timestamps + + t.belongs_to :site + t.uuid :actor_id, index: true + t.string :aasm_state, null: false + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 740f5d87..e4d39ad0 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -562,6 +562,20 @@ CREATE TABLE public.activity_pubs ( ); +-- +-- Name: actor_moderations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.actor_moderations ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + site_id bigint, + actor_id uuid, + aasm_state character varying NOT NULL +); + + -- -- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: - -- @@ -1756,6 +1770,14 @@ ALTER TABLE ONLY public.activity_pubs ADD CONSTRAINT activity_pubs_pkey PRIMARY KEY (id); +-- +-- Name: actor_moderations actor_moderations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.actor_moderations + ADD CONSTRAINT actor_moderations_pkey PRIMARY KEY (id); + + -- -- Name: blazer_audits blazer_audits_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2107,6 +2129,20 @@ CREATE INDEX index_activity_pub_instances_on_hostname ON public.activity_pub_ins CREATE UNIQUE INDEX index_activity_pubs_on_site_id_and_object_id_and_object_type ON public.activity_pubs USING btree (site_id, object_id, object_type); +-- +-- Name: index_actor_moderations_on_actor_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_actor_moderations_on_actor_id ON public.actor_moderations USING btree (actor_id); + + +-- +-- Name: index_actor_moderations_on_site_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_actor_moderations_on_site_id ON public.actor_moderations USING btree (site_id); + + -- -- Name: index_blazer_audits_on_query_id; Type: INDEX; Schema: public; Owner: - -- @@ -2616,6 +2652,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240226134335'), ('20240227134845'), ('20240227142019'), -('20240228171335'); +('20240228171335'), +('20240228202830'); From aeb8f884935964ec00e2d213042bea33b13fddb2 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 17:44:21 -0300 Subject: [PATCH 100/297] =?UTF-8?q?feat:=20crear=20el=20estado=20de=20mode?= =?UTF-8?q?raci=C3=B3n=20si=20no=20existe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 12545915..f52525f1 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -158,6 +158,8 @@ module Api a.save! + site.actor_moderations.find_or_create_by(actor: a) + ActivityPub::ActorFetchJob.perform_later(site: site, actor: a) end end From 6eaf00c7ad1597a2bac9b22e1719f10ad8e70b7b Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 17:52:24 -0300 Subject: [PATCH 101/297] fix: recibir actividades correctamente --- .../api/v1/webhooks/social_inbox_controller.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index f52525f1..34ef1d6c 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -5,6 +5,9 @@ module Api module Webhooks # Recibe webhooks de la Social Inbox # + # @todo Mover todo a un Job que obtenga el objeto remoto antes de + # instanciar el objeto localmente en lugar de arreglarlo después y + # poder responder lo más rápido posible el webhook. # @see {https://www.w3.org/TR/activitypub/} class SocialInboxController < BaseController include Api::V1::Webhooks::Concerns::WebhookConcern @@ -28,6 +31,7 @@ module Api instance.present? object.present? activity_pub.present? + activity.update_activity_pub_state! end rescue ActiveRecord::RecordInvalid => e @@ -125,7 +129,7 @@ module Api # # @return [ActivityPub] def activity_pub - @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, object: object) + @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, instance: instance, object: object) end # Crea la actividad y la vincula con el estado @@ -135,7 +139,7 @@ module Api @activity ||= ActivityPub::Activity .type_from(original_activity) - .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub).tap do |a| + .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| a.content = original_activity.dup a.content[:object] = object.uri a.save! From 6d9a64f7283a488b59fd32d325acac1ec6a34af9 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 17:58:00 -0300 Subject: [PATCH 102/297] feat: moderar en la social inbox --- app/models/activity_pub/actor.rb | 11 ++++++++++ app/models/actor_moderation.rb | 37 ++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index 92750946..a5171815 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -13,5 +13,16 @@ class ActivityPub has_many :actor_moderation has_many :activity_pubs, as: :object has_many :activities + + # Obtiene el nombre de la Actor como mención, solo si obtuvimos el + # contenido de antemano. + # + # @return [String, nil] + def mention + return if content['preferredUsername'].blank? + return if instance.blank? + + @mention ||= "@#{content['preferredUsername']}@#{instance.hostname}" + end end end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 69fbf3bd..efbde33b 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -15,18 +15,51 @@ class ActorModeration < ApplicationRecord event :pause do transitions from: %i[allowed blocked reported], to: :paused + + before do + pause_remotely! + end end - event :allowed do + event :allow do transitions from: %i[paused blocked reported], to: :allowed + + before do + allow_remotely! + end end - event :blocked do + event :block do transitions from: %i[paused allowed], to: :blocked + + before do + block_remotely! + end end event :reported do transitions from: %i[blocked], to: :reported end end + + def pause_remotely! + raise AASM::InvalidTransition unless + actor.mention && + site.social_inbox.allowlist.delete(list: [actor.mention]).ok? && + site.social_inbox.blocklist.delete(list: [actor.mention]).ok? + end + + def allow_remotely! + raise AASM::InvalidTransition unless + actor.mention && + site.social_inbox.allowlist.post(list: [actor.mention]).ok? && + site.social_inbox.blocklist.delete(list: [actor.mention]).ok? + end + + def block_remotely! + raise AASM::InvalidTransition unless + actor.mention && + site.social_inbox.allowlist.delete(list: [actor.mention]).ok? && + site.social_inbox.blocklist.post(list: [actor.mention]).ok? + end end From e07da7858e02309f0571428dfd754de0bc38687b Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 18:27:15 -0300 Subject: [PATCH 103/297] =?UTF-8?q?feat:=20cambiar=20a=20orden=20descendie?= =?UTF-8?q?nte=20por=20fecha=20de=20modificaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/processors/actor_moderation_processor.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 app/processors/actor_moderation_processor.rb diff --git a/app/processors/actor_moderation_processor.rb b/app/processors/actor_moderation_processor.rb new file mode 100644 index 00000000..efd12666 --- /dev/null +++ b/app/processors/actor_moderation_processor.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Gestiona los filtros de ActorModeration +class ActorModerationProcessor < Rubanok::Processor + # En orden descendiente para encontrar le últime Actor + prepare do + raw.order(updated_at: :desc) + end + + map :actor_state, activate_always: true do |state: 'paused'| + raw.where(aasm_state: state) + end +end From 0e2f6276eb501b0787442f096fe916dca0bfd4ba Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 18:31:28 -0300 Subject: [PATCH 104/297] feat: siempre actualizar la fecha de le actore al guardar una actividad --- app/models/activity_pub/activity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub/activity.rb b/app/models/activity_pub/activity.rb index a220b831..1147c5b8 100644 --- a/app/models/activity_pub/activity.rb +++ b/app/models/activity_pub/activity.rb @@ -16,7 +16,7 @@ class ActivityPub include ActivityPub::Concerns::JsonLdConcern belongs_to :activity_pub - belongs_to :actor + belongs_to :actor, touch: true has_one :object, through: :activity_pub validates :activity_pub_id, presence: true From 25c9963284887f581758d2f524e9b81ab9ed4991 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 18:35:34 -0300 Subject: [PATCH 105/297] =?UTF-8?q?feat:=20mostrar=20las=20cuentas=20en=20?= =?UTF-8?q?la=20cola=20de=20moderaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/moderation_queue_controller.rb | 1 + app/processors/actor_moderation_processor.rb | 4 ++-- app/views/moderation_queue/_accounts.haml | 4 ++-- app/views/moderation_queue/index.haml | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index fd7d2acb..a9611a1b 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -11,6 +11,7 @@ class ModerationQueueController < ApplicationController # @todo cambiar el estado por query @activity_pubs = site.activity_pubs @instance_moderations = rubanok_process(site.instance_moderations, with: InstanceModerationProcessor) + @actor_moderations = rubanok_process(site.actor_moderations, with: ActorModerationProcessor) end # Perfil remoto de usuarie diff --git a/app/processors/actor_moderation_processor.rb b/app/processors/actor_moderation_processor.rb index efd12666..a3035654 100644 --- a/app/processors/actor_moderation_processor.rb +++ b/app/processors/actor_moderation_processor.rb @@ -7,7 +7,7 @@ class ActorModerationProcessor < Rubanok::Processor raw.order(updated_at: :desc) end - map :actor_state, activate_always: true do |state: 'paused'| - raw.where(aasm_state: state) + map :actor_state, activate_always: true do |actor_state: 'paused'| + raw.where(aasm_state: actor_state) end end diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index c88a2de6..5630a6d7 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -5,6 +5,6 @@ -# Filtros = render 'components/profiles_filters' .col-12 - - @moderation_queue.map{ |c| c['attributedTo'] }.uniq.each do |remote_profile| + - actor_moderations.find_each do |actor_moderation| %hr - = render 'account', profile: remote_profile + = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content diff --git a/app/views/moderation_queue/index.haml b/app/views/moderation_queue/index.haml index 0fb1c968..df2c219d 100644 --- a/app/views/moderation_queue/index.haml +++ b/app/views/moderation_queue/index.haml @@ -9,7 +9,7 @@ %hr - summary = t('.accounts') = render 'layouts/details', summary: summary do - = render 'moderation_queue/accounts', site: @site, post: @post, moderation_queue: @moderation_queue + = render 'moderation_queue/accounts', site: @site, post: @post, actor_moderations: @actor_moderations %hr - summary = t('.comments') = render 'layouts/details', summary: summary do From 2edafc8901a92e81534f3d50ff9c5fea797488ed Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 18:42:48 -0300 Subject: [PATCH 106/297] feat: ver la cuenta de cuentas por moderar --- app/views/components/_profiles_filters.haml | 4 ++-- app/views/components/_profiles_show_submenu.haml | 7 ++++--- app/views/moderation_queue/_accounts.haml | 2 +- config/locales/en.yml | 9 +++++---- config/locales/es.yml | 9 +++++---- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/views/components/_profiles_filters.haml b/app/views/components/_profiles_filters.haml index 0088afef..c6397e69 100644 --- a/app/views/components/_profiles_filters.haml +++ b/app/views/components/_profiles_filters.haml @@ -1,6 +1,6 @@ .d-flex.py-2 = render 'components/dropdown', text: t('.text_checked') do - = render 'components/profiles_checked_submenu' + = render 'components/profiles_checked_submenu' = render 'components/dropdown', text: t('.text_show') do - = render 'components/profiles_show_submenu' \ No newline at end of file + = render 'components/profiles_show_submenu', actor_moderations: actor_moderations diff --git a/app/views/components/_profiles_show_submenu.haml b/app/views/components/_profiles_show_submenu.haml index 2ba949b1..703e4a15 100644 --- a/app/views/components/_profiles_show_submenu.haml +++ b/app/views/components/_profiles_show_submenu.haml @@ -1,3 +1,4 @@ -= render 'components/dropdown_item', text: t('.submenu_accept'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_reject'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_block'), path: '/' \ No newline at end of file +- ActorModeration.aasm.states.map(&:name).each do |actor_state| + = render 'components/dropdown_item', + text: t(".submenu_#{actor_state}", count: actor_moderations.unscope(where: :aasm_state).public_send(actor_state).count), + path: site_moderation_queue_path(params.permit(:state, :actor_state).merge(actor_state: actor_state)) diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index 5630a6d7..44c2893e 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -3,7 +3,7 @@ = render 'components/select_all', id: 'actors' .col-11 -# Filtros - = render 'components/profiles_filters' + = render 'components/profiles_filters', actor_moderations: actor_moderations .col-12 - actor_moderations.find_each do |actor_moderation| %hr diff --git a/config/locales/en.yml b/config/locales/en.yml index aaa5a451..c4bff0a9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -85,10 +85,11 @@ en: submenu_reject: Reject submenu_block: Block profiles_show_submenu: - submenu_accept: Accept - submenu_block: Block - submenu_reject: Reject - block_lists: + submenu_paused: "Paused (%{count})" + submenu_allowed: "Allowed (%{count})" + submenu_blocked: "Blocked (%{count})" + submenu_reported: "Reported (%{count})" + block_lists: title: Block lists comments_btn_box: text_pause: Pause diff --git a/config/locales/es.yml b/config/locales/es.yml index 8fe01b83..9409eddd 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -85,10 +85,11 @@ es: submenu_reject: Rechazado submenu_block: Bloqueado profiles_show_submenu: - submenu_accept: Aceptado - submenu_block: Bloqueado - submenu_reject: Rechazado - block_lists: + submenu_paused: "Pausadas (%{count})" + submenu_allowed: "Permitidas (%{count})" + submenu_blocked: "Bloqueadas (%{count})" + submenu_reported: "Reportadas (%{count})" + block_lists: title: Listas de bloqueo comments_btn_box: text_pause: Pausa From 0539eb8f2245d4c8573aa744352f1ea72b9f74ae Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 18:51:41 -0300 Subject: [PATCH 107/297] =?UTF-8?q?feat:=20avisar=20cuando=20no=20se=20enc?= =?UTF-8?q?ontr=C3=B3=20nada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/moderation_queue/_accounts.haml | 2 ++ app/views/moderation_queue/_instances.haml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index 44c2893e..c7dba0a1 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -5,6 +5,8 @@ -# Filtros = render 'components/profiles_filters', actor_moderations: actor_moderations .col-12 + - if actor_moderations.count.zero? + %h4= t('moderation_queue.nothing') - actor_moderations.find_each do |actor_moderation| %hr = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index a45da354..77b6adea 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -9,7 +9,7 @@ .col-12 - if instance_moderations.count.zero? - %h3= t('moderation_queue.nothing') + %h4= t('moderation_queue.nothing') - instance_moderations.each do |instance_moderation| - cache [instance_moderation.aasm_state, instance_moderation.instance] do From 5370a542ff52be582e927262ed57c8ffce8da42a Mon Sep 17 00:00:00 2001 From: f Date: Wed, 28 Feb 2024 19:10:37 -0300 Subject: [PATCH 108/297] feat: cambiar el estado de les actores desde el panel --- .../actor_moderations_controller.rb | 20 +++++++++++++++++++ .../moderation_queue_controller.rb | 2 +- app/models/actor_moderation.rb | 6 +++++- app/policies/actor_moderation_policy.rb | 16 +++++++++++++++ app/views/components/_profiles_btn_box.haml | 10 ++++++---- app/views/moderation_queue/_account.haml | 7 ++++--- config/locales/en.yml | 6 +++--- config/locales/es.yml | 6 +++--- 8 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 app/controllers/actor_moderations_controller.rb create mode 100644 app/policies/actor_moderation_policy.rb diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb new file mode 100644 index 00000000..543ca74d --- /dev/null +++ b/app/controllers/actor_moderations_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Gestiona la cola de moderación de actores +class ActorModerationsController < ApplicationController + ActorModeration.aasm.events.map(&:name).each do |actor_event| + define_method(actor_event) do + authorize actor_moderation + + actor_moderation.public_send(:"#{actor_event}!") if actor_moderation.public_send(:"may_#{actor_event}?") + + redirect_back fallback_location: site_moderation_queue_path(**(session[:moderation_queue_filters] || {})) + end + end + + private + + def actor_moderation + @actor_moderation ||= site.actor_moderations.find(params[:actor_moderation_id]) + end +end diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index a9611a1b..df36dfbe 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -6,7 +6,7 @@ class ModerationQueueController < ApplicationController def index dummy_data - session[:moderation_queue_filtered_by_state] = params[:state] + session[:moderation_queue_filters] = params.permit(:state, :actor_state) # @todo cambiar el estado por query @activity_pubs = site.activity_pubs diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index efbde33b..f186dd69 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -7,6 +7,10 @@ class ActorModeration < ApplicationRecord belongs_to :site belongs_to :actor, class_name: 'ActivityPub::Actor' + def self.events + aasm.events.map(&:name) + end + aasm do state :paused, initial: true state :allowed @@ -37,7 +41,7 @@ class ActorModeration < ApplicationRecord end end - event :reported do + event :report do transitions from: %i[blocked], to: :reported end end diff --git a/app/policies/actor_moderation_policy.rb b/app/policies/actor_moderation_policy.rb new file mode 100644 index 00000000..07a9a752 --- /dev/null +++ b/app/policies/actor_moderation_policy.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Solo les usuaries pueden moderar actores +ActorModerationPolicy = Struct.new(:usuarie, :actor_moderation) do + ActorModeration.events.each do |actor_event| + define_method(:"#{actor_event}?") do + actor_moderation.site.usuarie? usuarie + end + end + + # En este paso tenemos varias cuentas por moderar pero todas son + # del mismo sitio. + def action_on_several? + actor_moderation.first.site.usuarie? usuarie + end +end diff --git a/app/views/components/_profiles_btn_box.haml b/app/views/components/_profiles_btn_box.haml index 06faa8a1..bc1fd7e4 100644 --- a/app/views/components/_profiles_btn_box.haml +++ b/app/views/components/_profiles_btn_box.haml @@ -1,7 +1,9 @@ -# Componente Botonera de Moderación de Cuentas (Remote_profile) - btn_class = 'btn-secondary' -= render 'components/btn_base', text: t('.text_approve'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_check'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_deny'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_report'), class: btn_class, href: '' \ No newline at end of file +- ActorModeration.events.each do |actor_event| + = render 'components/btn_base', + text: t(".text_#{actor_event}"), + path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), + class: btn_class, + disabled: !actor_moderation.public_send(:"may_#{actor_event}?") diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index 27ea30d1..24fbb270 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -3,9 +3,10 @@ = render 'components/checkbox', id: profile['id'], name: 'actor[]', value: profile['id'], data: { target: 'select-all.input' } .col-11 %h4 - %a{href: profile['id']}= profile['preferredUsername'] - =profile['summary'].html_safe + %a{href: profile['id']}= sanitize profile['name'] + .mb-3 + = sanitize profile['summary'].html_safe -# Botones de Moderación .d-flex.pb-4 - = render 'components/profiles_btn_box' + = render 'components/profiles_btn_box', actor_moderation: actor_moderation diff --git a/config/locales/en.yml b/config/locales/en.yml index c4bff0a9..3c8e8851 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -102,9 +102,9 @@ en: text_allow: Allow everything text_deny: Block instance profiles_btn_box: - text_approve: Always approve - text_check: Always check - text_deny: Block + text_pause: Always check + text_allow: Always approve + text_block: Block text_report: Report moderation_queue: everything: 'Select all' diff --git a/config/locales/es.yml b/config/locales/es.yml index 9409eddd..d2c30ef6 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -102,9 +102,9 @@ es: text_allow: Permitir todo text_deny: Bloquear instancia profiles_btn_box: - text_approve: Aprobar siempre - text_check: Revisar siempre - text_deny: Bloquear + text_pause: Revisar siempre + text_allow: Aprobar siempre + text_block: Bloquear text_report: Reportar moderation_queue: everything: 'Seleccionar todo' From cf46988d9db2aa34bb26ae5dbd521923da28ed23 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 29 Feb 2024 15:53:32 -0300 Subject: [PATCH 109/297] fixup! feat: cambiar el estado de les actores desde el panel --- app/views/actor_moderations/show.haml | 4 ++++ config/routes.rb | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 app/views/actor_moderations/show.haml diff --git a/app/views/actor_moderations/show.haml b/app/views/actor_moderations/show.haml new file mode 100644 index 00000000..ba0fc257 --- /dev/null +++ b/app/views/actor_moderations/show.haml @@ -0,0 +1,4 @@ +.row.justify-content-center + .col-md-8 + %h1= t('.profile') + = render 'moderation_queue/remote_profile', remote_profile: @remote_profile diff --git a/config/routes.rb b/config/routes.rb index d70f8339..f6b412e2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,6 +71,12 @@ Rails.application.routes.draw do patch :instance_moderations_action_on_several, to: 'instance_moderations#action_on_several' patch :fediblock_states_action_on_several, to: 'fediblock_states#action_on_several' + resources :actor_moderations, only: [] do + ActorModeration.events.each do |actor_event| + patch actor_event, to: "actor_moderations##{actor_event}" + end + end + # Gestionar artículos según idioma nested do scope '/(:locale)', constraint: /[a-z]{2}(-[A-Z]{2})?/ do From 3ba23a8b8c49ae25accd7bbcfaef99f6779d8a05 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 29 Feb 2024 15:54:53 -0300 Subject: [PATCH 110/297] feat: poder ir al perfil desde la lista --- .../actor_moderations_controller.rb | 7 ++++- .../moderation_queue_controller.rb | 5 ---- app/views/actor_moderations/show.haml | 8 +++-- app/views/components/_actor.haml | 21 ++++++++++++++ app/views/components/_profiles_btn_box.haml | 15 +++++----- app/views/moderation_queue/_account.haml | 2 +- .../moderation_queue/_remote_profile.haml | 29 ------------------- .../moderation_queue/remote_profile.haml | 4 --- config/locales/en.yml | 17 ++++++----- config/locales/es.yml | 17 ++++++----- config/routes.rb | 3 +- 11 files changed, 61 insertions(+), 67 deletions(-) create mode 100644 app/views/components/_actor.haml delete mode 100644 app/views/moderation_queue/_remote_profile.haml delete mode 100644 app/views/moderation_queue/remote_profile.haml diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 543ca74d..f4637d70 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -12,9 +12,14 @@ class ActorModerationsController < ApplicationController end end + # Ver el perfil remoto + def show + @remote_profile = actor_moderation.actor.content + end + private def actor_moderation - @actor_moderation ||= site.actor_moderations.find(params[:actor_moderation_id]) + @actor_moderation ||= site.actor_moderations.find(params[:actor_moderation_id] || params[:id]) end end diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index df36dfbe..95639b35 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -14,11 +14,6 @@ class ModerationQueueController < ApplicationController @actor_moderations = rubanok_process(site.actor_moderations, with: ActorModerationProcessor) end - # Perfil remoto de usuarie - def remote_profile - dummy_data - end - # todon.nl está usando /api/v2/instance # mauve.moe usa /api/v1/instance def instances diff --git a/app/views/actor_moderations/show.haml b/app/views/actor_moderations/show.haml index ba0fc257..7b62f672 100644 --- a/app/views/actor_moderations/show.haml +++ b/app/views/actor_moderations/show.haml @@ -1,4 +1,8 @@ .row.justify-content-center - .col-md-8 + .col-12.col-md-8 %h1= t('.profile') - = render 'moderation_queue/remote_profile', remote_profile: @remote_profile + = render 'components/actor', remote_profile: @remote_profile + .col-12.col-md-8 + = render 'components/profiles_btn_box', actor_moderation: @actor_moderation + -# + = render 'moderation_queue/comments', moderation_queue: @moderation_queue diff --git a/app/views/components/_actor.haml b/app/views/components/_actor.haml new file mode 100644 index 00000000..f5d6efae --- /dev/null +++ b/app/views/components/_actor.haml @@ -0,0 +1,21 @@ +-# Componente Remote_Profile + +.py-2 + %dl + %dt= t('.profile_name') + %dd= sanitize remote_profile['name'] + + %dt= t('.preferred_name') + %dd= sanitize remote_profile['preferredUsername'] + + %dt= t('.profile_id') + %dd + = link_to sanitize(remote_profile['id']) + + - if remote_profile['published'].present? + %dt= t('.profile_published') + %dd + = render 'layouts/time', time: sanitize(remote_profile['published']) + %dt= t('.profile_summary') + %dd + = sanitize remote_profile['summary'] diff --git a/app/views/components/_profiles_btn_box.haml b/app/views/components/_profiles_btn_box.haml index bc1fd7e4..bd994f84 100644 --- a/app/views/components/_profiles_btn_box.haml +++ b/app/views/components/_profiles_btn_box.haml @@ -1,9 +1,10 @@ -# Componente Botonera de Moderación de Cuentas (Remote_profile) -- btn_class = 'btn-secondary' -- ActorModeration.events.each do |actor_event| - = render 'components/btn_base', - text: t(".text_#{actor_event}"), - path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), - class: btn_class, - disabled: !actor_moderation.public_send(:"may_#{actor_event}?") +.d-flex.flex-row + - btn_class = 'btn-secondary' + - ActorModeration.events.each do |actor_event| + = render 'components/btn_base', + text: t(".text_#{actor_event}"), + path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), + class: btn_class, + disabled: !actor_moderation.public_send(:"may_#{actor_event}?") diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index 24fbb270..cdcc0ad4 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -3,7 +3,7 @@ = render 'components/checkbox', id: profile['id'], name: 'actor[]', value: profile['id'], data: { target: 'select-all.input' } .col-11 %h4 - %a{href: profile['id']}= sanitize profile['name'] + = link_to sanitize(profile['name']), site_actor_moderation_path(id: actor_moderation) .mb-3 = sanitize profile['summary'].html_safe diff --git a/app/views/moderation_queue/_remote_profile.haml b/app/views/moderation_queue/_remote_profile.haml deleted file mode 100644 index 92cf8e96..00000000 --- a/app/views/moderation_queue/_remote_profile.haml +++ /dev/null @@ -1,29 +0,0 @@ --# Componente Remote_Profile - -.flex.py-2.mx-2 - %dl - %dt= t('.profile_name') - %dd= remote_profile['name'] - - %dt= t('.preferred_name') - %dd= remote_profile['preferredUsername'] - - %dt= t('.profile_id') - %dd - %a{ href: 'https://mastodon.mauve.moe/users/mauve' }= remote_profile['id'] - - %dt= t('.profile_published') - %dd - = render 'layouts/time', time: remote_profile['published'] - %dt= t('.profile_summary') - %dd - %p= remote_profile['summary'].html_safe - - = render 'moderation_queue/comments', moderation_queue: @moderation_queue - -%dl.mt-5 - %dt= t('.profile_name') - %dd= remote_profile['name'] - --# Botones de Moderación -= render 'components/profiles_btn_box' diff --git a/app/views/moderation_queue/remote_profile.haml b/app/views/moderation_queue/remote_profile.haml deleted file mode 100644 index ba0fc257..00000000 --- a/app/views/moderation_queue/remote_profile.haml +++ /dev/null @@ -1,4 +0,0 @@ -.row.justify-content-center - .col-md-8 - %h1= t('.profile') - = render 'moderation_queue/remote_profile', remote_profile: @remote_profile diff --git a/config/locales/en.yml b/config/locales/en.yml index 3c8e8851..62e9a1a7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -106,6 +106,15 @@ en: text_allow: Always approve text_block: Block text_report: Report + actor_moderations: + show: + user: Username + profile: Profile + profile_name: Profile name + preferred_name: Name in Fediverse + profile_id: ID + profile_published: Published + profile_summary: Summary moderation_queue: everything: 'Select all' nothing: "There's nothing for this filter" @@ -117,14 +126,6 @@ en: comment: source_profile: Source Profile reply_to: Reply to - remote_profile: - user: Username - profile: Profile - profile_name: Profile name - preferred_name: Name in Fediverse - profile_id: ID - profile_published: Published - profile_summary: Summary instances: title: My block lists description: Description diff --git a/config/locales/es.yml b/config/locales/es.yml index d2c30ef6..3dc68d98 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -106,6 +106,15 @@ es: text_allow: Aprobar siempre text_block: Bloquear text_report: Reportar + actor_moderations: + show: + user: Nombre de usuarie + profile: Cuenta de Origen + profile_name: Nombre de la cuenta + preferred_name: Nombre en el Fediverso + profile_id: ID + profile_published: Publicada + profile_summary: Presentación moderation_queue: everything: 'Seleccionar todo' nothing: 'No hay nada para este filtro' @@ -117,14 +126,6 @@ es: comment: source_profile: Cuenta de Origen reply_to: En respuesta a - remote_profile: - user: Nombre de usuario - profile: Cuenta de Origen - profile_name: Nombre de la Cuenta - preferred_name: Nombre en el Fediverso - profile_id: ID - profile_published: Publicada - profile_summary: Presentación instances: title: Mis listas de bloqueo description: Descripción de listas de bloqueo diff --git a/config/routes.rb b/config/routes.rb index f6b412e2..ceffcd26 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -59,7 +59,6 @@ Rails.application.routes.draw do post 'collaborate', to: 'collaborations#accept_collaboration' get 'moderation_queue', to: 'moderation_queue#index' - get 'remote_profile', to: 'moderation_queue#remote_profile' get 'instances', to: 'moderation_queue#instances' resources :instance_moderations, only: [] do @@ -71,7 +70,7 @@ Rails.application.routes.draw do patch :instance_moderations_action_on_several, to: 'instance_moderations#action_on_several' patch :fediblock_states_action_on_several, to: 'fediblock_states#action_on_several' - resources :actor_moderations, only: [] do + resources :actor_moderations, only: %i[show] do ActorModeration.events.each do |actor_event| patch actor_event, to: "actor_moderations##{actor_event}" end From cbba822f31b12d5607210aa2dec749915d8184c7 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 29 Feb 2024 16:27:48 -0300 Subject: [PATCH 111/297] feat: recordar el estado del details --- .../controllers/details_controller.js | 17 +++++++++++++++++ app/views/layouts/_details.haml | 9 +++++++-- app/views/moderation_queue/index.haml | 9 +++------ 3 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 app/javascript/controllers/details_controller.js diff --git a/app/javascript/controllers/details_controller.js b/app/javascript/controllers/details_controller.js new file mode 100644 index 00000000..57935e1e --- /dev/null +++ b/app/javascript/controllers/details_controller.js @@ -0,0 +1,17 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = []; + + connect() { + const state = window.sessionStorage.getItem(this.element.id); + + if (state === "open") { + this.element.setAttribute("open", true); + } + } + + store(event = undefined) { + window.sessionStorage.setItem(this.element.id, event.newState); + } +} diff --git a/app/views/layouts/_details.haml b/app/views/layouts/_details.haml index 99ba4894..ce38bddd 100644 --- a/app/views/layouts/_details.haml +++ b/app/views/layouts/_details.haml @@ -1,6 +1,11 @@ --# Detail Cola de Moderación +-# + Detail Cola de Moderación -%details.details.py-2 + @param :id [String] El ID opcional sirve para mantener el historial de + cuál estaba abierto y recuperarlo al cargar la página + @param :summary [String] El resumen + +%details.details.py-2{ id: local_assigns[:id], data: { controller: 'details', action: 'toggle->details#store' } } %summary .row .col-11.pr-2 diff --git a/app/views/moderation_queue/index.haml b/app/views/moderation_queue/index.haml index df2c219d..799fc641 100644 --- a/app/views/moderation_queue/index.haml +++ b/app/views/moderation_queue/index.haml @@ -3,14 +3,11 @@ %h1= t('.title') .row .col - - summary = t('.instances') - = render 'layouts/details', summary: summary do + = render 'layouts/details', id: 'summary', summary: t('.instances') do = render 'moderation_queue/instances', site: @site, instance_moderations: @instance_moderations, fediblock_states: @site.fediblock_states %hr - - summary = t('.accounts') - = render 'layouts/details', summary: summary do + = render 'layouts/details', id: 'accounts', summary: t('.accounts') do = render 'moderation_queue/accounts', site: @site, post: @post, actor_moderations: @actor_moderations %hr - - summary = t('.comments') - = render 'layouts/details', summary: summary do + = render 'layouts/details', id: 'comments', summary: t('.comments') do = render 'moderation_queue/comments', site: @site, post: @post, moderation_queue: @moderation_queue From 6c61aec60e177d522e0dacd7d50152c6ec72b5d0 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 29 Feb 2024 16:45:06 -0300 Subject: [PATCH 112/297] feat: al modificar un fediblock, cambiar el estado a todes les actores --- app/models/actor_moderation.rb | 12 ++++++++++++ app/models/fediblock_state.rb | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index f186dd69..1d9bae5d 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -7,6 +7,18 @@ class ActorModeration < ApplicationRecord belongs_to :site belongs_to :actor, class_name: 'ActivityPub::Actor' + # 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 + + # Todos los eventos de la máquina de estados + # + # @return [Array] def self.events aasm.events.map(&:name) end diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index a160c43a..180a45b5 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -37,6 +37,11 @@ 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. + ActorModeration.where(actor_id: actor_ids).paused.block_all! end end @@ -52,15 +57,27 @@ class FediblockState < ApplicationRecord disable_remotely! instance_moderations.pause_all! + + # Volver a pausar todes les actores de esta instancia que fueron + # bloqueades. + ActorModeration.where(actor_id: actor_ids).blocked.pause_all! end end end private + def actor_ids + ActivityPub::Actor.where(instance_id: instance_ids).pluck(:id) + end + + def instance_ids + fediblock.instances.pluck(:id) + end + # Todas las instancias de moderación de este sitio def instance_moderations - site.instance_moderations.where(instance_id: fediblock.instances.pluck(:id)) + site.instance_moderations.where(instance_id: instance_ids) end # @return [Array] From 67d9731b1e37606f6b150704fd9d853b6caf34e3 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 15:04:07 -0300 Subject: [PATCH 113/297] feat: enviar reportes remotos --- .../activity_pub/remote_flags_controller.rb | 27 +++++++++++ app/jobs/activity_pub/remote_flag_job.rb | 25 ++++++++++ app/models/activity_pub/actor.rb | 1 + app/models/deploy_social_distributed_press.rb | 9 +--- app/models/site/social_distributed_press.rb | 8 ++++ app/models/social_inbox.rb | 26 ++++++++--- config/routes.rb | 4 ++ ...201155_create_activity_pub_remote_flags.rb | 16 +++++++ db/structure.sql | 46 ++++++++++++++++++- 9 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 app/controllers/api/v1/activity_pub/remote_flags_controller.rb create mode 100644 app/jobs/activity_pub/remote_flag_job.rb create mode 100644 db/migrate/20240229201155_create_activity_pub_remote_flags.rb diff --git a/app/controllers/api/v1/activity_pub/remote_flags_controller.rb b/app/controllers/api/v1/activity_pub/remote_flags_controller.rb new file mode 100644 index 00000000..23245b8b --- /dev/null +++ b/app/controllers/api/v1/activity_pub/remote_flags_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Api + module V1 + module ActivityPub + # Devuelve los reportes remotos hechos + # + # @todo Verificar la firma. Por ahora no es necesario porque no es + # posible obtener remotamente todos los reportes y se identifican por + # UUIDv4. + class RemoteFlagsController < BaseController + skip_forgery_protection + + def show + render json: (remote_flag&.content || {}), content_type: 'application/activity+json' + end + + private + + # @return [ActivityPub::RemoteFlag,nil] + def remote_flag + @remote_flag ||= ::ActivityPub::RemoteFlag.find(params[:id]) + end + end + end + end +end diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb new file mode 100644 index 00000000..332d31ac --- /dev/null +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Envía un reporte directamente a la instancia remota +# +# @todo El panel debería ser su propia instancia y firmar sus propios +# mensajes. +# @todo Como la Social Inbox no soporta enviar actividades +# a destinataries que no sean seguidores, enviamos el reporte +# directamente a la instancia. +# @see {https://github.com/hyphacoop/social.distributed.press/issues/14} +class ActivityPub + class RemoteFlagJob < ApplicationJob + self.priority = 30 + + def perform(remote_flag:) + client = remote_flag.site.social_inbox.client_for(remote_flag.actor.content['inbox']) + response = client.post(endpoint: '', body: remote_flag.content) + + raise 'No se pudo enviar el reporte' unless response.ok? + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { remote_flag: remote_flag.id, response: response.parsed_response }) + raise + end + end +end diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index a5171815..fe6052bf 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -13,6 +13,7 @@ class ActivityPub has_many :actor_moderation has_many :activity_pubs, as: :object has_many :activities + has_many :remote_flags # Obtiene el nombre de la Actor como mención, solo si obtuvimos el # contenido de antemano. diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index 9f968f36..eec8189b 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -58,13 +58,6 @@ class DeploySocialDistributedPress < Deploy private - # Obtiene el hostname de la API de Sutty - # - # @return [String] - def api_hostname - Rails.application.routes.default_url_options[:host].sub('panel', 'api') - end - # Crea los hooks en la Social Inbox para que nos avise de actividades # nuevas # @@ -80,7 +73,7 @@ class DeploySocialDistributedPress < Deploy webhook_class.new.call({ method: 'POST', url: Rails.application.routes.url_helpers.public_send( - event_url, site_id: site.name, host: api_hostname + event_url, site_id: site.name, host: site.social_inbox_hostname ), headers: { 'X-Social-Inbox': rol.token diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index e916bf3e..0716a670 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -15,6 +15,7 @@ class Site has_many :actor_moderations has_many :fediblock_states has_many :instances, through: :instance_moderations + has_many :remote_flags, class_name: 'ActivityPub::RemoteFlag' before_save :generate_private_key_pem!, unless: :private_key_pem? @@ -23,6 +24,13 @@ class Site @social_inbox ||= SocialInbox.new(site: self) end + # Obtiene el hostname de la API de Sutty + # + # @return [String] + def social_inbox_hostname + Rails.application.routes.default_url_options[:host].sub('panel', 'api') + end + private # Genera la llave privada y la almacena diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 6677a320..183ebfb0 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -37,13 +37,25 @@ class SocialInbox # @return [DistributedPress::V1::Social::Client] def client - @client ||= DistributedPress::V1::Social::Client.new( - url: site.config.dig('activity_pub', 'url'), - public_key_url: public_key_url, - private_key_pem: site.private_key_pem, - logger: Rails.logger, - cache_store: HTTParty::Cache::Store::Redis.new(redis_url: ENV['REDIS_SERVER']) - ) + @client ||= client_for site.config.dig('activity_pub', 'url') + end + + # Permite enviar mensajes directo a otro servidor + # + # @param url [String] + # @return [DistributedPress::V1::Social::Client] + def client_for(url) + raise "Falló generar un cliente" if url.blank? + + @client_for ||= {} + @client_for[url] ||= + DistributedPress::V1::Social::Client.new( + url: url, + public_key_url: public_key_url, + private_key_pem: site.private_key_pem, + logger: Rails.logger, + cache_store: HTTParty::Cache::Store::Redis.new(redis_url: ENV['REDIS_SERVER']) + ) end # @return [DistributedPress::V1::Social::Inbox] diff --git a/config/routes.rb b/config/routes.rb index ceffcd26..32936d42 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,10 @@ Rails.application.routes.draw do namespace :v1 do resources :csp_reports, only: %i[create] + namespace :activity_pub do + resources :remote_flags, only: %i[show] + end + resources :sites, only: %i[index], constraints: { site_id: /[a-z0-9\-.]+/, id: /[a-z0-9\-.]+/ } do get :'invitades/cookie', to: 'invitades#cookie' post :'posts/:layout', to: 'posts#create', as: :posts diff --git a/db/migrate/20240229201155_create_activity_pub_remote_flags.rb b/db/migrate/20240229201155_create_activity_pub_remote_flags.rb new file mode 100644 index 00000000..c60aca22 --- /dev/null +++ b/db/migrate/20240229201155_create_activity_pub_remote_flags.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Lleva el registro de reportes remotos +class CreateActivityPubRemoteFlags < ActiveRecord::Migration[6.1] + def change + create_table :activity_pub_remote_flags, id: :uuid do |t| + t.timestamps + t.belongs_to :site + t.uuid :actor_id, index: true + + t.text :message + + t.index %i[site_id actor_id], unique: true + end + end +end diff --git a/db/structure.sql b/db/structure.sql index e4d39ad0..6cdb49f1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -546,6 +546,20 @@ CREATE TABLE public.activity_pub_objects ( ); +-- +-- Name: activity_pub_remote_flags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pub_remote_flags ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + site_id bigint, + actor_id uuid, + message text +); + + -- -- Name: activity_pubs; Type: TABLE; Schema: public; Owner: - -- @@ -1762,6 +1776,14 @@ ALTER TABLE ONLY public.activity_pub_objects ADD CONSTRAINT activity_pub_objects_pkey PRIMARY KEY (id); +-- +-- Name: activity_pub_remote_flags activity_pub_remote_flags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pub_remote_flags + ADD CONSTRAINT activity_pub_remote_flags_pkey PRIMARY KEY (id); + + -- -- Name: activity_pubs activity_pubs_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2122,6 +2144,27 @@ CREATE INDEX index_activity_pub_actors_on_uri ON public.activity_pub_actors USIN CREATE INDEX index_activity_pub_instances_on_hostname ON public.activity_pub_instances USING btree (hostname); +-- +-- Name: index_activity_pub_remote_flags_on_actor_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_remote_flags_on_actor_id ON public.activity_pub_remote_flags USING btree (actor_id); + + +-- +-- Name: index_activity_pub_remote_flags_on_site_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_activity_pub_remote_flags_on_site_id ON public.activity_pub_remote_flags USING btree (site_id); + + +-- +-- Name: index_activity_pub_remote_flags_on_site_id_and_actor_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_activity_pub_remote_flags_on_site_id_and_actor_id ON public.activity_pub_remote_flags USING btree (site_id, actor_id); + + -- -- Name: index_activity_pubs_on_site_id_and_object_id_and_object_type; Type: INDEX; Schema: public; Owner: - -- @@ -2653,6 +2696,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240227134845'), ('20240227142019'), ('20240228171335'), -('20240228202830'); +('20240228202830'), +('20240229201155'); From 3fa19ee8b042e5e0a35481450ba07f3d734f3392 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 15:51:54 -0300 Subject: [PATCH 114/297] feat: reportar desde el panel --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- .../actor_moderations_controller.rb | 12 ++++++++++- app/models/activity_pub/remote_flag.rb | 20 +++++++++++++++++++ app/models/actor_moderation.rb | 9 +++++++++ app/views/components/_profiles_btn_box.haml | 11 +++++++++- app/views/moderation_queue/_account.haml | 5 +++-- config/locales/en.yml | 1 + config/locales/es.yml | 1 + ...224_add_remote_flag_to_actor_moderation.rb | 18 +++++++++++++++++ db/structure.sql | 6 ++++-- 11 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 app/models/activity_pub/remote_flag.rb create mode 100644 db/migrate/20240301181224_add_remote_flag_to_actor_moderation.rb diff --git a/Gemfile b/Gemfile index d720a2d6..f4125f65 100644 --- a/Gemfile +++ b/Gemfile @@ -39,7 +39,7 @@ gem 'devise-i18n' gem 'devise_invitable' gem 'redis-client' gem 'hiredis-client' -gem 'distributed-press-api-client', '~> 0.4.0rc2' +gem 'distributed-press-api-client', '~> 0.4.0rc3' gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n' gem 'exception_notification' gem 'fast_blank' diff --git a/Gemfile.lock b/Gemfile.lock index 72a30af1..366b58a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -166,12 +166,12 @@ GEM devise_invitable (2.0.9) actionmailer (>= 5.0) devise (>= 4.6) - distributed-press-api-client (0.4.0rc2) + distributed-press-api-client (0.4.0rc3) addressable (~> 2.3, >= 2.3.0) climate_control dry-schema httparty (~> 0.18) - httparty-cache (~> 0.0.4) + httparty-cache (~> 0.0.6) json (~> 2.1, >= 2.1.0) jwt (~> 2.6.0) dotenv (2.8.1) @@ -272,7 +272,7 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - httparty-cache (0.0.5) + httparty-cache (0.0.6) httparty (~> 0.18) i18n (1.14.1) concurrent-ruby (~> 1.0) @@ -626,7 +626,7 @@ DEPENDENCIES devise devise-i18n devise_invitable - distributed-press-api-client (~> 0.4.0rc2) + distributed-press-api-client (~> 0.4.0rc3) dotenv-rails down ed25519 diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index f4637d70..7450835b 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -2,10 +2,13 @@ # Gestiona la cola de moderación de actores class ActorModerationsController < ApplicationController - ActorModeration.aasm.events.map(&:name).each do |actor_event| + ActorModeration.events.each do |actor_event| define_method(actor_event) do authorize actor_moderation + # Crea una RemoteFlag si se envían los parámetros adecuados + actor_moderation.update(actor_moderation_params) if actor_event == :report + actor_moderation.public_send(:"#{actor_event}!") if actor_moderation.public_send(:"may_#{actor_event}?") redirect_back fallback_location: site_moderation_queue_path(**(session[:moderation_queue_filters] || {})) @@ -22,4 +25,11 @@ class ActorModerationsController < ApplicationController def actor_moderation @actor_moderation ||= site.actor_moderations.find(params[:actor_moderation_id] || params[:id]) end + + def actor_moderation_params + params.require(:actor_moderation).permit(remote_flag_attributes: %i[message]).tap do |p| + p[:remote_flag_attributes][:site_id] = actor_moderation.site_id + p[:remote_flag_attributes][:actor_id] = actor_moderation.actor_id + end + end end diff --git a/app/models/activity_pub/remote_flag.rb b/app/models/activity_pub/remote_flag.rb new file mode 100644 index 00000000..b790c4b1 --- /dev/null +++ b/app/models/activity_pub/remote_flag.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class ActivityPub + class RemoteFlag < ApplicationRecord + belongs_to :actor + belongs_to :site + + # Genera la actividad a enviar + def content + { + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => Rails.application.routes.url_helpers.v1_activity_pub_remote_flag_url(self, host: site.social_inbox_hostname), + 'type' => 'Flag', + 'actor' => ENV.fetch('PANEL_ACTOR_ID') { "https://#{ENV['SUTTY']}/about.jsonld" }, + 'content' => message.to_s, + 'object' => [ actor.uri ] + } + end + end +end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 1d9bae5d..c6f8bfc0 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -5,8 +5,11 @@ class ActorModeration < ApplicationRecord include AASM belongs_to :site + belongs_to :remote_flag, class_name: 'ActivityPub::RemoteFlag' belongs_to :actor, class_name: 'ActivityPub::Actor' + 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) @@ -53,8 +56,14 @@ class ActorModeration < ApplicationRecord end end + # Al reportar, necesitamos asociar una RemoteFlag para poder + # enviarla. event :report do transitions from: %i[blocked], to: :reported + + before do + ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) + end end end diff --git a/app/views/components/_profiles_btn_box.haml b/app/views/components/_profiles_btn_box.haml index bd994f84..3ec95e59 100644 --- a/app/views/components/_profiles_btn_box.haml +++ b/app/views/components/_profiles_btn_box.haml @@ -1,5 +1,13 @@ -# Componente Botonera de Moderación de Cuentas (Remote_profile) +- form_params = {} +- form_params[:report] = { actor_moderation: { remote_flag_attributes: { message: '' } } } +- I18n.available_locales.each do |locale| + - form_params[:report][:actor_moderation][:remote_flag_attributes][:message] += t(locale) + - form_params[:report][:actor_moderation][:remote_flag_attributes][:message] += ': ' + - form_params[:report][:actor_moderation][:remote_flag_attributes][:message] += t('.report_message', locale: locale, panel_actor_mention: ENV.fetch('PANEL_ACTOR_MENTION') { '@sutty@sutty.nl' }) + - form_params[:report][:actor_moderation][:remote_flag_attributes][:message] += '\n\n' + .d-flex.flex-row - btn_class = 'btn-secondary' - ActorModeration.events.each do |actor_event| @@ -7,4 +15,5 @@ text: t(".text_#{actor_event}"), path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), class: btn_class, - disabled: !actor_moderation.public_send(:"may_#{actor_event}?") + disabled: !actor_moderation.public_send(:"may_#{actor_event}?"), + params: form_params[actor_event] diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index cdcc0ad4..5e574c71 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -8,5 +8,6 @@ = sanitize profile['summary'].html_safe -# Botones de Moderación - .d-flex.pb-4 - = render 'components/profiles_btn_box', actor_moderation: actor_moderation + - cache actor_moderation do + .d-flex.pb-4 + = render 'components/profiles_btn_box', actor_moderation: actor_moderation diff --git a/config/locales/en.yml b/config/locales/en.yml index 62e9a1a7..4aea89f7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -106,6 +106,7 @@ en: text_allow: Always approve text_block: Block text_report: Report + report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." actor_moderations: show: user: Username diff --git a/config/locales/es.yml b/config/locales/es.yml index 3dc68d98..7b033859 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -106,6 +106,7 @@ es: text_allow: Aprobar siempre text_block: Bloquear text_report: Reportar + report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." actor_moderations: show: user: Nombre de usuarie diff --git a/db/migrate/20240301181224_add_remote_flag_to_actor_moderation.rb b/db/migrate/20240301181224_add_remote_flag_to_actor_moderation.rb new file mode 100644 index 00000000..63e4ce1b --- /dev/null +++ b/db/migrate/20240301181224_add_remote_flag_to_actor_moderation.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Las acciones de moderación pueden tener un reporte remoto asociado +class AddRemoteFlagToActorModeration < ActiveRecord::Migration[6.1] + def up + add_column :actor_moderations, :remote_flag_id, :uuid, null: true + + ActivityPub::RemoteFlag.all.find_each do |remote_flag| + actor_moderation = ActorModeration.find_by(actor_id: remote_flag.actor_id) + + actor_moderation&.update_column(:remote_flag_id, remote_flag.id) + end + end + + def down + remove_column :actor_moderations, :remote_flag_id + end +end diff --git a/db/structure.sql b/db/structure.sql index 6cdb49f1..38a56b46 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -586,7 +586,8 @@ CREATE TABLE public.actor_moderations ( updated_at timestamp(6) without time zone NOT NULL, site_id bigint, actor_id uuid, - aasm_state character varying NOT NULL + aasm_state character varying NOT NULL, + remote_flag_id uuid ); @@ -2697,6 +2698,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240227142019'), ('20240228171335'), ('20240228202830'), -('20240229201155'); +('20240229201155'), +('20240301181224'); From f8b45866336c097267261bc34c80bfb5afd1e4d5 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 16:11:33 -0300 Subject: [PATCH 115/297] feat: poder cambiar el estado a varies actores --- .../actor_moderations_controller.rb | 18 +++++++++++++ .../components/_profiles_checked_submenu.haml | 6 ++--- app/views/moderation_queue/_account.haml | 2 +- app/views/moderation_queue/_accounts.haml | 26 ++++++++++--------- config/locales/en.yml | 4 +-- config/locales/es.yml | 8 +++--- config/routes.rb | 2 ++ 7 files changed, 43 insertions(+), 23 deletions(-) diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 7450835b..907f21c6 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -20,6 +20,24 @@ class ActorModerationsController < ApplicationController @remote_profile = actor_moderation.actor.content end + def action_on_several + actor_moderations = site.actor_moderations.where(id: params[:actor_moderation]) + + authorize actor_moderations + + action = params[:actor_moderation_action].to_sym + method = :"#{action}!" + may = :"may_#{action}?" + + return unless ActorModeration.events.include? action + + ActorModeration.transaction do + actor_moderations.find_each do |actor_moderation| + actor_moderation.public_send(method) if actor_moderation.public_send(may) + end + end + end + private def actor_moderation diff --git a/app/views/components/_profiles_checked_submenu.haml b/app/views/components/_profiles_checked_submenu.haml index 8d8f8940..c0b99aa5 100644 --- a/app/views/components/_profiles_checked_submenu.haml +++ b/app/views/components/_profiles_checked_submenu.haml @@ -1,4 +1,2 @@ -= render 'components/dropdown_item', text: t('.submenu_pause'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_accept'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_reject'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_block'), path: '/' +- ActorModeration.events.each do |actor_event| + = render 'components/dropdown_button', text: t(".submenu_#{actor_event}"), name: 'actor_moderation_action', value: actor_event diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index 5e574c71..e891b4ad 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -1,6 +1,6 @@ .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: profile['id'], name: 'actor[]', value: profile['id'], data: { target: 'select-all.input' } + = render 'components/checkbox', id: actor_moderation.id, name: 'actor_moderation[]', value: actor_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 = link_to sanitize(profile['name']), site_actor_moderation_path(id: actor_moderation) diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index c7dba0a1..35b0b86e 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -1,12 +1,14 @@ -.row.no-gutters.pt-2{ data: { controller: 'select-all' } } - .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'actors' - .col-11 - -# Filtros - = render 'components/profiles_filters', actor_moderations: actor_moderations - .col-12 - - if actor_moderations.count.zero? - %h4= t('moderation_queue.nothing') - - actor_moderations.find_each do |actor_moderation| - %hr - = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content +%form{ action: site_actor_moderations_action_on_several_path, method: :post } + .row.no-gutters.pt-2{ data: { controller: 'select-all' } } + .col-1.d-flex.align-items-center + = render 'components/select_all', id: 'actors' + .col-11 + -# Filtros + = render 'components/profiles_filters', actor_moderations: actor_moderations + .col-12 + - if actor_moderations.count.zero? + %h4= t('moderation_queue.nothing') + - actor_moderations.find_each do |actor_moderation| + - cache [actor_moderation, actor_moderation.actor] do + %hr + = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content diff --git a/config/locales/en.yml b/config/locales/en.yml index 4aea89f7..6d5baf5a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -81,9 +81,9 @@ en: text_checked: With selected profiles_checked_submenu: submenu_pause: Pause - submenu_accept: Accept - submenu_reject: Reject + submenu_allow: Allow submenu_block: Block + submenu_report: Report profiles_show_submenu: submenu_paused: "Paused (%{count})" submenu_allowed: "Allowed (%{count})" diff --git a/config/locales/es.yml b/config/locales/es.yml index 7b033859..13534822 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -80,10 +80,10 @@ es: text_show: Ver text_checked: Con los marcados profiles_checked_submenu: - submenu_pause: Pausado - submenu_accept: Aceptado - submenu_reject: Rechazado - submenu_block: Bloqueado + submenu_pause: Pausar + submenu_allow: Aceptar + submenu_block: Bloquear + submenu_report: Reportar profiles_show_submenu: submenu_paused: "Pausadas (%{count})" submenu_allowed: "Permitidas (%{count})" diff --git a/config/routes.rb b/config/routes.rb index 32936d42..9d825a3c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -80,6 +80,8 @@ Rails.application.routes.draw do end end + patch :actor_moderations_action_on_several, to: 'actor_moderations#action_on_several' + # Gestionar artículos según idioma nested do scope '/(:locale)', constraint: /[a-z]{2}(-[A-Z]{2})?/ do From 567f0a10fb9f8427157992352be9ebe8dfd636f5 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 16:12:34 -0300 Subject: [PATCH 116/297] =?UTF-8?q?fix:=20no=20fallar=20si=20no=20se=20pue?= =?UTF-8?q?de=20hacer=20la=20acci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/instance_moderations_controller.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/instance_moderations_controller.rb b/app/controllers/instance_moderations_controller.rb index d25e1450..dc9e1dfa 100644 --- a/app/controllers/instance_moderations_controller.rb +++ b/app/controllers/instance_moderations_controller.rb @@ -24,14 +24,14 @@ class InstanceModerationsController < ApplicationController action = params[:instance_moderation_action].to_sym method = :"#{action}!" + may = :"may_#{action}?" + events = instance_moderation.aasm.events.map(&:name) + + return unless events.include? action InstanceModeration.transaction do instance_moderations.find_each do |instance_moderation| - events = instance_moderation.aasm.events.map(&:name) - - next unless events.include? action - - instance_moderation.public_send(method) + instance_moderation.public_send(method) if instance_moderation.public_send(may) end end end From 163f3fa7375e624bb7e30a6076f019c2d413eddd Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 16:47:00 -0300 Subject: [PATCH 117/297] fix: namespace --- .../v1/webhooks/social_inbox_controller.rb | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 34ef1d6c..9c1a62ba 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -25,7 +25,8 @@ module Api # Devuelve un error si el token no es válido usuarie.present? - ActivityPub.transaction do + ::ActivityPub.transaction do + # Crea todos los registros necesarios y actualiza el estado actor.present? instance.present? @@ -47,7 +48,7 @@ module Api # # @todo DRY def onapproved - ActivityPub.transaction do + ::ActivityPub.transaction do actor.present? instance.present? object.present? @@ -63,7 +64,7 @@ module Api # # @todo DRY def onrejected - ActivityPub.transaction do + ::ActivityPub.transaction do actor.present? instance.present? object.present? @@ -110,7 +111,7 @@ module Api # # @return [ActivityPub::Object] def object - @object ||= ActivityPub::Object.find_or_initialize_by(uri: object_uri).tap do |o| + @object ||= ::ActivityPub::Object.find_or_initialize_by(uri: object_uri).tap do |o| # XXX: Si el objeto es una actividad, esto siempre va a ser # Generic o.type ||= 'ActivityPub::Object::Generic' @@ -120,7 +121,7 @@ module Api # XXX: el objeto necesita ser guardado antes de poder # procesarlo - ActivityPub::FetchJob.perform_later(site: site, object: o) unless object_embedded? + ::ActivityPub::FetchJob.perform_later(site: site, object: o) unless object_embedded? end end @@ -137,7 +138,7 @@ module Api # @return [ActivityPub::Activity] def activity @activity ||= - ActivityPub::Activity + ::ActivityPub::Activity .type_from(original_activity) .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| a.content = original_activity.dup @@ -151,20 +152,20 @@ module Api # # @return [Actor] def actor - @actor ||= ActivityPub::Actor.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| + @actor ||= ::ActivityPub::Actor.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| unless a.instance - a.instance = ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) + a.instance = ::ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) site.instance_moderations.find_or_create_by(instance: a.instance) - ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) + ::ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) end a.save! site.actor_moderations.find_or_create_by(actor: a) - ActivityPub::ActorFetchJob.perform_later(site: site, actor: a) + ::ActivityPub::ActorFetchJob.perform_later(site: site, actor: a) end end From 4bef3096210f73b2be57016f21d5e8d2c49a82dc Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 16:47:11 -0300 Subject: [PATCH 118/297] fix: soportar flags --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 9c1a62ba..c6aad11f 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -91,6 +91,7 @@ module Api def object_uri @object_uri ||= case original_activity[:object] + when Array then original_activity[:object].first when String then original_activity[:object] when Hash then original_activity.dig(:object, :id) end From 2e1087107af045aa0563c1f0dd5078b27d6bc225 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 16:47:23 -0300 Subject: [PATCH 119/297] fix: indexar por el tipo actual --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index c6aad11f..23bd749d 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -131,7 +131,7 @@ module Api # # @return [ActivityPub] def activity_pub - @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, instance: instance, object: object) + @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, instance: instance, object_id: object.id, object_type: object.type) end # Crea la actividad y la vincula con el estado From 09e56b88ed5bfa2329b153ef33cf78a534ed5a40 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 16:51:40 -0300 Subject: [PATCH 120/297] =?UTF-8?q?fix:=20detectar=20si=20viene=20vac?= =?UTF-8?q?=C3=ADo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 23bd749d..3de32104 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -96,7 +96,7 @@ module Api when Hash then original_activity.dig(:object, :id) end ensure - raise ActiveRecord::RecordNotFound, 'object id missing' unless @object_uri + raise ActiveRecord::RecordNotFound, 'object id missing' if @object_uri.blank? end # Atajo a la instancia From 22f730b805dbcfc4daa16bde2147872d07250ceb Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 17:14:24 -0300 Subject: [PATCH 121/297] fix: guardar el tipo correcto de objeto si lo soportamos --- .../api/v1/webhooks/social_inbox_controller.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 3de32104..dec2ee97 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -116,7 +116,15 @@ module Api # XXX: Si el objeto es una actividad, esto siempre va a ser # Generic o.type ||= 'ActivityPub::Object::Generic' - o.content = original_object if object_embedded? + + if object_embedded? + o.content = original_object + begin + type = original_object[:type].presence + o.type = "ActivityPub::Object::#{type}".constantize if type + rescue NameError + end + end o.save! From 85c45d48236f0a7040e2e23545ee08bfc6f2fb9e Mon Sep 17 00:00:00 2001 From: f Date: Fri, 1 Mar 2024 17:29:08 -0300 Subject: [PATCH 122/297] =?UTF-8?q?fix:=20no=20duplicar=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 7 +------ app/models/activity_pub.rb | 3 ++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index dec2ee97..fda51f49 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -89,12 +89,7 @@ module Api # # @return [String] def object_uri - @object_uri ||= - case original_activity[:object] - when Array then original_activity[:object].first - when String then original_activity[:object] - when Hash then original_activity.dig(:object, :id) - end + @object_uri ||= ::ActivityPub.uri_from_object(original_activity[:object]) ensure raise ActiveRecord::RecordNotFound, 'object id missing' if @object_uri.blank? end diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 1afeee96..86c3f240 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -24,8 +24,9 @@ class ActivityPub < ApplicationRecord # @return [String, nil] def self.uri_from_object(object) case object + when Array then uri_from_object(object.first) when String then object - when Hash then object['id'] + when Hash then (object['id'] || object[:id]) end end From b255acf2fd98d2541a8bf6277390a7dc06609843 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 11:59:29 -0300 Subject: [PATCH 123/297] feat: comentarios por actore --- .../v1/webhooks/social_inbox_controller.rb | 2 +- .../moderation_queue_controller.rb | 1 + app/models/activity_pub.rb | 1 + app/processors/activity_pub_processor.rb | 15 ++++++++ app/views/moderation_queue/_comment.haml | 37 +++++++++---------- app/views/moderation_queue/_comments.haml | 10 ++--- app/views/moderation_queue/index.haml | 4 +- ...301202955_add_actor_id_to_activity_pubs.rb | 16 ++++++++ db/structure.sql | 6 ++- 9 files changed, 62 insertions(+), 30 deletions(-) create mode 100644 app/processors/activity_pub_processor.rb create mode 100644 db/migrate/20240301202955_add_actor_id_to_activity_pubs.rb diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index fda51f49..464a0ffe 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -134,7 +134,7 @@ module Api # # @return [ActivityPub] def activity_pub - @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, instance: instance, object_id: object.id, object_type: object.type) + @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, actor: actor, instance: instance, object_id: object.id, object_type: object.type) end # Crea la actividad y la vincula con el estado diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index 95639b35..b803ccc9 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -12,6 +12,7 @@ class ModerationQueueController < ApplicationController @activity_pubs = site.activity_pubs @instance_moderations = rubanok_process(site.instance_moderations, with: InstanceModerationProcessor) @actor_moderations = rubanok_process(site.actor_moderations, with: ActorModerationProcessor) + @moderation_queue = rubanok_process(site.activity_pubs, with: ActivityPubProcessor) end # todon.nl está usando /api/v2/instance diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 86c3f240..b52c2a76 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -13,6 +13,7 @@ class ActivityPub < ApplicationRecord belongs_to :instance belongs_to :site belongs_to :object, polymorphic: true + belongs_to :actor has_many :activities validates :site_id, presence: true diff --git a/app/processors/activity_pub_processor.rb b/app/processors/activity_pub_processor.rb new file mode 100644 index 00000000..52cdb6d3 --- /dev/null +++ b/app/processors/activity_pub_processor.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Gestiona los filtros de ActivityPub +class ActivityPubProcessor < Rubanok::Processor + # En orden descendiente para encontrar la última actividad + # + # Por ahora solo queremos moderar comentarios. + prepare do + raw.where(object_type: %w[ActivityPub::Object::Note ActivityPub::Object::Article]).order(updated_at: :desc) + end + + map :activity_pub_state, activate_always: true do |activity_pub_state: 'paused'| + raw.where(aasm_state: activity_pub_state) + end +end diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index afd2a335..495aaf55 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -4,24 +4,21 @@ .col-1 = render 'components/checkbox', id: comment['id'] .col-11 - .row.no-gutters - .col.col-lg-10.d-inline-flex.justify-content-between - %h4 - %a{ href: comment['attributedTo'] }= profile['preferredUsername'] + .d-flex.flex-row.align-items-center.justify-content-between + %h4.mb-0 + %a{ href: comment['attributedTo'] }= sanitize profile['preferredUsername'] + %small = render 'layouts/time', time: comment['published'] - - if comment['inReplyTo'] - .row.no-gutters - .col.p-0 - %p - %span= t('.reply_to') - %span - %a{ href: comment['inReplyTo'] }= comment['inReplyTo'] - .row.no-gutters - .col.p-0 - - if comment['summary'] - - summary = comment['summary'] - = render 'layouts/details', summary: summary do - %p= comment['content'].html_safe - - else - %p= comment['content'].html_safe - + - if comment['inReplyTo'].present? + %dl + %dt.d-inline + %small= t('.reply_to') + %dd.d-inline + %small + %a{ href: comment['inReplyTo'] }= sanitize comment['inReplyTo'] + %div + - if comment['summary'].present? + = render 'layouts/details', summary: comment['summary'], summary_class: 'h5' do + = sanitize comment['content'] + - else + = sanitize comment['content'] diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index eadfd78b..4fe84652 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -1,14 +1,14 @@ .row.no-gutters.pt-2 .col-1.d-flex.align-items-center - = render 'components/checkbox', id: moderation_queue.first['id'] + = render 'components/select_all', id: 'select-all-comments' .col-md-9 -# Filtros = render 'components/comments_filters' -- moderation_queue.each do |comment| +- moderation_queue.each do |activity_pub| %hr - = render 'comment', comment: comment, profile: comment['attributedTo'] - + = render 'comment', comment: activity_pub.object.content, profile: activity_pub.actor.content + -# Botones moderación .d-flex.justify-content-center - = render 'components/comments_btn_box', comment: comment \ No newline at end of file + = render 'components/comments_btn_box', comment: activity_pub.object.content diff --git a/app/views/moderation_queue/index.haml b/app/views/moderation_queue/index.haml index 799fc641..80f0bd7c 100644 --- a/app/views/moderation_queue/index.haml +++ b/app/views/moderation_queue/index.haml @@ -7,7 +7,7 @@ = render 'moderation_queue/instances', site: @site, instance_moderations: @instance_moderations, fediblock_states: @site.fediblock_states %hr = render 'layouts/details', id: 'accounts', summary: t('.accounts') do - = render 'moderation_queue/accounts', site: @site, post: @post, actor_moderations: @actor_moderations + = render 'moderation_queue/accounts', site: @site, actor_moderations: @actor_moderations %hr = render 'layouts/details', id: 'comments', summary: t('.comments') do - = render 'moderation_queue/comments', site: @site, post: @post, moderation_queue: @moderation_queue + = render 'moderation_queue/comments', site: @site, moderation_queue: @moderation_queue diff --git a/db/migrate/20240301202955_add_actor_id_to_activity_pubs.rb b/db/migrate/20240301202955_add_actor_id_to_activity_pubs.rb new file mode 100644 index 00000000..37db4bfc --- /dev/null +++ b/db/migrate/20240301202955_add_actor_id_to_activity_pubs.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Relaciona estados de actividades con les actores que las hicieron +class AddActorIdToActivityPubs < ActiveRecord::Migration[6.1] + def up + add_column :activity_pubs, :actor_id, :uuid + + ActivityPub.all.find_each do |activity_pub| + activity_pub.update_column(:actor_id, activity_pub.activities.last.actor_id) + end + end + + def down + remove_column :activity_pubs, :actor_id + end +end diff --git a/db/structure.sql b/db/structure.sql index 38a56b46..ca3a868b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -572,7 +572,8 @@ CREATE TABLE public.activity_pubs ( object_id uuid NOT NULL, object_type character varying NOT NULL, aasm_state character varying NOT NULL, - instance_id uuid + instance_id uuid, + actor_id uuid ); @@ -2699,6 +2700,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240228171335'), ('20240228202830'), ('20240229201155'), -('20240301181224'); +('20240301181224'), +('20240301202955'); From d1a87177a58664da0c420e17764e297fdb74ffa9 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 12:01:32 -0300 Subject: [PATCH 124/297] fix: eliminar indice unico en realidad lo queremos mantener... --- ...240301194154_remove_unique_index_from_activity_pubs.rb | 8 ++++++++ db/structure.sql | 8 +------- 2 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20240301194154_remove_unique_index_from_activity_pubs.rb diff --git a/db/migrate/20240301194154_remove_unique_index_from_activity_pubs.rb b/db/migrate/20240301194154_remove_unique_index_from_activity_pubs.rb new file mode 100644 index 00000000..0fa80e60 --- /dev/null +++ b/db/migrate/20240301194154_remove_unique_index_from_activity_pubs.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# A veces tenemos varias acciones sobre el mismo objeto +class RemoveUniqueIndexFromActivityPubs < ActiveRecord::Migration[6.1] + def change + remove_index :activity_pubs, %i[site_id object_id object_type], unique: true + end +end diff --git a/db/structure.sql b/db/structure.sql index ca3a868b..ff6cf895 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2167,13 +2167,6 @@ CREATE INDEX index_activity_pub_remote_flags_on_site_id ON public.activity_pub_r CREATE UNIQUE INDEX index_activity_pub_remote_flags_on_site_id_and_actor_id ON public.activity_pub_remote_flags USING btree (site_id, actor_id); --- --- Name: index_activity_pubs_on_site_id_and_object_id_and_object_type; Type: INDEX; Schema: public; Owner: - --- - -CREATE UNIQUE INDEX index_activity_pubs_on_site_id_and_object_id_and_object_type ON public.activity_pubs USING btree (site_id, object_id, object_type); - - -- -- Name: index_actor_moderations_on_actor_id; Type: INDEX; Schema: public; Owner: - -- @@ -2701,6 +2694,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240228202830'), ('20240229201155'), ('20240301181224'), +('20240301194154'), ('20240301202955'); From b8e7e53ebd651332cae10f0c23dfb8239a3d1089 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 13:09:36 -0300 Subject: [PATCH 125/297] feat: limpieza de texto --- app/helpers/application_helper.rb | 22 ++++++++++++++++++---- app/models/metadata_template.rb | 14 ++------------ app/views/posts/show.haml | 1 - config/application.rb | 5 +++++ 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9f7be213..146846f0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -33,10 +33,24 @@ module ApplicationHelper end end - # Devuelve todas las etiquetas HTML que queremos mantener - def all_html_tags - %w[h1 h2 h3 h4 h5 h6 p a ul ol li table tr td th tbody thead - tfoot em strong sup blockquote cite pre section article] + # Sanitizador que elimina todo + # + # @param html [String] + # @return [String] + def text_plain(html) + sanitize(html, tags: [], attributes: []) + end + + # Sanitizador con etiquetas y atributos por defecto + # + # @param html [String] + # @param options [Hash] + # @return [String] + def sanitize(html, options = {}) + options[:tags] ||= Sutty::ALLOWED_TAGS + options[:attributes] ||= Sutty::ALLOWED_ATTRIBUTES + + super(html, options) end # Genera HTML y limpia etiquetas innecesarias diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 823443d2..a9765918 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -190,8 +190,8 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, sanitizer .sanitize(string.tr("\r", '').unicode_normalize, - tags: allowed_tags, - attributes: allowed_attributes) + tags: Sutty::ALLOWED_TAGS, + attributes: Sutty::ALLOWED_ATTRIBUTES) .strip .html_safe end @@ -200,16 +200,6 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, @sanitizer ||= Rails::Html::Sanitizer.safe_list_sanitizer.new end - def allowed_attributes - @allowed_attributes ||= %w[style href src alt controls data-align data-multimedia data-multimedia-inner id - name rel target referrerpolicy class colspan rowspan role data-turbo start type reversed].freeze - end - - def allowed_tags - @allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure blockquote - figcaption a sub sup small table thead tbody tfoot tr th td br code].freeze - end - # Decifra el valor # # XXX: Otros tipos de valores necesitan implementar su propio método diff --git a/app/views/posts/show.haml b/app/views/posts/show.haml index ec191d87..10fe64e3 100644 --- a/app/views/posts/show.haml +++ b/app/views/posts/show.haml @@ -20,7 +20,6 @@ post: @post, attribute: attr, metadata: metadata, site: @site, - tags: all_html_tags, locale: @locale, dir: dir) diff --git a/config/application.rb b/config/application.rb index 27a21cc6..ed7e5a78 100644 --- a/config/application.rb +++ b/config/application.rb @@ -37,6 +37,11 @@ if %w[development test].include? ENV['RAILS_ENV'] end module Sutty + ALLOWED_ATTRIBUTES = %w[style href src alt controls data-align data-multimedia data-multimedia-inner id name rel + target referrerpolicy class colspan rowspan role data-turbo start type reversed].freeze + ALLOWED_TAGS = %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure blockquote + figcaption a sub sup small table thead tbody tfoot tr th td br code].freeze + # Sutty! class Application < Rails::Application # Initialize configuration defaults for originally generated Rails From 01c042b51265222638d32c8a06d89d66c4f8cf38 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 13:11:17 -0300 Subject: [PATCH 126/297] fix: limpiar html remoto --- app/views/components/_actor.haml | 8 ++++---- app/views/moderation_queue/_account.haml | 4 ++-- app/views/moderation_queue/_comment.haml | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/views/components/_actor.haml b/app/views/components/_actor.haml index 68aa3f90..3983d617 100644 --- a/app/views/components/_actor.haml +++ b/app/views/components/_actor.haml @@ -3,18 +3,18 @@ .py-2 %dl %dt= t('.profile_name') - %dd= sanitize remote_profile['name'] + %dd= text_plain remote_profile['name'] %dt= t('.preferred_name') - %dd= sanitize remote_profile['preferredUsername'] + %dd= text_plain remote_profile['preferredUsername'] %dt= t('.profile_id') %dd - = link_to sanitize(remote_profile['id']) + = link_to text_plain(remote_profile['id']) - if remote_profile['published'].present? %dt= t('.profile_published') %dd - = render 'layouts/time', time: sanitize(remote_profile['published']) + = render 'layouts/time', time: text_plain(remote_profile['published']) %dt= t('.profile_summary') %dd= sanitize remote_profile['summary'] diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index e891b4ad..fee90316 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -3,9 +3,9 @@ = render 'components/checkbox', id: actor_moderation.id, name: 'actor_moderation[]', value: actor_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 - = link_to sanitize(profile['name']), site_actor_moderation_path(id: actor_moderation) + = link_to text_plain(profile['name']), site_actor_moderation_path(id: actor_moderation) .mb-3 - = sanitize profile['summary'].html_safe + = sanitize profile['summary'] -# Botones de Moderación - cache actor_moderation do diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index 495aaf55..e0e625fd 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -1,24 +1,26 @@ -# Componente Comentario +- in_reply_to = text_plain comment['inReplyTo'] + .row.no-gutters .col-1 = render 'components/checkbox', id: comment['id'] .col-11 .d-flex.flex-row.align-items-center.justify-content-between %h4.mb-0 - %a{ href: comment['attributedTo'] }= sanitize profile['preferredUsername'] + %a{ href: text_plain(comment['attributedTo']) }= text_plain profile['preferredUsername'] %small = render 'layouts/time', time: comment['published'] - - if comment['inReplyTo'].present? + - if in_reply_to.present? %dl %dt.d-inline %small= t('.reply_to') %dd.d-inline %small - %a{ href: comment['inReplyTo'] }= sanitize comment['inReplyTo'] + %a{ href: in_reply_to) }= in_reply_to %div - if comment['summary'].present? - = render 'layouts/details', summary: comment['summary'], summary_class: 'h5' do + = render 'layouts/details', summary: text_plain(comment['summary']), summary_class: 'h5' do = sanitize comment['content'] - else = sanitize comment['content'] From 6110172324f6f267f5d10a1b33e2458f061ea67a Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 13:12:22 -0300 Subject: [PATCH 127/297] feat: poder cambiar el nivel de summary --- app/assets/stylesheets/application.scss | 34 ++++++++++++++----------- app/views/layouts/_details.haml | 9 +++++-- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index a24af4c8..cdf97b5b 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -561,24 +561,28 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1); // details styles .details { - summary { + & > summary { list-style: none; - cursor: default; - position: relative; + cursor: pointer; + + .hide-when-open { + display: inline; + } + + .show-when-open { + display: none; + } } - summary::after { - content: '▶'; - font-size: 1.8rem; - position: absolute; - left: 97%; - bottom: 3%; - transform: rotate(180deg); - } + &[open] { & > summary { - &::after { - transform: rotate(90deg); - } + .hide-when-open { + display: none; + } + + .show-when-open { + display: inline; + } } } -} +} diff --git a/app/views/layouts/_details.haml b/app/views/layouts/_details.haml index 3260bfcb..a21f46c1 100644 --- a/app/views/layouts/_details.haml +++ b/app/views/layouts/_details.haml @@ -4,8 +4,13 @@ @param :id [String] El ID opcional sirve para mantener el historial de cuál estaba abierto y recuperarlo al cargar la página @param :summary [String] El resumen + @param :summary_class [String] Clases para el summary + +- local_assigns[:summary_class] ||= 'h3' %details.details.py-2{ id: local_assigns[:id], data: { controller: 'details', action: 'toggle->details#store' } } - %summary - %h3.py-2.pr-2= summary + %summary.d-flex.flex-row.align-items-center.justify-content-between{ class: local_assigns[:summary_class] } + %span= summary + %span.hide-when-open ▶ + %span.show-when-open ▼ = yield From d8487ea7e9b29ef389d8a1c3ebd790bcf52f6ebe Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 13:17:21 -0300 Subject: [PATCH 128/297] fixup! fix: limpiar html remoto --- app/views/moderation_queue/_comment.haml | 11 ++++++----- app/views/moderation_queue/_instance.haml | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index e0e625fd..29888ef7 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -1,6 +1,7 @@ -# Componente Comentario - in_reply_to = text_plain comment['inReplyTo'] +- summary = text_plain(comment['summary']) .row.no-gutters .col-1 @@ -10,17 +11,17 @@ %h4.mb-0 %a{ href: text_plain(comment['attributedTo']) }= text_plain profile['preferredUsername'] %small - = render 'layouts/time', time: comment['published'] + = render 'layouts/time', time: text_plain(comment['published']) - if in_reply_to.present? %dl %dt.d-inline %small= t('.reply_to') %dd.d-inline %small - %a{ href: in_reply_to) }= in_reply_to - %div - - if comment['summary'].present? - = render 'layouts/details', summary: text_plain(comment['summary']), summary_class: 'h5' do + %a{ href: in_reply_to }= in_reply_to + .content + - if summary.present? + = render 'layouts/details', summary: summary, summary_class: 'h5' do = sanitize comment['content'] - else = sanitize comment['content'] diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index 73655e1b..97d23f10 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -7,11 +7,12 @@ .col-11 %h4 %a{ href: instance.uri }= sanitize(instance.content['title']) || instance.hostname - %p= sanitize instance.content['description'] + .content + = sanitize instance.content['description'] - if usuaries.present? %dl %dt.d-inline= t('.users') - %dd.d-inline= sanitize usuaries.to_s + %dd.d-inline= text_plain usuaries.to_s -# Botones moderación .d-flex.pb-4 From 5fabe9cd832753e90b2f27f9e5cab77c7c2c6a0d Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 13:49:07 -0300 Subject: [PATCH 129/297] feat: acciones sobre comentarios --- app/controllers/activity_pubs_controller.rb | 22 +++++++++++++++++ app/models/activity_pub.rb | 3 +++ app/models/actor_moderation.rb | 10 +++----- app/models/concerns/aasm_events_concern.rb | 14 +++++++++++ app/views/components/_btn_base.haml | 1 + app/views/components/_comments_btn_box.haml | 12 +++++----- app/views/moderation_queue/_comment.haml | 12 +++++++--- app/views/moderation_queue/_comments.haml | 26 ++++++++++----------- config/locales/en.yml | 2 +- config/locales/es.yml | 5 ++-- config/routes.rb | 8 +++++++ 11 files changed, 82 insertions(+), 33 deletions(-) create mode 100644 app/controllers/activity_pubs_controller.rb create mode 100644 app/models/concerns/aasm_events_concern.rb diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb new file mode 100644 index 00000000..dfe388a4 --- /dev/null +++ b/app/controllers/activity_pubs_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# Gestiona acciones de moderación +class ActivityPubsController < ApplicationController + ActivityPub.events.each do |event| + define_method(event) do + activity_pub.public_send(:"#{event}!") if activity_pub.public_send(:"may_#{event}?") + + redirect_back fallback_location: site_moderation_queue_path(**(session[:moderation_queue_filters] || {})) + end + end + + def action_on_several + + end + + private + + def activity_pub + @activity_pub ||= site.activity_pubs.find(params[:activity_pub_id]) + end +end diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index b52c2a76..95fa3bf3 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -9,6 +9,9 @@ # @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions} class ActivityPub < ApplicationRecord include AASM + include AasmEventsConcern + + IGNORED_EVENTS = %i[remove] belongs_to :instance belongs_to :site diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index c6f8bfc0..1fc1a42a 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -3,6 +3,9 @@ # Mantiene la relación entre Site y Actor class ActorModeration < ApplicationRecord include AASM + include AasmEventsConcern + + IGNORED_EVENTS = [] belongs_to :site belongs_to :remote_flag, class_name: 'ActivityPub::RemoteFlag' @@ -19,13 +22,6 @@ class ActorModeration < ApplicationRecord self.update_all(aasm_state: 'paused', updated_at: Time.now) end - # Todos los eventos de la máquina de estados - # - # @return [Array] - def self.events - aasm.events.map(&:name) - 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 new file mode 100644 index 00000000..79250ea9 --- /dev/null +++ b/app/models/concerns/aasm_events_concern.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module AasmEventsConcern + extend ActiveSupport::Concern + + included do + # Todos los eventos de la máquina de estados + # + # @return [Array] + def self.events + aasm.events.map(&:name) - self::IGNORED_EVENTS + end + end +end diff --git a/app/views/components/_btn_base.haml b/app/views/components/_btn_base.haml index 677b88f1..a950ad5a 100644 --- a/app/views/components/_btn_base.haml +++ b/app/views/components/_btn_base.haml @@ -1,6 +1,7 @@ -# Componente Botón general Moderación - local_assigns[:method] ||= 'patch' +- local_assigns[:class] ||= 'btn-secondary' - local_assigns[:class] = "btn #{local_assigns[:class]}" -# @todo path es obligatorio diff --git a/app/views/components/_comments_btn_box.haml b/app/views/components/_comments_btn_box.haml index 8b8d7268..285eefdb 100644 --- a/app/views/components/_comments_btn_box.haml +++ b/app/views/components/_comments_btn_box.haml @@ -1,8 +1,8 @@ -# Componente Botonera de Comentarios -- btn_class = 'btn-secondary py-1 px-2' -= render 'components/btn_base', text: t('.text_pause'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_reject'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_accept'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_reply'), class: btn_class, href: '' -= render 'components/btn_base', text: t('.text_report'), class: btn_class, href: '' \ No newline at end of file +.d-flex.flex-row + - ActivityPub.events.each do |event| + = render 'components/btn_base', + text: t(".text_#{event}"), + path: public_send(:"site_activity_pub_#{event}_path", activity_pub_id: activity_pub), + disabled: !activity_pub.public_send(:"may_#{event}?") diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index 29888ef7..ffaf76cf 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -1,11 +1,16 @@ --# Componente Comentario +-# + Componente Comentario + + @param profile [Hash] + @param comment [Hash] + @param activity_pub [ActivityPub] - in_reply_to = text_plain comment['inReplyTo'] -- summary = text_plain(comment['summary']) +- summary = text_plain comment['summary'] .row.no-gutters .col-1 - = render 'components/checkbox', id: comment['id'] + = render 'components/checkbox', id: activity_pub.id, name: 'activity_pub[]', value: activity_pub.id, data: { target: 'select-all.input' } .col-11 .d-flex.flex-row.align-items-center.justify-content-between %h4.mb-0 @@ -25,3 +30,4 @@ = sanitize comment['content'] - else = sanitize comment['content'] + = render 'components/comments_btn_box', activity_pub: activity_pub diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index 4fe84652..97f77e3c 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -1,14 +1,14 @@ -.row.no-gutters.pt-2 - .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'select-all-comments' - .col-md-9 - -# Filtros - = render 'components/comments_filters' +%form{ action: site_activity_pubs_action_on_several_path, method: :post, data: { controller: 'select-all' } } + .row.no-gutters.pt-2 + .col-1.d-flex.align-items-center + = render 'components/select_all', id: 'select-all-comments' + .col-md-9 + -# Filtros + = render 'components/comments_filters' -- moderation_queue.each do |activity_pub| - %hr - = render 'comment', comment: activity_pub.object.content, profile: activity_pub.actor.content - - -# Botones moderación - .d-flex.justify-content-center - = render 'components/comments_btn_box', comment: activity_pub.object.content + - if moderation_queue.count.zero? + %h4= t('moderation_queue.nothing') + - moderation_queue.each do |activity_pub| + - cache [activity_pub, activity_pub.object, activity_pub.actor] do + %hr + = render 'comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub diff --git a/config/locales/en.yml b/config/locales/en.yml index 6d5baf5a..f69d60b0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -93,8 +93,8 @@ en: title: Block lists comments_btn_box: text_pause: Pause + text_approve: Approve text_reject: Reject - text_accept: Accept text_reply: Reply text_report: Report instances_btn_box: diff --git a/config/locales/es.yml b/config/locales/es.yml index 13534822..a0aaa4d6 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -92,10 +92,9 @@ es: block_lists: title: Listas de bloqueo comments_btn_box: - text_pause: Pausa + text_pause: Pausar + text_approve: Aceptar text_reject: Rechazar - text_accept: Aceptar Publicación - text_reply: Responder text_report: Reportar instances_btn_box: text_check: Moderar caso por caso diff --git a/config/routes.rb b/config/routes.rb index 9d825a3c..8809767b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -82,6 +82,14 @@ Rails.application.routes.draw do patch :actor_moderations_action_on_several, to: 'actor_moderations#action_on_several' + resources :activity_pub, only: [] do + ActivityPub.events.each do |event| + patch event, to: "activity_pubs##{event}" + end + end + + patch :activity_pubs_action_on_several, to: 'activity_pubs#action_on_several' + # Gestionar artículos según idioma nested do scope '/(:locale)', constraint: /[a-z]{2}(-[A-Z]{2})?/ do From a2204779af2bca3f5b158e59553fa6c8787986b0 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 13:59:01 -0300 Subject: [PATCH 130/297] feat: filtrar por estado del comentario --- app/helpers/moderation_queue_helper.rb | 7 +++++++ app/models/activity_pub.rb | 1 + app/models/actor_moderation.rb | 1 + app/models/concerns/aasm_events_concern.rb | 7 +++++++ app/views/components/_comments_filters.haml | 2 +- app/views/components/_comments_show_submenu.haml | 8 ++++---- app/views/components/_profiles_show_submenu.haml | 4 ++-- app/views/moderation_queue/_comments.haml | 2 +- config/locales/en.yml | 8 ++++---- config/locales/es.yml | 8 ++++---- 10 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 app/helpers/moderation_queue_helper.rb diff --git a/app/helpers/moderation_queue_helper.rb b/app/helpers/moderation_queue_helper.rb new file mode 100644 index 00000000..3681bec3 --- /dev/null +++ b/app/helpers/moderation_queue_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ModerationQueueHelper + def filter_states(**args) + params.permit(:state, :actor_state, :activity_pub_state).merge(**args) + end +end diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 95fa3bf3..51ee0d71 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -12,6 +12,7 @@ class ActivityPub < ApplicationRecord include AasmEventsConcern IGNORED_EVENTS = %i[remove] + IGNORED_STATES = %i[removed] belongs_to :instance belongs_to :site diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 1fc1a42a..421d4c6e 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -6,6 +6,7 @@ class ActorModeration < ApplicationRecord include AasmEventsConcern IGNORED_EVENTS = [] + IGNORED_STATES = [] belongs_to :site belongs_to :remote_flag, class_name: 'ActivityPub::RemoteFlag' diff --git a/app/models/concerns/aasm_events_concern.rb b/app/models/concerns/aasm_events_concern.rb index 79250ea9..59ea243f 100644 --- a/app/models/concerns/aasm_events_concern.rb +++ b/app/models/concerns/aasm_events_concern.rb @@ -10,5 +10,12 @@ module AasmEventsConcern def self.events aasm.events.map(&:name) - self::IGNORED_EVENTS end + + # Todos los estados de la máquina de estados + # + # @return [Array] + def self.states + aasm.states.map(&:name) - self::IGNORED_STATES + end end end diff --git a/app/views/components/_comments_filters.haml b/app/views/components/_comments_filters.haml index 7c453088..b4597be5 100644 --- a/app/views/components/_comments_filters.haml +++ b/app/views/components/_comments_filters.haml @@ -3,4 +3,4 @@ = render 'components/comments_checked_submenu' = render 'components/dropdown', text: t('.text_show') do - = render 'components/comments_show_submenu' \ No newline at end of file + = render 'components/comments_show_submenu', activity_pubs: activity_pubs diff --git a/app/views/components/_comments_show_submenu.haml b/app/views/components/_comments_show_submenu.haml index 0308b926..eb037975 100644 --- a/app/views/components/_comments_show_submenu.haml +++ b/app/views/components/_comments_show_submenu.haml @@ -1,4 +1,4 @@ -= render 'components/dropdown_item', text: t('.submenu_pause'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_accept'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_report'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_reject'), path: '/' \ No newline at end of file +- ActivityPub.states.each do |state| + = render 'components/dropdown_item', + text: t(".submenu_#{state}", count: activity_pubs.unscope(where: :aasm_state).public_send(state).count), + path: site_moderation_queue_path(filter_states(activity_pub_state: state)) diff --git a/app/views/components/_profiles_show_submenu.haml b/app/views/components/_profiles_show_submenu.haml index 703e4a15..0209ef2f 100644 --- a/app/views/components/_profiles_show_submenu.haml +++ b/app/views/components/_profiles_show_submenu.haml @@ -1,4 +1,4 @@ -- ActorModeration.aasm.states.map(&:name).each do |actor_state| +- ActorModeration.states.each do |actor_state| = render 'components/dropdown_item', text: t(".submenu_#{actor_state}", count: actor_moderations.unscope(where: :aasm_state).public_send(actor_state).count), - path: site_moderation_queue_path(params.permit(:state, :actor_state).merge(actor_state: actor_state)) + path: site_moderation_queue_path(filter_states(actor_state: actor_state)) diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index 97f77e3c..f243b02a 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -4,7 +4,7 @@ = render 'components/select_all', id: 'select-all-comments' .col-md-9 -# Filtros - = render 'components/comments_filters' + = render 'components/comments_filters', activity_pubs: moderation_queue - if moderation_queue.count.zero? %h4= t('moderation_queue.nothing') diff --git a/config/locales/en.yml b/config/locales/en.yml index f69d60b0..eecd5ebd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -72,10 +72,10 @@ en: submenu_accept: Accept submenu_reject: Reject comments_show_submenu: - submenu_pause: Pause - submenu_accept: Accept - submenu_report: Report - submenu_reject: Reject + submenu_paused: "Paused (%{count})" + submenu_approved: "Approved (%{count})" + submenu_rejected: "Rejected (%{count})" + submenu_reported: "Reported (%{count})" profiles_filters: text_show: Show text_checked: With selected diff --git a/config/locales/es.yml b/config/locales/es.yml index a0aaa4d6..0f9eb68a 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -72,10 +72,10 @@ es: submenu_accept: Aceptado submenu_reject: Rechazado comments_show_submenu: - submenu_pause: Pausado - submenu_accept: Aceptado - submenu_report: Reportado - submenu_reject: Rechazado + submenu_paused: "Pausados (%{count})" + submenu_approved: "Aprobados (%{count})" + submenu_rejected: "Rechazados (%{count})" + submenu_reported: "Reportados (%{count})" profiles_filters: text_show: Ver text_checked: Con los marcados From 0b3f4e33fb4f32671f11bc6b2593adfe3a60524b Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 14:25:19 -0300 Subject: [PATCH 131/297] =?UTF-8?q?fix:=20siempre=20es=20un=20bot=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://blog.saeloun.com/2021/08/24/rails-7-button-to-rendering/ --- app/views/components/_btn_base.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/components/_btn_base.haml b/app/views/components/_btn_base.haml index a950ad5a..4d8566d3 100644 --- a/app/views/components/_btn_base.haml +++ b/app/views/components/_btn_base.haml @@ -5,4 +5,5 @@ - local_assigns[:class] = "btn #{local_assigns[:class]}" -# @todo path es obligatorio -= button_to text, local_assigns[:path], **local_assigns += button_to local_assigns[:path], **local_assigns do + = text From 27d7589f9aab294283df57a14fa3b9a7cb7c7ee9 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 14:26:41 -0300 Subject: [PATCH 132/297] fix: no se pueden anidar formularios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit usando el atributo form, podemos crear un formulario vacío y colocar inputs por fuera de su contexto. de lo contrario el primer botón de acción individual no funciona --- app/models/instance_moderation.rb | 7 +++-- app/views/components/_checkbox.haml | 2 +- app/views/components/_dropdown_button.haml | 2 +- .../_instances_checked_submenu.haml | 5 ++- app/views/components/_instances_filters.haml | 2 +- app/views/components/_select_all.haml | 2 +- app/views/moderation_queue/_comment.haml | 2 +- app/views/moderation_queue/_instance.haml | 2 +- app/views/moderation_queue/_instances.haml | 31 ++++++++++--------- 9 files changed, 28 insertions(+), 27 deletions(-) diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 10b5e8e0..7447cc89 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -3,6 +3,10 @@ # Mantiene el registro de relaciones entre sitios e instancias class InstanceModeration < ApplicationRecord include AASM + include AasmEventsConcern + + IGNORED_EVENTS = [] + IGNORED_STATES = [] belongs_to :site belongs_to :instance, class_name: 'ActivityPub::Instance' @@ -11,9 +15,6 @@ class InstanceModeration < ApplicationRecord # todas las que no estén bloqueadas ya. scope :may_block, -> { where.not(aasm_state: 'blocked') } scope :may_pause, -> { where.not(aasm_state: 'paused') } - scope :paused, -> { where(aasm_state: 'paused') } - scope :blocked, -> { where(aasm_state: 'blocked') } - scope :allowed, -> { where(aasm_state: 'allowed') } # Bloquear instancias en masa def self.block_all! diff --git a/app/views/components/_checkbox.haml b/app/views/components/_checkbox.haml index 596ff074..68f1a663 100644 --- a/app/views/components/_checkbox.haml +++ b/app/views/components/_checkbox.haml @@ -1,5 +1,5 @@ -# Componente Checkbox - local_assigns[:name] ||= id .custom-control.custom-checkbox - %input.custom-control-input{ type: 'checkbox', id: id, **local_assigns } + %input.custom-control-input{ form: local_assigns[:form_id], type: 'checkbox', id: id, **local_assigns } %label.custom-control-label{ for: id }= yield diff --git a/app/views/components/_dropdown_button.haml b/app/views/components/_dropdown_button.haml index 8b0c4684..c8c98209 100644 --- a/app/views/components/_dropdown_button.haml +++ b/app/views/components/_dropdown_button.haml @@ -1,4 +1,4 @@ -# @param name [String] @param value [String] -%button.dropdown-item{type: 'submit', data: { target: 'dropdown.item' }, name: name, value: value }= text +%button.dropdown-item{type: 'submit', data: { target: 'dropdown.item' }, name: name, value: value, form: local_assigns[:form_id] }= text diff --git a/app/views/components/_instances_checked_submenu.haml b/app/views/components/_instances_checked_submenu.haml index c3573ead..2d28fb1c 100644 --- a/app/views/components/_instances_checked_submenu.haml +++ b/app/views/components/_instances_checked_submenu.haml @@ -1,3 +1,2 @@ -= render 'components/dropdown_button', text: t('.submenu_pause'), name: 'instance_moderation_action', value: 'pause' -= render 'components/dropdown_button', text: t('.submenu_allow'), name: 'instance_moderation_action', value: 'allow' -= render 'components/dropdown_button', text: t('.submenu_block'), name: 'instance_moderation_action', value: 'block' +- InstanceModeration.events.each do |event| + = render 'components/dropdown_button', text: t(".submenu_#{event}"), name: 'instance_moderation_action', value: event, form_id: form_id diff --git a/app/views/components/_instances_filters.haml b/app/views/components/_instances_filters.haml index fe40ced3..2c05693a 100644 --- a/app/views/components/_instances_filters.haml +++ b/app/views/components/_instances_filters.haml @@ -1,6 +1,6 @@ .d-flex.py-2 = render 'components/dropdown', text: t('.text_checked') do - = render 'components/instances_checked_submenu' + = render 'components/instances_checked_submenu', form_id: form_id = render 'components/dropdown', text: t('.text_show') do = render 'components/instances_show_submenu', site: site diff --git a/app/views/components/_select_all.haml b/app/views/components/_select_all.haml index 2603dfd3..68711c4a 100644 --- a/app/views/components/_select_all.haml +++ b/app/views/components/_select_all.haml @@ -1,4 +1,4 @@ -# @param id [String] -= render 'components/checkbox', id: id, data: { action: 'select-all#toggle', target: 'select-all.toggle' } do += render 'components/checkbox', id: id, form: local_assigns[:form_id], data: { action: 'select-all#toggle', target: 'select-all.toggle' } do %span.sr-only= t('.label') diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index ffaf76cf..33ebc722 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -10,7 +10,7 @@ .row.no-gutters .col-1 - = render 'components/checkbox', id: activity_pub.id, name: 'activity_pub[]', value: activity_pub.id, data: { target: 'select-all.input' } + = render 'components/checkbox', id: activity_pub.id, name: 'activity_pub[]', value: activity_pub.id, data: { target: 'select-all.input' }, form: form_id .col-11 .d-flex.flex-row.align-items-center.justify-content-between %h4.mb-0 diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index 97d23f10..05510724 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -3,7 +3,7 @@ .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: instance.hostname, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } + = render 'components/checkbox', id: instance.hostname, form_id: form_id, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 %a{ href: instance.uri }= sanitize(instance.content['title']) || instance.hostname diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 77b6adea..83b0c772 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -1,22 +1,23 @@ +- form_id = 'instance_moderation_action_on_several' %section - %form{ action: site_instance_moderations_action_on_several_path, method: :post } - .row.no-gutters.pt-2{ data: { controller: 'select-all' } } - .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'instances' - .col-11 - -# Filtros - = render 'components/instances_filters', site: site + %form{ id: form_id, action: site_instance_moderations_action_on_several_path, method: :post } + .row.no-gutters.pt-2{ data: { controller: 'select-all' } } + .col-1.d-flex.align-items-center + = render 'components/select_all', id: 'instances', form_id: form_id + .col-11 + -# Filtros + = render 'components/instances_filters', site: site, form_id: form_id - .col-12 - - if instance_moderations.count.zero? - %h4= t('moderation_queue.nothing') + .col-12 + - if instance_moderations.count.zero? + %h4= t('moderation_queue.nothing') - - instance_moderations.each do |instance_moderation| - - cache [instance_moderation.aasm_state, instance_moderation.instance] do - %hr - = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance + - instance_moderations.each do |instance_moderation| + - cache [instance_moderation.aasm_state, instance_moderation.instance] do + %hr + = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance, form_id: form_id - %hr + %hr %div %h3.mt-5= t('moderation_queue.instances.title') %lead= t('moderation_queue.instances.description') From 5211c9bd28c089ea6db5b568bf20e2497e5a1597 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 14:39:06 -0300 Subject: [PATCH 133/297] fix: formularios --- .../components/_profiles_checked_submenu.haml | 2 +- app/views/components/_profiles_filters.haml | 2 +- app/views/moderation_queue/_account.haml | 2 +- app/views/moderation_queue/_accounts.haml | 31 ++++++++++--------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/app/views/components/_profiles_checked_submenu.haml b/app/views/components/_profiles_checked_submenu.haml index c0b99aa5..13e016ca 100644 --- a/app/views/components/_profiles_checked_submenu.haml +++ b/app/views/components/_profiles_checked_submenu.haml @@ -1,2 +1,2 @@ - ActorModeration.events.each do |actor_event| - = render 'components/dropdown_button', text: t(".submenu_#{actor_event}"), name: 'actor_moderation_action', value: actor_event + = render 'components/dropdown_button', text: t(".submenu_#{actor_event}"), name: 'actor_moderation_action', value: actor_event, form_id: form_id diff --git a/app/views/components/_profiles_filters.haml b/app/views/components/_profiles_filters.haml index c6397e69..a2593f4c 100644 --- a/app/views/components/_profiles_filters.haml +++ b/app/views/components/_profiles_filters.haml @@ -1,6 +1,6 @@ .d-flex.py-2 = render 'components/dropdown', text: t('.text_checked') do - = render 'components/profiles_checked_submenu' + = render 'components/profiles_checked_submenu', form_id: form_id = render 'components/dropdown', text: t('.text_show') do = render 'components/profiles_show_submenu', actor_moderations: actor_moderations diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index fee90316..f63b6f6f 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -1,6 +1,6 @@ .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: actor_moderation.id, name: 'actor_moderation[]', value: actor_moderation.id, data: { target: 'select-all.input' } + = render 'components/checkbox', id: actor_moderation.id, form_id: form_id, name: 'actor_moderation[]', value: actor_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 = link_to text_plain(profile['name']), site_actor_moderation_path(id: actor_moderation) diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index 35b0b86e..53d2f28e 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -1,14 +1,17 @@ -%form{ action: site_actor_moderations_action_on_several_path, method: :post } - .row.no-gutters.pt-2{ data: { controller: 'select-all' } } - .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'actors' - .col-11 - -# Filtros - = render 'components/profiles_filters', actor_moderations: actor_moderations - .col-12 - - if actor_moderations.count.zero? - %h4= t('moderation_queue.nothing') - - actor_moderations.find_each do |actor_moderation| - - cache [actor_moderation, actor_moderation.actor] do - %hr - = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content +- form_id = 'actor_moderations_action_on_several' + += form_tag site_actor_moderations_action_on_several_path, id: form_id, method: :patch + +.row.no-gutters.pt-2{ data: { controller: 'select-all' } } + .col-1.d-flex.align-items-center + = render 'components/select_all', id: 'actors', form_id: form_id + .col-11 + -# Filtros + = render 'components/profiles_filters', actor_moderations: actor_moderations, form_id: form_id + .col-12 + - if actor_moderations.count.zero? + %h4= t('moderation_queue.nothing') + - actor_moderations.find_each do |actor_moderation| + - cache [actor_moderation, actor_moderation.actor] do + %hr + = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content, form_id: form_id From 8e681cdba79024e6fb584654025ce730d9c5d90a Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 14:47:20 -0300 Subject: [PATCH 134/297] fix: sincronizar controladores --- app/controllers/activity_pubs_controller.rb | 8 +++-- .../actor_moderations_controller.rb | 6 +++- .../concerns/moderation_concern.rb | 13 ++++++++ .../instance_moderations_controller.rb | 32 ++++++------------- .../moderation_queue_controller.rb | 2 +- app/policies/activity_pub_policy.rb | 16 ++++++++++ app/policies/instance_moderation_policy.rb | 14 +++----- 7 files changed, 55 insertions(+), 36 deletions(-) create mode 100644 app/controllers/concerns/moderation_concern.rb create mode 100644 app/policies/activity_pub_policy.rb diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index dfe388a4..057e65f7 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -2,16 +2,20 @@ # Gestiona acciones de moderación class ActivityPubsController < ApplicationController + include ModerationConcern + ActivityPub.events.each do |event| define_method(event) do + authorize activity_pub + activity_pub.public_send(:"#{event}!") if activity_pub.public_send(:"may_#{event}?") - redirect_back fallback_location: site_moderation_queue_path(**(session[:moderation_queue_filters] || {})) + redirect_to_moderation_queue! end end def action_on_several - + redirect_to_moderation_queue! end private diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 907f21c6..eadc2165 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -2,6 +2,8 @@ # Gestiona la cola de moderación de actores class ActorModerationsController < ApplicationController + include ModerationConcern + ActorModeration.events.each do |actor_event| define_method(actor_event) do authorize actor_moderation @@ -11,7 +13,7 @@ class ActorModerationsController < ApplicationController actor_moderation.public_send(:"#{actor_event}!") if actor_moderation.public_send(:"may_#{actor_event}?") - redirect_back fallback_location: site_moderation_queue_path(**(session[:moderation_queue_filters] || {})) + redirect_to_moderation_queue! end end @@ -29,6 +31,8 @@ class ActorModerationsController < ApplicationController method = :"#{action}!" may = :"may_#{action}?" + redirect_to_moderation_queue! + return unless ActorModeration.events.include? action ActorModeration.transaction do diff --git a/app/controllers/concerns/moderation_concern.rb b/app/controllers/concerns/moderation_concern.rb new file mode 100644 index 00000000..9a4f1c16 --- /dev/null +++ b/app/controllers/concerns/moderation_concern.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ModerationConcern + extend ActiveSupport::Concern + + included do + private + + def redirect_to_moderation_queue! + redirect_back fallback_location: site_moderation_queue_path(**(session[:moderation_queue_filters] || {})) + end + end +end diff --git a/app/controllers/instance_moderations_controller.rb b/app/controllers/instance_moderations_controller.rb index dc9e1dfa..270f0588 100644 --- a/app/controllers/instance_moderations_controller.rb +++ b/app/controllers/instance_moderations_controller.rb @@ -2,19 +2,16 @@ # Actualiza la relación entre un sitio y una instancia class InstanceModerationsController < ApplicationController - before_action :authorize_policy, except: %i[action_on_several] - around_action :redirect_to_moderation_queue! + include ModerationConcern - def pause - instance_moderation.pause! if instance_moderation.may_pause? - end + InstanceModeration.events.each do |event| + define_method(event) do + authorize instance_moderation - def allow - instance_moderation.allow! if instance_moderation.may_allow? - end + instance_moderation.public_send(:"#{event}!") if instance_moderation.public_send(:"may_#{event}?") - def block - instance_moderation.block! if instance_moderation.may_block? + redirect_to_moderation_queue! + end end def action_on_several @@ -25,9 +22,10 @@ class InstanceModerationsController < ApplicationController action = params[:instance_moderation_action].to_sym method = :"#{action}!" may = :"may_#{action}?" - events = instance_moderation.aasm.events.map(&:name) - return unless events.include? action + redirect_to_moderation_queue! + + return unless InstanceModeration.events.include? action InstanceModeration.transaction do instance_moderations.find_each do |instance_moderation| @@ -38,18 +36,8 @@ class InstanceModerationsController < ApplicationController private - def redirect_to_moderation_queue!(&action) - redirect_back fallback_location: site_moderation_queue_path, state: session[:moderation_queue_filtered_by_state] - - yield - end - # @return [InstanceModeration] def instance_moderation @instance_moderation ||= site.instance_moderations.find(params[:instance_moderation_id]) end - - def authorize_policy - authorize instance_moderation - end end diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index b803ccc9..6a628aaa 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -6,7 +6,7 @@ class ModerationQueueController < ApplicationController def index dummy_data - session[:moderation_queue_filters] = params.permit(:state, :actor_state) + session[:moderation_queue_filters] = params.permit(:state, :actor_state, :activity_pub_state) # @todo cambiar el estado por query @activity_pubs = site.activity_pubs diff --git a/app/policies/activity_pub_policy.rb b/app/policies/activity_pub_policy.rb new file mode 100644 index 00000000..f5755840 --- /dev/null +++ b/app/policies/activity_pub_policy.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Solo les usuaries pueden moderar comentarios +ActivityPubPolicy = Struct.new(:usuarie, :activity_pub) do + ActivityPub.events.each do |event| + define_method(:"#{event}?") do + activity_pub.site.usuarie? usuarie + end + end + + # En este paso tenemos varias instancias por moderar pero todas son + # del mismo sitio. + def action_on_several? + activity_pub.first.site.usuarie? usuarie + end +end diff --git a/app/policies/instance_moderation_policy.rb b/app/policies/instance_moderation_policy.rb index c07455b3..13ebfeca 100644 --- a/app/policies/instance_moderation_policy.rb +++ b/app/policies/instance_moderation_policy.rb @@ -2,16 +2,10 @@ # Solo les usuaries pueden moderar instancias InstanceModerationPolicy = Struct.new(:usuarie, :instance_moderation) do - def pause? - instance_moderation.site.usuarie? usuarie - end - - def allow? - pause? - end - - def block? - pause? + InstanceModeration.events.each do |event| + define_method(:"#{event}?") do + instance_moderation.site.usuarie? usuarie + end end # En este paso tenemos varias instancias por moderar pero todas son From a7d33976ec7c5da2d3a7a5257eee7ba66d329ce9 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 14:59:17 -0300 Subject: [PATCH 135/297] fixup! fix: no se pueden anidar formularios --- app/views/moderation_queue/_instances.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 83b0c772..a707d48a 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -1,6 +1,8 @@ - form_id = 'instance_moderation_action_on_several' + %section - %form{ id: form_id, action: site_instance_moderations_action_on_several_path, method: :post } + = form_tag site_instance_moderations_action_on_several_path, id: form_id, method: :patch + .row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center = render 'components/select_all', id: 'instances', form_id: form_id From 3967a631632c09a1310c4248c7b1a61406284d3e Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 15:10:20 -0300 Subject: [PATCH 136/297] feat: extraer en un componente --- app/views/components/_select_all_container.haml | 13 +++++++++++++ app/views/moderation_queue/_accounts.haml | 2 +- app/views/moderation_queue/_instances.haml | 3 +-- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 app/views/components/_select_all_container.haml diff --git a/app/views/components/_select_all_container.haml b/app/views/components/_select_all_container.haml new file mode 100644 index 00000000..5fa91e2d --- /dev/null +++ b/app/views/components/_select_all_container.haml @@ -0,0 +1,13 @@ +-# + Contenedor para las acciones en masa. + + Es un formulario auto-contenido, que permite colocar los elementos + fuera del formulario para evitar anidarlos. Mientras los elementos + tengan el atributo `form` con el mismo parámetro `form_id`, el + navegador los va a asignar a este formulario. + + @param path [String] + @param form_id [String] + += form_tag path, id: form_id, method: :patch do + -# nada diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index 53d2f28e..65ff953f 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -1,6 +1,6 @@ - form_id = 'actor_moderations_action_on_several' -= form_tag site_actor_moderations_action_on_several_path, id: form_id, method: :patch += render 'components/select_all_container', path: site_actor_moderations_action_on_several_path, form_id: form_id .row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index a707d48a..d9db967f 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -1,8 +1,7 @@ - form_id = 'instance_moderation_action_on_several' %section - = form_tag site_instance_moderations_action_on_several_path, id: form_id, method: :patch - + = render 'components/select_all_container', path: site_instance_moderations_action_on_several_path, form_id: form_id .row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center = render 'components/select_all', id: 'instances', form_id: form_id From b201c3de1881c0e20f8b53468ea69f62b8aeb386 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 15:11:29 -0300 Subject: [PATCH 137/297] feat: filtros y acciones para comentarios --- .../components/_comments_checked_submenu.haml | 5 ++-- app/views/components/_comments_filters.haml | 2 +- app/views/moderation_queue/_comments.haml | 27 ++++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/views/components/_comments_checked_submenu.haml b/app/views/components/_comments_checked_submenu.haml index 4998e5c7..fa3d7612 100644 --- a/app/views/components/_comments_checked_submenu.haml +++ b/app/views/components/_comments_checked_submenu.haml @@ -1,3 +1,2 @@ -= render 'components/dropdown_item', text: t('.submenu_pause'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_accept'), path: '/' -= render 'components/dropdown_item', text: t('.submenu_reject'), path: '/' \ No newline at end of file +- ActivityPub.events.each do |event| + = render 'components/dropdown_button', form_id: form_id, text: t(".submenu_#{event}"), name: 'activity_pub_action', value: event diff --git a/app/views/components/_comments_filters.haml b/app/views/components/_comments_filters.haml index b4597be5..15f5173d 100644 --- a/app/views/components/_comments_filters.haml +++ b/app/views/components/_comments_filters.haml @@ -1,6 +1,6 @@ .d-flex.py-2 = render 'components/dropdown', text: t('.text_checked') do - = render 'components/comments_checked_submenu' + = render 'components/comments_checked_submenu', form_id: form_id = render 'components/dropdown', text: t('.text_show') do = render 'components/comments_show_submenu', activity_pubs: activity_pubs diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index f243b02a..bf1e94a0 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -1,14 +1,17 @@ -%form{ action: site_activity_pubs_action_on_several_path, method: :post, data: { controller: 'select-all' } } - .row.no-gutters.pt-2 - .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'select-all-comments' - .col-md-9 - -# Filtros - = render 'components/comments_filters', activity_pubs: moderation_queue +- form_id = 'activity_pub_action_on_several' - - if moderation_queue.count.zero? - %h4= t('moderation_queue.nothing') - - moderation_queue.each do |activity_pub| - - cache [activity_pub, activity_pub.object, activity_pub.actor] do += render 'components/select_all_container', path: site_activity_pubs_action_on_several_path, form_id: form_id + +.row.no-gutters.pt-2{ data: { controller: 'select-all' } } + .col-1.d-flex.align-items-center + = render 'components/select_all', id: 'select-all-comments', form_id: form_id + .col-md-9 + -# Filtros + = render 'components/comments_filters', activity_pubs: moderation_queue, form_id: form_id + .col-12 + - if moderation_queue.count.zero? + %h4= t('moderation_queue.nothing') + - moderation_queue.each do |activity_pub| + -# cache [activity_pub, activity_pub.object, activity_pub.actor] do %hr - = render 'comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub + = render 'comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form_id: form_id From b7989808655e29cfad5db4d04755b28194108a88 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 15:13:01 -0300 Subject: [PATCH 138/297] feat: acciones masivas para comentarios --- app/controllers/activity_pubs_controller.rb | 16 ++++++++++++++++ config/locales/en.yml | 3 ++- config/locales/es.yml | 7 ++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index 057e65f7..9bbbb9cc 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -15,7 +15,23 @@ class ActivityPubsController < ApplicationController end def action_on_several + activity_pubs = site.activity_pubs.where(id: params[:activity_pub]) + + authorize activity_pubs + + action = params[:activity_pub_action].to_sym + method = :"#{action}!" + may = :"may_#{action}?" + redirect_to_moderation_queue! + + return unless ActivityPub.events.include? action + + ActivityPub.transaction do + activity_pubs.find_each do |activity_pub| + activity_pub.public_send(method) if activity_pub.public_send(may) + end + end end private diff --git a/config/locales/en.yml b/config/locales/en.yml index eecd5ebd..a10b9f3a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -69,8 +69,9 @@ en: text_checked: With selected comments_checked_submenu: submenu_pause: Pause - submenu_accept: Accept + submenu_approve: Approve submenu_reject: Reject + submenu_report: Report comments_show_submenu: submenu_paused: "Paused (%{count})" submenu_approved: "Approved (%{count})" diff --git a/config/locales/es.yml b/config/locales/es.yml index 0f9eb68a..f5487e47 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -68,9 +68,10 @@ es: text_show: Ver text_checked: Con los marcados comments_checked_submenu: - submenu_pause: Pausado - submenu_accept: Aceptado - submenu_reject: Rechazado + submenu_pause: Pausar + submenu_approve: Aprobar + submenu_reject: Rechazar + submenu_report: Reportar comments_show_submenu: submenu_paused: "Pausados (%{count})" submenu_approved: "Aprobados (%{count})" From ccd3df2038b893b0ead29682df691dbb81862dfa Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 16:46:43 -0300 Subject: [PATCH 139/297] feat: aprobar o rechazar actividades --- app/models/activity_pub.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 51ee0d71..838eea80 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -64,14 +64,26 @@ class ActivityPub < ApplicationRecord transitions from: %i[approved rejected], to: :paused end - # La actividad se aprueba + # La actividad se aprueba, informándole a la Social Inbox que está + # aprobada. También recibimos la aprobación via + # webhook a modo de confirmación. event :approve do transitions from: %i[paused rejected], to: :approved + + before do + raise AASM::InvalidTransition unless + site.social_inbox.inbox.accept(id: object.uri).ok? + end end # La actividad fue rechazada event :reject do transitions from: %i[paused approved], to: :rejected + + before do + raise AASM::InvalidTransition unless + site.social_inbox.inbox.reject(id: object.uri).ok? + end end # Solo podemos reportarla luego de rechazarla From 0e2a4c42882774f56a494e29fb674401b10904e2 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 16:52:28 -0300 Subject: [PATCH 140/297] =?UTF-8?q?fix:=20todav=C3=ADa=20no=20se=20puede?= =?UTF-8?q?=20volver=20de=20una=20actividad=20rechazada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 838eea80..0cd44814 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -61,14 +61,14 @@ class ActivityPub < ApplicationRecord # Si un objeto previamente aprobado fue actualizado, volvemos a # pausarlo. event :pause do - transitions from: %i[approved rejected], to: :paused + transitions from: %i[approved], to: :paused end # La actividad se aprueba, informándole a la Social Inbox que está # aprobada. También recibimos la aprobación via # webhook a modo de confirmación. event :approve do - transitions from: %i[paused rejected], to: :approved + transitions from: %i[paused], to: :approved before do raise AASM::InvalidTransition unless From 1fe6199cea375c319e6eb3423985151337d091e8 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 16:53:23 -0300 Subject: [PATCH 141/297] =?UTF-8?q?fix:=20todav=C3=ADa=20no=20se=20puede?= =?UTF-8?q?=20pausar=20luego=20de=20aprobar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 0cd44814..6f0d884c 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -58,12 +58,6 @@ class ActivityPub < ApplicationRecord end end - # Si un objeto previamente aprobado fue actualizado, volvemos a - # pausarlo. - event :pause do - transitions from: %i[approved], to: :paused - end - # La actividad se aprueba, informándole a la Social Inbox que está # aprobada. También recibimos la aprobación via # webhook a modo de confirmación. From c49182e278edd723a29fcad7671a138ee1f1d623 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 17:14:46 -0300 Subject: [PATCH 142/297] feat: poder bloquear en masa --- .../actor_moderations_controller.rb | 27 ++++++++++++++++--- app/views/components/_profiles_btn_box.haml | 12 +-------- config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index eadc2165..2d834015 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -9,7 +9,7 @@ class ActorModerationsController < ApplicationController authorize actor_moderation # Crea una RemoteFlag si se envían los parámetros adecuados - actor_moderation.update(actor_moderation_params) if actor_event == :report + actor_moderation.update(actor_moderation_params(actor_moderation)) if actor_event == :report actor_moderation.public_send(:"#{actor_event}!") if actor_moderation.public_send(:"may_#{actor_event}?") @@ -37,7 +37,11 @@ class ActorModerationsController < ApplicationController ActorModeration.transaction do actor_moderations.find_each do |actor_moderation| - actor_moderation.public_send(method) if actor_moderation.public_send(may) + next unless actor_moderation.public_send(may) + + actor_moderation.update(actor_moderation_params(actor_moderation)) if action == :report + + actor_moderation.public_send(method) end end end @@ -48,10 +52,25 @@ class ActorModerationsController < ApplicationController @actor_moderation ||= site.actor_moderations.find(params[:actor_moderation_id] || params[:id]) end - def actor_moderation_params - params.require(:actor_moderation).permit(remote_flag_attributes: %i[message]).tap do |p| + # @return [String] + def panel_actor_mention + @panel_actor_mention ||= ENV.fetch('PANEL_ACTOR_MENTION', '@sutty@sutty.nl') + end + + # @return [Hash] + def actor_moderation_params(actor_moderation) + { remote_flag_attributes: { id: actor_moderation.remote_flag_id, message: '' } }.tap do |p| p[:remote_flag_attributes][:site_id] = actor_moderation.site_id p[:remote_flag_attributes][:actor_id] = actor_moderation.actor_id + + I18n.available_locales.each do |locale| + p[:remote_flag_attributes][:message].tap do |m| + m += I18n.t(locale) + m += ': ' + m += I18n.t('actor_moderations.report_message', locale: locale, panel_actor_mention: panel_actor_mention) + m += '\n\n' + end + end end end end diff --git a/app/views/components/_profiles_btn_box.haml b/app/views/components/_profiles_btn_box.haml index 3ec95e59..073c142e 100644 --- a/app/views/components/_profiles_btn_box.haml +++ b/app/views/components/_profiles_btn_box.haml @@ -1,13 +1,4 @@ -# Componente Botonera de Moderación de Cuentas (Remote_profile) - -- form_params = {} -- form_params[:report] = { actor_moderation: { remote_flag_attributes: { message: '' } } } -- I18n.available_locales.each do |locale| - - form_params[:report][:actor_moderation][:remote_flag_attributes][:message] += t(locale) - - form_params[:report][:actor_moderation][:remote_flag_attributes][:message] += ': ' - - form_params[:report][:actor_moderation][:remote_flag_attributes][:message] += t('.report_message', locale: locale, panel_actor_mention: ENV.fetch('PANEL_ACTOR_MENTION') { '@sutty@sutty.nl' }) - - form_params[:report][:actor_moderation][:remote_flag_attributes][:message] += '\n\n' - .d-flex.flex-row - btn_class = 'btn-secondary' - ActorModeration.events.each do |actor_event| @@ -15,5 +6,4 @@ text: t(".text_#{actor_event}"), path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), class: btn_class, - disabled: !actor_moderation.public_send(:"may_#{actor_event}?"), - params: form_params[actor_event] + disabled: !actor_moderation.public_send(:"may_#{actor_event}?") diff --git a/config/locales/en.yml b/config/locales/en.yml index a10b9f3a..fe8798f4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -107,7 +107,6 @@ en: text_allow: Always approve text_block: Block text_report: Report - report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." actor_moderations: show: user: Username @@ -117,6 +116,7 @@ en: profile_id: ID profile_published: Published profile_summary: Summary + report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." moderation_queue: everything: 'Select all' nothing: "There's nothing for this filter" diff --git a/config/locales/es.yml b/config/locales/es.yml index f5487e47..e22fdf57 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -106,7 +106,6 @@ es: text_allow: Aprobar siempre text_block: Bloquear text_report: Reportar - report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." actor_moderations: show: user: Nombre de usuarie @@ -116,6 +115,7 @@ es: profile_id: ID profile_published: Publicada profile_summary: Presentación + report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." moderation_queue: everything: 'Seleccionar todo' nothing: 'No hay nada para este filtro' From 67a56c540aff22e01c76a42dbb5c71ba6f414ee6 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Mar 2024 17:45:16 -0300 Subject: [PATCH 143/297] feat: no mostrar opciones que no se pueden ejecutar con el filtro actual --- app/models/concerns/aasm_events_concern.rb | 10 ++++++++++ app/views/components/_comments_checked_submenu.haml | 8 ++++++-- app/views/components/_comments_filters.haml | 7 +++++-- app/views/components/_instances_checked_submenu.haml | 2 +- app/views/components/_instances_filters.haml | 7 +++++-- app/views/components/_profiles_checked_submenu.haml | 2 +- app/views/components/_profiles_filters.haml | 7 +++++-- app/views/moderation_queue/_comments.haml | 2 +- 8 files changed, 34 insertions(+), 11 deletions(-) diff --git a/app/models/concerns/aasm_events_concern.rb b/app/models/concerns/aasm_events_concern.rb index 59ea243f..418368d8 100644 --- a/app/models/concerns/aasm_events_concern.rb +++ b/app/models/concerns/aasm_events_concern.rb @@ -11,6 +11,16 @@ module AasmEventsConcern aasm.events.map(&:name) - self::IGNORED_EVENTS end + # Encuentra todos los eventos que se pueden ejecutar con el filtro + # actual. + # + # @return [Array] + def self.transitionable_events(current_state) + self.events.select do |event| + aasm.events.find { |x| x.name == event }.transitions_from_state? current_state + end + end + # Todos los estados de la máquina de estados # # @return [Array] diff --git a/app/views/components/_comments_checked_submenu.haml b/app/views/components/_comments_checked_submenu.haml index fa3d7612..a09da426 100644 --- a/app/views/components/_comments_checked_submenu.haml +++ b/app/views/components/_comments_checked_submenu.haml @@ -1,2 +1,6 @@ -- ActivityPub.events.each do |event| - = render 'components/dropdown_button', form_id: form_id, text: t(".submenu_#{event}"), name: 'activity_pub_action', value: event +- current_state = params[:activity_pub_state]&.to_sym || ActivityPub.states.first + +- ActivityPub.aasm.events.each do |event| + - next if ActivityPub::IGNORED_EVENTS.include? event.name + - next unless event.transitions_from_state?(current_state) + = render 'components/dropdown_button', form_id: form_id, text: t(".submenu_#{event.name}"), name: 'activity_pub_action', value: event.name diff --git a/app/views/components/_comments_filters.haml b/app/views/components/_comments_filters.haml index 15f5173d..35cd5dda 100644 --- a/app/views/components/_comments_filters.haml +++ b/app/views/components/_comments_filters.haml @@ -1,6 +1,9 @@ +- current_state = params[:activity_pub_state]&.to_sym || ActivityPub.states.first + .d-flex.py-2 - = render 'components/dropdown', text: t('.text_checked') do - = render 'components/comments_checked_submenu', form_id: form_id + - if ActivityPub.transitionable_events(current_state).present? + = render 'components/dropdown', text: t('.text_checked') do + = render 'components/comments_checked_submenu', form_id: form_id = render 'components/dropdown', text: t('.text_show') do = render 'components/comments_show_submenu', activity_pubs: activity_pubs diff --git a/app/views/components/_instances_checked_submenu.haml b/app/views/components/_instances_checked_submenu.haml index 2d28fb1c..4c45b7ab 100644 --- a/app/views/components/_instances_checked_submenu.haml +++ b/app/views/components/_instances_checked_submenu.haml @@ -1,2 +1,2 @@ -- InstanceModeration.events.each do |event| +- InstanceModeration.transitionable_events(current_state).each do |event| = render 'components/dropdown_button', text: t(".submenu_#{event}"), name: 'instance_moderation_action', value: event, form_id: form_id diff --git a/app/views/components/_instances_filters.haml b/app/views/components/_instances_filters.haml index 2c05693a..9e8509c4 100644 --- a/app/views/components/_instances_filters.haml +++ b/app/views/components/_instances_filters.haml @@ -1,6 +1,9 @@ +- current_state = params[:state]&.to_sym || InstanceModeration.states.first + .d-flex.py-2 - = render 'components/dropdown', text: t('.text_checked') do - = render 'components/instances_checked_submenu', form_id: form_id + - if InstanceModeration.transitionable_events(current_state).present? + = render 'components/dropdown', text: t('.text_checked') do + = render 'components/instances_checked_submenu', form_id: form_id, current_state: current_state = render 'components/dropdown', text: t('.text_show') do = render 'components/instances_show_submenu', site: site diff --git a/app/views/components/_profiles_checked_submenu.haml b/app/views/components/_profiles_checked_submenu.haml index 13e016ca..66a0fa78 100644 --- a/app/views/components/_profiles_checked_submenu.haml +++ b/app/views/components/_profiles_checked_submenu.haml @@ -1,2 +1,2 @@ -- ActorModeration.events.each do |actor_event| +- ActorModeration.transitionable_events(current_state).each do |actor_event| = render 'components/dropdown_button', text: t(".submenu_#{actor_event}"), name: 'actor_moderation_action', value: actor_event, form_id: form_id diff --git a/app/views/components/_profiles_filters.haml b/app/views/components/_profiles_filters.haml index a2593f4c..bf7fb48a 100644 --- a/app/views/components/_profiles_filters.haml +++ b/app/views/components/_profiles_filters.haml @@ -1,6 +1,9 @@ +- current_state = params[:actor_state]&.to_sym || ActorModeration.states.first + .d-flex.py-2 - = render 'components/dropdown', text: t('.text_checked') do - = render 'components/profiles_checked_submenu', form_id: form_id + - if ActorModeration.transitionable_events(current_state).present? + = render 'components/dropdown', text: t('.text_checked') do + = render 'components/profiles_checked_submenu', form_id: form_id, current_state: current_state = render 'components/dropdown', text: t('.text_show') do = render 'components/profiles_show_submenu', actor_moderations: actor_moderations diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index bf1e94a0..436777db 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -5,7 +5,7 @@ .row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center = render 'components/select_all', id: 'select-all-comments', form_id: form_id - .col-md-9 + .col-11 -# Filtros = render 'components/comments_filters', activity_pubs: moderation_queue, form_id: form_id .col-12 From ff428c652713c0d412b0660cb5788d274fec71fa Mon Sep 17 00:00:00 2001 From: f Date: Tue, 5 Mar 2024 14:18:06 -0300 Subject: [PATCH 144/297] feat: poder reportar comentarios --- app/controllers/activity_pubs_controller.rb | 5 +++- .../actor_moderations_controller.rb | 24 +------------------ .../concerns/moderation_concern.rb | 21 ++++++++++++++++ app/jobs/activity_pub/remote_flag_job.rb | 2 +- app/models/activity_pub.rb | 7 ++++++ app/models/activity_pub/remote_flag.rb | 7 +++++- config/locales/en.yml | 1 + config/locales/es.yml | 1 + .../20240305164653_change_remote_flags.rb | 12 ++++++++++ db/structure.sql | 6 +++-- 10 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 db/migrate/20240305164653_change_remote_flags.rb diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index 9bbbb9cc..37702d96 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -8,6 +8,7 @@ class ActivityPubsController < ApplicationController define_method(event) do authorize activity_pub + activity_pub.update(remote_flag_params(activity_pub)) if event == :report activity_pub.public_send(:"#{event}!") if activity_pub.public_send(:"may_#{event}?") redirect_to_moderation_queue! @@ -29,7 +30,9 @@ class ActivityPubsController < ApplicationController ActivityPub.transaction do activity_pubs.find_each do |activity_pub| - activity_pub.public_send(method) if activity_pub.public_send(may) + next unless activity_pub.public_send(may) + + activity_pub.public_send(method) end end end diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 2d834015..56adda4a 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -9,7 +9,7 @@ class ActorModerationsController < ApplicationController authorize actor_moderation # Crea una RemoteFlag si se envían los parámetros adecuados - actor_moderation.update(actor_moderation_params(actor_moderation)) if actor_event == :report + actor_moderation.update(remote_flag_params(actor_moderation)) if actor_event == :report actor_moderation.public_send(:"#{actor_event}!") if actor_moderation.public_send(:"may_#{actor_event}?") @@ -51,26 +51,4 @@ class ActorModerationsController < ApplicationController def actor_moderation @actor_moderation ||= site.actor_moderations.find(params[:actor_moderation_id] || params[:id]) end - - # @return [String] - def panel_actor_mention - @panel_actor_mention ||= ENV.fetch('PANEL_ACTOR_MENTION', '@sutty@sutty.nl') - end - - # @return [Hash] - def actor_moderation_params(actor_moderation) - { remote_flag_attributes: { id: actor_moderation.remote_flag_id, message: '' } }.tap do |p| - p[:remote_flag_attributes][:site_id] = actor_moderation.site_id - p[:remote_flag_attributes][:actor_id] = actor_moderation.actor_id - - I18n.available_locales.each do |locale| - p[:remote_flag_attributes][:message].tap do |m| - m += I18n.t(locale) - m += ': ' - m += I18n.t('actor_moderations.report_message', locale: locale, panel_actor_mention: panel_actor_mention) - m += '\n\n' - end - end - end - end end diff --git a/app/controllers/concerns/moderation_concern.rb b/app/controllers/concerns/moderation_concern.rb index 9a4f1c16..5b4c276d 100644 --- a/app/controllers/concerns/moderation_concern.rb +++ b/app/controllers/concerns/moderation_concern.rb @@ -9,5 +9,26 @@ module ModerationConcern def redirect_to_moderation_queue! redirect_back fallback_location: site_moderation_queue_path(**(session[:moderation_queue_filters] || {})) end + + # @return [String] + def panel_actor_mention + @panel_actor_mention ||= ENV.fetch('PANEL_ACTOR_MENTION', '@sutty@sutty.nl') + end + + def remote_flag_params(model) + { remote_flag_attributes: { id: model.remote_flag_id, message: '' } }.tap do |p| + p[:remote_flag_attributes][:site_id] = model.site_id + p[:remote_flag_attributes][:actor_id] = model.actor_id + + I18n.available_locales.each do |locale| + p[:remote_flag_attributes][:message].tap do |m| + m << I18n.t(locale) + m << ': ' + m << I18n.t('remote_flags.report_message', locale: locale, panel_actor_mention: panel_actor_mention) + m << '\n\n' + end + end + end + end end end diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb index 332d31ac..30796923 100644 --- a/app/jobs/activity_pub/remote_flag_job.rb +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -13,7 +13,7 @@ class ActivityPub self.priority = 30 def perform(remote_flag:) - client = remote_flag.site.social_inbox.client_for(remote_flag.actor.content['inbox']) + client = remote_flag.site.social_inbox.client_for(remote_flag.actor&.content['inbox']) response = client.post(endpoint: '', body: remote_flag.content) raise 'No se pudo enviar el reporte' unless response.ok? diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 6f0d884c..23f4324f 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -18,12 +18,15 @@ class ActivityPub < ApplicationRecord belongs_to :site belongs_to :object, polymorphic: true belongs_to :actor + belongs_to :remote_flag, class_name: 'ActivityPub::RemoteFlag' has_many :activities validates :site_id, presence: true validates :object_id, presence: true validates :aasm_state, presence: true, inclusion: { in: %w[paused approved rejected reported removed] } + accepts_nested_attributes_for :remote_flag + # Encuentra la URI de un objeto # # @return [String, nil] @@ -83,6 +86,10 @@ class ActivityPub < ApplicationRecord # Solo podemos reportarla luego de rechazarla event :report do transitions from: :rejected, to: :reported + + before do + ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) + end end end end diff --git a/app/models/activity_pub/remote_flag.rb b/app/models/activity_pub/remote_flag.rb index b790c4b1..a302503b 100644 --- a/app/models/activity_pub/remote_flag.rb +++ b/app/models/activity_pub/remote_flag.rb @@ -5,6 +5,11 @@ class ActivityPub belongs_to :actor belongs_to :site + has_one :actor_moderation + has_many :activity_pubs + # XXX: source_type es obligatorio para el `through` + has_many :objects, through: :activity_pubs, source_type: 'ActivityPub::Object::Note' + # Genera la actividad a enviar def content { @@ -13,7 +18,7 @@ class ActivityPub 'type' => 'Flag', 'actor' => ENV.fetch('PANEL_ACTOR_ID') { "https://#{ENV['SUTTY']}/about.jsonld" }, 'content' => message.to_s, - 'object' => [ actor.uri ] + 'object' => [ actor.uri ] + objects.pluck(:uri) } end end diff --git a/config/locales/en.yml b/config/locales/en.yml index fe8798f4..0ca90aa7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -116,6 +116,7 @@ en: profile_id: ID profile_published: Published profile_summary: Summary + remote_flags: report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." moderation_queue: everything: 'Select all' diff --git a/config/locales/es.yml b/config/locales/es.yml index e22fdf57..40a1a18c 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -115,6 +115,7 @@ es: profile_id: ID profile_published: Publicada profile_summary: Presentación + remote_flags: report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." moderation_queue: everything: 'Seleccionar todo' diff --git a/db/migrate/20240305164653_change_remote_flags.rb b/db/migrate/20240305164653_change_remote_flags.rb new file mode 100644 index 00000000..258f3335 --- /dev/null +++ b/db/migrate/20240305164653_change_remote_flags.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Agrega relaciones en las remote flags +class ChangeRemoteFlags < ActiveRecord::Migration[6.1] + def up + add_column :activity_pubs, :remote_flag_id, :uuid, index: true, null: true + end + + def down + remove_column :activity_pubs, :remote_flag_id + end +end diff --git a/db/structure.sql b/db/structure.sql index ff6cf895..55a5ecb0 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -573,7 +573,8 @@ CREATE TABLE public.activity_pubs ( object_type character varying NOT NULL, aasm_state character varying NOT NULL, instance_id uuid, - actor_id uuid + actor_id uuid, + remote_flag_id uuid ); @@ -2695,6 +2696,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240229201155'), ('20240301181224'), ('20240301194154'), -('20240301202955'); +('20240301202955'), +('20240305164653'); From 054dbc31e03bb7df899e978b988b46a59e41ad18 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 5 Mar 2024 14:19:33 -0300 Subject: [PATCH 145/297] =?UTF-8?q?fix:=20no=20fallar=20si=20la=20activida?= =?UTF-8?q?d=20ya=20hab=C3=ADa=20cambiado=20de=20estado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 464a0ffe..548781fa 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -53,7 +53,7 @@ module Api instance.present? object.present? activity.present? - activity_pub.approve! + activity_pub.approve! if activity_pub.may_approve? end head :accepted @@ -69,7 +69,7 @@ module Api instance.present? object.present? activity.present? - activity_pub.reject! + activity_pub.reject! if activity_pub.may_reject? end head :accepted From 30fd6c28eec100ba8205f468d4fcf5f4f7e2cfea Mon Sep 17 00:00:00 2001 From: f Date: Tue, 5 Mar 2024 15:46:33 -0300 Subject: [PATCH 146/297] feat: enviar un reporte por actore --- app/controllers/activity_pubs_controller.rb | 14 ++++++++++++++ app/controllers/concerns/moderation_concern.rb | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index 37702d96..2b54cacd 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -28,6 +28,20 @@ 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 + 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) diff --git a/app/controllers/concerns/moderation_concern.rb b/app/controllers/concerns/moderation_concern.rb index 5b4c276d..3b9d818f 100644 --- a/app/controllers/concerns/moderation_concern.rb +++ b/app/controllers/concerns/moderation_concern.rb @@ -16,7 +16,7 @@ module ModerationConcern end def remote_flag_params(model) - { remote_flag_attributes: { id: model.remote_flag_id, message: '' } }.tap do |p| + { remote_flag_attributes: { id: model.remote_flag_id, message: ''.dup } }.tap do |p| p[:remote_flag_attributes][:site_id] = model.site_id p[:remote_flag_attributes][:actor_id] = model.actor_id From 5b53a9813f163ecffe592be15f5c146eca1fca61 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 5 Mar 2024 16:00:47 -0300 Subject: [PATCH 147/297] feat: enviar una sola vez el reporte remoto y volver a enviarlo si le agregamos reportes --- app/controllers/activity_pubs_controller.rb | 2 ++ app/jobs/activity_pub/remote_flag_job.rb | 6 ++++++ app/models/activity_pub.rb | 2 +- app/models/activity_pub/remote_flag.rb | 21 +++++++++++++++++++ app/models/actor_moderation.rb | 2 +- ...0240305184854_add_state_to_remote_flags.rb | 8 +++++++ db/structure.sql | 6 ++++-- 7 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20240305184854_add_state_to_remote_flags.rb diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index 2b54cacd..c8f86ef0 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -35,6 +35,8 @@ class ActivityPubsController < ApplicationController 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. diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb index 30796923..7d8131db 100644 --- a/app/jobs/activity_pub/remote_flag_job.rb +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -13,10 +13,16 @@ class ActivityPub self.priority = 30 def perform(remote_flag:) + return if remote_flag.can_queue? + + remote_flag.queue! + client = remote_flag.site.social_inbox.client_for(remote_flag.actor&.content['inbox']) response = client.post(endpoint: '', body: remote_flag.content) raise 'No se pudo enviar el reporte' unless response.ok? + + remote_flag.send! rescue Exception => e ExceptionNotifier.notify_exception(e, data: { remote_flag: remote_flag.id, response: response.parsed_response }) raise diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 23f4324f..b07fe790 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -88,7 +88,7 @@ class ActivityPub < ApplicationRecord transitions from: :rejected, to: :reported before do - ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) + ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting? end end end diff --git a/app/models/activity_pub/remote_flag.rb b/app/models/activity_pub/remote_flag.rb index a302503b..25f1b743 100644 --- a/app/models/activity_pub/remote_flag.rb +++ b/app/models/activity_pub/remote_flag.rb @@ -2,6 +2,27 @@ class ActivityPub class RemoteFlag < ApplicationRecord + include AASM + include AasmEventsConcern + + aasm do + state :waiting, initial: true + state :queued + state :sent + + event :queue do + transitions from: :waiting, to: :queued + end + + event :send do + transitions from: :queued, to: :sent + end + + event :resend do + transitions from: :sent, to: :waiting + end + end + belongs_to :actor belongs_to :site diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 421d4c6e..d7eea709 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -59,7 +59,7 @@ class ActorModeration < ApplicationRecord transitions from: %i[blocked], to: :reported before do - ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) + ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting? end end end diff --git a/db/migrate/20240305184854_add_state_to_remote_flags.rb b/db/migrate/20240305184854_add_state_to_remote_flags.rb new file mode 100644 index 00000000..7ff78dfb --- /dev/null +++ b/db/migrate/20240305184854_add_state_to_remote_flags.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Estado de los reportes remotos +class AddStateToRemoteFlags < ActiveRecord::Migration[6.1] + def change + add_column :activity_pub_remote_flags, :aasm_state, :string, null: false, default: 'waiting' + end +end diff --git a/db/structure.sql b/db/structure.sql index 55a5ecb0..97bd372e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -556,7 +556,8 @@ CREATE TABLE public.activity_pub_remote_flags ( updated_at timestamp(6) without time zone NOT NULL, site_id bigint, actor_id uuid, - message text + message text, + aasm_state character varying DEFAULT 'waiting'::character varying NOT NULL ); @@ -2697,6 +2698,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240301181224'), ('20240301194154'), ('20240301202955'), -('20240305164653'); +('20240305164653'), +('20240305184854'); From b1dee5d5676954610fd54e52ab00c0cfd9bb90e4 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 5 Mar 2024 16:10:06 -0300 Subject: [PATCH 148/297] fix: dejar de cargar datos de prueba --- app/controllers/application_controller.rb | 10 - .../moderation_queue_controller.rb | 8 - config/routes.rb | 1 - db/seeds/blocklists.yml | 7 - db/seeds/instances.yaml | 285 ------------------ db/seeds/moderation_queue.yaml | 153 ---------- db/seeds/remote_profile.yaml | 106 ------- 7 files changed, 570 deletions(-) delete mode 100644 db/seeds/blocklists.yml delete mode 100644 db/seeds/instances.yaml delete mode 100644 db/seeds/moderation_queue.yaml delete mode 100644 db/seeds/remote_profile.yaml diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b55176ec..05fa98e9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -27,16 +27,6 @@ class ApplicationController < ActionController::Base end private - # Traer datos de muestra de la cola de moderación - def dummy_data - @moderation_queue = YAML.safe_load(File.read(Rails.root.join('db', 'seeds', 'moderation_queue.yaml'))) - @remote_profile = YAML.safe_load(File.read(Rails.root.join('db', 'seeds', 'remote_profile.yaml'))) - @instances = YAML.safe_load(File.read(Rails.root.join('db', 'seeds', 'instances.yaml'))) - @blocklists= YAML.safe_load(File.read(Rails.root.join('db', 'seeds', 'blocklists.yml'))) - @moderation_queue.each do |activity| - activity['attributedTo'] = @remote_profile - end - end def notify_unconfirmed_email return unless current_usuarie diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index 6a628aaa..0df62499 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -4,8 +4,6 @@ class ModerationQueueController < ApplicationController # Cola de moderación viendo todo el sitio def index - dummy_data - session[:moderation_queue_filters] = params.permit(:state, :actor_state, :activity_pub_state) # @todo cambiar el estado por query @@ -14,10 +12,4 @@ class ModerationQueueController < ApplicationController @actor_moderations = rubanok_process(site.actor_moderations, with: ActorModerationProcessor) @moderation_queue = rubanok_process(site.activity_pubs, with: ActivityPubProcessor) end - - # todon.nl está usando /api/v2/instance - # mauve.moe usa /api/v1/instance - def instances - dummy_data - end end diff --git a/config/routes.rb b/config/routes.rb index 8809767b..054b7f4d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -63,7 +63,6 @@ Rails.application.routes.draw do post 'collaborate', to: 'collaborations#accept_collaboration' get 'moderation_queue', to: 'moderation_queue#index' - get 'instances', to: 'moderation_queue#instances' resources :instance_moderations, only: [] do patch :pause, to: 'instance_moderations#pause' diff --git a/db/seeds/blocklists.yml b/db/seeds/blocklists.yml deleted file mode 100644 index 54dfe6c9..00000000 --- a/db/seeds/blocklists.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -- id: gardenfence - title: Gardenfence - link: 'https://gardenfence.github.io/' -- id: lista - title: Lista - link: '#' diff --git a/db/seeds/instances.yaml b/db/seeds/instances.yaml deleted file mode 100644 index bf326832..00000000 --- a/db/seeds/instances.yaml +++ /dev/null @@ -1,285 +0,0 @@ ---- -- domain: todon.nl - title: Todon.nl - version: 4.2.3 - source_url: https://github.com/mastodon/mastodon - description: Radicaal linkse anti-autoritaire server. Voor anarchisten, socialisten, - (klimaat)activisten, LHBTQIA+, antiracisten, antifascisten, antikapitalisten, - intersectionelen, veganisten, mensenrechten, enz. - usage: - users: - active_month: 372 - thumbnail: - url: https://todon.nl/system/site_uploads/files/000/000/004/@1x/297e509bc8a81f62.png - blurhash: UXAw3zN4M|xsoga#WBay9DxntQRmITocofWE - versions: - "@1x": https://todon.nl/system/site_uploads/files/000/000/004/@1x/297e509bc8a81f62.png - "@2x": https://todon.nl/system/site_uploads/files/000/000/004/@2x/297e509bc8a81f62.png - languages: - - en - configuration: - urls: - streaming: wss://todon.nl - status: https://status.todon.eu - accounts: - max_featured_tags: 10 - statuses: - max_characters: 1312 - max_media_attachments: 4 - characters_reserved_per_url: 23 - media_attachments: - supported_mime_types: - - image/jpeg - - image/png - - image/gif - - image/heic - - image/heif - - image/webp - - image/avif - - video/webm - - video/mp4 - - video/quicktime - - video/ogg - - audio/wave - - audio/wav - - audio/x-wav - - audio/x-pn-wave - - audio/vnd.wave - - audio/ogg - - audio/vorbis - - audio/mpeg - - audio/mp3 - - audio/webm - - audio/flac - - audio/aac - - audio/m4a - - audio/x-m4a - - audio/mp4 - - audio/3gpp - - video/x-ms-asf - image_size_limit: 16777216 - image_matrix_limit: 33177600 - video_size_limit: 103809024 - video_frame_rate_limit: 120 - video_matrix_limit: 8294400 - polls: - max_options: 4 - max_characters_per_option: 50 - min_expiration: 300 - max_expiration: 2629746 - translation: - enabled: true - registrations: - enabled: false - approval_required: false - message: | -

¡No pasarán!

- -

Je kunt tijdelijk geen nieuw account op Todon.nl aanvragen.

- - - -

Ga naar joinmastodon.org of FediDB Network om een andere server te vinden.

- -

It is temporary not possible to request on account on Todon.nl.

- - - -

Go to joinmastodon.org or FediDB Network to find another server.

- url: - max_toot_chars: 1312 - contact: - email: todon@posteo.eu - account: - id: '1' - username: admin - acct: admin - display_name: "Admin \U0001F913 Todon.nl (mod)" - locked: false - bot: false - discoverable: false - group: false - created_at: '2017-04-28T00:00:00.000Z' - note: "

This account is used for \U0001F399 Todon.nl announcements and ⚖️ - moderation.

\U0001F6AB Don't follow this account when you are not - on Todon.nl.

New? First read our \U0001F469‍\U0001F3EB Todon 101 \U0001F469‍\U0001F393 - at https://wiki.todon.eu/todon/101

⚖️ - For all our moderators go to https://wiki.todon.nl/todon/moderators

\U0001F4DD Public toots from this account - are in English.

\U0001F515 Criticism is fine, but people who do false - accusations are muted.

✉ todon@posteo.eu

#nobot

" - url: https://todon.nl/@admin - uri: https://todon.nl/users/admin - avatar: https://todon.nl/system/accounts/avatars/000/000/001/original/2db61726225ed3e6.png - avatar_static: https://todon.nl/system/accounts/avatars/000/000/001/original/2db61726225ed3e6.png - header: https://todon.nl/system/accounts/headers/000/000/001/original/fb3a846cbc20aa09.png - header_static: https://todon.nl/system/accounts/headers/000/000/001/original/fb3a846cbc20aa09.png - followers_count: 3164 - following_count: 8 - statuses_count: 724 - last_status_at: '2024-01-12' - noindex: true - emojis: [] - roles: - - id: '3' - name: Admin - color: "#595aff" - fields: - - name: "\U0001F4DC Terms of Service" - value: wiki.todon.nl/todon/terms_en - verified_at: '2018-11-01T14:39:45.465+00:00' - - name: ℹ️ Wiki - value: wiki.todon.nl/todon/informatio - verified_at: '2018-11-01T14:40:54.679+00:00' - - name: "\U0001F4CA Status" - value: status.todon.eu - verified_at: '2023-10-26T20:38:30.185+00:00' - - name: "\U0001F4B3️ Donations" - value: wiki.todon.eu/todon/donations - verified_at: '2022-11-02T00:06:31.865+00:00' - rules: - - id: '1' - text: We do not accept racism (in all its forms, incl. hate against Muslims, antisemitism, - apartheid and casteism - see our Terms of Service for our complete definition). - - id: '2' - text: We do not accept hate against lesbians, gays, bisexuals, pansexuals, transgenders, - non-binary people, intersexual people, queer people in general, etc. - - id: '4' - text: Sexism, misogyny and hate against black women (misogynoir). - - id: '6' - text: We do not accept ableism (incl. COVID-19 denial/downplaying and anti-vax) - and body-shaming. - - id: '8' - text: We do not accept harassment and trolling. - - id: '10' - text: We also do not accept other forms of hate speech. - - id: '11' - text: We do not accept (sexual) abuse of minors, adults and animals (also not - virtual). - - id: '13' - text: We do not accept glorification of violence, calls for murder, death threats, - terrorism and militarism. - - id: '15' - text: We do not accept (neo)colonialism (incl. Zionism), imperialism in all forms - and nationalism (above all nationalism of nation states, incl. flags/symbols - of those on Todon.*, see our Terms of Service). - - id: '16' - text: We do not accept fascism, right-wing populism, and right-wing and religious - extremism. - - id: '17' - text: We do not accept evangelisation and other forms of religious propaganda - [local only], and extreme sects and cults. - - id: '19' - text: We do not accept Marxist-Leninists, Stalinists, Maoists or other followers - of extreme authoritarian (so called) communist/socialist ideologies/regimes - (aka tankies). - - id: '20' - text: We do not accept capitalists, including so called 'anarcho-capitalists' - (aka ancaps) and neoliberals. - - id: '21' - text: We do not accept anthropogenic climate change denial, downplaying the climate - crisis, greenwashing and deceptive climate solutions (like nuclear energy). - - id: '27' - text: We do not accept (right-wing) conspiracy 'theories', hoaxes, fake news and - other forms of disinformation. - - id: '28' - text: Another rule in our terms of service at wiki.todon.eu/todon/terms_en. Explain - in the final step. -- uri: mastodon.mauve.moe - title: Mauvestodon - short_description: Escape ship from centralized social media run by Mauve. - description: Chat about random techie and anarchist stuff. - email: contact@mauve.moe - version: 3.5.10 - urls: - streaming_api: wss://mastodon.mauve.moe - stats: - user_count: 12 - status_count: 3287 - domain_count: 11625 - thumbnail: https://mastodon.mauve.moe/system/site_uploads/files/000/000/001/original/mauvesoftwareinc.png - languages: - - en - registrations: false - approval_required: false - invites_enabled: true - configuration: - statuses: - max_characters: 500 - max_media_attachments: 4 - characters_reserved_per_url: 23 - media_attachments: - supported_mime_types: - - image/jpeg - - image/png - - image/gif - - video/webm - - video/mp4 - - video/quicktime - - video/ogg - - audio/wave - - audio/wav - - audio/x-wav - - audio/x-pn-wave - - audio/ogg - - audio/vorbis - - audio/mpeg - - audio/mp3 - - audio/webm - - audio/flac - - audio/aac - - audio/m4a - - audio/x-m4a - - audio/mp4 - - audio/3gpp - - video/x-ms-asf - image_size_limit: 10485760 - image_matrix_limit: 16777216 - video_size_limit: 41943040 - video_frame_rate_limit: 60 - video_matrix_limit: 2304000 - polls: - max_options: 4 - max_characters_per_option: 50 - min_expiration: 300 - max_expiration: 2629746 - contact_account: - id: '1' - username: admin - acct: admin - display_name: '' - locked: false - bot: false - discoverable: true - group: false - created_at: '2022-04-25T00:00:00.000Z' - note: '' - url: https://mastodon.mauve.moe/@admin - avatar: https://mastodon.mauve.moe/system/accounts/avatars/000/000/001/original/8c21e71667b48a95.png - avatar_static: https://mastodon.mauve.moe/system/accounts/avatars/000/000/001/original/8c21e71667b48a95.png - header: https://mastodon.mauve.moe/headers/original/missing.png - header_static: https://mastodon.mauve.moe/headers/original/missing.png - followers_count: 0 - following_count: 0 - statuses_count: 0 - last_status_at: '2023-01-30' - emojis: [] - fields: - - name: Alternatel Contact - value: @mauve - verified_at: - rules: [] diff --git a/db/seeds/moderation_queue.yaml b/db/seeds/moderation_queue.yaml deleted file mode 100644 index c7075c7e..00000000 --- a/db/seeds/moderation_queue.yaml +++ /dev/null @@ -1,153 +0,0 @@ ---- -- "@context": - - https://www.w3.org/ns/activitystreams - - ostatus: http://ostatus.org# - atomUri: ostatus:atomUri - inReplyToAtomUri: ostatus:inReplyToAtomUri - conversation: ostatus:conversation - sensitive: as:sensitive - toot: http://joinmastodon.org/ns# - votersCount: toot:votersCount - Hashtag: as:Hashtag - id: https://mastodon.mauve.moe/users/mauve/statuses/111462305634770041 - type: Note - summary: - inReplyTo: https://mastodon.mauve.moe/users/mauve/statuses/111461923538534886 - published: '2023-11-23T22:50:10Z' - url: https://mastodon.mauve.moe/@mauve/111462305634770041 - attributedTo: https://mastodon.mauve.moe/users/mauve - to: - - https://www.w3.org/ns/activitystreams#Public - cc: - - https://mastodon.mauve.moe/users/mauve/followers - - https://hypha.coop/about.jsonld - sensitive: false - atomUri: https://mastodon.mauve.moe/users/mauve/statuses/111462305634770041 - inReplyToAtomUri: https://mastodon.mauve.moe/users/mauve/statuses/111461923538534886 - conversation: tag:mastodon.mauve.moe,2023-11-23:objectId=551471:objectType=Conversation - content:

Follow @HyphaCoop@hypha.coop for our announcement post on the 5th!

-

- contentMap: - en:

Follow @HyphaCoop@hypha.coop for our announcement post on the 5th!

-

- attachment: [] - tag: - - type: Mention - href: https://hypha.coop/about.jsonld - name: "@dripline@hypha.coop" - - type: Hashtag - href: https://mastodon.mauve.moe/tags/p2p - name: "#p2p" - - type: Hashtag - href: https://mastodon.mauve.moe/tags/activitypub - name: "#activitypub" - - type: Hashtag - href: https://mastodon.mauve.moe/tags/fediverse - name: "#fediverse" - replies: - id: https://mastodon.mauve.moe/users/mauve/statuses/111462305634770041/replies - type: Collection - first: - type: CollectionPage - next: https://mastodon.mauve.moe/users/mauve/statuses/111462305634770041/replies?only_other_accounts=true&page=true - partOf: https://mastodon.mauve.moe/users/mauve/statuses/111462305634770041/replies - items: [] -- "@context": - - https://www.w3.org/ns/activitystreams - - "@language": es - sensitive: as:sensitive - type: Note - id: https://sutty.nl/lanzamiento-de-publicaciones-distribuidas-en-el-fediverso-a-trav%C3%A9s-de-sutty/ - summary: Lanzamiento de publicaciones distribuidas en el Fediverso a través de Sutty - published: '2023-12-04T21:53:05+00:00' - updated: '2023-12-05T20:41:34+00:00' - attributedTo: https://sutty.nl/about.jsonld - to: - - https://www.w3.org/ns/activitystreams#Public - cc: - - https://social.distributed.press/v1/@sutty@sutty.nl/followers - inReplyTo: https://hypha.coop/dripline/announcing-dp-social-inbox/ - sensitive: true - content: | -

Estamos felices y orgulloses de anunciar el lanzamiento de la funcionalidad que permite la publicación en el Fediverso de los artículos de todos los sitios creados a través de Sutty.

Gracias al trabajo conjunto con Distributed Press, Hypha y apoyado por la Filecoin Foundation for the Distributed Web, Sutty hace posible que la seguridad de tu sitio estático se combine con la rápida difusión de tu contenido a través de las redes sociales libres y descentralizadas que constituyen el Fediverso.

Esto se logró a través del desarollo y la integración de dos componentes, trabajados en forma conjunta y colaborativa:

    -
  1. Social Inbox, desarrollado principalmente Distributed Press. Aporta la funcionalidad de recibir artículos, responder y mencionar otras cuentas en el Fediverso.

  2. -
  3. Jekyll Activity Pub Plugin, desarrollado principalmente por Sutty. Permite integrar Social Inbox en todos los sitios estáticos generados en Jekyll, admitiendo así la publicación automática de contenido del sitio en el Fediverso.

  4. -

Sutty integra la funcionalidad completa en su CMS para sitios estáticos en Jekyll, permitiendo gestionarla desde una interfaz en continua mejora de su usabilidad.

Si todavía no estás familiarizade con estos nombres y conceptos, te invitamos a conocer más a continuación, en la sección “Para tecno-curioses”.

Qué significa

    -
  • Que si creás tu sitio web a través de Sutty, tenés nuevas posibilidades de difundir tus contenidos e interactuar en redes digitales.

  • -
  • Que tus artículos pueden ser publicados en las redes del Fediverso.

  • -
  • Que tu sitio tendrá un perfil o usuarie personalizable desde el panel en una instancia de Sutty propia.

  • -
  • Que les usuaries del Fediverso pueden seguir tus publicaciones.

  • -
  • Que les usuaries del Fediverso que te sigan podrán leer tus publicaciones, mencionarte y responderte.

  • -
  • Que podrás interactuar con les usuaries del Fediverse con las opciones de responderles y mencionarles.

  • -

Qué permite hacer

    -
  • Activar la publicación en el Fediverso para todos los sitios de Sutty.

  • -
  • Activar la publicación de los artículos que quieras en el Fediverso.

  • -
  • Responder comentarios desde los artículos de tu sitio en Sutty.

  • -
  • Personalizar la cuenta que Sutty crea automáticamente de tu sitio en el Fediverso.

  • -
  • Reportar o informar de usuaries o instancias abusivos mediante nuestro formulario de contacto.

  • -

Qué se viene

    -
  • Mejoras en la integración de las respuestas como comentarios en el sitio.

  • -
  • Incorporación de menciones desde el panel de Sutty.

  • -
  • Mejoras en la interfaz general del panel.

  • -
  • Nuevas implementaciones para una mejor moderación.

  • -
  • Mejor compatibilidad con diversas redes en el Fediverso (Mastodon+Glitch, Pleroma, Ktistec).

  • -
  • Mejoras que permitirán diferenciar el contenido a publicar en el Fediverso y en el sitio de Sutty.

  • -
  • La posibilidad de exportar tu cuenta a una instancia del Fediverso desde tu panel.

  • -
  • La posibilidad de que Sutty anuncie tu contenido y/o usuarie del Fediverse en forma automática para atraer seguidorxs. (Ahora, podés hacerlo a través de un formulario).

  • -
  • Acceder a la lista de seguidorxs y seguides desde tu panel.

  • -
  • Seguir, dejar de seguir, bloquear usuaries y/o instancias del Fediverso desde el panel.

  • -

¡Quiero usarlo!

Te invitamos a dar tus primeros pasos de la mano de nuestro tutorial.

Para tecno-curioses

Cómo funciona

Los sitios web y las redes sociales parecen ser especies distintas dentro del Universo de Internet. Al mismo tiempo, las redes sociales corporativas y concentradas como Instagram, Facebook, X (ex Twitter), entre otras, demostraron ser hostiles con algunos grupos o colectivos sociales en particular (censurando contenido, persiguiendo pezones, ocultando publicaciones por color de piel y de pelo, etc.) y con todes sus usuaries en general (vendiendo data en forma masiva, violando acuerdos de privacidad, eligiendo diseños de interfaz y uso que generan ansiedad y adicción, etc.). Pese a esto, siguen funcionando como espacios obligados a la hora de publicitar un emprendimiento o difundir noticias urgentes.

El Fediverso es una red federada, descentralizada y distribuida de redes sociales libres, cada una con sus características, preferencias, grupos de usuaries. Están diseñadas para facilitar el diálogo entre todas ellas. Es decir, para que los contenidos puedan ser visibles y se puedan generar respuestas entre usuaries, fomentando una cultura de participación y pluralidad de voces, basadas en estándares de desarrollo libre y que buscan ser éticos antes que con fines de lucro sin fin.

Los sitios web siguen siendo formatos para medios de comunicación que, debido a sus características, favorecen la difusión de contenidos como artículos multimedia. Permiten adecuar un estilo a una identidad visual del medio, mantener secciones y contenido institucional variado, entre otras cosas.

Las redes sociales se destacan por sus características de inmediatez, favoreciendo un flujo dialógico en tiempo real con otros tiempos de atención y características de navegación que lo hacen más breve, rápido, a veces efímero. Los medios de comunicación (personas o emprendimientos mediáticos) suelen utilizarlos para llamar la atención sobre contenidos publicados en sus sitios, apostando a la divulgación rápida y las discusiones que puedan darse entre usuaries.

La funcionalidad que desarrollamos en Sutty contempla los casos de uso en los que un contenido quiera ser compartido a más personas, en menor tiempo, con la posibilidad de generar diálogos. Las particularidades de nuestros sitios y redes sociales libres generan condiciones favorables para la libertad de expresión, que preferimos llamar Derecho a la Comunicación, evadiendo las variadas y cada vez más sofisticadas formas de censura de las plataformas corporativas tradicionales. Un contenido reproducido en varios lugares al mismo tiempo ayuda a su divulgación y es ideal para aquellas voces y discursos contrahegemónicos en la web y su supervivencia al paso del tiempo, preservando la memoria popular.

Cómo funciona el Fediverso en la moderación

El Fediverso intenta funcionar como comunidades en línea interconectadas que se autogobiernan en las formas de cuidados colectivos. Así, cada instancia podría ser algo así como un municipio que aloja diferentes cuentas/usuaries bajo unas reglas consensuadas y que pueden ser puestas en discusión si fuera necesario. De esta forma, es posible regular la circulación de contenidos fascistas y discursos de odio que puedan dañar no solamente la participación de diverses usuaries sino también su salud.

Para ello, cada instancia elige sus formas de moderación y puede excluir otras instancias con denuncias previas de contenidos antidemocráticos, odiantes o contrarios a los valores y cuidados de sus habitantes.

En Sutty en particular, nos interesan las estrategias y los mecanismos de cuidados colectivos, por lo que seguimos diseñando modelos que permitan sostenerlos en nuestras tecnologías. Podés revisar nuestros términos y condiciones, política de privacidad y acuerdos de convivencia para más información.

¿Te interesa participar?

Si sos parte de una organización social, grupo de activismo o colectivo social que pensás que podría beneficiarse de estas características, te invitamos a contactarnos a través de nuestro formulario. Estamos busando mejorar los usos de las tecnologías para ustedes y valoramos sus experiencias.

Otras posibilidades de integración de Social Inbox en sitios estáticos

Si te interesa incorporar esta funcionalidad para otros gestores de sitios estáticos, no dudes en contactarnos. Además, mantenete al tanto de las novedades que compartimos en https://dweb.sutty.nl y en nuestro blog https://sutty.nl/blog

Recomendado para saber más

- name: Lanzamiento de publicaciones distribuidas en el Fediverso a través de Sutty - contentMap: - es: | -

Estamos felices y orgulloses de anunciar el lanzamiento de la funcionalidad que permite la publicación en el Fediverso de los artículos de todos los sitios creados a través de Sutty.

Gracias al trabajo conjunto con Distributed Press, Hypha y apoyado por la Filecoin Foundation for the Distributed Web, Sutty hace posible que la seguridad de tu sitio estático se combine con la rápida difusión de tu contenido a través de las redes sociales libres y descentralizadas que constituyen el Fediverso.

Esto se logró a través del desarollo y la integración de dos componentes, trabajados en forma conjunta y colaborativa:

    -
  1. Social Inbox, desarrollado principalmente Distributed Press. Aporta la funcionalidad de recibir artículos, responder y mencionar otras cuentas en el Fediverso.

  2. -
  3. Jekyll Activity Pub Plugin, desarrollado principalmente por Sutty. Permite integrar Social Inbox en todos los sitios estáticos generados en Jekyll, admitiendo así la publicación automática de contenido del sitio en el Fediverso.

  4. -

Sutty integra la funcionalidad completa en su CMS para sitios estáticos en Jekyll, permitiendo gestionarla desde una interfaz en continua mejora de su usabilidad.

Si todavía no estás familiarizade con estos nombres y conceptos, te invitamos a conocer más a continuación, en la sección “Para tecno-curioses”.

Qué significa

    -
  • Que si creás tu sitio web a través de Sutty, tenés nuevas posibilidades de difundir tus contenidos e interactuar en redes digitales.

  • -
  • Que tus artículos pueden ser publicados en las redes del Fediverso.

  • -
  • Que tu sitio tendrá un perfil o usuarie personalizable desde el panel en una instancia de Sutty propia.

  • -
  • Que les usuaries del Fediverso pueden seguir tus publicaciones.

  • -
  • Que les usuaries del Fediverso que te sigan podrán leer tus publicaciones, mencionarte y responderte.

  • -
  • Que podrás interactuar con les usuaries del Fediverse con las opciones de responderles y mencionarles.

  • -

Qué permite hacer

    -
  • Activar la publicación en el Fediverso para todos los sitios de Sutty.

  • -
  • Activar la publicación de los artículos que quieras en el Fediverso.

  • -
  • Responder comentarios desde los artículos de tu sitio en Sutty.

  • -
  • Personalizar la cuenta que Sutty crea automáticamente de tu sitio en el Fediverso.

  • -
  • Reportar o informar de usuaries o instancias abusivos mediante nuestro formulario de contacto.

  • -

Qué se viene

    -
  • Mejoras en la integración de las respuestas como comentarios en el sitio.

  • -
  • Incorporación de menciones desde el panel de Sutty.

  • -
  • Mejoras en la interfaz general del panel.

  • -
  • Nuevas implementaciones para una mejor moderación.

  • -
  • Mejor compatibilidad con diversas redes en el Fediverso (Mastodon+Glitch, Pleroma, Ktistec).

  • -
  • Mejoras que permitirán diferenciar el contenido a publicar en el Fediverso y en el sitio de Sutty.

  • -
  • La posibilidad de exportar tu cuenta a una instancia del Fediverso desde tu panel.

  • -
  • La posibilidad de que Sutty anuncie tu contenido y/o usuarie del Fediverse en forma automática para atraer seguidorxs. (Ahora, podés hacerlo a través de un formulario).

  • -
  • Acceder a la lista de seguidorxs y seguides desde tu panel.

  • -
  • Seguir, dejar de seguir, bloquear usuaries y/o instancias del Fediverso desde el panel.

  • -

¡Quiero usarlo!

Te invitamos a dar tus primeros pasos de la mano de nuestro tutorial.

Para tecno-curioses

Cómo funciona

Los sitios web y las redes sociales parecen ser especies distintas dentro del Universo de Internet. Al mismo tiempo, las redes sociales corporativas y concentradas como Instagram, Facebook, X (ex Twitter), entre otras, demostraron ser hostiles con algunos grupos o colectivos sociales en particular (censurando contenido, persiguiendo pezones, ocultando publicaciones por color de piel y de pelo, etc.) y con todes sus usuaries en general (vendiendo data en forma masiva, violando acuerdos de privacidad, eligiendo diseños de interfaz y uso que generan ansiedad y adicción, etc.). Pese a esto, siguen funcionando como espacios obligados a la hora de publicitar un emprendimiento o difundir noticias urgentes.

El Fediverso es una red federada, descentralizada y distribuida de redes sociales libres, cada una con sus características, preferencias, grupos de usuaries. Están diseñadas para facilitar el diálogo entre todas ellas. Es decir, para que los contenidos puedan ser visibles y se puedan generar respuestas entre usuaries, fomentando una cultura de participación y pluralidad de voces, basadas en estándares de desarrollo libre y que buscan ser éticos antes que con fines de lucro sin fin.

Los sitios web siguen siendo formatos para medios de comunicación que, debido a sus características, favorecen la difusión de contenidos como artículos multimedia. Permiten adecuar un estilo a una identidad visual del medio, mantener secciones y contenido institucional variado, entre otras cosas.

Las redes sociales se destacan por sus características de inmediatez, favoreciendo un flujo dialógico en tiempo real con otros tiempos de atención y características de navegación que lo hacen más breve, rápido, a veces efímero. Los medios de comunicación (personas o emprendimientos mediáticos) suelen utilizarlos para llamar la atención sobre contenidos publicados en sus sitios, apostando a la divulgación rápida y las discusiones que puedan darse entre usuaries.

La funcionalidad que desarrollamos en Sutty contempla los casos de uso en los que un contenido quiera ser compartido a más personas, en menor tiempo, con la posibilidad de generar diálogos. Las particularidades de nuestros sitios y redes sociales libres generan condiciones favorables para la libertad de expresión, que preferimos llamar Derecho a la Comunicación, evadiendo las variadas y cada vez más sofisticadas formas de censura de las plataformas corporativas tradicionales. Un contenido reproducido en varios lugares al mismo tiempo ayuda a su divulgación y es ideal para aquellas voces y discursos contrahegemónicos en la web y su supervivencia al paso del tiempo, preservando la memoria popular.

Cómo funciona el Fediverso en la moderación

El Fediverso intenta funcionar como comunidades en línea interconectadas que se autogobiernan en las formas de cuidados colectivos. Así, cada instancia podría ser algo así como un municipio que aloja diferentes cuentas/usuaries bajo unas reglas consensuadas y que pueden ser puestas en discusión si fuera necesario. De esta forma, es posible regular la circulación de contenidos fascistas y discursos de odio que puedan dañar no solamente la participación de diverses usuaries sino también su salud.

Para ello, cada instancia elige sus formas de moderación y puede excluir otras instancias con denuncias previas de contenidos antidemocráticos, odiantes o contrarios a los valores y cuidados de sus habitantes.

En Sutty en particular, nos interesan las estrategias y los mecanismos de cuidados colectivos, por lo que seguimos diseñando modelos que permitan sostenerlos en nuestras tecnologías. Podés revisar nuestros términos y condiciones, política de privacidad y acuerdos de convivencia para más información.

¿Te interesa participar?

Si sos parte de una organización social, grupo de activismo o colectivo social que pensás que podría beneficiarse de estas características, te invitamos a contactarnos a través de nuestro formulario. Estamos busando mejorar los usos de las tecnologías para ustedes y valoramos sus experiencias.

Otras posibilidades de integración de Social Inbox en sitios estáticos

Si te interesa incorporar esta funcionalidad para otros gestores de sitios estáticos, no dudes en contactarnos. Además, mantenete al tanto de las novedades que compartimos en https://dweb.sutty.nl y en nuestro blog https://sutty.nl/blog

Recomendado para saber más

- attachment: - - type: Document - mediaType: image/png - url: https://sutty.nl/public/8r7b6ohqy6xzgngxbol6337q8jj9/milestone_2_activity_pub_2.png - name: Botones de colores para activar la "Web Disribuida" y el "Fediverso". diff --git a/db/seeds/remote_profile.yaml b/db/seeds/remote_profile.yaml deleted file mode 100644 index 1a670d6b..00000000 --- a/db/seeds/remote_profile.yaml +++ /dev/null @@ -1,106 +0,0 @@ ---- -"@context": -- https://www.w3.org/ns/activitystreams -- https://w3id.org/security/v1 -- manuallyApprovesFollowers: as:manuallyApprovesFollowers - toot: http://joinmastodon.org/ns# - featured: - "@id": toot:featured - "@type": "@id" - featuredTags: - "@id": toot:featuredTags - "@type": "@id" - alsoKnownAs: - "@id": as:alsoKnownAs - "@type": "@id" - movedTo: - "@id": as:movedTo - "@type": "@id" - schema: http://schema.org# - PropertyValue: schema:PropertyValue - value: schema:value - discoverable: toot:discoverable - Device: toot:Device - Ed25519Signature: toot:Ed25519Signature - Ed25519Key: toot:Ed25519Key - Curve25519Key: toot:Curve25519Key - EncryptedMessage: toot:EncryptedMessage - publicKeyBase64: toot:publicKeyBase64 - deviceId: toot:deviceId - claim: - "@type": "@id" - "@id": toot:claim - fingerprintKey: - "@type": "@id" - "@id": toot:fingerprintKey - identityKey: - "@type": "@id" - "@id": toot:identityKey - devices: - "@type": "@id" - "@id": toot:devices - messageFranking: toot:messageFranking - messageType: toot:messageType - cipherText: toot:cipherText - suspended: toot:suspended - focalPoint: - "@container": "@list" - "@id": toot:focalPoint -id: https://mastodon.mauve.moe/users/mauve -type: Person -following: https://mastodon.mauve.moe/users/mauve/following -followers: https://mastodon.mauve.moe/users/mauve/followers -inbox: https://mastodon.mauve.moe/users/mauve/inbox -outbox: https://mastodon.mauve.moe/users/mauve/outbox -featured: https://mastodon.mauve.moe/users/mauve/collections/featured -featuredTags: https://mastodon.mauve.moe/users/mauve/collections/tags -preferredUsername: mauve -name: "Mauve \U0001F441\U0001F49C" -summary: "

Occult Enby that's making local-first software with peer to peer - protocols, mesh networks, and the web.

Also exploring what a local-first - cyberspace might look like in my spare time.

" -url: https://mastodon.mauve.moe/@mauve -manuallyApprovesFollowers: false -discoverable: true -published: '2022-04-25T00:00:00Z' -devices: https://mastodon.mauve.moe/users/mauve/collections/devices -alsoKnownAs: -- https://infosec.exchange/users/RangerMauve -publicKey: - id: https://mastodon.mauve.moe/users/mauve#main-key - owner: https://mastodon.mauve.moe/users/mauve - publicKeyPem: | - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxjxu6bRQOjH4caQu7JgZ - umIWFeX0ZdbVnofElev2d9JByqcDoWhmaks3RYdW71RDPNrr0JxqZvUbIw9kQBng - 7iQ9YTcXTdJ/N9CQoB22msffYkEIw4ilehCDXdchNs4aoVAUwI8IhkM0p/itz6gK - 75C3CQv74Y7rHUJC8ob2p4KUwRUyhgzyhp8QWwCAn/RZ28wP8EbjWF9IskMRo9vq - WUX+Io6hpADRkSwZGoOSW2zxCEBVco6tRmABTte8I0WcAucLyMEyfGMlUvxRew4D - zAWoEBS8SyqM68vUabbZYLns6kya34tvsf1NkvajDGrfgU3D0LlGX++tOa6N9Pkf - XwIDAQAB - -----END PUBLIC KEY----- -tag: [] -attachment: -- type: PropertyValue - name: Pronouns - value: they/them/it -- type: PropertyValue - name: Email - value: mauve@mauve.moe -- type: PropertyValue - name: Matrix - value: @mauve:mauve.moe -- type: PropertyValue - name: Github/Twitter - value: "@RangerMauve" -endpoints: - sharedInbox: https://mastodon.mauve.moe/inbox -icon: - type: Image - mediaType: image/png - url: https://mastodon.mauve.moe/system/accounts/avatars/000/000/002/original/e4b910cee121b1b8.png -image: - type: Image - mediaType: image/png - url: https://mastodon.mauve.moe/system/accounts/headers/000/000/002/original/a96f990025091662.png From 2df9f721cd4b9f6831f8f891aad1f4c08785f295 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 5 Mar 2024 16:10:53 -0300 Subject: [PATCH 149/297] =?UTF-8?q?feat:=20ver=20los=20art=C3=ADculos=20de?= =?UTF-8?q?=20le=20actore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/actor_moderations_controller.rb | 1 + app/views/actor_moderations/show.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 56adda4a..00874321 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -20,6 +20,7 @@ class ActorModerationsController < ApplicationController # Ver el perfil remoto def show @remote_profile = actor_moderation.actor.content + @moderation_queue = site.activity_pubs.where(actor_id: actor_moderation.actor_id) end def action_on_several diff --git a/app/views/actor_moderations/show.haml b/app/views/actor_moderations/show.haml index 7b62f672..633c1be5 100644 --- a/app/views/actor_moderations/show.haml +++ b/app/views/actor_moderations/show.haml @@ -4,5 +4,5 @@ = render 'components/actor', remote_profile: @remote_profile .col-12.col-md-8 = render 'components/profiles_btn_box', actor_moderation: @actor_moderation - -# - = render 'moderation_queue/comments', moderation_queue: @moderation_queue + .col-12.col-md-8 + = render 'moderation_queue/comments', moderation_queue: @moderation_queue From 0da69de6a73543d90c032b09bd04b74be5c8cbef Mon Sep 17 00:00:00 2001 From: f Date: Tue, 5 Mar 2024 16:24:16 -0300 Subject: [PATCH 150/297] feat: filtros comunes --- app/controllers/actor_moderations_controller.rb | 3 ++- .../concerns/moderation_filters_concern.rb | 15 +++++++++++++++ app/controllers/moderation_queue_controller.rb | 4 ++-- app/processors/instance_moderation_processor.rb | 4 ++-- app/views/components/_comments_show_submenu.haml | 2 +- app/views/components/_dropdown_item.haml | 2 +- app/views/components/_instances_filters.haml | 2 +- app/views/components/_instances_show_submenu.haml | 7 ++++--- app/views/components/_profiles_show_submenu.haml | 2 +- app/views/moderation_queue/_instances.haml | 2 +- 10 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 app/controllers/concerns/moderation_filters_concern.rb diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 00874321..6b924677 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -3,6 +3,7 @@ # Gestiona la cola de moderación de actores class ActorModerationsController < ApplicationController include ModerationConcern + include ModerationFiltersConcern ActorModeration.events.each do |actor_event| define_method(actor_event) do @@ -20,7 +21,7 @@ class ActorModerationsController < ApplicationController # Ver el perfil remoto def show @remote_profile = actor_moderation.actor.content - @moderation_queue = site.activity_pubs.where(actor_id: actor_moderation.actor_id) + @moderation_queue = rubanok_process(site.activity_pubs.where(actor_id: actor_moderation.actor_id), with: ActivityPubProcessor) end def action_on_several diff --git a/app/controllers/concerns/moderation_filters_concern.rb b/app/controllers/concerns/moderation_filters_concern.rb new file mode 100644 index 00000000..25293a4f --- /dev/null +++ b/app/controllers/concerns/moderation_filters_concern.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ModerationFiltersConcern + extend ActiveSupport::Concern + + included do + before_action :store_filters_in_session!, only: %i[index show] + + private + + def store_filters_in_session! + session[:moderation_queue_filters] = params.permit(:instance_state, :actor_state, :activity_pub_state) + end + end +end diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index 0df62499..eebd9eae 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -2,10 +2,10 @@ # Cola de moderación de ActivityPub class ModerationQueueController < ApplicationController + include ModerationFiltersConcern + # Cola de moderación viendo todo el sitio def index - session[:moderation_queue_filters] = params.permit(:state, :actor_state, :activity_pub_state) - # @todo cambiar el estado por query @activity_pubs = site.activity_pubs @instance_moderations = rubanok_process(site.instance_moderations, with: InstanceModerationProcessor) diff --git a/app/processors/instance_moderation_processor.rb b/app/processors/instance_moderation_processor.rb index 908beaf7..eb9a7c8b 100644 --- a/app/processors/instance_moderation_processor.rb +++ b/app/processors/instance_moderation_processor.rb @@ -6,7 +6,7 @@ class InstanceModerationProcessor < Rubanok::Processor raw.includes(:instance).order('activity_pub_instances.hostname') end - map :state, activate_always: true do |state: 'paused'| - raw.where(aasm_state: state) + map :instance_state, activate_always: true do |instance_state: 'paused'| + raw.where(aasm_state: instance_state) end end diff --git a/app/views/components/_comments_show_submenu.haml b/app/views/components/_comments_show_submenu.haml index eb037975..60c02501 100644 --- a/app/views/components/_comments_show_submenu.haml +++ b/app/views/components/_comments_show_submenu.haml @@ -1,4 +1,4 @@ - ActivityPub.states.each do |state| = render 'components/dropdown_item', text: t(".submenu_#{state}", count: activity_pubs.unscope(where: :aasm_state).public_send(state).count), - path: site_moderation_queue_path(filter_states(activity_pub_state: state)) + path: filter_states(activity_pub_state: state) diff --git a/app/views/components/_dropdown_item.haml b/app/views/components/_dropdown_item.haml index 3f79403d..e5b16950 100644 --- a/app/views/components/_dropdown_item.haml +++ b/app/views/components/_dropdown_item.haml @@ -1,4 +1,4 @@ -# @param :text [String] Contenido del link - @param :path [String] Link + @param :path [String,Hash] Link = link_to text, path, class: 'dropdown-item', data: { target: 'dropdown.item' } diff --git a/app/views/components/_instances_filters.haml b/app/views/components/_instances_filters.haml index 9e8509c4..2c23fd72 100644 --- a/app/views/components/_instances_filters.haml +++ b/app/views/components/_instances_filters.haml @@ -6,4 +6,4 @@ = render 'components/instances_checked_submenu', form_id: form_id, current_state: current_state = render 'components/dropdown', text: t('.text_show') do - = render 'components/instances_show_submenu', site: site + = render 'components/instances_show_submenu', instance_moderations: instance_moderations diff --git a/app/views/components/_instances_show_submenu.haml b/app/views/components/_instances_show_submenu.haml index 811d65c7..c56df547 100644 --- a/app/views/components/_instances_show_submenu.haml +++ b/app/views/components/_instances_show_submenu.haml @@ -1,3 +1,4 @@ -= render 'components/dropdown_item', text: t('.submenu_paused', count: site.instance_moderations.paused.count), path: site_moderation_queue_path(state: 'paused') -= render 'components/dropdown_item', text: t('.submenu_allowed', count: site.instance_moderations.allowed.count), path: site_moderation_queue_path(state: 'allowed') -= render 'components/dropdown_item', text: t('.submenu_blocked', count: site.instance_moderations.blocked.count), path: site_moderation_queue_path(state: 'blocked') +- InstanceModeration.states.each do |state| + = render 'components/dropdown_item', + text: t(".submenu_#{state}", count: instance_moderations.unscope(where: :aasm_state).public_send(state).count), + path: filter_states(instance_state: state) diff --git a/app/views/components/_profiles_show_submenu.haml b/app/views/components/_profiles_show_submenu.haml index 0209ef2f..99694698 100644 --- a/app/views/components/_profiles_show_submenu.haml +++ b/app/views/components/_profiles_show_submenu.haml @@ -1,4 +1,4 @@ - ActorModeration.states.each do |actor_state| = render 'components/dropdown_item', text: t(".submenu_#{actor_state}", count: actor_moderations.unscope(where: :aasm_state).public_send(actor_state).count), - path: site_moderation_queue_path(filter_states(actor_state: actor_state)) + path: filter_states(actor_state: actor_state) diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index d9db967f..3954ce65 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -7,7 +7,7 @@ = render 'components/select_all', id: 'instances', form_id: form_id .col-11 -# Filtros - = render 'components/instances_filters', site: site, form_id: form_id + = render 'components/instances_filters', instance_moderations: instance_moderations, form_id: form_id .col-12 - if instance_moderations.count.zero? From 44ef583a23dcc2ac29b728ef7990c605bf1c1355 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 14:12:04 -0300 Subject: [PATCH 151/297] fix: send es un nombre reservado --- app/jobs/activity_pub/remote_flag_job.rb | 2 +- app/models/activity_pub/remote_flag.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb index 7d8131db..26db937a 100644 --- a/app/jobs/activity_pub/remote_flag_job.rb +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -22,7 +22,7 @@ class ActivityPub raise 'No se pudo enviar el reporte' unless response.ok? - remote_flag.send! + remote_flag.report! rescue Exception => e ExceptionNotifier.notify_exception(e, data: { remote_flag: remote_flag.id, response: response.parsed_response }) raise diff --git a/app/models/activity_pub/remote_flag.rb b/app/models/activity_pub/remote_flag.rb index 25f1b743..76143414 100644 --- a/app/models/activity_pub/remote_flag.rb +++ b/app/models/activity_pub/remote_flag.rb @@ -14,7 +14,7 @@ class ActivityPub transitions from: :waiting, to: :queued end - event :send do + event :report do transitions from: :queued, to: :sent end From 19d998086c0f7e1d2813b68f3f8d4c31c16d75d3 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 14:31:32 -0300 Subject: [PATCH 152/297] fix: raise --- app/models/activity_pub.rb | 6 ++---- app/models/actor_moderation.rb | 6 +++--- app/models/fediblock_state.rb | 4 ++-- app/models/instance_moderation.rb | 6 +++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index b07fe790..3887d512 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -68,8 +68,7 @@ class ActivityPub < ApplicationRecord transitions from: %i[paused], to: :approved before do - raise AASM::InvalidTransition unless - site.social_inbox.inbox.accept(id: object.uri).ok? + raise unless site.social_inbox.inbox.accept(id: object.uri).ok? end end @@ -78,8 +77,7 @@ class ActivityPub < ApplicationRecord transitions from: %i[paused approved], to: :rejected before do - raise AASM::InvalidTransition unless - site.social_inbox.inbox.reject(id: object.uri).ok? + raise unless site.social_inbox.inbox.reject(id: object.uri).ok? end end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index d7eea709..5d44a021 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -65,21 +65,21 @@ class ActorModeration < ApplicationRecord end def pause_remotely! - raise AASM::InvalidTransition unless + raise unless actor.mention && site.social_inbox.allowlist.delete(list: [actor.mention]).ok? && site.social_inbox.blocklist.delete(list: [actor.mention]).ok? end def allow_remotely! - raise AASM::InvalidTransition unless + raise unless actor.mention && site.social_inbox.allowlist.post(list: [actor.mention]).ok? && site.social_inbox.blocklist.delete(list: [actor.mention]).ok? end def block_remotely! - raise AASM::InvalidTransition unless + raise unless actor.mention && site.social_inbox.allowlist.delete(list: [actor.mention]).ok? && site.social_inbox.blocklist.post(list: [actor.mention]).ok? diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index 180a45b5..214e2f5e 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -89,14 +89,14 @@ class FediblockState < ApplicationRecord # Al deshabilitar, las instancias pasan a ser analizadas caso por caso def disable_remotely! - raise AASM::InvalidTransition unless + raise unless site.social_inbox.blocklist.delete(list: list_names).ok? && site.social_inbox.allowlist.delete(list: list_names).ok? end # Al habilitar, se bloquean todas las instancias de la lista def enable_remotely! - raise AASM::InvalidTransition unless + raise unless site.social_inbox.blocklist.post(list: list_names).ok? && site.social_inbox.allowlist.delete(list: list_names).ok? end diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 7447cc89..ef04b7ff 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -60,7 +60,7 @@ class InstanceModeration < ApplicationRecord # # @return [Boolean] def pause_remotely! - raise AASM::InvalidTransition unless + raise unless site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? end @@ -69,7 +69,7 @@ class InstanceModeration < ApplicationRecord # # @return [Boolean] def block_remotely! - raise AASM::InvalidTransition unless + raise unless site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? && site.social_inbox.blocklist.post(list: [instance.list_name]).ok? end @@ -78,7 +78,7 @@ class InstanceModeration < ApplicationRecord # # @return [Boolean] def allow_remotely! - raise AASM::InvalidTransition unless + raise unless site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && site.social_inbox.allowlist.post(list: [instance.list_name]).ok? end From fbd741960bd178b473d194980f16e63f1e190e8f Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 14:42:08 -0300 Subject: [PATCH 153/297] =?UTF-8?q?fix:=20crear=20hooks=20y=20blocklists?= =?UTF-8?q?=20despu=C3=A9s=20de=20publicar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/deploy_social_distributed_press.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index eec8189b..b7525dca 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -7,9 +7,6 @@ class DeploySocialDistributedPress < Deploy # Solo luego de publicar remotamente DEPENDENCIES = %i[deploy_distributed_press deploy_rsync deploy_full_rsync].freeze - after_save :create_hooks! - after_create :enable_fediblocks! - # Envía las notificaciones def deploy(output: false) with_tempfile(site.private_key_pem) do |file| @@ -18,6 +15,10 @@ class DeploySocialDistributedPress < Deploy run %(bundle exec jekyll notify --trace --key #{key} --destination "#{dest}"), output: output end + + + create_hooks! + enable_fediblocks! end # Igual que DeployLocal From ea34b2c676e079948cbf51a05a039d9383f2f828 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 15:03:17 -0300 Subject: [PATCH 154/297] fix: hostnames --- app/models/fediblock_state.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index 214e2f5e..b5258fb6 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -82,8 +82,8 @@ class FediblockState < ApplicationRecord # @return [Array] def list_names - @list_names ||= fediblock.instances.map do |instance| - "@*@#{instance}" + @list_names ||= fediblock.hostnames.map do |hostname| + "@*@#{hostname}" end end From 38ba8795debf571c1e6173116f56d4c594f2ba7d Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 15:36:00 -0300 Subject: [PATCH 155/297] fix: el reporte remoto es opcional --- app/models/activity_pub.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 3887d512..33cd4d45 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -18,7 +18,7 @@ class ActivityPub < ApplicationRecord belongs_to :site belongs_to :object, polymorphic: true belongs_to :actor - belongs_to :remote_flag, class_name: 'ActivityPub::RemoteFlag' + belongs_to :remote_flag, optional: true, class_name: 'ActivityPub::RemoteFlag' has_many :activities validates :site_id, presence: true From b334d49654a295b439de085ef0cd0352c7f47645 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 15:41:46 -0300 Subject: [PATCH 156/297] fix: actualizar al estado remoto --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 548781fa..c40857e0 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -53,7 +53,8 @@ module Api instance.present? object.present? activity.present? - activity_pub.approve! if activity_pub.may_approve? + activity_pub.update(aasm_state: 'approved') + activity.update_activity_pub_state! end head :accepted @@ -69,7 +70,7 @@ module Api instance.present? object.present? activity.present? - activity_pub.reject! if activity_pub.may_reject? + activity_pub.update(aasm_state: 'rejected') end head :accepted From 5c22015fe2d7ca3c418005ba4e8a989560270640 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 15:47:27 -0300 Subject: [PATCH 157/297] fix: el reporte remoto es opcional --- app/models/actor_moderation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 5d44a021..e06ffbb1 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -9,7 +9,7 @@ class ActorModeration < ApplicationRecord IGNORED_STATES = [] belongs_to :site - belongs_to :remote_flag, class_name: 'ActivityPub::RemoteFlag' + belongs_to :remote_flag, optional: true, class_name: 'ActivityPub::RemoteFlag' belongs_to :actor, class_name: 'ActivityPub::Actor' accepts_nested_attributes_for :remote_flag From 019101ba1e07448eceec8e390070f632420d72bc Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 15:54:47 -0300 Subject: [PATCH 158/297] feat: almacenar el perfil de le actore en la base de datos --- app/jobs/activity_pub/actor_fetch_job.rb | 2 +- app/models/activity_pub/actor.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/actor_fetch_job.rb b/app/jobs/activity_pub/actor_fetch_job.rb index 71107151..1190e936 100644 --- a/app/jobs/activity_pub/actor_fetch_job.rb +++ b/app/jobs/activity_pub/actor_fetch_job.rb @@ -19,7 +19,7 @@ class ActivityPub return unless response.ok? return if response.miss? && actor.content.present? - actor.update(content: FastJsonparser.parse(response.body)) + actor.object.update(content: FastJsonparser.parse(response.body)) end end end diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index fe6052bf..0a39dcde 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -25,5 +25,13 @@ class ActivityPub @mention ||= "@#{content['preferredUsername']}@#{instance.hostname}" end + + def object + @object ||= ActivityPub::Object::Person.find_or_initialize_by(uri: uri) + end + + def content + object.content + end end end From 5f5b929aa89098726ddb55098756d01e060fd9dc Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 15:59:48 -0300 Subject: [PATCH 159/297] fix: traducciones --- config/locales/en.yml | 17 ++++++++--------- config/locales/es.yml | 17 ++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 0ca90aa7..0f010c89 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -50,6 +50,14 @@ en: pm: pm format: '%-I:%M %p' components: + actor: + user: Username + profile: Profile + profile_name: Profile name + preferred_name: Name in Fediverse + profile_id: ID + profile_published: Published + profile_summary: Summary block_list: know_more: Know more instances_blocked: Instances blocked @@ -107,15 +115,6 @@ en: text_allow: Always approve text_block: Block text_report: Report - actor_moderations: - show: - user: Username - profile: Profile - profile_name: Profile name - preferred_name: Name in Fediverse - profile_id: ID - profile_published: Published - profile_summary: Summary remote_flags: report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." moderation_queue: diff --git a/config/locales/es.yml b/config/locales/es.yml index 40a1a18c..0a2538a7 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -50,6 +50,14 @@ es: pm: pm format: '%-H:%M' components: + actor: + user: Nombre de usuarie + profile: Cuenta de Origen + profile_name: Nombre de la cuenta + preferred_name: Nombre en el Fediverso + profile_id: ID + profile_published: Publicada + profile_summary: Presentación block_list: know_more: Saber más (en inglés) instances_blocked: Instancias bloqueadas @@ -106,15 +114,6 @@ es: text_allow: Aprobar siempre text_block: Bloquear text_report: Reportar - actor_moderations: - show: - user: Nombre de usuarie - profile: Cuenta de Origen - profile_name: Nombre de la cuenta - preferred_name: Nombre en el Fediverso - profile_id: ID - profile_published: Publicada - profile_summary: Presentación remote_flags: report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." moderation_queue: From 2e04bc8eac09bcfc4929ca9670971fcf5da35056 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 17:15:21 -0300 Subject: [PATCH 160/297] =?UTF-8?q?fix:=20las=20actividades=20est=C3=A1n?= =?UTF-8?q?=20duplicadas=20con=20respecto=20a=20su=20estado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit por ejemplo, el borrado de une actore puede estar dirigido a todos los sitios, con lo que se crea varias veces (aunque se ejecuta solo una) --- app/models/activity_pub/activity.rb | 2 ++ app/models/activity_pub/actor.rb | 3 +++ app/models/activity_pub/concerns/json_ld_concern.rb | 2 -- app/models/activity_pub/object.rb | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/activity_pub/activity.rb b/app/models/activity_pub/activity.rb index 1147c5b8..ee555474 100644 --- a/app/models/activity_pub/activity.rb +++ b/app/models/activity_pub/activity.rb @@ -20,6 +20,8 @@ class ActivityPub has_one :object, through: :activity_pub validates :activity_pub_id, presence: true + # Las actividades son únicas con respecto a su estado + validates :uri, presence: true, uniqueness: { scope: :activity_pub_id, message: 'estado duplicado' } # Siempre en orden descendiente para saber el último estado default_scope -> { order(created_at: :desc) } diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index 0a39dcde..919bc5e0 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -15,6 +15,9 @@ class ActivityPub has_many :activities has_many :remote_flags + # Les actores son únicxs a toda la base de datos + validates :uri, presence: true, uniqueness: true + # Obtiene el nombre de la Actor como mención, solo si obtuvimos el # contenido de antemano. # diff --git a/app/models/activity_pub/concerns/json_ld_concern.rb b/app/models/activity_pub/concerns/json_ld_concern.rb index bc30330c..282027df 100644 --- a/app/models/activity_pub/concerns/json_ld_concern.rb +++ b/app/models/activity_pub/concerns/json_ld_concern.rb @@ -6,8 +6,6 @@ class ActivityPub extend ActiveSupport::Concern included do - validates :uri, presence: true, uniqueness: true - # Cuando asignamos contenido, obtener la URI si no lo hicimos ya before_save :uri_from_content!, unless: :uri? diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index c196160f..15b07bee 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -5,6 +5,9 @@ class ActivityPub class Object < ApplicationRecord include ActivityPub::Concerns::JsonLdConcern + # Los objetos son únicos a toda la base de datos + validates :uri, presence: true, uniqueness: true + has_many :activity_pubs, as: :object # Encontrar le Actor por su relación con el objeto From 00f865f31542a15496355ea878954656323941f4 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 17:22:01 -0300 Subject: [PATCH 161/297] fix: comentario en perfil de actore --- app/models/activity_pub/activity/delete.rb | 10 ++++++++-- app/views/moderation_queue/_comments.haml | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index f6ff6536..5de20478 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -14,9 +14,15 @@ class ActivityPub # @see {https://docs.joinmastodon.org/spec/security/#ld} def update_activity_pub_state! ActivityPub.transaction do - ActivityPub::Object.find_by(uri: ActivityPub.uri_from_object(content['object']))&.activity_pubs&.find_each(&:remove!) + object = ActivityPub::Object.find_by(uri: ActivityPub.uri_from_object(content['object'])) - activity_pub.remove! + if object + object.activity_pubs.find_each do |activity_pub| + activity_pub.remove! if activity_pub.may_remove? + end + end + + activity_pub.remove! if activity_pub.may_remove? end end end diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index 436777db..316b097f 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -14,4 +14,4 @@ - moderation_queue.each do |activity_pub| -# cache [activity_pub, activity_pub.object, activity_pub.actor] do %hr - = render 'comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form_id: form_id + = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form_id: form_id From 7aa14bd292bafbee9fd0a4dea874ae639e0a9737 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 17:36:13 -0300 Subject: [PATCH 162/297] fix: poder ir al perfil --- app/views/components/_actor.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/components/_actor.haml b/app/views/components/_actor.haml index 3983d617..c58beae0 100644 --- a/app/views/components/_actor.haml +++ b/app/views/components/_actor.haml @@ -1,5 +1,7 @@ -# Componente Remote_Profile +- uri = text_plain(remote_profile['id']) + .py-2 %dl %dt= t('.profile_name') @@ -10,7 +12,7 @@ %dt= t('.profile_id') %dd - = link_to text_plain(remote_profile['id']) + = link_to uri, uri - if remote_profile['published'].present? %dt= t('.profile_published') From a8f184ecbf80b91336a7ee6a2ce5d4177f4e044e Mon Sep 17 00:00:00 2001 From: f Date: Wed, 6 Mar 2024 17:45:21 -0300 Subject: [PATCH 163/297] feat: validar que las uris sean uris --- app/models/activity_pub/activity.rb | 2 +- app/models/activity_pub/actor.rb | 2 +- app/models/activity_pub/object.rb | 2 +- app/validators/url_validator.rb | 21 +++++++++++++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 app/validators/url_validator.rb diff --git a/app/models/activity_pub/activity.rb b/app/models/activity_pub/activity.rb index ee555474..af005ff3 100644 --- a/app/models/activity_pub/activity.rb +++ b/app/models/activity_pub/activity.rb @@ -21,7 +21,7 @@ class ActivityPub validates :activity_pub_id, presence: true # Las actividades son únicas con respecto a su estado - validates :uri, presence: true, uniqueness: { scope: :activity_pub_id, message: 'estado duplicado' } + validates :uri, presence: true, url: true, uniqueness: { scope: :activity_pub_id, message: 'estado duplicado' } # Siempre en orden descendiente para saber el último estado default_scope -> { order(created_at: :desc) } diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index 919bc5e0..b03145e7 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -16,7 +16,7 @@ class ActivityPub has_many :remote_flags # Les actores son únicxs a toda la base de datos - validates :uri, presence: true, uniqueness: true + validates :uri, presence: true, url: true, uniqueness: true # Obtiene el nombre de la Actor como mención, solo si obtuvimos el # contenido de antemano. diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index 15b07bee..3fde326b 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -6,7 +6,7 @@ class ActivityPub include ActivityPub::Concerns::JsonLdConcern # Los objetos son únicos a toda la base de datos - validates :uri, presence: true, uniqueness: true + validates :uri, presence: true, url: true, uniqueness: true has_many :activity_pubs, as: :object diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb new file mode 100644 index 00000000..291f9288 --- /dev/null +++ b/app/validators/url_validator.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# Valida URLs +# +# @see {https://storck.io/posts/better-http-url-validation-in-ruby-on-rails/} +class UrlValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if value.blank? + record.errors.add(attribute, :url_missing) + return + end + + uri = URI.parse(value) + + record.errors.add(attribute, :scheme_missing) if uri.scheme.blank? + record.errors.add(attribute, :host_missing) if uri.host.blank? + record.errors.add(attribute, :path_missing) if uri.path.blank? + rescue URI::Error + record.errors.add(attribute, :invalid) + end +end From 66e59ee5ad8143630b07965560c3951d5cb2cac2 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 7 Mar 2024 16:54:09 -0300 Subject: [PATCH 164/297] feat: algunos tipos son actores --- app/models/activity_pub/object.rb | 8 ++++++ app/models/activity_pub/object/application.rb | 4 ++- app/models/activity_pub/object/audio.rb | 10 +++++++ .../object/concerns/actor_type_concern.rb | 27 +++++++++++++++++++ app/models/activity_pub/object/document.rb | 10 +++++++ app/models/activity_pub/object/event.rb | 10 +++++++ app/models/activity_pub/object/group.rb | 10 +++++++ app/models/activity_pub/object/image.rb | 10 +++++++ .../activity_pub/object/organization.rb | 4 ++- app/models/activity_pub/object/page.rb | 10 +++++++ app/models/activity_pub/object/person.rb | 4 ++- app/models/activity_pub/object/place.rb | 10 +++++++ app/models/activity_pub/object/profile.rb | 10 +++++++ .../activity_pub/object/relationship.rb | 10 +++++++ app/models/activity_pub/object/service.rb | 10 +++++++ app/models/activity_pub/object/tombstone.rb | 10 +++++++ app/models/activity_pub/object/video.rb | 10 +++++++ 17 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 app/models/activity_pub/object/audio.rb create mode 100644 app/models/activity_pub/object/concerns/actor_type_concern.rb create mode 100644 app/models/activity_pub/object/document.rb create mode 100644 app/models/activity_pub/object/event.rb create mode 100644 app/models/activity_pub/object/group.rb create mode 100644 app/models/activity_pub/object/image.rb create mode 100644 app/models/activity_pub/object/page.rb create mode 100644 app/models/activity_pub/object/place.rb create mode 100644 app/models/activity_pub/object/profile.rb create mode 100644 app/models/activity_pub/object/relationship.rb create mode 100644 app/models/activity_pub/object/service.rb create mode 100644 app/models/activity_pub/object/tombstone.rb create mode 100644 app/models/activity_pub/object/video.rb diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index 3fde326b..b33c1957 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -16,5 +16,13 @@ class ActivityPub def actor ActivityPub::Actor.find_by(uri: content['actor']) end + + def actor_type? + false + end + + def object_type? + true + end end end diff --git a/app/models/activity_pub/object/application.rb b/app/models/activity_pub/object/application.rb index 99ac935c..d26a7757 100644 --- a/app/models/activity_pub/object/application.rb +++ b/app/models/activity_pub/object/application.rb @@ -5,6 +5,8 @@ # Una aplicación o instancia class ActivityPub class Object - class Application < ActivityPub::Object; end + class Application < ActivityPub::Object + include Concerns::ActorTypeConcern + end end end diff --git a/app/models/activity_pub/object/audio.rb b/app/models/activity_pub/object/audio.rb new file mode 100644 index 00000000..48caea44 --- /dev/null +++ b/app/models/activity_pub/object/audio.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Audio = +# +# Representa artículos +class ActivityPub + class Object + class Audio < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/concerns/actor_type_concern.rb b/app/models/activity_pub/object/concerns/actor_type_concern.rb new file mode 100644 index 00000000..bb840601 --- /dev/null +++ b/app/models/activity_pub/object/concerns/actor_type_concern.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class ActivityPub + class Object + module Concerns + module ActorTypeConcern + extend ActiveSupport::Concern + + included do + # El objeto referencia a une Actor + # + # @see {https://www.w3.org/TR/activitystreams-vocabulary/#actor-types} + def actor_type? + true + end + + # El objeto es un objeto + # + # @see {https://www.w3.org/TR/activitystreams-vocabulary/#object-types} + def object_type? + false + end + end + end + end + end +end diff --git a/app/models/activity_pub/object/document.rb b/app/models/activity_pub/object/document.rb new file mode 100644 index 00000000..d7444514 --- /dev/null +++ b/app/models/activity_pub/object/document.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Document = +# +# Representa artículos +class ActivityPub + class Object + class Document < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/event.rb b/app/models/activity_pub/object/event.rb new file mode 100644 index 00000000..9fa1f6fc --- /dev/null +++ b/app/models/activity_pub/object/event.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Event = +# +# Representa artículos +class ActivityPub + class Object + class Event < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/group.rb b/app/models/activity_pub/object/group.rb new file mode 100644 index 00000000..08d11d0d --- /dev/null +++ b/app/models/activity_pub/object/group.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Group = +class ActivityPub + class Object + class Group < ActivityPub::Object + include Concerns::ActorTypeConcern + end + end +end diff --git a/app/models/activity_pub/object/image.rb b/app/models/activity_pub/object/image.rb new file mode 100644 index 00000000..9939a14b --- /dev/null +++ b/app/models/activity_pub/object/image.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Image = +# +# Representa artículos +class ActivityPub + class Object + class Image < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/organization.rb b/app/models/activity_pub/object/organization.rb index e3385232..e820c305 100644 --- a/app/models/activity_pub/object/organization.rb +++ b/app/models/activity_pub/object/organization.rb @@ -5,6 +5,8 @@ # Una organización class ActivityPub class Object - class Organization < ActivityPub::Object; end + class Organization < ActivityPub::Object + include Concerns::ActorTypeConcern + end end end diff --git a/app/models/activity_pub/object/page.rb b/app/models/activity_pub/object/page.rb new file mode 100644 index 00000000..f05503e2 --- /dev/null +++ b/app/models/activity_pub/object/page.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Page = +# +# Representa artículos +class ActivityPub + class Object + class Page < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/person.rb b/app/models/activity_pub/object/person.rb index a6a85d43..5bcab596 100644 --- a/app/models/activity_pub/object/person.rb +++ b/app/models/activity_pub/object/person.rb @@ -5,6 +5,8 @@ # Una persona, el perfil de une actore class ActivityPub class Object - class Person < ActivityPub::Object; end + class Person < ActivityPub::Object + include Concerns::ActorTypeConcern + end end end diff --git a/app/models/activity_pub/object/place.rb b/app/models/activity_pub/object/place.rb new file mode 100644 index 00000000..f04032ed --- /dev/null +++ b/app/models/activity_pub/object/place.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Place = +# +# Representa artículos +class ActivityPub + class Object + class Place < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/profile.rb b/app/models/activity_pub/object/profile.rb new file mode 100644 index 00000000..8f7183a2 --- /dev/null +++ b/app/models/activity_pub/object/profile.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Profile = +# +# Representa artículos +class ActivityPub + class Object + class Profile < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/relationship.rb b/app/models/activity_pub/object/relationship.rb new file mode 100644 index 00000000..ece995b4 --- /dev/null +++ b/app/models/activity_pub/object/relationship.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Relationship = +# +# Representa artículos +class ActivityPub + class Object + class Relationship < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/service.rb b/app/models/activity_pub/object/service.rb new file mode 100644 index 00000000..a276ea5b --- /dev/null +++ b/app/models/activity_pub/object/service.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Service = +class ActivityPub + class Object + class Service < ActivityPub::Object + include Concerns::ActorTypeConcern + end + end +end diff --git a/app/models/activity_pub/object/tombstone.rb b/app/models/activity_pub/object/tombstone.rb new file mode 100644 index 00000000..88f136b9 --- /dev/null +++ b/app/models/activity_pub/object/tombstone.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Tombstone = +# +# Representa artículos +class ActivityPub + class Object + class Tombstone < ActivityPub::Object; end + end +end diff --git a/app/models/activity_pub/object/video.rb b/app/models/activity_pub/object/video.rb new file mode 100644 index 00000000..fa4bbffb --- /dev/null +++ b/app/models/activity_pub/object/video.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# = Video = +# +# Representa artículos +class ActivityPub + class Object + class Video < ActivityPub::Object; end + end +end From 5a7331e00e44074bd9639d9023f707721665655b Mon Sep 17 00:00:00 2001 From: f Date: Thu, 7 Mar 2024 17:17:58 -0300 Subject: [PATCH 165/297] =?UTF-8?q?feat:=20cuando=20une=20actore=20es=20el?= =?UTF-8?q?iminade,=20hay=20que=20eliminar=20sus=20estados=20de=20moderaci?= =?UTF-8?q?=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub/activity/delete.rb | 8 +++++++- app/models/activity_pub/object.rb | 7 ++++++- .../object/concerns/actor_type_concern.rb | 7 +++++++ app/models/actor_moderation.rb | 15 +++++++++++++-- .../20240307201510_remove_actor_moderations.rb | 13 +++++++++++++ 5 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20240307201510_remove_actor_moderations.rb diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 5de20478..6a23a8b5 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -16,10 +16,16 @@ class ActivityPub ActivityPub.transaction do object = ActivityPub::Object.find_by(uri: ActivityPub.uri_from_object(content['object'])) - if object + if object.present? object.activity_pubs.find_each do |activity_pub| activity_pub.remove! if activity_pub.may_remove? end + + # Encontrar todas las acciones de moderación de le actore + # eliminade y moverlas a eliminar. + if object.actor_type? && object.actor.present? + ActorModeration.where(actor_id: object.actor.id).remove_all! + end end activity_pub.remove! if activity_pub.may_remove? diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index b33c1957..9061c4c5 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -14,7 +14,12 @@ class ActivityPub # # @return [ActivityPub::Actor,nil] def actor - ActivityPub::Actor.find_by(uri: content['actor']) + ActivityPub::Actor.find_by(uri: actor_uri) + end + + # @return [String] + def actor_uri + content['attributedTo'] end def actor_type? diff --git a/app/models/activity_pub/object/concerns/actor_type_concern.rb b/app/models/activity_pub/object/concerns/actor_type_concern.rb index bb840601..b2a643c7 100644 --- a/app/models/activity_pub/object/concerns/actor_type_concern.rb +++ b/app/models/activity_pub/object/concerns/actor_type_concern.rb @@ -7,6 +7,13 @@ class ActivityPub extend ActiveSupport::Concern included do + # La URI de le Actor en este caso es la misma id + # + # @return [String] + def actor_uri + uri + end + # El objeto referencia a une Actor # # @see {https://www.w3.org/TR/activitystreams-vocabulary/#actor-types} diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index e06ffbb1..01613f72 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -5,8 +5,8 @@ class ActorModeration < ApplicationRecord include AASM include AasmEventsConcern - IGNORED_EVENTS = [] - IGNORED_STATES = [] + IGNORED_EVENTS = %i[remove] + IGNORED_STATES = %i[removed] belongs_to :site belongs_to :remote_flag, optional: true, class_name: 'ActivityPub::RemoteFlag' @@ -23,11 +23,16 @@ class ActorModeration < ApplicationRecord 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 state :blocked state :reported + state :removed event :pause do transitions from: %i[allowed blocked reported], to: :paused @@ -62,6 +67,12 @@ class ActorModeration < ApplicationRecord ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting? end end + + # Si un perfil es eliminado remotamente, tenemos que dejar de + # mostrarlo y todas sus actividades. + event :remove do + transitions to: :removed + end end def pause_remotely! diff --git a/db/migrate/20240307201510_remove_actor_moderations.rb b/db/migrate/20240307201510_remove_actor_moderations.rb new file mode 100644 index 00000000..92c6d23a --- /dev/null +++ b/db/migrate/20240307201510_remove_actor_moderations.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Mover todes les actores eliminades +class RemoveActorModerations < ActiveRecord::Migration[6.1] + def up + actor_ids = + ActivityPub.where(aasm_state: 'removed', object_type: 'ActivityPub::Object::Person').distinct.pluck(:actor_id) + + ActorModeration.where(id: actor_ids).remove_all! + end + + def down; end +end From b0b8e6877efee116e707efea7ea05145387a7c00 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 7 Mar 2024 17:21:03 -0300 Subject: [PATCH 166/297] =?UTF-8?q?fixup!=20feat:=20cuando=20une=20actore?= =?UTF-8?q?=20es=20eliminade,=20hay=20que=20eliminar=20sus=20estados=20de?= =?UTF-8?q?=20moderaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/migrate/20240307201510_remove_actor_moderations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20240307201510_remove_actor_moderations.rb b/db/migrate/20240307201510_remove_actor_moderations.rb index 92c6d23a..b451c589 100644 --- a/db/migrate/20240307201510_remove_actor_moderations.rb +++ b/db/migrate/20240307201510_remove_actor_moderations.rb @@ -6,7 +6,7 @@ class RemoveActorModerations < ActiveRecord::Migration[6.1] actor_ids = ActivityPub.where(aasm_state: 'removed', object_type: 'ActivityPub::Object::Person').distinct.pluck(:actor_id) - ActorModeration.where(id: actor_ids).remove_all! + ActorModeration.where(actor_id: actor_ids).remove_all! end def down; end From 2370cf73108b315c833f370f876209896efe7067 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 7 Mar 2024 17:44:40 -0300 Subject: [PATCH 167/297] =?UTF-8?q?feat:=20eliminar=20m=C3=A1s=20actores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20240307203039_remove_actor_moderations2.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 db/migrate/20240307203039_remove_actor_moderations2.rb diff --git a/db/migrate/20240307203039_remove_actor_moderations2.rb b/db/migrate/20240307203039_remove_actor_moderations2.rb new file mode 100644 index 00000000..dabc7ed7 --- /dev/null +++ b/db/migrate/20240307203039_remove_actor_moderations2.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Algunes quedaron como genéricxs +class RemoveActorModerations2 < ActiveRecord::Migration[6.1] + def up + actor_uris = ActivityPub::Activity.where(type: 'ActivityPub::Activity::Delete').distinct.pluck(Arel.sql("content->>'object'")) + actor_ids = ActivityPub::Actor.where(uri: actor_uris).ids + + ActorModeration.where(actor_id: actor_ids).remove_all! + end + + def down; end +end From 21401d93b6c79a34a55e8d3d8c97cd029c96428a Mon Sep 17 00:00:00 2001 From: f Date: Thu, 7 Mar 2024 17:46:32 -0300 Subject: [PATCH 168/297] =?UTF-8?q?fixup!=20feat:=20eliminar=20m=C3=A1s=20?= =?UTF-8?q?actores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/migrate/20240307203039_remove_actor_moderations2.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20240307203039_remove_actor_moderations2.rb b/db/migrate/20240307203039_remove_actor_moderations2.rb index dabc7ed7..555a4ffe 100644 --- a/db/migrate/20240307203039_remove_actor_moderations2.rb +++ b/db/migrate/20240307203039_remove_actor_moderations2.rb @@ -3,7 +3,7 @@ # Algunes quedaron como genéricxs class RemoveActorModerations2 < ActiveRecord::Migration[6.1] def up - actor_uris = ActivityPub::Activity.where(type: 'ActivityPub::Activity::Delete').distinct.pluck(Arel.sql("content->>'object'")) + actor_uris = ActivityPub::Activity.unscope(:order).where(type: 'ActivityPub::Activity::Delete').distinct.pluck(Arel.sql("content->>'object'")) actor_ids = ActivityPub::Actor.where(uri: actor_uris).ids ActorModeration.where(actor_id: actor_ids).remove_all! From 550dba08c5cafc9ce9aeee120177fdb0b904be62 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 11:24:57 -0300 Subject: [PATCH 169/297] fix: migraciones --- db/structure.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/db/structure.sql b/db/structure.sql index 97bd372e..ed58ebec 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2699,6 +2699,8 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240301194154'), ('20240301202955'), ('20240305164653'), -('20240305184854'); +('20240305184854'), +('20240307201510'), +('20240307203039'); From fc77f8e9f406c854155f966e2f7b8fc869e6218c Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 13:08:24 -0300 Subject: [PATCH 170/297] fix: form es opcional #15329 --- app/views/components/_checkbox.haml | 3 ++- app/views/components/_comments_checked_submenu.haml | 5 ++++- app/views/components/_comments_filters.haml | 5 ++++- app/views/components/_dropdown_button.haml | 2 +- app/views/components/_instances_checked_submenu.haml | 5 ++++- app/views/components/_instances_filters.haml | 5 ++++- app/views/components/_profiles_checked_submenu.haml | 5 ++++- app/views/components/_profiles_filters.haml | 5 ++++- app/views/components/_select_all.haml | 2 +- app/views/components/_select_all_container.haml | 4 ++-- app/views/moderation_queue/_account.haml | 5 ++++- app/views/moderation_queue/_accounts.haml | 8 ++++---- app/views/moderation_queue/_comment.haml | 3 ++- app/views/moderation_queue/_comments.haml | 8 ++++---- app/views/moderation_queue/_instance.haml | 2 +- app/views/moderation_queue/_instances.haml | 8 ++++---- 16 files changed, 49 insertions(+), 26 deletions(-) diff --git a/app/views/components/_checkbox.haml b/app/views/components/_checkbox.haml index 68f1a663..a58c85b7 100644 --- a/app/views/components/_checkbox.haml +++ b/app/views/components/_checkbox.haml @@ -1,5 +1,6 @@ -# Componente Checkbox - local_assigns[:name] ||= id + .custom-control.custom-checkbox - %input.custom-control-input{ form: local_assigns[:form_id], type: 'checkbox', id: id, **local_assigns } + %input.custom-control-input{ type: 'checkbox', id: id, **local_assigns.compact } %label.custom-control-label{ for: id }= yield diff --git a/app/views/components/_comments_checked_submenu.haml b/app/views/components/_comments_checked_submenu.haml index a09da426..d94e12a9 100644 --- a/app/views/components/_comments_checked_submenu.haml +++ b/app/views/components/_comments_checked_submenu.haml @@ -1,6 +1,9 @@ +-# + @param form [String] + - current_state = params[:activity_pub_state]&.to_sym || ActivityPub.states.first - ActivityPub.aasm.events.each do |event| - next if ActivityPub::IGNORED_EVENTS.include? event.name - next unless event.transitions_from_state?(current_state) - = render 'components/dropdown_button', form_id: form_id, text: t(".submenu_#{event.name}"), name: 'activity_pub_action', value: event.name + = render 'components/dropdown_button', form: form, text: t(".submenu_#{event.name}"), name: 'activity_pub_action', value: event.name diff --git a/app/views/components/_comments_filters.haml b/app/views/components/_comments_filters.haml index 35cd5dda..cf8c1aa2 100644 --- a/app/views/components/_comments_filters.haml +++ b/app/views/components/_comments_filters.haml @@ -1,9 +1,12 @@ +-# + @params form [String] + - current_state = params[:activity_pub_state]&.to_sym || ActivityPub.states.first .d-flex.py-2 - if ActivityPub.transitionable_events(current_state).present? = render 'components/dropdown', text: t('.text_checked') do - = render 'components/comments_checked_submenu', form_id: form_id + = render 'components/comments_checked_submenu', form: form = render 'components/dropdown', text: t('.text_show') do = render 'components/comments_show_submenu', activity_pubs: activity_pubs diff --git a/app/views/components/_dropdown_button.haml b/app/views/components/_dropdown_button.haml index c8c98209..c0f12754 100644 --- a/app/views/components/_dropdown_button.haml +++ b/app/views/components/_dropdown_button.haml @@ -1,4 +1,4 @@ -# @param name [String] @param value [String] -%button.dropdown-item{type: 'submit', data: { target: 'dropdown.item' }, name: name, value: value, form: local_assigns[:form_id] }= text +%button.dropdown-item{type: 'submit', data: { target: 'dropdown.item' }, name: name, value: value, **local_assigns.compact } diff --git a/app/views/components/_instances_checked_submenu.haml b/app/views/components/_instances_checked_submenu.haml index 4c45b7ab..7c9dbd87 100644 --- a/app/views/components/_instances_checked_submenu.haml +++ b/app/views/components/_instances_checked_submenu.haml @@ -1,2 +1,5 @@ +-# + @params form [String] + - InstanceModeration.transitionable_events(current_state).each do |event| - = render 'components/dropdown_button', text: t(".submenu_#{event}"), name: 'instance_moderation_action', value: event, form_id: form_id + = render 'components/dropdown_button', text: t(".submenu_#{event}"), name: 'instance_moderation_action', value: event, form: form diff --git a/app/views/components/_instances_filters.haml b/app/views/components/_instances_filters.haml index 2c23fd72..730184bd 100644 --- a/app/views/components/_instances_filters.haml +++ b/app/views/components/_instances_filters.haml @@ -1,9 +1,12 @@ +-# + @params form [String] + - current_state = params[:state]&.to_sym || InstanceModeration.states.first .d-flex.py-2 - if InstanceModeration.transitionable_events(current_state).present? = render 'components/dropdown', text: t('.text_checked') do - = render 'components/instances_checked_submenu', form_id: form_id, current_state: current_state + = render 'components/instances_checked_submenu', form: form, current_state: current_state = render 'components/dropdown', text: t('.text_show') do = render 'components/instances_show_submenu', instance_moderations: instance_moderations diff --git a/app/views/components/_profiles_checked_submenu.haml b/app/views/components/_profiles_checked_submenu.haml index 66a0fa78..04c86fd4 100644 --- a/app/views/components/_profiles_checked_submenu.haml +++ b/app/views/components/_profiles_checked_submenu.haml @@ -1,2 +1,5 @@ +-# + @params form [String] + - ActorModeration.transitionable_events(current_state).each do |actor_event| - = render 'components/dropdown_button', text: t(".submenu_#{actor_event}"), name: 'actor_moderation_action', value: actor_event, form_id: form_id + = render 'components/dropdown_button', text: t(".submenu_#{actor_event}"), name: 'actor_moderation_action', value: actor_event, form: form diff --git a/app/views/components/_profiles_filters.haml b/app/views/components/_profiles_filters.haml index bf7fb48a..3f830ec8 100644 --- a/app/views/components/_profiles_filters.haml +++ b/app/views/components/_profiles_filters.haml @@ -1,9 +1,12 @@ +-# + @params form [String] + - current_state = params[:actor_state]&.to_sym || ActorModeration.states.first .d-flex.py-2 - if ActorModeration.transitionable_events(current_state).present? = render 'components/dropdown', text: t('.text_checked') do - = render 'components/profiles_checked_submenu', form_id: form_id, current_state: current_state + = render 'components/profiles_checked_submenu', form: form, current_state: current_state = render 'components/dropdown', text: t('.text_show') do = render 'components/profiles_show_submenu', actor_moderations: actor_moderations diff --git a/app/views/components/_select_all.haml b/app/views/components/_select_all.haml index 68711c4a..9778cd13 100644 --- a/app/views/components/_select_all.haml +++ b/app/views/components/_select_all.haml @@ -1,4 +1,4 @@ -# @param id [String] -= render 'components/checkbox', id: id, form: local_assigns[:form_id], data: { action: 'select-all#toggle', target: 'select-all.toggle' } do += render 'components/checkbox', id: id, data: { action: 'select-all#toggle', target: 'select-all.toggle', **local_assigns.compact } do %span.sr-only= t('.label') diff --git a/app/views/components/_select_all_container.haml b/app/views/components/_select_all_container.haml index 5fa91e2d..8c8d9426 100644 --- a/app/views/components/_select_all_container.haml +++ b/app/views/components/_select_all_container.haml @@ -7,7 +7,7 @@ navegador los va a asignar a este formulario. @param path [String] - @param form_id [String] + @param form [String] -= form_tag path, id: form_id, method: :patch do += form_tag path, id: form, method: :patch do -# nada diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index f63b6f6f..6b4c67fc 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -1,6 +1,9 @@ +-# + @params form [String] + .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: actor_moderation.id, form_id: form_id, name: 'actor_moderation[]', value: actor_moderation.id, data: { target: 'select-all.input' } + = render 'components/checkbox', id: actor_moderation.id, form: form, name: 'actor_moderation[]', value: actor_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 = link_to text_plain(profile['name']), site_actor_moderation_path(id: actor_moderation) diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index 65ff953f..abc02b31 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -1,17 +1,17 @@ - form_id = 'actor_moderations_action_on_several' -= render 'components/select_all_container', path: site_actor_moderations_action_on_several_path, form_id: form_id += render 'components/select_all_container', path: site_actor_moderations_action_on_several_path, form: form_id .row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'actors', form_id: form_id + = render 'components/select_all', id: 'actors', form: form_id .col-11 -# Filtros - = render 'components/profiles_filters', actor_moderations: actor_moderations, form_id: form_id + = render 'components/profiles_filters', actor_moderations: actor_moderations, form: form_id .col-12 - if actor_moderations.count.zero? %h4= t('moderation_queue.nothing') - actor_moderations.find_each do |actor_moderation| - cache [actor_moderation, actor_moderation.actor] do %hr - = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content, form_id: form_id + = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content, form: form_id diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index 33ebc722..90579a9c 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -1,6 +1,7 @@ -# Componente Comentario + @param form [String] @param profile [Hash] @param comment [Hash] @param activity_pub [ActivityPub] @@ -10,7 +11,7 @@ .row.no-gutters .col-1 - = render 'components/checkbox', id: activity_pub.id, name: 'activity_pub[]', value: activity_pub.id, data: { target: 'select-all.input' }, form: form_id + = render 'components/checkbox', id: activity_pub.id, name: 'activity_pub[]', value: activity_pub.id, data: { target: 'select-all.input' }, form: form .col-11 .d-flex.flex-row.align-items-center.justify-content-between %h4.mb-0 diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index 316b097f..72240287 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -1,17 +1,17 @@ - form_id = 'activity_pub_action_on_several' -= render 'components/select_all_container', path: site_activity_pubs_action_on_several_path, form_id: form_id += render 'components/select_all_container', path: site_activity_pubs_action_on_several_path, form: form_id .row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'select-all-comments', form_id: form_id + = render 'components/select_all', id: 'select-all-comments', form: form_id .col-11 -# Filtros - = render 'components/comments_filters', activity_pubs: moderation_queue, form_id: form_id + = render 'components/comments_filters', activity_pubs: moderation_queue, form: form_id .col-12 - if moderation_queue.count.zero? %h4= t('moderation_queue.nothing') - moderation_queue.each do |activity_pub| -# cache [activity_pub, activity_pub.object, activity_pub.actor] do %hr - = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form_id: form_id + = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form: form_id diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index 05510724..7cf3b085 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -3,7 +3,7 @@ .row.no-gutters.pt-2 .col-1 - = render 'components/checkbox', id: instance.hostname, form_id: form_id, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } + = render 'components/checkbox', id: instance.hostname, form: form, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 %a{ href: instance.uri }= sanitize(instance.content['title']) || instance.hostname diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 3954ce65..9a5349ba 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -1,13 +1,13 @@ - form_id = 'instance_moderation_action_on_several' %section - = render 'components/select_all_container', path: site_instance_moderations_action_on_several_path, form_id: form_id + = render 'components/select_all_container', path: site_instance_moderations_action_on_several_path, form: form_id .row.no-gutters.pt-2{ data: { controller: 'select-all' } } .col-1.d-flex.align-items-center - = render 'components/select_all', id: 'instances', form_id: form_id + = render 'components/select_all', id: 'instances', form: form_id .col-11 -# Filtros - = render 'components/instances_filters', instance_moderations: instance_moderations, form_id: form_id + = render 'components/instances_filters', instance_moderations: instance_moderations, form: form_id .col-12 - if instance_moderations.count.zero? @@ -16,7 +16,7 @@ - instance_moderations.each do |instance_moderation| - cache [instance_moderation.aasm_state, instance_moderation.instance] do %hr - = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance, form_id: form_id + = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance, form: form_id %hr %div From 4dff3180306ee4014e58aee69452b53bb0d9fc47 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 14:10:21 -0300 Subject: [PATCH 171/297] feat: al desactivar un fediblock, ignorar otros fediblocks --- app/models/fediblock_state.rb | 76 ++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index b5258fb6..e50abaef 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -27,12 +27,14 @@ class FediblockState < ApplicationRecord transitions from: :disabled, to: :enabled before do - enable_remotely! + # Bloquear todos las instancias de este Fediblock + enable_remotely! list_names(fediblock.hostnames) # Al actualizar el estado en masa garantizamos que las # instancias que ya existen queden sincronizadas con el bloqueo # en masa que acabamos de hacer. - instance_moderations.block_all! + instance_ids = fediblock.instances.ids + site.instance_moderations.where(instance_id: instance_ids).block_all! # Luego esta tarea crea las que falten e ignora las que ya se # bloquearon. @@ -41,11 +43,13 @@ class FediblockState < ApplicationRecord # 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 - # Al deshabilitar, las listas pasan a modo pausa. + # Al deshabilitar, las listas pasan a modo pausa, a menos que estén + # activas en otros listados. # # @todo No cambiar el estado si se habían habilitado manualmente, # pero esto implica que tenemos que encontrar las que sí y quitarlas @@ -54,12 +58,19 @@ class FediblockState < ApplicationRecord transitions from: :enabled, to: :disabled before do - disable_remotely! + # Deshabilitar todas las instancias que no estén habilitadas por + # otros fediblocks + disable_remotely! list_names(unique_hostnames) - instance_moderations.pause_all! + # Pausar todas las moderaciones de las instancias que no estén + # 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. + # 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 @@ -67,37 +78,48 @@ class FediblockState < ApplicationRecord private - def actor_ids - ActivityPub::Actor.where(instance_id: instance_ids).pluck(:id) - end - - def instance_ids - fediblock.instances.pluck(:id) - end - - # Todas las instancias de moderación de este sitio - def instance_moderations - site.instance_moderations.where(instance_id: instance_ids) - end - + # Devuelve los hostnames únicos a esta instancia. + # # @return [Array] - def list_names - @list_names ||= fediblock.hostnames.map do |hostname| + def unique_hostnames + @unique_hostnames ||= + begin + other_enabled_fediblock_ids = + site.fediblock_states.enabled.where.not(id: id).pluck(:fediblock_id) + other_enabled_hostnames = + ActivityPub::Fediblock + .where(id: other_enabled_fediblock_ids) + .pluck(:hostnames) + .flatten + .uniq + + fediblock.hostnames - other_enabled_hostnames + end + end + + # @param hostnames [Array] + # @return [Array] + def list_names(hostnames) + hostnames.map do |hostname| "@*@#{hostname}" end end # Al deshabilitar, las instancias pasan a ser analizadas caso por caso - def disable_remotely! + # + # @param list [Array] + def disable_remotely!(list) raise unless - site.social_inbox.blocklist.delete(list: list_names).ok? && - site.social_inbox.allowlist.delete(list: list_names).ok? + site.social_inbox.blocklist.delete(list: list).ok? && + site.social_inbox.allowlist.delete(list: list).ok? end # Al habilitar, se bloquean todas las instancias de la lista - def enable_remotely! + # + # @param list [Array] + def enable_remotely!(list) raise unless - site.social_inbox.blocklist.post(list: list_names).ok? && - site.social_inbox.allowlist.delete(list: list_names).ok? + site.social_inbox.blocklist.post(list: list).ok? && + site.social_inbox.allowlist.delete(list: list).ok? end end From 995c80fed1d3eb837122e1656a41261c544aaae3 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 14:42:27 -0300 Subject: [PATCH 172/297] fix: no colgar toda la cola si algo falla en la api --- app/controllers/fediblock_states_controller.rb | 13 ++++++++++++- app/jobs/activity_pub/instance_moderation_job.rb | 7 +++++++ config/locales/en.yml | 6 ++++++ config/locales/es.yml | 7 +++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/controllers/fediblock_states_controller.rb b/app/controllers/fediblock_states_controller.rb index 6d9737c3..4d9cc968 100644 --- a/app/controllers/fediblock_states_controller.rb +++ b/app/controllers/fediblock_states_controller.rb @@ -11,11 +11,22 @@ class FediblockStatesController < ApplicationController elsif fediblock_state.may_disable? fediblock_state.disable! end + + flash[:success] = I18n.t('fediblock_states.action_on_several.success') + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name }) + + flash.delete(:success) + flash[:error] = I18n.t('fediblock_states.action_on_several.error') end # Bloquear otras instancias if custom_blocklist.present? - ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: custom_blocklist) + if ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: custom_blocklist) + flash[:success] = I18n.t('fediblock_states.action_on_several.custom_blocklist_success') + else + flash[:error] = I18n.t('fediblock_states.action_on_several.custom_blocklist_error') + end end redirect_to site_moderation_queue_path diff --git a/app/jobs/activity_pub/instance_moderation_job.rb b/app/jobs/activity_pub/instance_moderation_job.rb index b205e68f..17def46e 100644 --- a/app/jobs/activity_pub/instance_moderation_job.rb +++ b/app/jobs/activity_pub/instance_moderation_job.rb @@ -14,6 +14,7 @@ class ActivityPub end instances = ActivityPub::Instance.where(hostname: hostnames) + success = true Site.transaction do # Crea todas las moderaciones de instancia con un estado por @@ -23,9 +24,15 @@ class ActivityPub # 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 end end + + success end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 0f010c89..565f0218 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -117,6 +117,12 @@ en: text_report: Report remote_flags: report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." + fediblock_states: + action_on_several: + success: "Blocklists have been enabled, you can find their instances by filtering by Blocked. Any pending account from these instances has also been blocked. You can approve them individually on the Accounts section." + error: "There was an error while enabling or disabling blocklists. We received a report and will be acting on it soon." + custom_blocklist_success: "Custom blocklist has been added, you can find the instances by filtering by Blocked. Any pending account from these instances has also been blocked. You can approve them individually on the Accounts section." + custom_blocklist_error: "There was an error while adding a custom blocklist. We received a report and will be acting on it soon." moderation_queue: everything: 'Select all' nothing: "There's nothing for this filter" diff --git a/config/locales/es.yml b/config/locales/es.yml index 0a2538a7..175ca661 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -116,6 +116,13 @@ es: text_report: Reportar remote_flags: report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." + 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." + error: "Hubo un error al activar o desactivar listas de bloqueo, ya recibimos el reporte y lo estaremos verificando." + custom_blocklist_success: "Se agregaron las instancias personalizadas a la lista de bloqueo, podés encontrarlas filtrando por Bloqueadas. Todas las cuentas de estas instancias pendientes de moderación han sido bloqueadas. Podés aprobarlas individualmente en la sección Cuentas." + custom_blocklist_error: "Hubo un error al agregar instancias personalizadas a la lista de bloqueo, ya recibimos el reporte y lo estaremos verificando." + actor_moderations: moderation_queue: everything: 'Seleccionar todo' nothing: 'No hay nada para este filtro' From cb30a3d02c8b9f6e90faff000683951dd9e5c0ab Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 15:07:32 -0300 Subject: [PATCH 173/297] fix: ser informatives --- app/controllers/actor_moderations_controller.rb | 16 +++++++++++++++- config/locales/en.yml | 16 ++++++++++++++++ config/locales/es.yml | 17 ++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 6b924677..7c2c3d82 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -14,6 +14,12 @@ class ActorModerationsController < ApplicationController actor_moderation.public_send(:"#{actor_event}!") if actor_moderation.public_send(:"may_#{actor_event}?") + flash[:success] = I18n.t("actor_moderations.#{actor_event}.success") + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params.permit!.to_h }) + + flash[:error] = I18n.t("actor_moderations.#{actor_event}.error") + ensure redirect_to_moderation_queue! end end @@ -21,7 +27,8 @@ class ActorModerationsController < ApplicationController # Ver el perfil remoto def show @remote_profile = actor_moderation.actor.content - @moderation_queue = rubanok_process(site.activity_pubs.where(actor_id: actor_moderation.actor_id), with: ActivityPubProcessor) + @moderation_queue = rubanok_process(site.activity_pubs.where(actor_id: actor_moderation.actor_id), + with: ActivityPubProcessor) end def action_on_several @@ -44,6 +51,13 @@ class ActorModerationsController < ApplicationController 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') end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 565f0218..2ae8484b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -117,6 +117,22 @@ en: text_report: Report remote_flags: report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." + actor_moderations: + action_on_several: + success: "Several accounts have changed moderation state. You can find them using the filters on the Accounts section." + error: "There was an error while changing moderation state. We received a report and will be acting on it soon." + pause: + success: "Account paused. All of their comments will need to be moderated individually on the Comments section." + error: "There was an error while pausing the account. We received a report and will be acting on it soon." + allow: + success: "Account allowed. All of their comments will be approved automatically." + error: "There was an error while allowing the account. We received a report and will be acting on it soon." + block: + success: "Account blocked. All of their comments will be rejected automatically. If you want to report it anonymously to their instance, please use the Report button." + error: "There was an error while blocking the account. We received a report and will be acting on it soon." + report: + success: "Account reported." + error: "There was an error while reporting the account. We received a report and will be acting on it soon." fediblock_states: action_on_several: success: "Blocklists have been enabled, you can find their instances by filtering by Blocked. Any pending account from these instances has also been blocked. You can approve them individually on the Accounts section." diff --git a/config/locales/es.yml b/config/locales/es.yml index 175ca661..29815a61 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -116,13 +116,28 @@ es: text_report: Reportar remote_flags: report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." + actor_moderations: + action_on_several: + success: "Se han modificado el estado de moderación de varias cuentas. Podés encontrarlas usando los filtros en la sección Cuentas." + error: "Hubo un error al modificar el estado de moderación de varias cuentas. Hemos recibido el reporte y lo estaremos verificando." + pause: + success: "Cuenta pausada. Todos los comentarios que haga necesitan ser aprobados manualmente en la sección Comentarios." + error: "No se pudo pausar la cuenta. Hemos recibido el reporte y lo estaremos verificando." + allow: + success: "Cuenta permitida. Todos los comentarios que haga serán aprobados inmediatamente." + error: "No se pudo permitir la cuenta. Hemos recibido el reporte y lo estaremos verificando." + block: + success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Si querés reportarla anónimamente a su instancia, podés usar el botón Reportar." + error: "No se pudo bloquear la cuenta. Hemos recibido el reporte y lo estaremos verificando." + report: + success: "Cuenta reportada a su instancia." + error: "No se pudo reportar la cuenta. 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." error: "Hubo un error al activar o desactivar listas de bloqueo, ya recibimos el reporte y lo estaremos verificando." custom_blocklist_success: "Se agregaron las instancias personalizadas a la lista de bloqueo, podés encontrarlas filtrando por Bloqueadas. Todas las cuentas de estas instancias pendientes de moderación han sido bloqueadas. Podés aprobarlas individualmente en la sección Cuentas." custom_blocklist_error: "Hubo un error al agregar instancias personalizadas a la lista de bloqueo, ya recibimos el reporte y lo estaremos verificando." - actor_moderations: moderation_queue: everything: 'Seleccionar todo' nothing: 'No hay nada para este filtro' From 8faa6d8ea8c7128f4b77987be117ddfde51d5d7e Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 15:18:19 -0300 Subject: [PATCH 174/297] =?UTF-8?q?feat:=20ser=20m=C3=A1s=20informative=20?= =?UTF-8?q?con=20los=20comentarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/activity_pubs_controller.rb | 15 +++++++++++++++ config/locales/en.yml | 13 +++++++++++++ config/locales/es.yml | 13 +++++++++++++ 3 files changed, 41 insertions(+) diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index c8f86ef0..1efe2b89 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -11,6 +11,12 @@ class ActivityPubsController < ApplicationController activity_pub.update(remote_flag_params(activity_pub)) if event == :report activity_pub.public_send(:"#{event}!") if activity_pub.public_send(:"may_#{event}?") + flash[:success] = I18n.t("activity_pubs.#{event}.success") + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params.permit!.to_h }) + + flash[:error] = I18n.t("activity_pubs.#{event}.error") + ensure redirect_to_moderation_queue! end end @@ -49,6 +55,15 @@ class ActivityPubsController < ApplicationController next unless activity_pub.public_send(may) 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') end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 2ae8484b..23df9e3c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -117,6 +117,19 @@ en: text_report: Report remote_flags: report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." + activity_pubs: + action_on_several: + success: "Several comments have changed moderation state. You can find them using the filters on the Comments section." + error: "There was an error while changing moderation state. We received a report and will be acting on it soon." + approve: + success: "Comment approved." + error: "There was an error while approving the comment. We received a report and will be acting on it soon." + reject: + success: "Comment rejected. You can report it using the Report button." + error: "There was an error while rejecting the comment. We received a report and will be acting on it soon." + report: + success: "Comment reported." + error: "There was an error while reporting the comment. We received a report and will be acting on it soon." actor_moderations: action_on_several: success: "Several accounts have changed moderation state. You can find them using the filters on the Accounts section." diff --git a/config/locales/es.yml b/config/locales/es.yml index 29815a61..1773a0c6 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -116,6 +116,19 @@ es: text_report: Reportar remote_flags: report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." + activity_pubs: + action_on_several: + success: "Se ha modificado el estado de moderación de varios comentarios. Podés encontrarlos usando los filtros en la sección Comentarios." + error: "Hubo un error al modificar el estado de moderación de varios comentarios. Hemos recibido el reporte y lo estaremos verificando." + approve: + success: "Comentario aprobado." + error: "No se puedo aprobar el comentario. Hemos recibido el reporte y lo estaremos verificando." + reject: + success: "Comentario rechazado. Podés reportarlo usando el botón Reportar." + error: "No se puedo rechazar el comentario. Hemos recibido el reporte y lo estaremos verificando." + report: + success: "Comentario reportado." + error: "No se puedo reportar el comentario. Hemos recibido el reporte y lo estaremos verificando." actor_moderations: action_on_several: success: "Se han modificado el estado de moderación de varias cuentas. Podés encontrarlas usando los filtros en la sección Cuentas." From 42be495465a0fddd2c97db8e5df263f979a48957 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 15:19:21 -0300 Subject: [PATCH 175/297] =?UTF-8?q?fix:=20no=20prometer=20que=20el=20repor?= =?UTF-8?q?te=20es=20an=C3=B3nimo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit le admin de la instancia remota siempre puede d0xear --- config/locales/en.yml | 2 +- config/locales/es.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 23df9e3c..73920b57 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -141,7 +141,7 @@ en: success: "Account allowed. All of their comments will be approved automatically." error: "There was an error while allowing the account. We received a report and will be acting on it soon." block: - success: "Account blocked. All of their comments will be rejected automatically. If you want to report it anonymously to their instance, please use the Report button." + success: "Account blocked. All of their comments will be rejected automatically. If you want to report it to their instance, please use the Report button." error: "There was an error while blocking the account. We received a report and will be acting on it soon." report: success: "Account reported." diff --git a/config/locales/es.yml b/config/locales/es.yml index 1773a0c6..467f15e8 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -131,7 +131,7 @@ es: error: "No se puedo reportar el comentario. Hemos recibido el reporte y lo estaremos verificando." actor_moderations: action_on_several: - success: "Se han modificado el estado de moderación de varias cuentas. Podés encontrarlas usando los filtros en la sección Cuentas." + success: "Se ha modificado el estado de moderación de varias cuentas. Podés encontrarlas usando los filtros en la sección Cuentas." error: "Hubo un error al modificar el estado de moderación de varias cuentas. Hemos recibido el reporte y lo estaremos verificando." pause: success: "Cuenta pausada. Todos los comentarios que haga necesitan ser aprobados manualmente en la sección Comentarios." @@ -140,7 +140,7 @@ es: success: "Cuenta permitida. Todos los comentarios que haga serán aprobados inmediatamente." error: "No se pudo permitir la cuenta. Hemos recibido el reporte y lo estaremos verificando." block: - success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Si querés reportarla anónimamente a su instancia, podés usar el botón Reportar." + success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Si querés reportarla a su instancia, podés usar el botón Reportar." error: "No se pudo bloquear la cuenta. Hemos recibido el reporte y lo estaremos verificando." report: success: "Cuenta reportada a su instancia." From 6b74bc454da0a307c32c145af683297d2bc202eb Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 15:20:08 -0300 Subject: [PATCH 176/297] fix: sin dummy data en posts --- app/controllers/posts_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 99dc6f7d..057c3068 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -38,7 +38,6 @@ class PostsController < ApplicationController @usuarie = site.usuarie? current_usuarie @site_stat = SiteStat.new(site) - dummy_data end def show @@ -82,7 +81,6 @@ class PostsController < ApplicationController authorize post breadcrumb post.title.value, site_post_path(site, post, locale: locale), match: :exact breadcrumb 'posts.edit', '' - dummy_data end def update From 071102fa3b82247fd7601f4548c3947106b6fd63 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 15:31:41 -0300 Subject: [PATCH 177/297] =?UTF-8?q?feat:=20al=20moderar=20una=20cuenta,=20?= =?UTF-8?q?tambi=C3=A9n=20moderar=20sus=20comentarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/actor_moderation.rb | 16 ++++++++++++++++ config/locales/en.yml | 4 ++-- config/locales/es.yml | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 01613f72..4b220a58 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -42,19 +42,35 @@ class ActorModeration < ApplicationRecord end end + # Al permitir una cuenta se permiten todos los comentarios + # pendientes de moderación que ya hizo. event :allow do transitions from: %i[paused blocked reported], to: :allowed before do allow_remotely! + + site.activity_pubs.paused.where(actor_id: self.actor_id).find_each do |activity_pub| + activity_pub.allow! if activity_pub.may_allow? + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, activity_pub: activity_pub_id }) + end end end + # Al bloquear una cuenta se bloquean todos los comentarios + # pendientes de moderación que hizo. event :block do transitions from: %i[paused allowed], to: :blocked before do block_remotely! + + site.activity_pubs.paused.where(actor_id: self.actor_id).find_each do |activity_pub| + activity_pub.reject! if activity_pub.may_reject? + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, activity_pub: activity_pub_id }) + end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 73920b57..48d9ca61 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -138,10 +138,10 @@ en: success: "Account paused. All of their comments will need to be moderated individually on the Comments section." error: "There was an error while pausing the account. We received a report and will be acting on it soon." allow: - success: "Account allowed. All of their comments will be approved automatically." + success: "Account allowed. All of their comments will be approved automatically. Any pending comments have been approved." error: "There was an error while allowing the account. We received a report and will be acting on it soon." block: - success: "Account blocked. All of their comments will be rejected automatically. If you want to report it to their instance, please use the Report button." + success: "Account blocked. All of their comments will be rejected automatically. Any pending comments have been rejected. If you want to report it to their instance, please use the Report button." error: "There was an error while blocking the account. We received a report and will be acting on it soon." report: success: "Account reported." diff --git a/config/locales/es.yml b/config/locales/es.yml index 467f15e8..3df608f5 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -137,10 +137,10 @@ es: success: "Cuenta pausada. Todos los comentarios que haga necesitan ser aprobados manualmente en la sección Comentarios." error: "No se pudo pausar la cuenta. Hemos recibido el reporte y lo estaremos verificando." allow: - success: "Cuenta permitida. Todos los comentarios que haga serán aprobados inmediatamente." + success: "Cuenta permitida. Todos los comentarios que haga serán aprobados inmediatamente. Todos los comentarios pendientes de moderación fueron aprobados." error: "No se pudo permitir la cuenta. Hemos recibido el reporte y lo estaremos verificando." block: - success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Si querés reportarla a su instancia, podés usar el botón Reportar." + success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Todos los comentarios pendientes de moderación fueron rechazados. Si querés reportarla a su instancia, podés usar el botón Reportar." error: "No se pudo bloquear la cuenta. Hemos recibido el reporte y lo estaremos verificando." report: success: "Cuenta reportada a su instancia." From 522cef8c9ae99ce5cc3c2d0ae911e5611064a7d3 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Mar 2024 15:55:01 -0300 Subject: [PATCH 178/297] feat: acciones en masa para actividades --- app/models/activity_pub.rb | 34 ++++++++++++++++++++-- app/models/actor_moderation.rb | 12 ++------ app/models/concerns/aasm_events_concern.rb | 8 +++++ 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 33cd4d45..fce340db 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -38,6 +38,28 @@ 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 @@ -68,7 +90,7 @@ class ActivityPub < ApplicationRecord transitions from: %i[paused], to: :approved before do - raise unless site.social_inbox.inbox.accept(id: object.uri).ok? + allow_remotely! end end @@ -77,7 +99,7 @@ class ActivityPub < ApplicationRecord transitions from: %i[paused approved], to: :rejected before do - raise unless site.social_inbox.inbox.reject(id: object.uri).ok? + reject_remotely! end end @@ -90,4 +112,12 @@ class ActivityPub < ApplicationRecord end end end + + def reject_remotely! + raise unless site.social_inbox.inbox.reject(id: object.uri).ok? + end + + def allow_remotely! + raise unless site.social_inbox.inbox.accept(id: object.uri).ok? + end end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 4b220a58..a510f1eb 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -50,11 +50,7 @@ class ActorModeration < ApplicationRecord before do allow_remotely! - site.activity_pubs.paused.where(actor_id: self.actor_id).find_each do |activity_pub| - activity_pub.allow! if activity_pub.may_allow? - rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, activity_pub: activity_pub_id }) - end + site.activity_pubs.paused.where(actor_id: self.actor_id).allow_all! end end @@ -66,11 +62,7 @@ class ActorModeration < ApplicationRecord before do block_remotely! - site.activity_pubs.paused.where(actor_id: self.actor_id).find_each do |activity_pub| - activity_pub.reject! if activity_pub.may_reject? - rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, activity_pub: activity_pub_id }) - end + site.activity_pubs.paused.where(actor_id: self.actor_id).reject_all! end end diff --git a/app/models/concerns/aasm_events_concern.rb b/app/models/concerns/aasm_events_concern.rb index 418368d8..b189dbe4 100644 --- a/app/models/concerns/aasm_events_concern.rb +++ b/app/models/concerns/aasm_events_concern.rb @@ -27,5 +27,13 @@ module AasmEventsConcern def self.states aasm.states.map(&:name) - self::IGNORED_STATES end + + # Envía notificación de errores + # + # @param exception [Exception] + # @param record [ApplicationRecord] + def notify_exception!(exception, record) + ExceptionNotifier.notify_exception(exception, data: { site: site.name, record_type: record.class.name, record_id: record.id }) + end end end From bf75d50cc35d578c410a117c42b32952a1fbab69 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 13:57:31 -0300 Subject: [PATCH 179/297] =?UTF-8?q?feat:=20modificar=20el=20estado=20de=20?= =?UTF-8?q?moderaci=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." From d2d327dc848b6b5e4abd318d71575effe125c89e Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 14:24:38 -0300 Subject: [PATCH 180/297] =?UTF-8?q?fixup!=20feat:=20modificar=20el=20estad?= =?UTF-8?q?o=20de=20moderaci=C3=B3n=20en=20masa=20#15328=20#15327?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 6 +++--- app/models/actor_moderation.rb | 6 +++--- app/models/instance_moderation.rb | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 38bf3b3a..61120a58 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -8,12 +8,12 @@ # # @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions} class ActivityPub < ApplicationRecord - include AASM - include AasmEventsConcern - IGNORED_EVENTS = %i[remove] IGNORED_STATES = %i[removed] + include AASM + include AasmEventsConcern + belongs_to :instance belongs_to :site belongs_to :object, polymorphic: true diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 7cb8827d..04c96ac0 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -2,12 +2,12 @@ # Mantiene la relación entre Site y Actor class ActorModeration < ApplicationRecord - include AASM - include AasmEventsConcern - IGNORED_EVENTS = %i[remove] IGNORED_STATES = %i[removed] + include AASM + include AasmEventsConcern + belongs_to :site belongs_to :remote_flag, optional: true, class_name: 'ActivityPub::RemoteFlag' belongs_to :actor, class_name: 'ActivityPub::Actor' diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 918c6ad0..1ed7d2c0 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -2,12 +2,12 @@ # Mantiene el registro de relaciones entre sitios e instancias class InstanceModeration < ApplicationRecord - include AASM - include AasmEventsConcern - IGNORED_EVENTS = [] IGNORED_STATES = [] + include AASM + include AasmEventsConcern + belongs_to :site belongs_to :instance, class_name: 'ActivityPub::Instance' From e783390747f2ab054f4765b353ca575ea3d7b802 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 14:28:26 -0300 Subject: [PATCH 181/297] =?UTF-8?q?fixup!=20fixup!=20feat:=20modificar=20e?= =?UTF-8?q?l=20estado=20de=20moderaci=C3=B3n=20en=20masa=20#15328=20#15327?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/en.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 48d9ca61..bc6b1285 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -146,6 +146,19 @@ en: report: success: "Account reported." error: "There was an error while reporting the account. We received a report and will be acting on it soon." + instance_moderations: + action_on_several: + success: "Several instances have changed moderation state. You can find them using the filters on the Instances section." + error: "There was an error while changing moderation state. We received a report and will be acting on it soon." + pause: + success: "Instance paused. All of their comments and accounts will need to be moderated individually." + error: "There was an error while pausing the instance. We received a report and will be acting on it soon." + allow: + success: "Instance allowed. All of their comments and accounts will be approved automatically. Any pending comments and accounts have been also approved." + error: "There was an error while allowing the instance. We received a report and will be acting on it soon." + block: + success: "Instance blocked. All of their comments and accounts will be rejected automatically. Any pending comments and accounts have been also rejected." + error: "There was an error while blocking the instance. We received a report and will be acting on it soon." fediblock_states: action_on_several: success: "Blocklists have been enabled, you can find their instances by filtering by Blocked. Any pending account from these instances has also been blocked. You can approve them individually on the Accounts section." From 20c6d6af5042360736d8ebd212da2b99e118ec41 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 14:30:43 -0300 Subject: [PATCH 182/297] feat: eliminar todos los comentarios de une actore eliminade --- app/models/actor_moderation.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 04c96ac0..783160c7 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -67,6 +67,10 @@ class ActorModeration < ApplicationRecord # mostrarlo y todas sus actividades. event :remove do transitions to: :removed + + before do + site.activity_pubs.where(actor_id: self.actor_id).remove_all! + end end end From 12f77e46581cc539fc6655f8f282c2d72147e71e Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 15:57:04 -0300 Subject: [PATCH 183/297] fix: actualizar cliente --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 366b58a5..7b19ba75 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -166,7 +166,7 @@ GEM devise_invitable (2.0.9) actionmailer (>= 5.0) devise (>= 4.6) - distributed-press-api-client (0.4.0rc3) + distributed-press-api-client (0.4.0) addressable (~> 2.3, >= 2.3.0) climate_control dry-schema @@ -626,7 +626,7 @@ DEPENDENCIES devise devise-i18n devise_invitable - distributed-press-api-client (~> 0.4.0rc3) + distributed-press-api-client (~> 0.4.0) dotenv-rails down ed25519 From 3c07ddf12c946303fddb5403b166faa6905b4ea1 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 15:59:42 -0300 Subject: [PATCH 184/297] =?UTF-8?q?fixup!=20fixup!=20fixup!=20feat:=20modi?= =?UTF-8?q?ficar=20el=20estado=20de=20moderaci=C3=B3n=20en=20masa=20#15328?= =?UTF-8?q?=20#15327?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub/remote_flag.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/activity_pub/remote_flag.rb b/app/models/activity_pub/remote_flag.rb index 76143414..1b6f5c5f 100644 --- a/app/models/activity_pub/remote_flag.rb +++ b/app/models/activity_pub/remote_flag.rb @@ -2,6 +2,9 @@ class ActivityPub class RemoteFlag < ApplicationRecord + IGNORED_EVENTS = [] + IGNORED_STATES = [] + include AASM include AasmEventsConcern From 4fde9793d733426a50ccf86104841cbdfe3140f8 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 17:09:21 -0300 Subject: [PATCH 185/297] fix: permitir que la tarea se reporte como hecha --- app/models/deploy_social_distributed_press.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index b7525dca..c7a103a4 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -13,12 +13,11 @@ class DeploySocialDistributedPress < Deploy key = Shellwords.escape file.path dest = Shellwords.escape destination - run %(bundle exec jekyll notify --trace --key #{key} --destination "#{dest}"), output: output + run(%(bundle exec jekyll notify --trace --key #{key} --destination "#{dest}"), output: output).tap do |_| + create_hooks! + enable_fediblocks! + end end - - - create_hooks! - enable_fediblocks! end # Igual que DeployLocal From d44bcc4098d0959d78c89ac564ee78ec2d01248b Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 17:30:47 -0300 Subject: [PATCH 186/297] fix: typo --- app/jobs/activity_pub/remote_flag_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb index 26db937a..f5650d53 100644 --- a/app/jobs/activity_pub/remote_flag_job.rb +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -13,7 +13,7 @@ class ActivityPub self.priority = 30 def perform(remote_flag:) - return if remote_flag.can_queue? + return if remote_flag.may_queue? remote_flag.queue! From 45d56502c11bb9c65f5538e08173052699f845ce Mon Sep 17 00:00:00 2001 From: f Date: Tue, 12 Mar 2024 17:44:40 -0300 Subject: [PATCH 187/297] fix: aprobar --- app/models/actor_moderation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 783160c7..ad29739f 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -37,7 +37,7 @@ class ActorModeration < ApplicationRecord before do allow_remotely! - site.activity_pubs.paused.where(actor_id: self.actor_id).allow_all! + site.activity_pubs.paused.where(actor_id: self.actor_id).approve_all! end end From 32042f32e23d95bb8dcde1976eb4d7203d411753 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 11:05:28 -0300 Subject: [PATCH 188/297] =?UTF-8?q?fix:=20no=20ignorar=20ning=C3=BAn=20eve?= =?UTF-8?q?nto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #15366 closes #15367 closes #15368 closes #15369 closes #15370 closes #15371 closes #15372 closes #15373 closes #15374 closes #15375 closes #15376 closes #15377 closes #15378 closes #15379 closes #15380 closes #15381 closes #15382 closes #15383 closes #15384 closes #15385 closes #15386 closes #15387 closes #15388 closes #15389 closes #15390 closes #15391 closes #15392 closes #15393 closes #15394 closes #15395 closes #15396 closes #15397 closes #15398 closes #15399 closes #15400 closes #15401 closes #15402 closes #15403 closes #15404 closes #15405 closes #15406 closes #15407 closes #15408 closes #15409 closes #15410 closes #15411 closes #15412 closes #15413 closes #15414 closes #15415 closes #15416 closes #15417 closes #15418 closes #15419 closes #15420 closes #15421 closes #15422 closes #15423 closes #15424 closes #15425 closes #15426 closes #15427 closes #15428 closes #15429 closes #15430 closes #15431 closes #15432 closes #15433 closes #15434 closes #15435 closes #15436 closes #15437 closes #15438 closes #15439 closes #15440 closes #15441 closes #15442 closes #15443 closes #15444 closes #15445 closes #15446 closes #15447 closes #15448 closes #15449 closes #15450 closes #15451 closes #15452 closes #15453 closes #15454 closes #15455 closes #15456 closes #15457 closes #15458 closes #15459 closes #15460 closes #15461 closes #15462 closes #15463 closes #15464 closes #15465 closes #15466 closes #15467 closes #15468 closes #15469 closes #15470 closes #15471 closes #15472 closes #15473 closes #15477 closes #15478 closes #15479 closes #15480 closes #15481 closes #15482 closes #15483 closes #15484 closes #15485 closes #15486 closes #15487 closes #15488 closes #15489 closes #15490 closes #15491 closes #15492 closes #15493 closes #15494 closes #15495 closes #15496 closes #15497 closes #15498 closes #15499 closes #15500 closes #15501 closes #15502 closes #15503 closes #15504 closes #15505 closes #15506 closes #15507 closes #15508 closes #15509 closes #15510 closes #15511 closes #15512 closes #15513 closes #15514 closes #15515 closes #15516 closes #15517 closes #15518 closes #15519 closes #15520 closes #15521 closes #15522 closes #15523 closes #15524 closes #15525 closes #15526 closes #15527 closes #15528 closes #15529 closes #15530 closes #15531 closes #15532 closes #15533 closes #15534 closes #15535 closes #15536 closes #15537 closes #15538 closes #15539 closes #15540 closes #15541 closes #15542 closes #15543 closes #15544 closes #15545 closes #15546 closes #15547 closes #15548 closes #15549 --- app/models/concerns/aasm_events_concern.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/aasm_events_concern.rb b/app/models/concerns/aasm_events_concern.rb index 967a61b3..08b4edd7 100644 --- a/app/models/concerns/aasm_events_concern.rb +++ b/app/models/concerns/aasm_events_concern.rb @@ -32,7 +32,7 @@ module AasmEventsConcern # scope actual. # # @return [Bool] Si hubo al menos un error, devuelve false. - self.events.each do |event| + self.aasm.events.map(&:name).each do |event| define_singleton_method(:"#{event}_all!") do success = true From 40edccf8bbbe9c272245a11674d9a0209ec57955 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 11:15:31 -0300 Subject: [PATCH 189/297] =?UTF-8?q?fixup!=20fix:=20no=20ignorar=20ning?= =?UTF-8?q?=C3=BAn=20evento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 4 +++- app/models/activity_pub/remote_flag.rb | 4 +++- app/models/actor_moderation.rb | 4 +++- app/models/instance_moderation.rb | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 61120a58..a6b1401b 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -12,7 +12,6 @@ class ActivityPub < ApplicationRecord IGNORED_STATES = %i[removed] include AASM - include AasmEventsConcern belongs_to :instance belongs_to :site @@ -91,6 +90,9 @@ class ActivityPub < ApplicationRecord end end + # Definir eventos en masa + include AasmEventsConcern + def reject_remotely! raise unless site.social_inbox.inbox.reject(id: object.uri).ok? end diff --git a/app/models/activity_pub/remote_flag.rb b/app/models/activity_pub/remote_flag.rb index 1b6f5c5f..70f09dcc 100644 --- a/app/models/activity_pub/remote_flag.rb +++ b/app/models/activity_pub/remote_flag.rb @@ -6,7 +6,6 @@ class ActivityPub IGNORED_STATES = [] include AASM - include AasmEventsConcern aasm do state :waiting, initial: true @@ -26,6 +25,9 @@ class ActivityPub end end + # Definir eventos en masa + include AasmEventsConcern + belongs_to :actor belongs_to :site diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index ad29739f..7e68f60b 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -6,7 +6,6 @@ class ActorModeration < ApplicationRecord IGNORED_STATES = %i[removed] include AASM - include AasmEventsConcern belongs_to :site belongs_to :remote_flag, optional: true, class_name: 'ActivityPub::RemoteFlag' @@ -74,6 +73,9 @@ class ActorModeration < ApplicationRecord end end + # Definir eventos en masa + include AasmEventsConcern + def pause_remotely! raise unless actor.mention && diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 1ed7d2c0..5b246cee 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -6,7 +6,6 @@ class InstanceModeration < ApplicationRecord IGNORED_STATES = [] include AASM - include AasmEventsConcern belongs_to :site belongs_to :instance, class_name: 'ActivityPub::Instance' @@ -51,6 +50,9 @@ class InstanceModeration < ApplicationRecord end end + # Definir eventos en masa + include AasmEventsConcern + # @return [Array] def actor_ids ActivityPub::Actor.where(instance_id: self.instance_id).ids From ef482aaedf2d8696408a4d40e642df8730b70447 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 11:41:04 -0300 Subject: [PATCH 190/297] =?UTF-8?q?fix:=20el=20m=C3=A9todo=20es=20de=20cla?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #15553 --- app/models/concerns/aasm_events_concern.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/aasm_events_concern.rb b/app/models/concerns/aasm_events_concern.rb index 08b4edd7..70fb5ed2 100644 --- a/app/models/concerns/aasm_events_concern.rb +++ b/app/models/concerns/aasm_events_concern.rb @@ -52,8 +52,8 @@ module AasmEventsConcern # # @param exception [Exception] # @param record [ApplicationRecord] - def notify_exception!(exception, record) - ExceptionNotifier.notify_exception(exception, data: { site: site.name, record_type: record.class.name, record_id: record.id }) + def self.notify_exception!(exception, record) + ExceptionNotifier.notify_exception(exception, data: { record_type: record.class.name, record_id: record.id }) end end end From f794e8057b3248dfafd1b259a0100a95db847c87 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 15:29:34 -0300 Subject: [PATCH 191/297] feat: no hacer acciones en cascada #15327 #15328 --- .../activity_pub/instance_moderation_job.rb | 13 ++++++++---- app/models/actor_moderation.rb | 8 ++------ app/models/concerns/aasm_events_concern.rb | 13 ++++++++++++ app/models/fediblock_state.rb | 16 +++++---------- app/models/instance_moderation.rb | 12 ++++------- config/locales/en.yml | 20 +++++++++---------- config/locales/es.yml | 20 +++++++++---------- 7 files changed, 53 insertions(+), 49 deletions(-) diff --git a/app/jobs/activity_pub/instance_moderation_job.rb b/app/jobs/activity_pub/instance_moderation_job.rb index e28be84e..214f8dd4 100644 --- a/app/jobs/activity_pub/instance_moderation_job.rb +++ b/app/jobs/activity_pub/instance_moderation_job.rb @@ -3,11 +3,10 @@ class ActivityPub # Bloquea varias instancias de una sola vez class InstanceModerationJob < ApplicationJob - self.priority = 50 - # @param :site [Site] # @param :hostnames [Array] - def perform(site:, hostnames:) + # @param :perform_remotely [Bool] + def perform(site:, hostnames:, perform_remotely: true) # Crear las instancias que no existan todavía hostnames.each do |hostname| ActivityPub::Instance.find_or_create_by(hostname: hostname) @@ -24,7 +23,13 @@ class ActivityPub site.instance_moderations.find_or_create_by(instance: instance) end - site.instance_moderations.where(instance_id: instances.ids).block_all! + scope = site.instance_moderations.where(instance_id: instances.ids) + + if perform_remotely + scope.block_all! + else + scope.block_all_without_callbacks! + end end end end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 7e68f60b..3b183a4f 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -28,27 +28,23 @@ class ActorModeration < ApplicationRecord end end - # Al permitir una cuenta se permiten todos los comentarios + # Al permitir una cuenta no se permiten todos los comentarios # pendientes de moderación que ya hizo. event :allow do transitions from: %i[paused blocked reported], to: :allowed before do allow_remotely! - - site.activity_pubs.paused.where(actor_id: self.actor_id).approve_all! end end - # Al bloquear una cuenta se bloquean todos los comentarios + # Al bloquear una cuenta no se bloquean todos los comentarios # pendientes de moderación que hizo. event :block do transitions from: %i[paused allowed], to: :blocked before do block_remotely! - - site.activity_pubs.paused.where(actor_id: self.actor_id).reject_all! end end diff --git a/app/models/concerns/aasm_events_concern.rb b/app/models/concerns/aasm_events_concern.rb index 70fb5ed2..4de5f748 100644 --- a/app/models/concerns/aasm_events_concern.rb +++ b/app/models/concerns/aasm_events_concern.rb @@ -46,6 +46,19 @@ module AasmEventsConcern success end + + # Ejecuta la transición del evento en la base de datos sin + # ejecutar los callbacks, sin modificar los items del scope que no + # pueden transicionar. + # + # @return [Integer] Registros modificados + define_singleton_method(:"#{event}_all_without_callbacks!") do + aasm_event = self.aasm.events.find { |e| e.name == event } + to_state = aasm_event.transitions.map(&:to).first + from_states = aasm_event.transitions.map(&:from) + + self.unscope(where: :aasm_state).where(aasm_state: from_states).update_all(aasm_state: to_state, updated_at: Time.now) + end end # Envía notificación de errores diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index dfdc4e34..9dde1db3 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -30,15 +30,9 @@ class FediblockState < ApplicationRecord # Bloquear todos las instancias de este Fediblock enable_remotely! list_names(fediblock.hostnames) - # Al actualizar el estado en masa garantizamos que las - # instancias que ya existen queden sincronizadas con el bloqueo - # en masa que acabamos de hacer. - instance_ids = fediblock.instances.ids - site.instance_moderations.where(instance_id: instance_ids).block_all! - # Luego esta tarea crea las que falten e ignora las que ya se # bloquearon. - ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames) + ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames, perform_remotely: false) end end @@ -56,10 +50,10 @@ class FediblockState < ApplicationRecord # otros fediblocks disable_remotely! list_names(unique_hostnames) - # Pausar todas las moderaciones de las instancias que no estén - # bloqueadas por otros fediblocks. - instance_ids = ActivityPub::Instance.where(hostname: unique_hostnames).ids - site.instance_moderations.where(instance_id: instance_ids).pause_all! + # Pausar todas las moderaciones de las instancias que no estén + # bloqueadas por otros fediblocks. + instance_ids = ActivityPub::Instance.where(hostname: unique_hostnames).ids + site.instance_moderations.where(instance_id: instance_ids).pause_all_without_callbacks! end end end diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 5b246cee..9cb6ffdc 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -25,27 +25,23 @@ class InstanceModeration < ApplicationRecord end end - # Al permitir, también permitimos todes les actores que no hayan - # tenido acciones de moderación. + # Al permitir, solo bloqueamos la instancia, sin modificar el estado + # de les actores y comentarios retroactivamente. 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. + # Al bloquear, solo bloqueamos la instancia, sin modificar el estado + # de les actores y comentarios retroactivamente. 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 diff --git a/config/locales/en.yml b/config/locales/en.yml index bc6b1285..d745e2d5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -132,38 +132,38 @@ en: error: "There was an error while reporting the comment. We received a report and will be acting on it soon." actor_moderations: action_on_several: - success: "Several accounts have changed moderation state. You can find them using the filters on the Accounts section." + success: "Several accounts have changed moderation state. You can find them using the filters on the Accounts section. No action was performed over existing Comments." error: "There was an error while changing moderation state. We received a report and will be acting on it soon." pause: - success: "Account paused. All of their comments will need to be moderated individually on the Comments section." + success: "Account paused. No action was performed on existing Comments." error: "There was an error while pausing the account. We received a report and will be acting on it soon." allow: - success: "Account allowed. All of their comments will be approved automatically. Any pending comments have been approved." + success: "Account allowed. All of their comments from now on will be approved automatically. No action was performed over existing Comments." error: "There was an error while allowing the account. We received a report and will be acting on it soon." block: - success: "Account blocked. All of their comments will be rejected automatically. Any pending comments have been rejected. If you want to report it to their instance, please use the Report button." + success: "Account blocked. All of their comments from now on will be rejected automatically. No action was performed over existing Comments. If you want to report it to their instance, please use the Report button." error: "There was an error while blocking the account. We received a report and will be acting on it soon." report: success: "Account reported." error: "There was an error while reporting the account. We received a report and will be acting on it soon." instance_moderations: action_on_several: - success: "Several instances have changed moderation state. You can find them using the filters on the Instances section." + success: "Several instances have changed moderation state. You can find them using the filters on the Instances section. No action was performed over existing Accounts and Comments." error: "There was an error while changing moderation state. We received a report and will be acting on it soon." pause: - success: "Instance paused. All of their comments and accounts will need to be moderated individually." + success: "Instance paused. All of their comments and accounts from now on will need to be moderated individually. No action was performed over existing Accounts and Comments." error: "There was an error while pausing the instance. We received a report and will be acting on it soon." allow: - success: "Instance allowed. All of their comments and accounts will be approved automatically. Any pending comments and accounts have been also approved." + success: "Instance allowed. All of their comments and accounts from now on will be approved automatically. No action was performed over existing Accounts and Comments." error: "There was an error while allowing the instance. We received a report and will be acting on it soon." block: - success: "Instance blocked. All of their comments and accounts will be rejected automatically. Any pending comments and accounts have been also rejected." + success: "Instance blocked. All of their comments and accounts from now on will be rejected automatically. No action was performed over existing Accounts and Comments." error: "There was an error while blocking the instance. We received a report and will be acting on it soon." fediblock_states: action_on_several: - success: "Blocklists have been enabled, you can find their instances by filtering by Blocked. Any pending account from these instances has also been blocked. You can approve them individually on the Accounts section." + success: "Blocklists have been enabled, you can find their instances by filtering by Blocked. You can approve them individually on the Accounts section. No action was performed over existing Accounts and Comments." error: "There was an error while enabling or disabling blocklists. We received a report and will be acting on it soon." - custom_blocklist_success: "Custom blocklist has been added, you can find the instances by filtering by Blocked. Any pending account from these instances has also been blocked. You can approve them individually on the Accounts section." + custom_blocklist_success: "Custom blocklist has been added, you can find the instances by filtering by Blocked. No action was performed over existing Accounts and Comments." custom_blocklist_error: "There was an error while adding a custom blocklist. We received a report and will be acting on it soon." moderation_queue: everything: 'Select all' diff --git a/config/locales/es.yml b/config/locales/es.yml index 3cc6c49a..a7b8d452 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -131,38 +131,38 @@ es: error: "No se puedo reportar el comentario. Hemos recibido el reporte y lo estaremos verificando." actor_moderations: action_on_several: - success: "Se ha modificado el estado de moderación de varias cuentas. Podés encontrarlas usando los filtros en la sección Cuentas." + success: "Se ha modificado el estado de moderación de varias cuentas. Podés encontrarlas usando los filtros en la sección Cuentas. No se modificaron comentarios pre-existentes." error: "Hubo un error al modificar el estado de moderación de varias cuentas. Hemos recibido el reporte y lo estaremos verificando." pause: - success: "Cuenta pausada. Todos los comentarios que haga necesitan ser aprobados manualmente en la sección Comentarios." + success: "Cuenta pausada. Todos los comentarios que haga necesitan ser aprobados manualmente en la sección Comentarios. No se modificaron comentarios pre-existentes." error: "No se pudo pausar la cuenta. Hemos recibido el reporte y lo estaremos verificando." allow: - success: "Cuenta permitida. Todos los comentarios que haga serán aprobados inmediatamente. Todos los comentarios pendientes de moderación fueron aprobados." + success: "Cuenta permitida. Todos los comentarios que haga serán aprobados inmediatamente. No se modificaron comentarios pre-existentes." error: "No se pudo permitir la cuenta. Hemos recibido el reporte y lo estaremos verificando." block: - success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Todos los comentarios pendientes de moderación fueron rechazados. Si querés reportarla a su instancia, podés usar el botón Reportar." + success: "Cuenta bloqueada. Todos los comentarios que haga serán rechazados inmediatamente. Si querés reportarla a su instancia, podés usar el botón Reportar. No se modificaron comentarios pre-existentes." error: "No se pudo bloquear la cuenta. Hemos recibido el reporte y lo estaremos verificando." 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." + success: "Se ha modificado el estado de moderación de varias instancias. Podés encontrarlas usando los filtros en la sección Instancias. No se modificaron cuentas y comentarios pre-existentes." 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." + success: "Instancia pausada. A partir de ahora, todos los comentarios y cuentas de esta instancia necesitan ser aprobados manualmente. No se ha modificado el estado de moderación de cuentas ni 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." + success: "Instancia permitida. A partir de ahora, todos los comentarios y cuentas pendientes serán aprobados inmediatamente. No se modificaron cuentas ni comentarios pre-existentes." 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." + success: "Instancia bloqueada. A partir de ahora, todos los comentarios y cuentas serán rechazados inmediatamente. No se modificaron cuentas ni comentarios pre-existentes." 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." + success: "Se habilitaron las listas de bloqueo, podés encontrar las instancias filtrando por Bloqueadas. Podés activarlas individualmente en la sección Cuentas. No se modificaron cuentas ni comentarios pre-existentes." error: "Hubo un error al activar o desactivar listas de bloqueo, ya recibimos el reporte y lo estaremos verificando." - custom_blocklist_success: "Se agregaron las instancias personalizadas a la lista de bloqueo, podés encontrarlas filtrando por Bloqueadas. Todas las cuentas de estas instancias pendientes de moderación han sido bloqueadas. Podés aprobarlas individualmente en la sección Cuentas." + custom_blocklist_success: "Se agregaron las instancias personalizadas a la lista de bloqueo, podés encontrarlas filtrando por Bloqueadas. Podés aprobarlas individualmente en la sección Cuentas. No se modificaron cuentas ni comentarios pre-existentes." custom_blocklist_error: "Hubo un error al agregar instancias personalizadas a la lista de bloqueo, ya recibimos el reporte y lo estaremos verificando." moderation_queue: everything: 'Seleccionar todo' From 882ab1679c0467929e5cfc18bbf75402143b4752 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 15:52:33 -0300 Subject: [PATCH 192/297] fix: a veces el objeto no existe (?) closes #15554 --- app/models/activity_pub.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index a6b1401b..53bb3de0 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -56,6 +56,8 @@ class ActivityPub < ApplicationRecord transitions to: :removed before do + next if object.blank? + object.update(content: {}) unless object.content.empty? end end From 56c583e0f43efab5d4ea37a725fb8cb2801c30b8 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 16:11:40 -0300 Subject: [PATCH 193/297] fix: serializar correctamente closes #15363 --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 5 +++-- app/jobs/activity_pub/fetch_job.rb | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index c40857e0..bdf359d2 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -125,8 +125,9 @@ module Api o.save! # XXX: el objeto necesita ser guardado antes de poder - # procesarlo - ::ActivityPub::FetchJob.perform_later(site: site, object: o) unless object_embedded? + # procesarlo. No usamos GlobalID porque el tipo de objeto + # cambia y produce un error de deserialización. + ::ActivityPub::FetchJob.perform_later(site: site, object_id: o.id) unless object_embedded? end end diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index e3fef993..6a4d163b 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -11,8 +11,11 @@ class ActivityPub class FetchJob < ApplicationJob self.priority = 50 - def perform(site:, object:) + def perform(site:, object_id:) ActivityPub::Object.transaction do + object = ::ActivityPub::Object.find(object_id) + + return if object.blank? return if object.activity_pubs.where(aasm_state: 'removed').count.positive? response = site.social_inbox.dereferencer.get(uri: object.uri) From ad9f9c3d618eb7cba2834e350a03dd7c7804bd0c Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 16:27:33 -0300 Subject: [PATCH 194/297] fixup! fix: serializar correctamente --- app/models/que_job.rb | 5 +++++ db/migrate/20240313192134_fix_fetch_jobs.rb | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 app/models/que_job.rb create mode 100644 db/migrate/20240313192134_fix_fetch_jobs.rb diff --git a/app/models/que_job.rb b/app/models/que_job.rb new file mode 100644 index 00000000..0bfffc92 --- /dev/null +++ b/app/models/que_job.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'que/active_record/model' + +class QueJob < Que::ActiveRecord::Model; end diff --git a/db/migrate/20240313192134_fix_fetch_jobs.rb b/db/migrate/20240313192134_fix_fetch_jobs.rb new file mode 100644 index 00000000..3b9d4ad8 --- /dev/null +++ b/db/migrate/20240313192134_fix_fetch_jobs.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class FixFetchJobs < ActiveRecord::Migration[6.1] + def up + QueJob.where("last_error_message like '%ActiveJob::DeserializationError%'").find_each do |job| + job.error_count = 0 + job.run_at = time.now + + job.args.first['arguments'].first['_aj_ruby2_keywords'].delete('object') + job.args.first['arguments'].first['_aj_ruby2_keywords'] << 'object_id' + + object = job.args.first['arguments'].first.delete('object')['_aj_globalid'] + job.args.first['arguments'].first['object_id'] = object.split('/').last + + job.save + end + end + + def down; end +end From 630f6b31fc93021d5e3ee93447f3b980f3dd1510 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 16:28:59 -0300 Subject: [PATCH 195/297] fixup! fixup! fix: serializar correctamente --- db/migrate/20240313192134_fix_fetch_jobs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20240313192134_fix_fetch_jobs.rb b/db/migrate/20240313192134_fix_fetch_jobs.rb index 3b9d4ad8..54ffa7e6 100644 --- a/db/migrate/20240313192134_fix_fetch_jobs.rb +++ b/db/migrate/20240313192134_fix_fetch_jobs.rb @@ -4,7 +4,7 @@ class FixFetchJobs < ActiveRecord::Migration[6.1] def up QueJob.where("last_error_message like '%ActiveJob::DeserializationError%'").find_each do |job| job.error_count = 0 - job.run_at = time.now + job.run_at = Time.now job.args.first['arguments'].first['_aj_ruby2_keywords'].delete('object') job.args.first['arguments'].first['_aj_ruby2_keywords'] << 'object_id' From 4dd75eedad7b8023a05bfb848697f71a8e575831 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 16:39:58 -0300 Subject: [PATCH 196/297] =?UTF-8?q?fix:=20prevenir=20doble=20renderizaci?= =?UTF-8?q?=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #15564 --- app/controllers/application_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 05fa98e9..cfa37067 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -77,6 +77,11 @@ class ApplicationController < ActionController::Base # Muestra una página 404 def page_not_found + self.response_body = nil + @_response_body = nil + + headers.delete('Location') + render 'application/page_not_found', status: :not_found end From 061fc81cb058221cd237baa6646b8f67d0fc1843 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 16:44:24 -0300 Subject: [PATCH 197/297] =?UTF-8?q?fixup!=20fix:=20prevenir=20doble=20rend?= =?UTF-8?q?erizaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/application_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cfa37067..b15157f7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -76,6 +76,8 @@ class ApplicationController < ActionController::Base end # Muestra una página 404 + # + # @see {https://github.com/rails/rails/issues/25106} def page_not_found self.response_body = nil @_response_body = nil From 93140f37aa6ca1436cb42523ad1cd4865abba097 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 16:46:04 -0300 Subject: [PATCH 198/297] fix: cachear correctamente closes #15567 --- app/views/moderation_queue/_comments.haml | 6 +++--- app/views/moderation_queue/_instances.haml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index 72240287..a4bfd9bd 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -12,6 +12,6 @@ - if moderation_queue.count.zero? %h4= t('moderation_queue.nothing') - moderation_queue.each do |activity_pub| - -# cache [activity_pub, activity_pub.object, activity_pub.actor] do - %hr - = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form: form_id + - cache [activity_pub, activity_pub.object, activity_pub.actor] do + %hr + = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form: form_id diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index 9a5349ba..dec7e6f3 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -14,7 +14,7 @@ %h4= t('moderation_queue.nothing') - instance_moderations.each do |instance_moderation| - - cache [instance_moderation.aasm_state, instance_moderation.instance] do + - cache [instance_moderation, instance_moderation.instance] do %hr = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance, form: form_id From ac38343a219953038540e3ecea460516f998fb54 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 16:55:34 -0300 Subject: [PATCH 199/297] =?UTF-8?q?fix:=20facilitar=20la=20b=C3=BAsqueda?= =?UTF-8?q?=20de=20instancias=20en=20la=20lista=20#15563?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/moderation_queue/_instance.haml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index 7cf3b085..e5121dad 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -1,12 +1,17 @@ - usuaries = instance.content.dig('usage', 'users', 'active_month') - usuaries ||= instance.content.dig('stats', 'user_count') +- title = sanitize(instance.content['title']) +- title ||= instance.hostname .row.no-gutters.pt-2 .col-1 = render 'components/checkbox', id: instance.hostname, form: form, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 - %a{ href: instance.uri }= sanitize(instance.content['title']) || instance.hostname + %a{ href: instance.uri } + = title + - if title.present? + = " (#{instance.hostname})".html_safe .content = sanitize instance.content['description'] - if usuaries.present? From 79e6ce787274aa928b2c726f94342597a74812bc Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 16:56:51 -0300 Subject: [PATCH 200/297] =?UTF-8?q?fixup!=20fix:=20facilitar=20la=20b?= =?UTF-8?q?=C3=BAsqueda=20de=20instancias=20en=20la=20lista=20#15563?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/moderation_queue/_instance.haml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index e5121dad..d66acd83 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -1,15 +1,13 @@ - usuaries = instance.content.dig('usage', 'users', 'active_month') - usuaries ||= instance.content.dig('stats', 'user_count') -- title = sanitize(instance.content['title']) -- title ||= instance.hostname +- title = sanitize(instance.content['title']) .row.no-gutters.pt-2 .col-1 = render 'components/checkbox', id: instance.hostname, form: form, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } .col-11 %h4 - %a{ href: instance.uri } - = title + %a{ href: instance.uri }= title || instance.hostname - if title.present? = " (#{instance.hostname})".html_safe .content From 97653d98b6d3f0d2d667eb153f5e70e8b9b272d1 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 17:02:01 -0300 Subject: [PATCH 201/297] fix: prevenir deadlocks closes #15555 closes #15556 closes #15557 closes #15558 closes #15559 closes #15560 --- app/models/activity_pub/activity/delete.rb | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 6a23a8b5..1fbe5691 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -13,22 +13,24 @@ class ActivityPub # lo haría la Social Inbox por nosotres. # @see {https://docs.joinmastodon.org/spec/security/#ld} def update_activity_pub_state! - ActivityPub.transaction do - object = ActivityPub::Object.find_by(uri: ActivityPub.uri_from_object(content['object'])) + ActiveRecord::Base.connection_pool.with_connection do + ActivityPub.transaction do + object = ActivityPub::Object.find_by(uri: ActivityPub.uri_from_object(content['object'])) - if object.present? - object.activity_pubs.find_each do |activity_pub| - activity_pub.remove! if activity_pub.may_remove? + if object.present? + object.activity_pubs.find_each do |activity_pub| + activity_pub.remove! if activity_pub.may_remove? + end + + # Encontrar todas las acciones de moderación de le actore + # eliminade y moverlas a eliminar. + if object.actor_type? && object.actor.present? + ActorModeration.where(actor_id: object.actor.id).remove_all! + end end - # Encontrar todas las acciones de moderación de le actore - # eliminade y moverlas a eliminar. - if object.actor_type? && object.actor.present? - ActorModeration.where(actor_id: object.actor.id).remove_all! - end + activity_pub.remove! if activity_pub.may_remove? end - - activity_pub.remove! if activity_pub.may_remove? end end end From 1b9e039fda52d2b1ee5779823b2ec7cb00873980 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 17:42:36 -0300 Subject: [PATCH 202/297] =?UTF-8?q?fix:=20errores=20de=20decompresi=C3=B3n?= =?UTF-8?q?=20#15332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 2 +- Gemfile.lock | 2 +- ...04105_brs_decompressor_corrupted_source_error.rb | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20240313204105_brs_decompressor_corrupted_source_error.rb diff --git a/Gemfile b/Gemfile index f4125f65..e314c172 100644 --- a/Gemfile +++ b/Gemfile @@ -39,7 +39,7 @@ gem 'devise-i18n' gem 'devise_invitable' gem 'redis-client' gem 'hiredis-client' -gem 'distributed-press-api-client', '~> 0.4.0rc3' +gem 'distributed-press-api-client', '~> 0.4.1' gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n' gem 'exception_notification' gem 'fast_blank' diff --git a/Gemfile.lock b/Gemfile.lock index 7b19ba75..751fc73e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -166,7 +166,7 @@ GEM devise_invitable (2.0.9) actionmailer (>= 5.0) devise (>= 4.6) - distributed-press-api-client (0.4.0) + distributed-press-api-client (0.4.1) addressable (~> 2.3, >= 2.3.0) climate_control dry-schema diff --git a/db/migrate/20240313204105_brs_decompressor_corrupted_source_error.rb b/db/migrate/20240313204105_brs_decompressor_corrupted_source_error.rb new file mode 100644 index 00000000..a0c29311 --- /dev/null +++ b/db/migrate/20240313204105_brs_decompressor_corrupted_source_error.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Comprueba que se pueden volver a correr las tareas que dieron error de +# decompresión +class BrsDecompressorCorruptedSourceError < ActiveRecord::Migration[6.1] + def up + raise unless HTTParty.get("https://mas.to/api/v2/instance", headers: { "Accept-Encoding": "br;q=1.0,gzip;q=1.0,deflate;q=0.6,identity;q=0.3" }).ok? + + QueJob.where("last_error_message like '%BRS::DecompressorCorruptedSourceError%'").update_all(error_count: 0, run_at: Time.now) + end + + def down; end +end From b6ed91df11eb238d55f6aedb95e164512046bfa7 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 13 Mar 2024 17:52:45 -0300 Subject: [PATCH 203/297] fix: siempre relacionar la instancia con el sitio #15563 --- app/controllers/api/v1/webhooks/social_inbox_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index bdf359d2..7b17c47b 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -162,11 +162,11 @@ module Api unless a.instance a.instance = ::ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) - site.instance_moderations.find_or_create_by(instance: a.instance) - ::ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) end + site.instance_moderations.find_or_create_by(instance: a.instance) + a.save! site.actor_moderations.find_or_create_by(actor: a) From 9d71c5fa0cb4de8725a1b6728cbb71a0edc087e2 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 11:20:04 -0300 Subject: [PATCH 204/297] fix: eliminar actores cuando no se pudo completar el objeto #15576 --- app/models/activity_pub/activity/delete.rb | 4 ++-- ...emove_actor_moderations_for_generic_objects.rb | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20240314141536_remove_actor_moderations_for_generic_objects.rb diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 1fbe5691..997b96ca 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -24,8 +24,8 @@ class ActivityPub # Encontrar todas las acciones de moderación de le actore # eliminade y moverlas a eliminar. - if object.actor_type? && object.actor.present? - ActorModeration.where(actor_id: object.actor.id).remove_all! + if (actor = ActivityPub::Actor.find_by(uri: o.uri)).present? + ActorModeration.where(actor_id: actor.id).remove_all! end end diff --git a/db/migrate/20240314141536_remove_actor_moderations_for_generic_objects.rb b/db/migrate/20240314141536_remove_actor_moderations_for_generic_objects.rb new file mode 100644 index 00000000..a60e755a --- /dev/null +++ b/db/migrate/20240314141536_remove_actor_moderations_for_generic_objects.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Elimina actores que no pudieron ser eliminades porque su perfil ya no +# existe. +class RemoveActorModerationsForGenericObjects < ActiveRecord::Migration[6.1] + def up + object_ids = ActivityPub.removed.where(object_type: 'ActivityPub::Object::Generic').distinct.pluck(:object_id) + uris = ActivityPub::Object.where(id: object_ids).pluck(:uri) + actor_ids = ActivityPub::Actor.where(uri: uris).ids + + ActorModeration.where(actor_id: actor_ids).remove_all! + end + + def down; end +end From 30f2f0a4423e623ba79ef385d3cba23e17a12edf Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 11:45:33 -0300 Subject: [PATCH 205/297] fix: gestionar las excepciones desde un solo lugar #15564 --- app/controllers/application_controller.rb | 16 --------------- app/controllers/concerns/exception_handler.rb | 20 ++++++++++++++++++- config/locales/en.yml | 1 + config/locales/es.yml | 1 + 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b15157f7..bd7b8c4b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,10 +12,6 @@ class ApplicationController < ActionController::Base before_action :notify_unconfirmed_email, unless: :devise_controller? around_action :set_locale - rescue_from Pundit::NilPolicyError, with: :page_not_found - rescue_from ActionController::RoutingError, with: :page_not_found - rescue_from ActionController::ParameterMissing, with: :page_not_found - before_action do Rack::MiniProfiler.authorize_request if current_usuarie&.email&.ends_with?('@' + ENV.fetch('SUTTY', 'sutty.nl')) end @@ -75,18 +71,6 @@ class ApplicationController < ActionController::Base I18n.with_locale(current_locale, &action) end - # Muestra una página 404 - # - # @see {https://github.com/rails/rails/issues/25106} - def page_not_found - self.response_body = nil - @_response_body = nil - - headers.delete('Location') - - render 'application/page_not_found', status: :not_found - end - # Necesario para poder acceder a Blazer. Solo les usuaries de este # sitio pueden acceder al panel. def require_usuarie diff --git a/app/controllers/concerns/exception_handler.rb b/app/controllers/concerns/exception_handler.rb index 8c4f54c8..7c1cd540 100644 --- a/app/controllers/concerns/exception_handler.rb +++ b/app/controllers/concerns/exception_handler.rb @@ -12,13 +12,31 @@ module ExceptionHandler rescue_from PageNotFound, with: :page_not_found rescue_from ActionController::RoutingError, with: :page_not_found rescue_from Pundit::NilPolicyError, with: :page_not_found + rescue_from Pundit::NilPolicyError, with: :page_not_found + rescue_from ActionController::RoutingError, with: :page_not_found + rescue_from ActionController::ParameterMissing, with: :page_not_found end def site_not_found + reset_response! + + flash[:error] = I18n.t('errors.site_not_found') + redirect_to sites_path end def page_not_found - send_file Rails.root.join('public', '404.html') + reset_response! + + render 'application/page_not_found', status: :not_found + end + + private + + def reset_response! + self.response_body = nil + @_response_body = nil + + headers.delete('Location') end end diff --git a/config/locales/en.yml b/config/locales/en.yml index d745e2d5..d67fe7d3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -390,6 +390,7 @@ en: lang: not_available: "This language is not yet available, would you help us by translating Sutty into it?" errors: + site_not_found: "Site not found, or maybe you don't have access to it." argument_error: 'Argument `%{argument}` must be an instance of %{class}' unknown_locale: 'Unknown %{locale} locale' posts: diff --git a/config/locales/es.yml b/config/locales/es.yml index a7b8d452..192b3298 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -389,6 +389,7 @@ es: lang: not_available: "Este idioma todavía no está disponible, ¿nos ayudas a agregarlo y mantenerlo?" errors: + site_not_found: "No encontramos ese sitio o quizás no tengas acceso." argument_error: 'El argumento `%{argument}` debe ser una instancia de %{class}' unknown_locale: 'El idioma %{locale} es desconocido' posts: From cfe70e3a899f136a2a2e004c5ae7ef91681fd4a2 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 11:57:06 -0300 Subject: [PATCH 206/297] fix: no cachear formularios #15570 --- app/views/moderation_queue/_account.haml | 14 ++++----- app/views/moderation_queue/_accounts.haml | 5 ++-- app/views/moderation_queue/_comment.haml | 35 +++++++++++----------- app/views/moderation_queue/_comments.haml | 5 ++-- app/views/moderation_queue/_instance.haml | 21 ++++++------- app/views/moderation_queue/_instances.haml | 5 ++-- 6 files changed, 42 insertions(+), 43 deletions(-) diff --git a/app/views/moderation_queue/_account.haml b/app/views/moderation_queue/_account.haml index 6b4c67fc..498d78f4 100644 --- a/app/views/moderation_queue/_account.haml +++ b/app/views/moderation_queue/_account.haml @@ -5,12 +5,12 @@ .col-1 = render 'components/checkbox', id: actor_moderation.id, form: form, name: 'actor_moderation[]', value: actor_moderation.id, data: { target: 'select-all.input' } .col-11 - %h4 - = link_to text_plain(profile['name']), site_actor_moderation_path(id: actor_moderation) - .mb-3 - = sanitize profile['summary'] + - cache [actor_moderation, profile] do + %h4 + = link_to text_plain(profile['name']), site_actor_moderation_path(id: actor_moderation) + .mb-3 + = sanitize profile['summary'] -# Botones de Moderación - - cache actor_moderation do - .d-flex.pb-4 - = render 'components/profiles_btn_box', actor_moderation: actor_moderation + .d-flex.pb-4 + = render 'components/profiles_btn_box', actor_moderation: actor_moderation diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index abc02b31..d8d76f0d 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -12,6 +12,5 @@ - if actor_moderations.count.zero? %h4= t('moderation_queue.nothing') - actor_moderations.find_each do |actor_moderation| - - cache [actor_moderation, actor_moderation.actor] do - %hr - = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content, form: form_id + %hr + = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content, form: form_id diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index 90579a9c..c6f6fd5c 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -13,22 +13,23 @@ .col-1 = render 'components/checkbox', id: activity_pub.id, name: 'activity_pub[]', value: activity_pub.id, data: { target: 'select-all.input' }, form: form .col-11 - .d-flex.flex-row.align-items-center.justify-content-between - %h4.mb-0 - %a{ href: text_plain(comment['attributedTo']) }= text_plain profile['preferredUsername'] - %small - = render 'layouts/time', time: text_plain(comment['published']) - - if in_reply_to.present? - %dl - %dt.d-inline - %small= t('.reply_to') - %dd.d-inline - %small - %a{ href: in_reply_to }= in_reply_to - .content - - if summary.present? - = render 'layouts/details', summary: summary, summary_class: 'h5' do + - cache [activity_pub, comment] do + .d-flex.flex-row.align-items-center.justify-content-between + %h4.mb-0 + %a{ href: text_plain(comment['attributedTo']) }= text_plain profile['preferredUsername'] + %small + = render 'layouts/time', time: text_plain(comment['published']) + - if in_reply_to.present? + %dl + %dt.d-inline + %small= t('.reply_to') + %dd.d-inline + %small + %a{ href: in_reply_to }= in_reply_to + .content + - if summary.present? + = render 'layouts/details', summary: summary, summary_class: 'h5' do + = sanitize comment['content'] + - else = sanitize comment['content'] - - else - = sanitize comment['content'] = render 'components/comments_btn_box', activity_pub: activity_pub diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index a4bfd9bd..68671f9e 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -12,6 +12,5 @@ - if moderation_queue.count.zero? %h4= t('moderation_queue.nothing') - moderation_queue.each do |activity_pub| - - cache [activity_pub, activity_pub.object, activity_pub.actor] do - %hr - = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form: form_id + %hr + = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form: form_id diff --git a/app/views/moderation_queue/_instance.haml b/app/views/moderation_queue/_instance.haml index d66acd83..c380089a 100644 --- a/app/views/moderation_queue/_instance.haml +++ b/app/views/moderation_queue/_instance.haml @@ -6,16 +6,17 @@ .col-1 = render 'components/checkbox', id: instance.hostname, form: form, name: 'instance_moderation[]', value: instance_moderation.id, data: { target: 'select-all.input' } .col-11 - %h4 - %a{ href: instance.uri }= title || instance.hostname - - if title.present? - = " (#{instance.hostname})".html_safe - .content - = sanitize instance.content['description'] - - if usuaries.present? - %dl - %dt.d-inline= t('.users') - %dd.d-inline= text_plain usuaries.to_s + - cache [instance_moderation, instance] do + %h4 + %a{ href: instance.uri }= title || instance.hostname + - if title.present? + = " (#{instance.hostname})".html_safe + .content + = sanitize instance.content['description'] + - if usuaries.present? + %dl + %dt.d-inline= t('.users') + %dd.d-inline= text_plain usuaries.to_s -# Botones moderación .d-flex.pb-4 diff --git a/app/views/moderation_queue/_instances.haml b/app/views/moderation_queue/_instances.haml index dec7e6f3..6bc08b95 100644 --- a/app/views/moderation_queue/_instances.haml +++ b/app/views/moderation_queue/_instances.haml @@ -14,9 +14,8 @@ %h4= t('moderation_queue.nothing') - instance_moderations.each do |instance_moderation| - - cache [instance_moderation, instance_moderation.instance] do - %hr - = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance, form: form_id + %hr + = render 'moderation_queue/instance', instance_moderation: instance_moderation, instance: instance_moderation.instance, form: form_id %hr %div From c57794b54e92cb6eed8dd42944b320aa9c513d7f Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 11:58:13 -0300 Subject: [PATCH 207/297] fixup! fix: eliminar actores cuando no se pudo completar el objeto #15576 --- app/models/activity_pub/activity/delete.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub/activity/delete.rb b/app/models/activity_pub/activity/delete.rb index 997b96ca..640c7ce9 100644 --- a/app/models/activity_pub/activity/delete.rb +++ b/app/models/activity_pub/activity/delete.rb @@ -24,7 +24,7 @@ class ActivityPub # Encontrar todas las acciones de moderación de le actore # eliminade y moverlas a eliminar. - if (actor = ActivityPub::Actor.find_by(uri: o.uri)).present? + if (actor = ActivityPub::Actor.find_by(uri: object.uri)).present? ActorModeration.where(actor_id: actor.id).remove_all! end end From 1de9d1a12695d97679c64b403c32256a4abf6ec1 Mon Sep 17 00:00:00 2001 From: maki Date: Thu, 14 Mar 2024 12:02:37 -0300 Subject: [PATCH 208/297] =?UTF-8?q?feat:=20breadcrumbs=20en=20Actividades?= =?UTF-8?q?=20de=20Moderaci=C3=B3n=20#15578?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/moderation_queue_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index eec0c70f..ca99d95b 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -2,6 +2,11 @@ # Cola de moderación de ActivityPub class ModerationQueueController < ApplicationController + before_action :authenticate_usuarie! + + breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path + breadcrumb 'sites.index', :sites_path, match: :exact + # Cola de moderación viendo todo el sitio def index dummy_data From 33cfdd199513221c8b4bf30f64fbe0b454c4be7a Mon Sep 17 00:00:00 2001 From: maki Date: Thu, 14 Mar 2024 12:21:50 -0300 Subject: [PATCH 209/297] fix: breadcrumbs en todas las acciones --- app/controllers/moderation_queue_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index ca99d95b..cbf2cec5 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -6,7 +6,7 @@ class ModerationQueueController < ApplicationController breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path breadcrumb 'sites.index', :sites_path, match: :exact - + # Cola de moderación viendo todo el sitio def index dummy_data @@ -15,11 +15,13 @@ class ModerationQueueController < ApplicationController # Perfil remoto de usuarie def remote_profile dummy_data + breadcrumb post.title.value, '' end # todon.nl está usando /api/v2/instance # mauve.moe usa /api/v1/instance def instances dummy_data + breadcrumb post.title.value, '' end end From 5bf26a7fb2f7b7fb73efd11f110ea8ec6bb44383 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 12:27:59 -0300 Subject: [PATCH 210/297] fix: arreglar las relaciones entre actividades y objetos --- app/jobs/activity_pub/fetch_job.rb | 4 ++++ ...0240314153017_fix_object_type_on_activity_pubs.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 6a4d163b..d7003c11 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -24,9 +24,13 @@ class ActivityPub return unless response.ok? return if response.miss? && object.content.present? + current_type = object.type content = FastJsonparser.parse(response.body) object.update(content: content, type: ActivityPub::Object.type_from(content).name) + + # Arreglar las relaciones con actividades también + ActivityPub.where(object_id: object.id).update_all(object_type: object.type) unless current_type == object.type end end end diff --git a/db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb b/db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb new file mode 100644 index 00000000..81149ad4 --- /dev/null +++ b/db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Arregla la relación rota entre ActivityPub y Objects +class FixObjectTypeOnActivityPubs < ActiveRecord::Migration[6.1] + def up + ActivityPub::Object.where.not(type: 'ActivityPub::Object::Generic').find_each do |object| + ActivityPub.where(object_id: object.id).update_all(type: object.type, updated_at: Time.now) + end + end + + def down; end +end From 5969662a7d679836c1936a178ee097641fdfeec6 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 15:42:44 -0300 Subject: [PATCH 211/297] fixup! fix: arreglar las relaciones entre actividades y objetos --- db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb b/db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb index 81149ad4..d5475f71 100644 --- a/db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb +++ b/db/migrate/20240314153017_fix_object_type_on_activity_pubs.rb @@ -4,7 +4,7 @@ class FixObjectTypeOnActivityPubs < ActiveRecord::Migration[6.1] def up ActivityPub::Object.where.not(type: 'ActivityPub::Object::Generic').find_each do |object| - ActivityPub.where(object_id: object.id).update_all(type: object.type, updated_at: Time.now) + ActivityPub.where(object_id: object.id).update_all(object_type: object.type, updated_at: Time.now) end end From 96d70100e46ad536d5e017b215f559a7a0615380 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 17:07:58 -0300 Subject: [PATCH 212/297] feat: auto-aprobar solicitudes de seguimiento --- app/models/activity_pub/activity/follow.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/models/activity_pub/activity/follow.rb b/app/models/activity_pub/activity/follow.rb index e383490a..393eb3b4 100644 --- a/app/models/activity_pub/activity/follow.rb +++ b/app/models/activity_pub/activity/follow.rb @@ -4,8 +4,17 @@ # # Una actividad de seguimiento se refiere siempre a une actore (el # sitio) y proviene de otre actore. +# +# Por ahora las solicitudes de seguimiento se auto-aprueban. class ActivityPub class Activity - class Follow < ActivityPub::Activity; end + class Follow < ActivityPub::Activity + # Auto-aprobar la solicitud de seguimiento + def update_activity_pub_state! + activity_pub.approve! + rescue Exception => e + ExceptionNotifier.notify_exception(e, { site: activity_pub.site.name, activity: self.id }) + end + end end end From d92953e5fa3acb9bc1e065dcd5f25d31b326e781 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 17:08:38 -0300 Subject: [PATCH 213/297] fix: poder volver a pausa si se edita el comentario --- app/models/activity_pub.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 53bb3de0..7453a8c3 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -49,6 +49,12 @@ class ActivityPub < ApplicationRecord # Le actore eliminó el objeto state :removed + # Se puede volver a pausa en caso de actualización remota, para + # revisar los cambios. + event :pause do + transitions to: :paused + end + # Recibir una acción de eliminación, eliminar el contenido de la # base de datos. Esto elimina el contenido para todos los sitios # porque estamos respetando lo que pidió le actore. From 1e44fba4b3b70a033bf7026d12ea2da9c3329be8 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 17:08:55 -0300 Subject: [PATCH 214/297] fix: aprobar la actividad, no el objeto --- app/models/activity_pub.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 7453a8c3..bd6c816e 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -101,11 +101,14 @@ class ActivityPub < ApplicationRecord # Definir eventos en masa include AasmEventsConcern + # Lo que tenemos que aprobar o rechazar es la última actividad + # disponible, que según el scope por defecto, va a ser la primera de + # la lista. def reject_remotely! - raise unless site.social_inbox.inbox.reject(id: object.uri).ok? + raise unless site.social_inbox.inbox.reject(id: activities.first.uri).ok? end def allow_remotely! - raise unless site.social_inbox.inbox.accept(id: object.uri).ok? + raise unless site.social_inbox.inbox.accept(id: activities.first.uri).ok? end end From f4379b45c27583c757dd9b1683e705fdbaf382d8 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 14 Mar 2024 17:58:41 -0300 Subject: [PATCH 215/297] feat: announce y like --- app/models/activity_pub/activity/announce.rb | 8 ++++++++ app/models/activity_pub/activity/like.rb | 8 ++++++++ db/migrate/20240314205923_fix_activity_type.rb | 12 ++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 app/models/activity_pub/activity/announce.rb create mode 100644 app/models/activity_pub/activity/like.rb create mode 100644 db/migrate/20240314205923_fix_activity_type.rb diff --git a/app/models/activity_pub/activity/announce.rb b/app/models/activity_pub/activity/announce.rb new file mode 100644 index 00000000..8ca58906 --- /dev/null +++ b/app/models/activity_pub/activity/announce.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class ActivityPub + class Activity + # Boost + class Announce < ActivityPub::Activity; end + end +end diff --git a/app/models/activity_pub/activity/like.rb b/app/models/activity_pub/activity/like.rb new file mode 100644 index 00000000..531cc32c --- /dev/null +++ b/app/models/activity_pub/activity/like.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class ActivityPub + class Activity + # Like + class Like < ActivityPub::Activity; end + end +end diff --git a/db/migrate/20240314205923_fix_activity_type.rb b/db/migrate/20240314205923_fix_activity_type.rb new file mode 100644 index 00000000..042de8eb --- /dev/null +++ b/db/migrate/20240314205923_fix_activity_type.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Soportar nuevos tipos +class FixActivityType < ActiveRecord::Migration[6.1] + def up + %w[Like Announce].each do |type| + ActivityPub::Activity.where(Arel.sql("content->>'type' = '#{type}'")).update_all(type: "ActivityPub::Activity::#{type}", updated_at: Time.now) + end + end + + def down; end +end From 8473927ceed6ea7dc8d01c30856499fcd200e998 Mon Sep 17 00:00:00 2001 From: maki Date: Fri, 15 Mar 2024 12:23:04 -0300 Subject: [PATCH 216/297] fix: breadcrumbs --- app/controllers/moderation_queue_controller.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index cbf2cec5..5e501329 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -10,18 +10,17 @@ class ModerationQueueController < ApplicationController # Cola de moderación viendo todo el sitio def index dummy_data + breadcrumb I18n.t('moderation_queue.index.title'), '' end # Perfil remoto de usuarie def remote_profile dummy_data - breadcrumb post.title.value, '' end # todon.nl está usando /api/v2/instance # mauve.moe usa /api/v1/instance def instances dummy_data - breadcrumb post.title.value, '' end end From 30d32a6e3be6e3fd27ddfe81fa3eeda935f29211 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 13:43:00 -0300 Subject: [PATCH 217/297] feat: confirmar el reporte remoto #15604 --- app/views/components/_btn_base.haml | 2 +- app/views/components/_comments_btn_box.haml | 5 ++++- app/views/components/_instances_btn_box.haml | 12 ++++++++---- app/views/components/_profiles_btn_box.haml | 7 ++++--- config/locales/en.yml | 6 ++++-- config/locales/es.yml | 4 +++- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/views/components/_btn_base.haml b/app/views/components/_btn_base.haml index 4d8566d3..f9227482 100644 --- a/app/views/components/_btn_base.haml +++ b/app/views/components/_btn_base.haml @@ -5,5 +5,5 @@ - local_assigns[:class] = "btn #{local_assigns[:class]}" -# @todo path es obligatorio -= button_to local_assigns[:path], **local_assigns do += button_to local_assigns[:path], **local_assigns.compact do = text diff --git a/app/views/components/_comments_btn_box.haml b/app/views/components/_comments_btn_box.haml index 285eefdb..1993e5cb 100644 --- a/app/views/components/_comments_btn_box.haml +++ b/app/views/components/_comments_btn_box.haml @@ -1,8 +1,11 @@ -# Componente Botonera de Comentarios +- local_data = { report: { confirm: t('.confirm_report') } } + .d-flex.flex-row - ActivityPub.events.each do |event| = render 'components/btn_base', text: t(".text_#{event}"), path: public_send(:"site_activity_pub_#{event}_path", activity_pub_id: activity_pub), - disabled: !activity_pub.public_send(:"may_#{event}?") + disabled: !activity_pub.public_send(:"may_#{event}?"), + data: local_data[event] diff --git a/app/views/components/_instances_btn_box.haml b/app/views/components/_instances_btn_box.haml index 74cad4a4..15c6c040 100644 --- a/app/views/components/_instances_btn_box.haml +++ b/app/views/components/_instances_btn_box.haml @@ -1,6 +1,10 @@ -# Componente botonera de moderación de Instancias -- btn_class = 'btn btn-secondary' -= render 'components/btn_base', path: site_instance_moderation_pause_path(instance_moderation_id: instance_moderation), text: t('.text_check'), class: btn_class, disabled: !instance_moderation.may_pause? -= render 'components/btn_base', path: site_instance_moderation_allow_path(instance_moderation_id: instance_moderation), text: t('.text_allow'), class: btn_class, disabled: !instance_moderation.may_allow? -= render 'components/btn_base', path: site_instance_moderation_block_path(instance_moderation_id: instance_moderation), text: t('.text_deny'), class: btn_class, disabled: !instance_moderation.may_block? +- local_data = {} +- InstanceModeration.events.each do |event| + = render 'components/btn_base', + path: public_send(:"site_instance_moderation_#{event}_path", instance_moderation_id: instance_moderation), + text: t(".text_#{event}"), + class: 'btn btn-secondary', + disabled: !instance_moderation.public_send(:"may_#{event}?"), + data: local_data[event] diff --git a/app/views/components/_profiles_btn_box.haml b/app/views/components/_profiles_btn_box.haml index 073c142e..488373b9 100644 --- a/app/views/components/_profiles_btn_box.haml +++ b/app/views/components/_profiles_btn_box.haml @@ -1,9 +1,10 @@ -# Componente Botonera de Moderación de Cuentas (Remote_profile) .d-flex.flex-row - - btn_class = 'btn-secondary' + - local_data = { report: { confirm: t('.confirm_report') } } - ActorModeration.events.each do |actor_event| = render 'components/btn_base', text: t(".text_#{actor_event}"), path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), - class: btn_class, - disabled: !actor_moderation.public_send(:"may_#{actor_event}?") + class: 'btn-secondary', + disabled: !actor_moderation.public_send(:"may_#{actor_event}?"), + data: local_data[actor_event] diff --git a/config/locales/en.yml b/config/locales/en.yml index d67fe7d3..6f76fe57 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -106,15 +106,17 @@ en: text_reject: Reject text_reply: Reply text_report: Report + confirm_report: "Send report to the remote instance?" instances_btn_box: - text_check: Check case by case + text_pause: Check case by case text_allow: Allow everything - text_deny: Block instance + text_block: Block instance profiles_btn_box: text_pause: Always check text_allow: Always approve text_block: Block text_report: Report + confirm_report: "Send report to the remote instance?" remote_flags: report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." activity_pubs: diff --git a/config/locales/es.yml b/config/locales/es.yml index 192b3298..7f71781c 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -105,8 +105,9 @@ es: text_approve: Aceptar text_reject: Rechazar text_report: Reportar + confirm_report: "¿Enviar el reporte a la instancia remota?" instances_btn_box: - text_check: Moderar caso por caso + text_pause: Moderar caso por caso text_allow: Permitir todo text_deny: Bloquear instancia profiles_btn_box: @@ -114,6 +115,7 @@ es: text_allow: Aprobar siempre text_block: Bloquear text_report: Reportar + confirm_report: "¿Enviar el reporte a la instancia remota?" remote_flags: report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." activity_pubs: From 86ba17328db4823140766bdf0dc82d7b5f6f01ed Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 14:01:00 -0300 Subject: [PATCH 218/297] feat: migas de pan para actores --- app/controllers/actor_moderations_controller.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index bc4a059b..769be1fa 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -5,6 +5,11 @@ class ActorModerationsController < ApplicationController include ModerationConcern include ModerationFiltersConcern + before_action :authenticate_usuarie! + + breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path + breadcrumb 'sites.index', :sites_path, match: :exact + ActorModeration.events.each do |actor_event| define_method(actor_event) do authorize actor_moderation @@ -26,9 +31,13 @@ class ActorModerationsController < ApplicationController # Ver el perfil remoto def show + breadcrumb I18n.t('moderation_queue.index.title'), site_moderation_queue_path(site) + @remote_profile = actor_moderation.actor.content @moderation_queue = rubanok_process(site.activity_pubs.where(actor_id: actor_moderation.actor_id), with: ActivityPubProcessor) + + breadcrumb @remote_profile['name'], '' end def action_on_several From 0fe043dd791dd30342f2f2197da4882e0aacfffb Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 14:14:27 -0300 Subject: [PATCH 219/297] fix: informar errores de json closes #15593 --- app/jobs/activity_pub/fetch_job.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index d7003c11..129908d3 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -31,6 +31,8 @@ class ActivityPub # Arreglar las relaciones con actividades también ActivityPub.where(object_id: object.id).update_all(object_type: object.type) unless current_type == object.type + rescue FastJsonparser::ParseError => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, body: response.body }) end end end From 5db750675217ab5226a5955745e0736d805695eb Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 14:26:12 -0300 Subject: [PATCH 220/297] =?UTF-8?q?fix:=20no=20se=20puede=20pausar=20todav?= =?UTF-8?q?=C3=ADa=20#15600?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index bd6c816e..840cf3b1 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -8,8 +8,8 @@ # # @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions} class ActivityPub < ApplicationRecord - IGNORED_EVENTS = %i[remove] - IGNORED_STATES = %i[removed] + IGNORED_EVENTS = %i[pause remove].freeze + IGNORED_STATES = %i[removed].freeze include AASM From 6412fc108efd2279582f88997f79fc19790f727d Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 14:31:54 -0300 Subject: [PATCH 221/297] =?UTF-8?q?fix:=20no=20se=20puede=20rechazar=20lue?= =?UTF-8?q?go=20de=20aprobar=20todav=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 840cf3b1..73006d1d 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -81,7 +81,7 @@ class ActivityPub < ApplicationRecord # La actividad fue rechazada event :reject do - transitions from: %i[paused approved], to: :rejected + transitions from: %i[paused], to: :rejected before do reject_remotely! From fcbff3e1c13a5b8500fd898e9bd2753ca070c853 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 16:52:42 -0300 Subject: [PATCH 222/297] fix: enviar el reporte firmado por el sitio #15605 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pasaban dos cosas: 1. para firmar correctamente, el cliente necesita recibir el path completo por su parámetro `endpoint` 2. la petición tiene que ser hecha por le misme actore que hace el reporte, como estábamos firmando con el sitio, mastodon creía que era un relay y esperaba que se envíen firmas ld --- .env | 2 ++ app/jobs/activity_pub/remote_flag_job.rb | 9 +++++++-- app/models/activity_pub/remote_flag.rb | 16 ++++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.env b/.env index 480175f8..fe503b11 100644 --- a/.env +++ b/.env @@ -39,3 +39,5 @@ GITLAB_PROJECT= GITLAB_TOKEN= PGVER=15 PGPID=/run/postgresql.pid +PANEL_ACTOR_MENTION=@sutty@sutty.nl +PANEL_ACTOR_SITE_ID=1 diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb index f5650d53..20833bd4 100644 --- a/app/jobs/activity_pub/remote_flag_job.rb +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -15,10 +15,15 @@ class ActivityPub def perform(remote_flag:) return if remote_flag.may_queue? + inbox = remote_flag.actor&.content&.[]('inbox') + + raise 'Inbox is missing for actor' if inbox.blank? + remote_flag.queue! - client = remote_flag.site.social_inbox.client_for(remote_flag.actor&.content['inbox']) - response = client.post(endpoint: '', body: remote_flag.content) + uri = URI.parse(inbox) + client = remote_flag.main_site.social_inbox.client_for(uri.origin) + response = client.post(endpoint: uri.path, body: remote_flag.content) raise 'No se pudo enviar el reporte' unless response.ok? diff --git a/app/models/activity_pub/remote_flag.rb b/app/models/activity_pub/remote_flag.rb index 70f09dcc..c3cc0fb0 100644 --- a/app/models/activity_pub/remote_flag.rb +++ b/app/models/activity_pub/remote_flag.rb @@ -2,8 +2,8 @@ class ActivityPub class RemoteFlag < ApplicationRecord - IGNORED_EVENTS = [] - IGNORED_STATES = [] + IGNORED_EVENTS = [].freeze + IGNORED_STATES = [].freeze include AASM @@ -42,10 +42,18 @@ class ActivityPub '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => Rails.application.routes.url_helpers.v1_activity_pub_remote_flag_url(self, host: site.social_inbox_hostname), 'type' => 'Flag', - 'actor' => ENV.fetch('PANEL_ACTOR_ID') { "https://#{ENV['SUTTY']}/about.jsonld" }, + 'actor' => main_site.social_inbox.actor_id, 'content' => message.to_s, - 'object' => [ actor.uri ] + objects.pluck(:uri) + 'object' => [actor.uri] + objects.pluck(:uri) } end + + # Este es el sitio principal que actúa como origen del reporte. + # Tiene que tener la Social Inbox habilitada al mismo tiempo. + # + # @return [Site] + def main_site + @main_site ||= Site.find(ENV.fetch('PANEL_ACTOR_SITE_ID') { 1 }) + end end end From 364b63a075b141a01e429d30b0030e149d86d67f Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 17:57:21 -0300 Subject: [PATCH 223/297] =?UTF-8?q?fix:=20notificar=20correctamente=20la?= =?UTF-8?q?=20excepci=C3=B3n=20#15608?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub/activity/follow.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub/activity/follow.rb b/app/models/activity_pub/activity/follow.rb index 393eb3b4..37dc9d26 100644 --- a/app/models/activity_pub/activity/follow.rb +++ b/app/models/activity_pub/activity/follow.rb @@ -13,7 +13,7 @@ class ActivityPub def update_activity_pub_state! activity_pub.approve! rescue Exception => e - ExceptionNotifier.notify_exception(e, { site: activity_pub.site.name, activity: self.id }) + ExceptionNotifier.notify_exception(e, data: { site: activity_pub.site.name, activity: self.id }) end end end From 00270b7829266120fa2e3beaa87ffde118cae38a Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 18:05:05 -0300 Subject: [PATCH 224/297] fix: no mostrar likes #15596 --- app/models/activity_pub.rb | 16 ++++++++++++++-- app/processors/activity_pub_processor.rb | 9 ++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 73006d1d..3671000c 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -6,6 +6,12 @@ # una actividad, puede estar destinada a varies actores dentro de Sutty, # con lo que generamos una cola para cada une. # +# +# @todo Ya que une actore puede hacer varias actividades sobre el mismo +# objeto, lo correcto sería que la actividad a moderar sea una sola en +# lugar de una lista acumulativa. Es decir cada ActivityPub representa +# el estado del conjunto (Actor, Object, Activity) +# # @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions} class ActivityPub < ApplicationRecord IGNORED_EVENTS = %i[pause remove].freeze @@ -105,10 +111,16 @@ class ActivityPub < ApplicationRecord # disponible, que según el scope por defecto, va a ser la primera de # la lista. def reject_remotely! - raise unless site.social_inbox.inbox.reject(id: activities.first.uri).ok? + fail! unless site.social_inbox.inbox.reject(id: activities.first.uri).ok? end def allow_remotely! - raise unless site.social_inbox.inbox.accept(id: activities.first.uri).ok? + with_failure_handling(activity: activities.first.uri) do + fail! unless site.social_inbox.inbox.accept(id: activities.first.uri).ok? + end + end + + def fail_message + activities.first&.uri || 'Activity missing' end end diff --git a/app/processors/activity_pub_processor.rb b/app/processors/activity_pub_processor.rb index 52cdb6d3..501b73a5 100644 --- a/app/processors/activity_pub_processor.rb +++ b/app/processors/activity_pub_processor.rb @@ -6,7 +6,14 @@ class ActivityPubProcessor < Rubanok::Processor # # Por ahora solo queremos moderar comentarios. prepare do - raw.where(object_type: %w[ActivityPub::Object::Note ActivityPub::Object::Article]).order(updated_at: :desc) + raw + .joins(:activities) + .where( + activity_pub_activities: { + type: %w[ActivityPub::Activity::Create ActivityPub::Activity::Update] + }, + object_type: %w[ActivityPub::Object::Note ActivityPub::Object::Article] + ).order(updated_at: :desc) end map :activity_pub_state, activate_always: true do |activity_pub_state: 'paused'| From 26e865661638e9af84acaf6dd0f331433603e89c Mon Sep 17 00:00:00 2001 From: f Date: Fri, 15 Mar 2024 18:08:34 -0300 Subject: [PATCH 225/297] fixup! fix: no mostrar likes #15596 --- app/models/activity_pub.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 3671000c..cae054cd 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -111,16 +111,10 @@ class ActivityPub < ApplicationRecord # disponible, que según el scope por defecto, va a ser la primera de # la lista. def reject_remotely! - fail! unless site.social_inbox.inbox.reject(id: activities.first.uri).ok? + raise unless site.social_inbox.inbox.reject(id: activities.first.uri).ok? end def allow_remotely! - with_failure_handling(activity: activities.first.uri) do - fail! unless site.social_inbox.inbox.accept(id: activities.first.uri).ok? - end - end - - def fail_message - activities.first&.uri || 'Activity missing' + raise unless site.social_inbox.inbox.accept(id: activities.first.uri).ok? end end From 3a0f1584c1d292997bc850e8bbd7810810bfc6b6 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 11:32:16 -0300 Subject: [PATCH 226/297] fix: poder volver al sitio --- app/controllers/actor_moderations_controller.rb | 1 + app/controllers/moderation_queue_controller.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 769be1fa..6316a4cf 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -31,6 +31,7 @@ class ActorModerationsController < ApplicationController # Ver el perfil remoto def show + breadcrumb site.title, site_posts_path(site) breadcrumb I18n.t('moderation_queue.index.title'), site_moderation_queue_path(site) @remote_profile = actor_moderation.actor.content diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index 3536dc95..4bd61e38 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -11,6 +11,7 @@ class ModerationQueueController < ApplicationController # Cola de moderación viendo todo el sitio def index + breadcrumb site.title, site_posts_path(site) breadcrumb I18n.t('moderation_queue.index.title'), '' # @todo cambiar el estado por query From adfd6c78bbb583ea0dc8936358152d771771fded Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 14:15:32 -0300 Subject: [PATCH 227/297] feat: procesar actividades en segundo plano MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit en lugar de hacerlo en el momento, respondemos lo más rápido posible a la social inbox, ya que un webhook fallado genera un error en la social inbox, que genera un error en la instancia remota, que va a volver a intentar muchas veces. ahora recibimos una vez, si falla el procesamiento, lo detenemos para que alguien humane actúe al respecto. --- .../v1/webhooks/social_inbox_controller.rb | 171 ++---------------- app/jobs/activity_pub/process_job.rb | 154 ++++++++++++++++ 2 files changed, 167 insertions(+), 158 deletions(-) create mode 100644 app/jobs/activity_pub/process_job.rb diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 7b17c47b..6ac91a51 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -5,196 +5,51 @@ module Api module Webhooks # Recibe webhooks de la Social Inbox # - # @todo Mover todo a un Job que obtenga el objeto remoto antes de - # instanciar el objeto localmente en lugar de arreglarlo después y - # poder responder lo más rápido posible el webhook. # @see {https://www.w3.org/TR/activitypub/} class SocialInboxController < BaseController include Api::V1::Webhooks::Concerns::WebhookConcern + # Validar que el token sea correcto + before_action :usuarie + # Cuando una actividad ingresa en la cola de moderación, la # recibimos por acá # - # Vamos a recibir Create, Update, Delete, Follow, Undo y obtener - # el objeto dentro de cada una para guardar un estado asociado - # al sitio. + # Vamos a recibir Create, Update, Delete, Follow, Undo, + # Announce, Like y obtener el objeto dentro de cada una para + # guardar un estado asociado al sitio. # # El objeto del estado puede ser un objeto o une actore, # dependiendo de la actividad. def moderationqueued - # Devuelve un error si el token no es válido - usuarie.present? + process! :paused - ::ActivityPub.transaction do - - # Crea todos los registros necesarios y actualiza el estado - actor.present? - instance.present? - object.present? - activity_pub.present? - - activity.update_activity_pub_state! - end - rescue ActiveRecord::RecordInvalid => e - ExceptionNotifier.notify_exception(e, - data: { site: site.name, usuarie: usuarie.email, - activity: original_activity }) - ensure head :accepted end # Cuando la Social Inbox acepta una actividad, la recibimos # igual y la guardamos por si cambiamos de idea. - # - # @todo DRY def onapproved - ::ActivityPub.transaction do - actor.present? - instance.present? - object.present? - activity.present? - activity_pub.update(aasm_state: 'approved') - activity.update_activity_pub_state! - end + process! :approved head :accepted end # Cuando la Social Inbox rechaza una actividad, la recibimos # igual y la guardamos por si cambiamos de idea. - # - # @todo DRY def onrejected - ::ActivityPub.transaction do - actor.present? - instance.present? - object.present? - activity.present? - activity_pub.update(aasm_state: 'rejected') - end + process! :rejected head :accepted end private - # Si el objeto ya viene incorporado en la actividad o lo tenemos - # que traer remotamente. + # Envía la actividad para procesamiento por separado. # - # @return [Bool] - def object_embedded? - @object_embedded ||= original_activity[:object].is_a?(Hash) - end - - # Encuentra la URI del objeto o falla si no la encuentra. - # - # @return [String] - def object_uri - @object_uri ||= ::ActivityPub.uri_from_object(original_activity[:object]) - ensure - raise ActiveRecord::RecordNotFound, 'object id missing' if @object_uri.blank? - end - - # Atajo a la instancia - # - # @return [ActivityPub::Instance] - def instance - actor.instance - end - - # Genera un objeto a partir de la actividad. Si el objeto ya - # existe, actualiza su contenido. Si el objeto no viene - # incorporado, obtenemos el contenido más tarde. - # - # @return [ActivityPub::Object] - def object - @object ||= ::ActivityPub::Object.find_or_initialize_by(uri: object_uri).tap do |o| - # XXX: Si el objeto es una actividad, esto siempre va a ser - # Generic - o.type ||= 'ActivityPub::Object::Generic' - - if object_embedded? - o.content = original_object - begin - type = original_object[:type].presence - o.type = "ActivityPub::Object::#{type}".constantize if type - rescue NameError - end - end - - o.save! - - # XXX: el objeto necesita ser guardado antes de poder - # procesarlo. No usamos GlobalID porque el tipo de objeto - # cambia y produce un error de deserialización. - ::ActivityPub::FetchJob.perform_later(site: site, object_id: o.id) unless object_embedded? - end - end - - # Genera el seguimiento del estado del objeto con respecto al - # sitio. - # - # @return [ActivityPub] - def activity_pub - @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, actor: actor, instance: instance, object_id: object.id, object_type: object.type) - end - - # Crea la actividad y la vincula con el estado - # - # @return [ActivityPub::Activity] - def activity - @activity ||= - ::ActivityPub::Activity - .type_from(original_activity) - .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| - a.content = original_activity.dup - a.content[:object] = object.uri - a.save! - end - end - - # Actor, si no hay instancia, la crea en el momento, junto con - # su estado de moderación. - # - # @return [Actor] - def actor - @actor ||= ::ActivityPub::Actor.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| - unless a.instance - a.instance = ::ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) - - ::ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) - end - - site.instance_moderations.find_or_create_by(instance: a.instance) - - a.save! - - site.actor_moderations.find_or_create_by(actor: a) - - ::ActivityPub::ActorFetchJob.perform_later(site: site, actor: a) - end - end - - # Descubre la actividad recibida, generando un error si la - # actividad no está dirigida a nosotres. - # - # @todo Validar formato - # @return [Hash] - def original_activity - @original_activity ||= FastJsonparser.parse(request.raw_post).tap do |activity| - raise '@context missing' unless activity[:@context].presence - raise 'id missing' unless activity[:id].presence - raise 'object missing' unless activity[:object].presence - rescue RuntimeError => e - raise ActiveRecord::RecordNotFound, e.message - end - end - - # @return [Hash,String] - def original_object - @original_object ||= original_activity[:object].dup.tap do |o| - o[:@context] = original_activity[:@context].dup - end + # @param initial_state [Symbol] + def process!(initial_state) + ::ActivityPub::ProcessJob.perform_later(site: site, body: request.raw_post, initial_state: initial_state) end end end diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb new file mode 100644 index 00000000..6554b44d --- /dev/null +++ b/app/jobs/activity_pub/process_job.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +class ActivityPub + # Procesar las actividades a medida que llegan + class ProcessJob < ApplicationJob + attr_reader :body + + # Procesa la actividad en segundo plano + # + # @param :body [String] + # @param :initial_state [Symbol,String] + def perform(site:, body:, initial_state: :paused) + @body = body + @site = site + + ActiveRecord::Base.connection_pool.with_connection do + ::ActivityPub.transaction do + # Crea todos los registros necesarios y actualiza el estado + actor.present? + instance.present? + object.present? + activity_pub.present? + activity_pub.update(aasm_state: initial_state) + + activity.update_activity_pub_state! + end + end + # Al generar una excepción, en lugar de seguir intentando, enviamos + # el reporte. + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, activity: original_activity }) + end + + private + + # Si el objeto ya viene incorporado en la actividad o lo tenemos + # que traer remotamente. + # + # @return [Bool] + def object_embedded? + @object_embedded ||= original_activity[:object].is_a?(Hash) + end + + # Encuentra la URI del objeto o falla si no la encuentra. + # + # @return [String] + def object_uri + @object_uri ||= ::ActivityPub.uri_from_object(original_activity[:object]) + ensure + raise ActiveRecord::RecordNotFound, 'object id missing' if @object_uri.blank? + end + + # Atajo a la instancia + # + # @return [ActivityPub::Instance] + def instance + actor.instance + end + + # Genera un objeto a partir de la actividad. Si el objeto ya + # existe, actualiza su contenido. Si el objeto no viene + # incorporado, obtenemos el contenido más tarde. + # + # @return [ActivityPub::Object] + def object + @object ||= ::ActivityPub::Object.find_or_initialize_by(uri: object_uri).tap do |o| + # XXX: Si el objeto es una actividad, esto siempre va a ser + # Generic + o.type ||= 'ActivityPub::Object::Generic' + + if object_embedded? + o.content = original_object + begin + type = original_object[:type].presence + o.type = "ActivityPub::Object::#{type}".constantize if type + rescue NameError + end + end + + o.save! + + # XXX: el objeto necesita ser guardado antes de poder + # procesarlo. No usamos GlobalID porque el tipo de objeto + # cambia y produce un error de deserialización. + ::ActivityPub::FetchJob.perform_later(site: site, object_id: o.id) unless object_embedded? + end + end + + # Genera el seguimiento del estado del objeto con respecto al + # sitio. + # + # @return [ActivityPub] + def activity_pub + @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, actor: actor, instance: instance, + object_id: object.id, object_type: object.type) + end + + # Crea la actividad y la vincula con el estado + # + # @return [ActivityPub::Activity] + def activity + @activity ||= + ::ActivityPub::Activity + .type_from(original_activity) + .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| + a.content = original_activity.dup + a.content[:object] = object.uri + a.save! + end + end + + # Actor, si no hay instancia, la crea en el momento, junto con + # su estado de moderación. + # + # @return [Actor] + def actor + @actor ||= ::ActivityPub::Actor.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| + unless a.instance + a.instance = ::ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) + + ::ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) + end + + site.instance_moderations.find_or_create_by(instance: a.instance) + + a.save! + + site.actor_moderations.find_or_create_by(actor: a) + + ::ActivityPub::ActorFetchJob.perform_later(site: site, actor: a) + end + end + + # @return [Hash,String] + def original_object + @original_object ||= original_activity[:object].dup.tap do |o| + o[:@context] = original_activity[:@context].dup + end + end + + # Descubre la actividad recibida, generando un error si la + # actividad no está dirigida a nosotres. + # + # @todo Validar formato con Dry::Schema + # @return [Hash] + def original_activity + @original_activity ||= FastJsonparser.parse(body).tap do |activity| + raise '@context missing' unless activity[:@context].present? + raise 'id missing' unless activity[:id].present? + raise 'object missing' unless activity[:object].present? + end + end + end +end From 1dae918a179ecca3f18edfe286e16d58b7546398 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 14:17:28 -0300 Subject: [PATCH 228/297] feat: poder gestionar la cola de tareas --- Gemfile | 1 + Gemfile.lock | 18 +++++++++++++++++- config/initializers/que_web.rb | 5 +++++ config/routes.rb | 3 +++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 config/initializers/que_web.rb diff --git a/Gemfile b/Gemfile index e314c172..e5e6ed10 100644 --- a/Gemfile +++ b/Gemfile @@ -83,6 +83,7 @@ gem 'rubanok' gem 'after_commit_everywhere', '~> 1.0' gem 'aasm' +gem 'que-web' # database gem 'hairtrigger' diff --git a/Gemfile.lock b/Gemfile.lock index 751fc73e..7087e013 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,6 +97,7 @@ GEM ast (2.4.2) autoprefixer-rails (10.4.13.0) execjs (~> 2) + base64 (0.2.0) bcrypt (3.1.20-x86_64-linux-musl) bcrypt_pbkdf (1.1.0-x86_64-linux-musl) benchmark-ips (2.12.0) @@ -370,6 +371,8 @@ GEM i18n (>= 0.6.10, < 2) request_store (~> 1.0) multi_xml (0.6.0) + mustermann (3.0.0) + ruby2_keywords (~> 0.0.1) net-imap (0.4.9) date net-protocol @@ -409,12 +412,18 @@ GEM pundit (2.3.1) activesupport (>= 3.0.0) que (2.2.1) + que-web (0.10.0) + que (>= 1) + sinatra racc (1.7.3-x86_64-linux-musl) rack (2.2.8) rack-cors (2.0.1) rack (>= 2.0.0) rack-mini-profiler (3.1.0) rack (>= 1.2.0) + rack-protection (3.2.0) + base64 (>= 0.1.0) + rack (~> 2.2, >= 2.2.4) rack-proxy (0.7.7) rack rack-test (2.1.0) @@ -513,6 +522,7 @@ GEM ruby-statistics (3.0.2) ruby-vips (2.2.0) ffi (~> 1.12) + ruby2_keywords (0.0.5) ruby2ruby (2.5.0) ruby_parser (~> 3.1) sexp_processor (~> 4.6) @@ -539,6 +549,11 @@ GEM sexp_processor (4.17.0) simpleidn (0.2.1) unf (~> 0.1.4) + sinatra (3.2.0) + mustermann (~> 3.0) + rack (~> 2.2, >= 2.2.4) + rack-protection (= 3.2.0) + tilt (~> 2.0) sourcemap (0.1.1) spring (4.1.1) spring-watcher-listen (2.1.0) @@ -626,7 +641,7 @@ DEPENDENCIES devise devise-i18n devise_invitable - distributed-press-api-client (~> 0.4.0) + distributed-press-api-client (~> 0.4.1) dotenv-rails down ed25519 @@ -670,6 +685,7 @@ DEPENDENCIES puma pundit que + que-web rack-cors rack-mini-profiler rails (~> 6.1.0) diff --git a/config/initializers/que_web.rb b/config/initializers/que_web.rb new file mode 100644 index 00000000..192256db --- /dev/null +++ b/config/initializers/que_web.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Que::Web.use(Rack::Auth::Basic) do |user, password| + [user, password] == [ENV['HTTP_BASIC_USER'], ENV['HTTP_BASIC_PASSWORD']] +end diff --git a/config/routes.rb b/config/routes.rb index 054b7f4d..4d43d66a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,9 @@ Rails.application.routes.draw do devise_for :usuaries get '/.well-known/change-password', to: redirect('/usuaries/edit') + require 'que/web' + mount Que::Web => '/que' + root 'application#index' constraints(Constraints::ApiSubdomain.new) do From 8f91b748ff6ed9536d13093efd72e228bb66de3f Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 14:27:28 -0300 Subject: [PATCH 229/297] =?UTF-8?q?fix:=20mostrar=20botones=20de=20acci?= =?UTF-8?q?=C3=B3n=20en=20masa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/components/_btn_base.haml | 4 ++-- app/views/components/_dropdown_button.haml | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/components/_btn_base.haml b/app/views/components/_btn_base.haml index f9227482..faa5c85f 100644 --- a/app/views/components/_btn_base.haml +++ b/app/views/components/_btn_base.haml @@ -3,7 +3,7 @@ - local_assigns[:method] ||= 'patch' - local_assigns[:class] ||= 'btn-secondary' - local_assigns[:class] = "btn #{local_assigns[:class]}" +- local_assigns.delete(:text) --# @todo path es obligatorio -= button_to local_assigns[:path], **local_assigns.compact do += button_to(path, **local_assigns.compact) do = text diff --git a/app/views/components/_dropdown_button.haml b/app/views/components/_dropdown_button.haml index c0f12754..d6de6c8e 100644 --- a/app/views/components/_dropdown_button.haml +++ b/app/views/components/_dropdown_button.haml @@ -1,4 +1,6 @@ -# @param name [String] @param value [String] -%button.dropdown-item{type: 'submit', data: { target: 'dropdown.item' }, name: name, value: value, **local_assigns.compact } + @param text [String] +- local_assigns.delete(:text) +%button.dropdown-item{type: 'submit', data: { target: 'dropdown.item' }, name: name, value: value, **local_assigns.compact }= text From 8ff5626e9968407d5b12f5e9a77fd3cb739ff77d Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 15:12:55 -0300 Subject: [PATCH 230/297] feat: no permitir entrar al evento si falla la request --- app/controllers/activity_pubs_controller.rb | 14 +++--- .../actor_moderations_controller.rb | 13 ++--- .../instance_moderations_controller.rb | 13 ++--- app/models/activity_pub.rb | 21 ++++---- app/models/actor_moderation.rb | 32 +++++-------- app/models/concerns/aasm_events_concern.rb | 29 ++++------- app/models/fediblock_state.rb | 48 ++++++++++--------- app/models/instance_moderation.rb | 28 ++++------- 8 files changed, 88 insertions(+), 110 deletions(-) diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index edece8f8..225311c2 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -9,14 +9,16 @@ class ActivityPubsController < ApplicationController authorize activity_pub activity_pub.update(remote_flag_params(activity_pub)) if event == :report - activity_pub.public_send(:"#{event}!") if activity_pub.public_send(:"may_#{event}?") - flash[:success] = I18n.t("activity_pubs.#{event}.success") - rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params.permit!.to_h }) + message = + if activity_pub.public_send(:"may_#{event}?") && activity_pub.public_send(:"#{event}!") + :success + else + :error + end + + flash[message] = I18n.t("activity_pubs.#{event}.#{message}") - flash[:error] = I18n.t("activity_pubs.#{event}.error") - ensure redirect_to_moderation_queue! end end diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 6316a4cf..cb7a63a9 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -17,14 +17,15 @@ class ActorModerationsController < ApplicationController # Crea una RemoteFlag si se envían los parámetros adecuados actor_moderation.update(remote_flag_params(actor_moderation)) if actor_event == :report - actor_moderation.public_send(:"#{actor_event}!") if actor_moderation.public_send(:"may_#{actor_event}?") + message = + if actor_moderation.public_send(:"may_#{actor_event}?") && actor_moderation.public_send(:"#{actor_event}!") + :success + else + :error + end - flash[:success] = I18n.t("actor_moderations.#{actor_event}.success") - rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params.permit!.to_h }) + flash[message] = I18n.t("actor_moderations.#{actor_event}.#{message}") - flash[:error] = I18n.t("actor_moderations.#{actor_event}.error") - ensure redirect_to_moderation_queue! end end diff --git a/app/controllers/instance_moderations_controller.rb b/app/controllers/instance_moderations_controller.rb index 06f5cfc1..13d7f428 100644 --- a/app/controllers/instance_moderations_controller.rb +++ b/app/controllers/instance_moderations_controller.rb @@ -8,14 +8,15 @@ class InstanceModerationsController < ApplicationController define_method(event) do authorize instance_moderation - instance_moderation.public_send(:"#{event}!") if instance_moderation.public_send(:"may_#{event}?") + message = + if instance_moderation.public_send(:"may_#{event}?") && instance_moderation.public_send(:"#{event}!") + :success + else + :error + end - 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[message] = I18n.t("instance_moderations.#{event}.#{message}") - flash[:error] = I18n.t("instance_moderations.#{event}.error") - ensure redirect_to_moderation_queue! end end diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index cae054cd..8142ecfb 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -47,14 +47,19 @@ class ActivityPub < ApplicationRecord # Todavía no hay una decisión sobre el objeto state :paused, initial: true # Le usuarie aprobó el objeto - state :approved + state :approved, before_enter: :allow_remotely! # Le usuarie rechazó el objeto - state :rejected + state :rejected, before_enter: :reject_remotely! # Le usuarie reportó el objeto state :reported # Le actore eliminó el objeto state :removed + # Gestionar todos los errores + error_on_all_events do |e| + ExceptionNotifier.notify_exception(e, data: { site: site.name, activity_pub: self.id, activity: activities.first.uri }) + end + # Se puede volver a pausa en caso de actualización remota, para # revisar los cambios. event :pause do @@ -67,7 +72,7 @@ class ActivityPub < ApplicationRecord event :remove do transitions to: :removed - before do + after do next if object.blank? object.update(content: {}) unless object.content.empty? @@ -79,26 +84,18 @@ class ActivityPub < ApplicationRecord # webhook a modo de confirmación. event :approve do transitions from: %i[paused], to: :approved - - before do - allow_remotely! - end end # La actividad fue rechazada event :reject do transitions from: %i[paused], to: :rejected - - before do - reject_remotely! - end end # Solo podemos reportarla luego de rechazarla event :report do transitions from: :rejected, to: :reported - before do + after do ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting? end end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 3b183a4f..c566b844 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -2,8 +2,8 @@ # Mantiene la relación entre Site y Actor class ActorModeration < ApplicationRecord - IGNORED_EVENTS = %i[remove] - IGNORED_STATES = %i[removed] + IGNORED_EVENTS = %i[remove].freeze + IGNORED_STATES = %i[removed].freeze include AASM @@ -14,38 +14,30 @@ class ActorModeration < ApplicationRecord accepts_nested_attributes_for :remote_flag aasm do - state :paused, initial: true - state :allowed - state :blocked + state :paused, initial: true, before_enter: :pause_remotely! + state :allowed, before_enter: :allow_remotely! + state :blocked, before_enter: :block_remotely! state :reported state :removed + error_on_all_events do |e| + ExceptionNotifier.notify_exception(e, data: { site: site.name, actor: actor.uri, actor_moderation: id }) + end + event :pause do transitions from: %i[allowed blocked reported], to: :paused - - before do - pause_remotely! - end end # Al permitir una cuenta no se permiten todos los comentarios # pendientes de moderación que ya hizo. event :allow do transitions from: %i[paused blocked reported], to: :allowed - - before do - allow_remotely! - end end # Al bloquear una cuenta no se bloquean todos los comentarios # pendientes de moderación que hizo. event :block do transitions from: %i[paused allowed], to: :blocked - - before do - block_remotely! - end end # Al reportar, necesitamos asociar una RemoteFlag para poder @@ -53,7 +45,7 @@ class ActorModeration < ApplicationRecord event :report do transitions from: %i[blocked], to: :reported - before do + after do ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting? end end @@ -63,8 +55,8 @@ class ActorModeration < ApplicationRecord event :remove do transitions to: :removed - before do - site.activity_pubs.where(actor_id: self.actor_id).remove_all! + after do + site.activity_pubs.where(actor_id: actor_id).remove_all! end end end diff --git a/app/models/concerns/aasm_events_concern.rb b/app/models/concerns/aasm_events_concern.rb index 4de5f748..788e9e1a 100644 --- a/app/models/concerns/aasm_events_concern.rb +++ b/app/models/concerns/aasm_events_concern.rb @@ -16,7 +16,7 @@ module AasmEventsConcern # # @return [Array] def self.transitionable_events(current_state) - self.events.select do |event| + events.select do |event| aasm.events.find { |x| x.name == event }.transitions_from_state? current_state end end @@ -32,19 +32,15 @@ module AasmEventsConcern # scope actual. # # @return [Bool] Si hubo al menos un error, devuelve false. - self.aasm.events.map(&:name).each do |event| + aasm.events.map(&:name).each do |event| define_singleton_method(:"#{event}_all!") do - success = true + successes = [] - self.find_each do |object| - object.public_send(:"#{event}!") if object.public_send(:"may_#{event}?") - rescue Exception => e - success = false - - notify_exception! e, object + find_each do |object| + successes << (object.public_send(:"may_#{event}?") && object.public_send(:"#{event}!")) end - success + successes.all? end # Ejecuta la transición del evento en la base de datos sin @@ -53,20 +49,13 @@ module AasmEventsConcern # # @return [Integer] Registros modificados define_singleton_method(:"#{event}_all_without_callbacks!") do - aasm_event = self.aasm.events.find { |e| e.name == event } + aasm_event = aasm.events.find { |e| e.name == event } to_state = aasm_event.transitions.map(&:to).first from_states = aasm_event.transitions.map(&:from) - self.unscope(where: :aasm_state).where(aasm_state: from_states).update_all(aasm_state: to_state, updated_at: Time.now) + unscope(where: :aasm_state).where(aasm_state: from_states).update_all(aasm_state: to_state, + updated_at: Time.now) end end - - # Envía notificación de errores - # - # @param exception [Exception] - # @param record [ApplicationRecord] - def self.notify_exception!(exception, record) - ExceptionNotifier.notify_exception(exception, data: { record_type: record.class.name, record_id: record.id }) - end end end diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index 9dde1db3..a6faa14f 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -20,20 +20,15 @@ class FediblockState < ApplicationRecord # Aunque queramos las listas habilitadas por defecto, tenemos que # habilitarlas luego de crearlas para poder generar la lista de # bloqueo en la Social Inbox. - state :disabled, initial: true - state :enabled + state :disabled, initial: true, before_enter: :disable_remotely_and_pause_instances! + state :enabled, before_enter: :enable_remotely_and_block_instances! + + error_on_all_events do |e| + ExceptionNotifier.notify_exception(e, data: { site: site.name, fediblock: id }) + end event :enable do transitions from: :disabled, to: :enabled - - before do - # Bloquear todos las instancias de este Fediblock - enable_remotely! list_names(fediblock.hostnames) - - # Luego esta tarea crea las que falten e ignora las que ya se - # bloquearon. - ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames, perform_remotely: false) - end end # Al deshabilitar, las listas pasan a modo pausa, a menos que estén @@ -44,22 +39,31 @@ class FediblockState < ApplicationRecord # de list_names event :disable do transitions from: :enabled, to: :disabled - - before do - # Deshabilitar todas las instancias que no estén habilitadas por - # otros fediblocks - disable_remotely! list_names(unique_hostnames) - - # Pausar todas las moderaciones de las instancias que no estén - # bloqueadas por otros fediblocks. - instance_ids = ActivityPub::Instance.where(hostname: unique_hostnames).ids - site.instance_moderations.where(instance_id: instance_ids).pause_all_without_callbacks! - end end end private + def enable_remotely_and_block_instances! + # Bloquear todos las instancias de este Fediblock + enable_remotely! list_names(fediblock.hostnames) + + # Luego esta tarea crea las que falten e ignora las que ya se + # bloquearon. + ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames, perform_remotely: false) + end + + def disable_remotely_and_pause_instances! + # Deshabilitar todas las instancias que no estén habilitadas por + # otros fediblocks + disable_remotely! list_names(unique_hostnames) + + # Pausar todas las moderaciones de las instancias que no estén + # bloqueadas por otros fediblocks. + instance_ids = ActivityPub::Instance.where(hostname: unique_hostnames).ids + site.instance_moderations.where(instance_id: instance_ids).pause_all_without_callbacks! + end + # Devuelve los hostnames únicos a esta instancia. # # @return [Array] diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 9cb6ffdc..7c76262e 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -2,8 +2,8 @@ # Mantiene el registro de relaciones entre sitios e instancias class InstanceModeration < ApplicationRecord - IGNORED_EVENTS = [] - IGNORED_STATES = [] + IGNORED_EVENTS = [].freeze + IGNORED_STATES = [].freeze include AASM @@ -11,38 +11,30 @@ class InstanceModeration < ApplicationRecord belongs_to :instance, class_name: 'ActivityPub::Instance' aasm do - state :paused, initial: true - state :allowed - state :blocked + state :paused, initial: true, before_enter: :pause_remotely! + state :allowed, before_enter: :allow_remotely! + state :blocked, before_enter: :block_remotely! + + error_on_all_events do |e| + ExceptionNotifier.notify_exception(e, data: { site: site.name, instance: instance.hostname, instance_moderation: id }) + end # 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 - - before do - pause_remotely! - end end # Al permitir, solo bloqueamos la instancia, sin modificar el estado # de les actores y comentarios retroactivamente. event :allow do transitions from: %i[paused blocked], to: :allowed - - before do - allow_remotely! - end end # Al bloquear, solo bloqueamos la instancia, sin modificar el estado # de les actores y comentarios retroactivamente. event :block do transitions from: %i[paused allowed], to: :blocked - - before do - block_remotely! - end end end @@ -51,7 +43,7 @@ class InstanceModeration < ApplicationRecord # @return [Array] def actor_ids - ActivityPub::Actor.where(instance_id: self.instance_id).ids + ActivityPub::Actor.where(instance_id: instance_id).ids end # Elimina la instancia de todas las listas From f6b2895b58a0f9bf2cd8cc19e52f112afe01e9d4 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 16:36:00 -0300 Subject: [PATCH 231/297] =?UTF-8?q?fix:=20aprobar=20las=20peticiones=20de?= =?UTF-8?q?=20seguimiento=20autom=C3=A1ticamente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/activity_pub/activity/follow.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/models/activity_pub/activity/follow.rb b/app/models/activity_pub/activity/follow.rb index 37dc9d26..b4c34d7a 100644 --- a/app/models/activity_pub/activity/follow.rb +++ b/app/models/activity_pub/activity/follow.rb @@ -11,9 +11,7 @@ class ActivityPub class Follow < ActivityPub::Activity # Auto-aprobar la solicitud de seguimiento def update_activity_pub_state! - activity_pub.approve! - rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: activity_pub.site.name, activity: self.id }) + activity_pub.approve! if activity_pub.may_approve? end end end From b972e53e4821c563eaebabb5e1cbcb899ea063f8 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 17:53:29 -0300 Subject: [PATCH 232/297] =?UTF-8?q?feat:=20guardar=20la=20menci=C3=B3n=20d?= =?UTF-8?q?e=20le=20autore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/fetch_job.rb | 7 +++++- app/models/activity_pub/actor.rb | 9 ++++--- .../20240316203721_add_mention_to_actors.rb | 24 +++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20240316203721_add_mention_to_actors.rb diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 129908d3..e9220bfc 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -29,8 +29,13 @@ class ActivityPub object.update(content: content, type: ActivityPub::Object.type_from(content).name) + return if current_type == object.type + + object = ::ActivityPub::Object.find(object_id) + object.actor.save if object.actor_type? + # Arreglar las relaciones con actividades también - ActivityPub.where(object_id: object.id).update_all(object_type: object.type) unless current_type == object.type + ActivityPub.where(object_id: object.id).update_all(object_type: object.type) rescue FastJsonparser::ParseError => e ExceptionNotifier.notify_exception(e, data: { site: site.name, body: response.body }) end diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index b03145e7..a8cc0d5d 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -18,19 +18,22 @@ class ActivityPub # Les actores son únicxs a toda la base de datos validates :uri, presence: true, url: true, uniqueness: true + before_save :mentionize! + # Obtiene el nombre de la Actor como mención, solo si obtuvimos el # contenido de antemano. # # @return [String, nil] - def mention + def mentionize! + return if mention.present? return if content['preferredUsername'].blank? return if instance.blank? - @mention ||= "@#{content['preferredUsername']}@#{instance.hostname}" + self.mention ||= "@#{content['preferredUsername']}@#{instance.hostname}" end def object - @object ||= ActivityPub::Object::Person.find_or_initialize_by(uri: uri) + @object ||= ActivityPub::Object.find_or_initialize_by(uri: uri) end def content diff --git a/db/migrate/20240316203721_add_mention_to_actors.rb b/db/migrate/20240316203721_add_mention_to_actors.rb new file mode 100644 index 00000000..caa4f526 --- /dev/null +++ b/db/migrate/20240316203721_add_mention_to_actors.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Guarda la mención en la tabla de actores +class AddMentionToActors < ActiveRecord::Migration[6.1] + def up + add_column :activity_pub_actors, :mention, :string, null: true + + actor_types = %w[ + ActivityPub::Object::Application + ActivityPub::Object::Group + ActivityPub::Object::Organization + ActivityPub::Object::Person + ActivityPub::Object::Service + ] + + ActivityPub::Object.where(type: actor_types).where.not(content: {}).find_each do |object| + ActivityPub::Actor.find_by_uri(object.uri)&.save + end + end + + def down + remove_column :activity_pub_actors, :mention + end +end From 769eae61574e859c0bfc6078785f7fb28ef6aadf Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 19:05:12 -0300 Subject: [PATCH 233/297] feat: sincronizar en segundo plano --- .../actor_moderations_controller.rb | 2 +- .../activity_pub/instance_moderation_job.rb | 2 + app/jobs/activity_pub/sync_lists_job.rb | 70 +++++++++++++++++++ app/models/actor_moderation.rb | 33 +++------ app/models/fediblock_state.rb | 55 ++++----------- app/models/instance_moderation.rb | 42 ++--------- 6 files changed, 100 insertions(+), 104 deletions(-) create mode 100644 app/jobs/activity_pub/sync_lists_job.rb diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index cb7a63a9..cd81e441 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -64,7 +64,7 @@ class ActorModerationsController < ApplicationController end end - message = actor_moderation.public_send(method) ? :success : :error + message = actor_moderations.public_send(method) ? :success : :error flash[message] = I18n.t("actor_moderations.action_on_several.#{message}") end diff --git a/app/jobs/activity_pub/instance_moderation_job.rb b/app/jobs/activity_pub/instance_moderation_job.rb index 214f8dd4..00100abf 100644 --- a/app/jobs/activity_pub/instance_moderation_job.rb +++ b/app/jobs/activity_pub/instance_moderation_job.rb @@ -30,6 +30,8 @@ class ActivityPub else scope.block_all_without_callbacks! end + + ActivityPub::SyncListsJob.perform_later(site: site) end end end diff --git a/app/jobs/activity_pub/sync_lists_job.rb b/app/jobs/activity_pub/sync_lists_job.rb new file mode 100644 index 00000000..39f6bdc9 --- /dev/null +++ b/app/jobs/activity_pub/sync_lists_job.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +class ActivityPub + # Sincroniza las listas de bloqueo y permitidas con el estado actual + # de la base de datos. + class SyncListsJob < ApplicationJob + # Siempre correr al final + self.priority = 100 + + attr_reader :logs + + # Ejecuta todas las requests y consolida los posibles errores. + # + # @param site [Site] + def run(site:) + @logs = {} + + instance_scope = site.instance_moderations.joins(:instance) + actor_scope = site.actor_moderations.joins(:actor) + + blocklist = wildcardize(instance_scope.blocked.pluck(:hostname)) + actor_scope.blocked.distinct.pluck(:mention).compact + allowlist = wildcardize(instance_scope.allowed.pluck(:hostname)) + actor_scope.allowed.distinct.pluck(:mention).compact + pauselist = wildcardize(instance_scope.paused.pluck(:hostname)) + actor_scope.paused.distinct.pluck(:mention).compact + + if blocklist.present? + Rails.logger.info "Bloqueando: #{blocklist.join(', ')}" + process(:blocked) { site.social_inbox.allowlist.delete(list: blocklist) } + process(:blocked) { site.social_inbox.blocklist.post(list: blocklist) } + end + + if allowlist.present? + Rails.logger.info "Permitiendo: #{allowlist.join(', ')}" + process(:allowed) { site.social_inbox.blocklist.delete(list: allowlist) } + process(:allowed) { site.social_inbox.allowlist.post(list: allowlist) } + end + + if pauselist.present? + Rails.logger.info "Pausando: #{pauselist.join(', ')}" + process(:paused) { site.social_inbox.blocklist.delete(list: pauselist) } + process(:paused) { site.social_inbox.allowlist.delete(list: pauselist) } + end + + # Si alguna falló, reintentar + raise if logs.present? + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, logs: logs, blocklist: blocklist, allowlist: allowlist, pauselist: pauselist }) + + raise + end + + private + + def process(stage) + response = yield + + return if response.ok? + + logs[stage] ||= [] + logs[stage] << { body: response.body, code: response.code } + end + + # @params hostnames [Array] + # @return [Array] + def wildcardize(hostnames) + hostnames.map do |hostname| + "@*@#{hostname}" + end + end + end +end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index c566b844..18149be4 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -14,9 +14,9 @@ class ActorModeration < ApplicationRecord accepts_nested_attributes_for :remote_flag aasm do - state :paused, initial: true, before_enter: :pause_remotely! - state :allowed, before_enter: :allow_remotely! - state :blocked, before_enter: :block_remotely! + state :paused, initial: true + state :allowed + state :blocked state :reported state :removed @@ -25,19 +25,19 @@ class ActorModeration < ApplicationRecord end event :pause do - transitions from: %i[allowed blocked reported], to: :paused + transitions from: %i[allowed blocked reported], to: :paused, after: :synchronize! end # Al permitir una cuenta no se permiten todos los comentarios # pendientes de moderación que ya hizo. event :allow do - transitions from: %i[paused blocked reported], to: :allowed + transitions from: %i[paused blocked reported], to: :allowed, after: :synchronize! end # Al bloquear una cuenta no se bloquean todos los comentarios # pendientes de moderación que hizo. event :block do - transitions from: %i[paused allowed], to: :blocked + transitions from: %i[paused allowed], to: :blocked, after: :synchronize! end # Al reportar, necesitamos asociar una RemoteFlag para poder @@ -64,24 +64,7 @@ class ActorModeration < ApplicationRecord # Definir eventos en masa include AasmEventsConcern - def pause_remotely! - raise unless - actor.mention && - site.social_inbox.allowlist.delete(list: [actor.mention]).ok? && - site.social_inbox.blocklist.delete(list: [actor.mention]).ok? - end - - def allow_remotely! - raise unless - actor.mention && - site.social_inbox.allowlist.post(list: [actor.mention]).ok? && - site.social_inbox.blocklist.delete(list: [actor.mention]).ok? - end - - def block_remotely! - raise unless - actor.mention && - site.social_inbox.allowlist.delete(list: [actor.mention]).ok? && - site.social_inbox.blocklist.post(list: [actor.mention]).ok? + def synchronize! + ActivityPub::SyncListsJob.perform_later(site: site) end end diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index a6faa14f..82912f76 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -20,8 +20,8 @@ class FediblockState < ApplicationRecord # Aunque queramos las listas habilitadas por defecto, tenemos que # habilitarlas luego de crearlas para poder generar la lista de # bloqueo en la Social Inbox. - state :disabled, initial: true, before_enter: :disable_remotely_and_pause_instances! - state :enabled, before_enter: :enable_remotely_and_block_instances! + state :disabled, initial: true, before_enter: :pause_unique_instances! + state :enabled, before_enter: :block_instances! error_on_all_events do |e| ExceptionNotifier.notify_exception(e, data: { site: site.name, fediblock: id }) @@ -38,32 +38,27 @@ class FediblockState < ApplicationRecord # pero esto implica que tenemos que encontrar las que sí y quitarlas # de list_names event :disable do - transitions from: :enabled, to: :disabled + transitions from: :enabled, to: :disabled, after: :synchronize! end end private - def enable_remotely_and_block_instances! - # Bloquear todos las instancias de este Fediblock - enable_remotely! list_names(fediblock.hostnames) - - # Luego esta tarea crea las que falten e ignora las que ya se - # bloquearon. - ActivityPub::InstanceModerationJob.perform_now(site: site, hostnames: fediblock.hostnames, perform_remotely: false) + def block_instances! + ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: fediblock.hostnames, perform_remotely: false) end - def disable_remotely_and_pause_instances! - # Deshabilitar todas las instancias que no estén habilitadas por - # otros fediblocks - disable_remotely! list_names(unique_hostnames) - - # Pausar todas las moderaciones de las instancias que no estén - # bloqueadas por otros fediblocks. + # Pausar todas las moderaciones de las instancias que no estén + # bloqueadas por otros fediblocks. + def pause_unique_instances! instance_ids = ActivityPub::Instance.where(hostname: unique_hostnames).ids site.instance_moderations.where(instance_id: instance_ids).pause_all_without_callbacks! end + def synchronize! + ActivityPub::SyncListsJob.perform_later(site: site) + end + # Devuelve los hostnames únicos a esta instancia. # # @return [Array] @@ -82,30 +77,4 @@ class FediblockState < ApplicationRecord fediblock.hostnames - other_enabled_hostnames end end - - # @param hostnames [Array] - # @return [Array] - def list_names(hostnames) - hostnames.map do |hostname| - "@*@#{hostname}" - end - end - - # Al deshabilitar, las instancias pasan a ser analizadas caso por caso - # - # @param list [Array] - def disable_remotely!(list) - raise unless - site.social_inbox.blocklist.delete(list: list).ok? && - site.social_inbox.allowlist.delete(list: list).ok? - end - - # Al habilitar, se bloquean todas las instancias de la lista - # - # @param list [Array] - def enable_remotely!(list) - raise unless - site.social_inbox.blocklist.post(list: list).ok? && - site.social_inbox.allowlist.delete(list: list).ok? - end end diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 7c76262e..5a1a5ed6 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -11,14 +11,18 @@ class InstanceModeration < ApplicationRecord belongs_to :instance, class_name: 'ActivityPub::Instance' aasm do - state :paused, initial: true, before_enter: :pause_remotely! - state :allowed, before_enter: :allow_remotely! - state :blocked, before_enter: :block_remotely! + state :paused, initial: true + state :allowed + state :blocked error_on_all_events do |e| ExceptionNotifier.notify_exception(e, data: { site: site.name, instance: instance.hostname, instance_moderation: id }) end + after_all_events do + ActivityPub::SyncListsJob.perform_later(site: site) + end + # Al volver la instancia a pausa no cambiamos el estado de # moderación de actores pre-existente. event :pause do @@ -40,36 +44,4 @@ class InstanceModeration < ApplicationRecord # Definir eventos en masa include AasmEventsConcern - - # @return [Array] - def actor_ids - ActivityPub::Actor.where(instance_id: instance_id).ids - end - - # Elimina la instancia de todas las listas - # - # @return [Boolean] - def pause_remotely! - raise unless - site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && - site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? - end - - # Deja de permitir la instancia - # - # @return [Boolean] - def block_remotely! - raise unless - site.social_inbox.allowlist.delete(list: [instance.list_name]).ok? && - site.social_inbox.blocklist.post(list: [instance.list_name]).ok? - end - - # Permite la instancia - # - # @return [Boolean] - def allow_remotely! - raise unless - site.social_inbox.blocklist.delete(list: [instance.list_name]).ok? && - site.social_inbox.allowlist.post(list: [instance.list_name]).ok? - end end From a65a9f2f504969755529bd51efc03934c4c51a81 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 19:16:30 -0300 Subject: [PATCH 234/297] feat: aprobar o rechazar en segundo plano --- app/jobs/activity_pub/inbox_job.rb | 16 ++++++++++++++++ app/models/activity_pub.rb | 23 ++++++++++------------- 2 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 app/jobs/activity_pub/inbox_job.rb diff --git a/app/jobs/activity_pub/inbox_job.rb b/app/jobs/activity_pub/inbox_job.rb new file mode 100644 index 00000000..93216d44 --- /dev/null +++ b/app/jobs/activity_pub/inbox_job.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class ActivityPub + class InboxJob < ApplicationJob + self.priority = 10 + + # @param :site [Site] + # @param :activity [String] + # @param :action [Symbol] + def perform(site:, activity:, action:) + response = site.social_inbox.inbox.public_send(action, id: activity) + + raise response.body unless response.ok? + end + end +end diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 8142ecfb..55b48cd7 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -47,9 +47,9 @@ class ActivityPub < ApplicationRecord # Todavía no hay una decisión sobre el objeto state :paused, initial: true # Le usuarie aprobó el objeto - state :approved, before_enter: :allow_remotely! + state :approved # Le usuarie rechazó el objeto - state :rejected, before_enter: :reject_remotely! + state :rejected # Le usuarie reportó el objeto state :reported # Le actore eliminó el objeto @@ -84,11 +84,19 @@ class ActivityPub < ApplicationRecord # webhook a modo de confirmación. event :approve do transitions from: %i[paused], to: :approved + + after do + ActivityPub::InboxJob.perform_later(site: site, activity: activities.first.uri, action: :accept) + end end # La actividad fue rechazada event :reject do transitions from: %i[paused], to: :rejected + + after do + ActivityPub::InboxJob.perform_later(site: site, activity: activities.first.uri, action: :reject) + end end # Solo podemos reportarla luego de rechazarla @@ -103,15 +111,4 @@ class ActivityPub < ApplicationRecord # Definir eventos en masa include AasmEventsConcern - - # Lo que tenemos que aprobar o rechazar es la última actividad - # disponible, que según el scope por defecto, va a ser la primera de - # la lista. - def reject_remotely! - raise unless site.social_inbox.inbox.reject(id: activities.first.uri).ok? - end - - def allow_remotely! - raise unless site.social_inbox.inbox.accept(id: activities.first.uri).ok? - end end From e8ce721af02e4aade19dc92a2047def267fcb4fe Mon Sep 17 00:00:00 2001 From: f Date: Sat, 16 Mar 2024 19:20:32 -0300 Subject: [PATCH 235/297] =?UTF-8?q?fix:=20traducci=C3=B3n=20faltante?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/es.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 7f71781c..39d35afe 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -109,7 +109,7 @@ es: instances_btn_box: text_pause: Moderar caso por caso text_allow: Permitir todo - text_deny: Bloquear instancia + text_block: Bloquear instancia profiles_btn_box: text_pause: Revisar siempre text_allow: Aprobar siempre From 883da8d881fc1480cc40370ffdcedf9e6556bdf4 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 17 Mar 2024 12:06:55 -0300 Subject: [PATCH 236/297] =?UTF-8?q?fix:=20quiz=C3=A1s=20no=20hay=20actor?= =?UTF-8?q?=20aun=20(=3F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/fetch_job.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index e9220bfc..42b2cb27 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -9,6 +9,8 @@ # autenticación. class ActivityPub class FetchJob < ApplicationJob + include Que::Unique + self.priority = 50 def perform(site:, object_id:) @@ -32,7 +34,7 @@ class ActivityPub return if current_type == object.type object = ::ActivityPub::Object.find(object_id) - object.actor.save if object.actor_type? + object.actor&.save if object.actor_type? # Arreglar las relaciones con actividades también ActivityPub.where(object_id: object.id).update_all(object_type: object.type) From bdb5175fcd96c02816bee1be498ccdd80d733486 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 17 Mar 2024 12:10:07 -0300 Subject: [PATCH 237/297] =?UTF-8?q?fixup!=20fix:=20quiz=C3=A1s=20no=20hay?= =?UTF-8?q?=20actor=20aun=20(=3F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/fetch_job.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 42b2cb27..bdb98e66 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -9,8 +9,6 @@ # autenticación. class ActivityPub class FetchJob < ApplicationJob - include Que::Unique - self.priority = 50 def perform(site:, object_id:) From 1623d618ee8743ada8d91d6c2ba2a670d20a5b53 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 11:21:13 -0300 Subject: [PATCH 238/297] fix: mostrar link externo al comentario #15638 --- app/models/activity_pub.rb | 36 ++++++++++++++++++++++++ app/views/moderation_queue/_comment.haml | 7 +++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 55b48cd7..913ac67d 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -43,6 +43,42 @@ class ActivityPub < ApplicationRecord end end + # Obtiene el campo `url` de diversas formas. Si es una String, asumir + # que es una URL, si es un Hash, asumir que es un Link, si es un + # Array de Strings, obtener la primera, si es de Hash, obtener el + # primer link con rel=canonical y mediaType=text/html + # + # De lo contrario devolver el ID. + # + # @todo Refactorizar + # @param object [Hash] + # @return [String] + def self.url_from_object(object) + raise unless object.respond_to?(:[]) + + url = + case object['url'] + when String then object['url'] + when Hash then object['href'] + # Esto es un lío porque queremos saber si es un Array o + # Array o mezcla y obtener el que más nos convenga o + # adivinar uno. + when Array + links = object['url'].map.with_index do |link, i| + case link + when Hash then link + else { 'href' => link.to_s } + end + end + + links.find do |link| + link['rel'] == 'canonical' && link['mediaType'] == 'text/html' + end&.[]('href') || links.first&.[]('href') + end + + url || object['id'] + end + aasm do # Todavía no hay una decisión sobre el objeto state :paused, initial: true diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index c6f6fd5c..787ae7c2 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -8,6 +8,8 @@ - in_reply_to = text_plain comment['inReplyTo'] - summary = text_plain comment['summary'] +-# @todo Generar un desplegable con todas las opciones +- url = text_plain ActivityPub.url_from_object(comment) .row.no-gutters .col-1 @@ -17,8 +19,9 @@ .d-flex.flex-row.align-items-center.justify-content-between %h4.mb-0 %a{ href: text_plain(comment['attributedTo']) }= text_plain profile['preferredUsername'] - %small - = render 'layouts/time', time: text_plain(comment['published']) + %a{ href: url } + %small + = render 'layouts/time', time: text_plain(comment['published']) - if in_reply_to.present? %dl %dt.d-inline From 7b87aaa93daf1d58a0b97ab89573043d239c4035 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 11:55:38 -0300 Subject: [PATCH 239/297] feat: referenciar objetos #15642 --- app/models/activity_pub/object.rb | 9 +++++++++ app/views/moderation_queue/_comment.haml | 9 +++++++++ app/views/moderation_queue/_comments.haml | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index 9061c4c5..1d5d4478 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -29,5 +29,14 @@ class ActivityPub def object_type? true end + + # Poder explorar propiedades remotas + # + # @return [DistributedPress::V1::Social::ReferencedObject] + def referenced(site) + require 'distributed_press/v1/social/referenced_object' + + @referenced ||= DistributedPress::V1::Social::ReferencedObject.new(object: content, dereferencer: site.social_inbox.dereferencer) + end end end diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index 787ae7c2..10f09106 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -1,12 +1,21 @@ -# Componente Comentario + @param site [Site] @param form [String] @param profile [Hash] @param comment [Hash] @param activity_pub [ActivityPub] - in_reply_to = text_plain comment['inReplyTo'] +:ruby + begin + if in_reply_to && (remote_object = object.referenced(site)['inReplyTo']) + in_reply_to = ActivityPub.url_from_object(remote_object) + end + rescue Exception => e + ExceptionNotifier.notify_exception(e, data: { site: site.name, object: comment }) + end - summary = text_plain comment['summary'] -# @todo Generar un desplegable con todas las opciones - url = text_plain ActivityPub.url_from_object(comment) diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index 68671f9e..583ef511 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -13,4 +13,4 @@ %h4= t('moderation_queue.nothing') - moderation_queue.each do |activity_pub| %hr - = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form: form_id + = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form: form_id, site: site, object: activity_pub.object From 9dc0ed7684ac56c456283baaabc678358abcda67 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 12:48:09 -0300 Subject: [PATCH 240/297] fix: reusar el reporto remoto #15648 --- app/controllers/concerns/moderation_concern.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/moderation_concern.rb b/app/controllers/concerns/moderation_concern.rb index 3b9d818f..8340ec2a 100644 --- a/app/controllers/concerns/moderation_concern.rb +++ b/app/controllers/concerns/moderation_concern.rb @@ -16,7 +16,9 @@ module ModerationConcern end def remote_flag_params(model) - { remote_flag_attributes: { id: model.remote_flag_id, message: ''.dup } }.tap do |p| + remote_flag = ActivityPub::RemoteFlag.find_by(actor_id: model.actor_id) + + { remote_flag_attributes: { id: remote_flag&.id, message: ''.dup } }.tap do |p| p[:remote_flag_attributes][:site_id] = model.site_id p[:remote_flag_attributes][:actor_id] = model.actor_id From bcfb7a5a7ffb3deec681767c54923244c47da0f9 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 12:55:27 -0300 Subject: [PATCH 241/297] fix: asignar la remote flag #15648 --- app/controllers/activity_pubs_controller.rb | 7 ++++++- app/controllers/actor_moderations_controller.rb | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index 225311c2..d5041a84 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -8,7 +8,12 @@ class ActivityPubsController < ApplicationController define_method(event) do authorize activity_pub - activity_pub.update(remote_flag_params(activity_pub)) if event == :report + if event == :report + remote_flag_params(activity_pub).tap do |p| + activity_pub.remote_flag_id = p[:remote_flag_attributes][:id] + activity_pub.update(p) + end + end message = if activity_pub.public_send(:"may_#{event}?") && activity_pub.public_send(:"#{event}!") diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index cd81e441..70aaf992 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -15,7 +15,12 @@ class ActorModerationsController < ApplicationController authorize actor_moderation # Crea una RemoteFlag si se envían los parámetros adecuados - actor_moderation.update(remote_flag_params(actor_moderation)) if actor_event == :report + if actor_event == :report + remote_flag_params(actor_moderation).tap do |p| + actor_moderation.remote_flag_id = p[:remote_flag_attributes][:id] + actor_moderation.update(p) + end + end message = if actor_moderation.public_send(:"may_#{actor_event}?") && actor_moderation.public_send(:"#{actor_event}!") From ceeb0009ef2fcd1faa01d5e59f2f77c2a0f124cb Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 14:40:11 -0300 Subject: [PATCH 242/297] fix: descubrir el tipo de objeto siempre closes #15656 closes #15657 closes #15658 closes #15660 closes #15661 closes #15662 closes #15663 closes #15664 closes #15665 closes #15666 closes #15667 closes #15668 closes #15669 closes #15670 closes #15671 closes #15672 closes #15673 closes #15674 closes #15675 closes #15676 closes #15677 closes #15678 closes #15679 closes #15680 closes #15681 closes #15682 closes #15683 closes #15684 closes #15685 closes #15686 closes #15687 closes #15688 closes #15689 closes #15690 closes #15691 closes #15692 closes #15693 closes #15694 closes #15695 closes #15696 closes #15697 closes #15698 closes #15699 closes #15700 closes #15701 closes #15702 closes #15704 closes #15705 closes #15706 closes #15707 closes #15708 --- app/jobs/activity_pub/process_job.rb | 13 +------------ app/models/activity_pub/object.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index 6554b44d..9b72be43 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -64,18 +64,7 @@ class ActivityPub # @return [ActivityPub::Object] def object @object ||= ::ActivityPub::Object.find_or_initialize_by(uri: object_uri).tap do |o| - # XXX: Si el objeto es una actividad, esto siempre va a ser - # Generic - o.type ||= 'ActivityPub::Object::Generic' - - if object_embedded? - o.content = original_object - begin - type = original_object[:type].presence - o.type = "ActivityPub::Object::#{type}".constantize if type - rescue NameError - end - end + o.content = original_object if object_embedded? o.save! diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index 1d5d4478..16cd6b01 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -5,6 +5,8 @@ class ActivityPub class Object < ApplicationRecord include ActivityPub::Concerns::JsonLdConcern + before_validation :type_from_content!, unless: :type? + # Los objetos son únicos a toda la base de datos validates :uri, presence: true, url: true, uniqueness: true @@ -38,5 +40,20 @@ class ActivityPub @referenced ||= DistributedPress::V1::Social::ReferencedObject.new(object: content, dereferencer: site.social_inbox.dereferencer) end + + private + + # Encuentra el tipo a partir del contenido, si existe. + # + # XXX: Si el objeto es una actividad, esto siempre va a ser + # Generic + def type_from_content! + self.type = + begin + "ActivityPub::Object::#{content['type'].presence || 'Generic'}".constantize + rescue NameError + ActivityPub::Object::Generic + end + end end end From e50ae70ebb7c254cc87769419a31b46582823c1e Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 15:06:41 -0300 Subject: [PATCH 243/297] fix: no actualizar si el contenido estaba cacheado --- app/jobs/activity_pub/fetch_job.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index bdb98e66..78a6dbee 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -22,7 +22,9 @@ class ActivityPub # @todo Fallar cuando la respuesta no funcione? return unless response.ok? - return if response.miss? && object.content.present? + # Ignorar si ya la caché fue revalidada y ya teníamos el + # contenido + return if response.hit? && object.content.present? current_type = object.type content = FastJsonparser.parse(response.body) From e6d4b4d3f16ff9f114e04530c1104d28df0fc0e6 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 15:10:16 -0300 Subject: [PATCH 244/297] fix: validar que el contenido del objeto sea el que queremos --- app/models/activity_pub/object.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index 16cd6b01..d37c9b88 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -9,6 +9,7 @@ class ActivityPub # Los objetos son únicos a toda la base de datos validates :uri, presence: true, url: true, uniqueness: true + validate :uri_is_content_id?, if: :content? has_many :activity_pubs, as: :object @@ -43,6 +44,12 @@ class ActivityPub private + def uri_is_content_id? + return if self.uri == content['id'] + + errors.add(:activity_pub_objects, 'El ID del objeto no coincide con su URI') + end + # Encuentra el tipo a partir del contenido, si existe. # # XXX: Si el objeto es una actividad, esto siempre va a ser From 75b6314e1dfc70cf53e716914b211664ee3dd768 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 15:46:03 -0300 Subject: [PATCH 245/297] =?UTF-8?q?fix:=20qu=C3=A9=20pasa=20con=20los=20ob?= =?UTF-8?q?jetos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/fetch_job.rb | 7 ++++--- .../20240318183846_fix_duplicate_objects.rb | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20240318183846_fix_duplicate_objects.rb diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 78a6dbee..f1a51e96 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -29,15 +29,16 @@ class ActivityPub current_type = object.type content = FastJsonparser.parse(response.body) - object.update(content: content, type: ActivityPub::Object.type_from(content).name) + object.update!(content: content, type: ActivityPub::Object.type_from(content).name) return if current_type == object.type object = ::ActivityPub::Object.find(object_id) - object.actor&.save if object.actor_type? + # Actualiza la mención + object.actor&.save! if object.actor_type? # Arreglar las relaciones con actividades también - ActivityPub.where(object_id: object.id).update_all(object_type: object.type) + ActivityPub.where(object_id: object.id).update_all(object_type: object.type, updated_at: Time.now) rescue FastJsonparser::ParseError => e ExceptionNotifier.notify_exception(e, data: { site: site.name, body: response.body }) end diff --git a/db/migrate/20240318183846_fix_duplicate_objects.rb b/db/migrate/20240318183846_fix_duplicate_objects.rb new file mode 100644 index 00000000..f863ba3e --- /dev/null +++ b/db/migrate/20240318183846_fix_duplicate_objects.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# De alguna forma se guardaron objetos duplicados! +class FixDuplicateObjects < ActiveRecord::Migration[6.1] + def up + ActivityPub::Object.group(:uri).count.select { |_, v| v > 1 }.keys.each do |uri| + objects = ActivityPub::Object.where(uri: uri) + deleted_ids = objects[1..].map(&:delete).map(&:id) + + ActivityPub.where(object_id: deleted_ids).update_all(object_id: object.first.id, updated_at: Time.now) + end + end + + def down; end +end From bd0df0a53a5a0c18cd9bcfcd0e89c8ecf569d0a0 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 15:47:38 -0300 Subject: [PATCH 246/297] =?UTF-8?q?fixup!=20fix:=20qu=C3=A9=20pasa=20con?= =?UTF-8?q?=20los=20objetos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/migrate/20240318183846_fix_duplicate_objects.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20240318183846_fix_duplicate_objects.rb b/db/migrate/20240318183846_fix_duplicate_objects.rb index f863ba3e..88d23c6f 100644 --- a/db/migrate/20240318183846_fix_duplicate_objects.rb +++ b/db/migrate/20240318183846_fix_duplicate_objects.rb @@ -7,7 +7,7 @@ class FixDuplicateObjects < ActiveRecord::Migration[6.1] objects = ActivityPub::Object.where(uri: uri) deleted_ids = objects[1..].map(&:delete).map(&:id) - ActivityPub.where(object_id: deleted_ids).update_all(object_id: object.first.id, updated_at: Time.now) + ActivityPub.where(object_id: deleted_ids).update_all(object_id: objects.first.id, updated_at: Time.now) end end From 376339aee50cd7e7353a2212c9aa8f4f68965581 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 16:45:00 -0300 Subject: [PATCH 247/297] fix: correr las consultas de todas formas --- app/jobs/activity_pub/fetch_job.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index f1a51e96..4d4d4483 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -21,6 +21,7 @@ class ActivityPub response = site.social_inbox.dereferencer.get(uri: object.uri) # @todo Fallar cuando la respuesta no funcione? + # @todo Eliminar en 410 Gone return unless response.ok? # Ignorar si ya la caché fue revalidada y ya teníamos el # contenido @@ -31,8 +32,6 @@ class ActivityPub object.update!(content: content, type: ActivityPub::Object.type_from(content).name) - return if current_type == object.type - object = ::ActivityPub::Object.find(object_id) # Actualiza la mención object.actor&.save! if object.actor_type? From 8034d206121c6ef7e1aecca48f2174e075af6426 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 16:55:20 -0300 Subject: [PATCH 248/297] fix: ignorar comentarios y cuentas sin contenido aun --- app/views/moderation_queue/_accounts.haml | 1 + app/views/moderation_queue/_comments.haml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/views/moderation_queue/_accounts.haml b/app/views/moderation_queue/_accounts.haml index d8d76f0d..257b0fbf 100644 --- a/app/views/moderation_queue/_accounts.haml +++ b/app/views/moderation_queue/_accounts.haml @@ -12,5 +12,6 @@ - if actor_moderations.count.zero? %h4= t('moderation_queue.nothing') - actor_moderations.find_each do |actor_moderation| + - next if actor_moderation.actor.content.empty? %hr = render 'account', actor_moderation: actor_moderation, profile: actor_moderation.actor.content, form: form_id diff --git a/app/views/moderation_queue/_comments.haml b/app/views/moderation_queue/_comments.haml index 583ef511..a7523517 100644 --- a/app/views/moderation_queue/_comments.haml +++ b/app/views/moderation_queue/_comments.haml @@ -12,5 +12,7 @@ - if moderation_queue.count.zero? %h4= t('moderation_queue.nothing') - moderation_queue.each do |activity_pub| + - next if activity_pub.object.content.empty? + - next if activity_pub.actor.content.empty? %hr = render 'moderation_queue/comment', comment: activity_pub.object.content, profile: activity_pub.actor.content, activity_pub: activity_pub, form: form_id, site: site, object: activity_pub.object From ab43c84ab1b0287a49150b9ab5011f61b76fc151 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 17:13:32 -0300 Subject: [PATCH 249/297] fix: indicar el filtro activo #15652 --- app/helpers/moderation_queue_helper.rb | 10 +++++++++- app/views/components/_comments_show_submenu.haml | 3 ++- app/views/components/_dropdown_item.haml | 3 ++- app/views/components/_instances_show_submenu.haml | 3 ++- app/views/components/_profiles_show_submenu.haml | 3 ++- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/helpers/moderation_queue_helper.rb b/app/helpers/moderation_queue_helper.rb index 3681bec3..c69364ae 100644 --- a/app/helpers/moderation_queue_helper.rb +++ b/app/helpers/moderation_queue_helper.rb @@ -2,6 +2,14 @@ module ModerationQueueHelper def filter_states(**args) - params.permit(:state, :actor_state, :activity_pub_state).merge(**args) + params.permit(:instance_state, :actor_state, :activity_pub_state).merge(**args) + end + + def active?(states, state_name, state) + if params[state_name].present? + params[state_name] == state.to_s + else + states.first == state + end end end diff --git a/app/views/components/_comments_show_submenu.haml b/app/views/components/_comments_show_submenu.haml index 60c02501..9964a62a 100644 --- a/app/views/components/_comments_show_submenu.haml +++ b/app/views/components/_comments_show_submenu.haml @@ -1,4 +1,5 @@ - ActivityPub.states.each do |state| = render 'components/dropdown_item', text: t(".submenu_#{state}", count: activity_pubs.unscope(where: :aasm_state).public_send(state).count), - path: filter_states(activity_pub_state: state) + path: filter_states(activity_pub_state: state), + class: ('active' if active?(ActivityPub.states, :activity_pub_state, state)) diff --git a/app/views/components/_dropdown_item.haml b/app/views/components/_dropdown_item.haml index e5b16950..a4d363a8 100644 --- a/app/views/components/_dropdown_item.haml +++ b/app/views/components/_dropdown_item.haml @@ -1,4 +1,5 @@ -# @param :text [String] Contenido del link @param :path [String,Hash] Link -= link_to text, path, class: 'dropdown-item', data: { target: 'dropdown.item' } +- local_assigns[:class] = "dropdown-item #{local_assigns[:class]}" += link_to text, path, class: local_assigns[:class], data: { target: 'dropdown.item' } diff --git a/app/views/components/_instances_show_submenu.haml b/app/views/components/_instances_show_submenu.haml index c56df547..6b9b747e 100644 --- a/app/views/components/_instances_show_submenu.haml +++ b/app/views/components/_instances_show_submenu.haml @@ -1,4 +1,5 @@ - InstanceModeration.states.each do |state| = render 'components/dropdown_item', text: t(".submenu_#{state}", count: instance_moderations.unscope(where: :aasm_state).public_send(state).count), - path: filter_states(instance_state: state) + path: filter_states(instance_state: state), + class: ('active' if active?(InstanceModeration.states, :instance_state, state)) diff --git a/app/views/components/_profiles_show_submenu.haml b/app/views/components/_profiles_show_submenu.haml index 99694698..bebfbe20 100644 --- a/app/views/components/_profiles_show_submenu.haml +++ b/app/views/components/_profiles_show_submenu.haml @@ -1,4 +1,5 @@ - ActorModeration.states.each do |actor_state| = render 'components/dropdown_item', text: t(".submenu_#{actor_state}", count: actor_moderations.unscope(where: :aasm_state).public_send(actor_state).count), - path: filter_states(actor_state: actor_state) + path: filter_states(actor_state: actor_state), + class: ('active' if active?(ActorModeration.states, :actor_state, actor_state)) From dae0346f0ddc3d15825f7d4232b16e9fe86f2c15 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 17:28:49 -0300 Subject: [PATCH 250/297] fix: pasar el sitio como argumento --- app/views/actor_moderations/show.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/actor_moderations/show.haml b/app/views/actor_moderations/show.haml index 633c1be5..ca5764f4 100644 --- a/app/views/actor_moderations/show.haml +++ b/app/views/actor_moderations/show.haml @@ -5,4 +5,4 @@ .col-12.col-md-8 = render 'components/profiles_btn_box', actor_moderation: @actor_moderation .col-12.col-md-8 - = render 'moderation_queue/comments', moderation_queue: @moderation_queue + = render 'moderation_queue/comments', site: @site, moderation_queue: @moderation_queue From 7c9e9758c5fecd4deb09a08f337e95439e0469da Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 17:51:29 -0300 Subject: [PATCH 251/297] =?UTF-8?q?fix:=20informar=20la=20uri=20que=20fall?= =?UTF-8?q?=C3=B3=20#15712?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/fetch_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 4d4d4483..9d14307a 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -39,7 +39,7 @@ class ActivityPub # Arreglar las relaciones con actividades también ActivityPub.where(object_id: object.id).update_all(object_type: object.type, updated_at: Time.now) rescue FastJsonparser::ParseError => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, body: response.body }) + ExceptionNotifier.notify_exception(e, data: { site: site.name, object: object.uri, body: response.body }) end end end From e2abe224d40f7572244f4969cedf3af1bd1a65df Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 17:54:07 -0300 Subject: [PATCH 252/297] fix: fallbacks #15659 --- app/controllers/actor_moderations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 70aaf992..739b1f46 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -44,7 +44,7 @@ class ActorModerationsController < ApplicationController @moderation_queue = rubanok_process(site.activity_pubs.where(actor_id: actor_moderation.actor_id), with: ActivityPubProcessor) - breadcrumb @remote_profile['name'], '' + breadcrumb @remote_profile['name'] || actor_moderation.actor.mention || actor_moderation.actor.uri, '' end def action_on_several From d13e56e692e65344b436d5585289b9268fe43669 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 18:09:32 -0300 Subject: [PATCH 253/297] =?UTF-8?q?fix:=20descripci=C3=B3n=20para=20las=20?= =?UTF-8?q?listas=20de=20bloqueo=20#15643?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 6f76fe57..075b62ec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -180,7 +180,7 @@ en: reply_to: Reply to instances: title: My block lists - description: Description + description: "Blocklists contain instances known for hosting hate speech, promote fascism, violence, sexual/gendered abuse and/or misinformation." custom_block: Custom block lists submit: Save block lists instance: diff --git a/config/locales/es.yml b/config/locales/es.yml index 39d35afe..2d4974b1 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -179,7 +179,7 @@ es: reply_to: En respuesta a instances: title: Mis listas de bloqueo - description: Descripción de listas de bloqueo + description: "Las listas de bloqueo contienen instancias conocidas por alojar discurso de odio, promover el fascismo, la violencia, abuso sexual y/o desinformación." custom_block: Lista personalizada de bloqueo submit: Guardar listas de bloqueo instance: From 979f3c1b3ac214856840a7bfa248603229dbdbfe Mon Sep 17 00:00:00 2001 From: f Date: Mon, 18 Mar 2024 18:12:02 -0300 Subject: [PATCH 254/297] fix: ayuda para la lista customizada #15643 --- app/views/moderation_queue/_block_instances_textarea.haml | 2 +- config/locales/en.yml | 3 +++ config/locales/es.yml | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/views/moderation_queue/_block_instances_textarea.haml b/app/views/moderation_queue/_block_instances_textarea.haml index 9729d4de..7daf0410 100644 --- a/app/views/moderation_queue/_block_instances_textarea.haml +++ b/app/views/moderation_queue/_block_instances_textarea.haml @@ -1,3 +1,3 @@ .form-group = label_tag 'custom_blocklist', t('moderation_queue.instances.custom_block') - = text_area_tag 'custom_blocklist', nil, class: 'form-control' + = text_area_tag 'custom_blocklist', nil, class: 'form-control', placeholder: t('moderation_queue.instances.custom_block_placeholder') diff --git a/config/locales/en.yml b/config/locales/en.yml index 075b62ec..35b857e6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -182,6 +182,9 @@ en: title: My block lists description: "Blocklists contain instances known for hosting hate speech, promote fascism, violence, sexual/gendered abuse and/or misinformation." custom_block: Custom block lists + custom_block_placeholder: | + a.doma.in + per.li.ne submit: Save block lists instance: users: "Users:" diff --git a/config/locales/es.yml b/config/locales/es.yml index 2d4974b1..77e611ed 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -181,6 +181,9 @@ es: title: Mis listas de bloqueo description: "Las listas de bloqueo contienen instancias conocidas por alojar discurso de odio, promover el fascismo, la violencia, abuso sexual y/o desinformación." custom_block: Lista personalizada de bloqueo + custom_block_placeholder: | + un.domin.io + por.lin.ea submit: Guardar listas de bloqueo instance: users: "Usuaries:" From b90f8864461e3b2d8a5f725a0a81cd9f4cc858e7 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 09:47:24 -0300 Subject: [PATCH 255/297] feat: fedipact --- app/models/activity_pub/fediblock.rb | 4 +-- ...240319124212_add_fedipact_to_fediblocks.rb | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20240319124212_add_fedipact_to_fediblocks.rb diff --git a/app/models/activity_pub/fediblock.rb b/app/models/activity_pub/fediblock.rb index 4abcb80f..ec66c032 100644 --- a/app/models/activity_pub/fediblock.rb +++ b/app/models/activity_pub/fediblock.rb @@ -31,8 +31,8 @@ class ActivityPub class FediblockDownloadError < ::StandardError; end - validates_presence_of :title, :url, :download_url, :format - validates_inclusion_of :format, in: %w[mastodon fediblock] + validates_presence_of :title, :url, :format + validates_inclusion_of :format, in: %w[mastodon fediblock none] HOSTNAME_HEADERS = { 'mastodon' => '#domain', diff --git a/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb b/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb new file mode 100644 index 00000000..648f2ee7 --- /dev/null +++ b/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Agrega threads.net a las listas de bloqueo +class AddFedipactToFediblocks < ActiveRecord::Migration[6.1] + def up + fedipact = + ActivityPub::Fediblock.create( + hostnames: %w[threads.net], + title: 'Fedipact', + url: 'https://fedipact.online/', + format: 'none' + ) + + DeploySocialDistributedPress.find_each do |deploy| + FediblockState.create(site: deploy.site, fediblock: fedipact, aasm_state: 'disabled').tap do |f| + f.enable! + end + end + end + + def down + fedipact = ActivityPub::Fediblock.find_by(url: 'https://fedipact.online/').delete + FediblockState.where(fediblock_id: fedipact.id).delete_all + end +end From c047858a6f418a7bbf42eb5d3730a5ed114916c1 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 09:49:34 -0300 Subject: [PATCH 256/297] fixup! feat: fedipact --- db/migrate/20240319124212_add_fedipact_to_fediblocks.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb b/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb index 648f2ee7..d78439b2 100644 --- a/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb +++ b/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb @@ -3,6 +3,8 @@ # Agrega threads.net a las listas de bloqueo class AddFedipactToFediblocks < ActiveRecord::Migration[6.1] def up + change_column :activity_pub_fediblocks, :download_url, :string, null: true + fedipact = ActivityPub::Fediblock.create( hostnames: %w[threads.net], @@ -21,5 +23,6 @@ class AddFedipactToFediblocks < ActiveRecord::Migration[6.1] def down fedipact = ActivityPub::Fediblock.find_by(url: 'https://fedipact.online/').delete FediblockState.where(fediblock_id: fedipact.id).delete_all + change_column :activity_pub_fediblocks, :download_url, :string, null: false end end From 48d8d2d8d802b46318f6a36c6beb9a1955683fa5 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 10:59:10 -0300 Subject: [PATCH 257/297] =?UTF-8?q?fix:=20la=20l=C3=B3gica=20estaba=20al?= =?UTF-8?q?=20rev=C3=A9s=20#15647?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/remote_flag_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb index 20833bd4..71751d39 100644 --- a/app/jobs/activity_pub/remote_flag_job.rb +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -13,7 +13,7 @@ class ActivityPub self.priority = 30 def perform(remote_flag:) - return if remote_flag.may_queue? + return unless remote_flag.may_queue? inbox = remote_flag.actor&.content&.[]('inbox') From 93ebb6431a211d75f462c86163ec9fbc5e9ea73d Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 11:20:49 -0300 Subject: [PATCH 258/297] =?UTF-8?q?fix:=20buscar=20un=20c=C3=B3digo=20de?= =?UTF-8?q?=20respuesta=20general?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/actor_fetch_job.rb | 2 +- app/jobs/activity_pub/fetch_job.rb | 2 +- app/jobs/activity_pub/inbox_job.rb | 2 +- app/jobs/activity_pub/instance_fetch_job.rb | 2 +- app/jobs/activity_pub/remote_flag_job.rb | 2 +- app/jobs/activity_pub/sync_lists_job.rb | 2 +- app/models/activity_pub/fediblock.rb | 2 +- app/models/deploy_social_distributed_press.rb | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/jobs/activity_pub/actor_fetch_job.rb b/app/jobs/activity_pub/actor_fetch_job.rb index 1190e936..598ecd83 100644 --- a/app/jobs/activity_pub/actor_fetch_job.rb +++ b/app/jobs/activity_pub/actor_fetch_job.rb @@ -16,7 +16,7 @@ class ActivityPub response = site.social_inbox.dereferencer.get(uri: actor.uri) # @todo Fallar cuando la respuesta no funcione? - return unless response.ok? + return unless response.success? return if response.miss? && actor.content.present? actor.object.update(content: FastJsonparser.parse(response.body)) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 9d14307a..beb585ff 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -22,7 +22,7 @@ class ActivityPub # @todo Fallar cuando la respuesta no funcione? # @todo Eliminar en 410 Gone - return unless response.ok? + return unless response.success? # Ignorar si ya la caché fue revalidada y ya teníamos el # contenido return if response.hit? && object.content.present? diff --git a/app/jobs/activity_pub/inbox_job.rb b/app/jobs/activity_pub/inbox_job.rb index 93216d44..cb807704 100644 --- a/app/jobs/activity_pub/inbox_job.rb +++ b/app/jobs/activity_pub/inbox_job.rb @@ -10,7 +10,7 @@ class ActivityPub def perform(site:, activity:, action:) response = site.social_inbox.inbox.public_send(action, id: activity) - raise response.body unless response.ok? + raise response.body unless response.success? end end end diff --git a/app/jobs/activity_pub/instance_fetch_job.rb b/app/jobs/activity_pub/instance_fetch_job.rb index 9c562f7d..dc84caf2 100644 --- a/app/jobs/activity_pub/instance_fetch_job.rb +++ b/app/jobs/activity_pub/instance_fetch_job.rb @@ -15,7 +15,7 @@ class ActivityPub response = site.social_inbox.dereferencer.get(uri: uri) - next unless response.ok? + next unless response.success? # @todo Validate schema next unless response.parsed_response.is_a?(DistributedPress::V1::Social::ReferencedObject) diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb index 71751d39..f586a92e 100644 --- a/app/jobs/activity_pub/remote_flag_job.rb +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -25,7 +25,7 @@ class ActivityPub client = remote_flag.main_site.social_inbox.client_for(uri.origin) response = client.post(endpoint: uri.path, body: remote_flag.content) - raise 'No se pudo enviar el reporte' unless response.ok? + raise 'No se pudo enviar el reporte' unless response.success? remote_flag.report! rescue Exception => e diff --git a/app/jobs/activity_pub/sync_lists_job.rb b/app/jobs/activity_pub/sync_lists_job.rb index 39f6bdc9..cc500bee 100644 --- a/app/jobs/activity_pub/sync_lists_job.rb +++ b/app/jobs/activity_pub/sync_lists_job.rb @@ -53,7 +53,7 @@ class ActivityPub def process(stage) response = yield - return if response.ok? + return if response.success? logs[stage] ||= [] logs[stage] << { body: response.body, code: response.code } diff --git a/app/models/activity_pub/fediblock.rb b/app/models/activity_pub/fediblock.rb index ec66c032..17897d79 100644 --- a/app/models/activity_pub/fediblock.rb +++ b/app/models/activity_pub/fediblock.rb @@ -52,7 +52,7 @@ class ActivityPub def process! response = client.get(download_url) - raise FediblockDownloadError unless response.ok? + raise FediblockDownloadError unless response.success? Fediblock.transaction do csv = response.parsed_response diff --git a/app/models/deploy_social_distributed_press.rb b/app/models/deploy_social_distributed_press.rb index c7a103a4..e7f97406 100644 --- a/app/models/deploy_social_distributed_press.rb +++ b/app/models/deploy_social_distributed_press.rb @@ -84,7 +84,7 @@ class DeploySocialDistributedPress < Deploy response = hook_client.put(event: event, hook: webhook) - raise ArgumentError, response.body unless response.ok? + raise ArgumentError, response.body unless response.success? rescue ArgumentError => e ExceptionNotifier.notify_exception(e, data: { site_id: site.name, usuarie_id: rol.usuarie_id }) end From 8d6d215e1af43cd2bc80f24f4aea5a5687f9d286 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 11:21:03 -0300 Subject: [PATCH 259/297] =?UTF-8?q?fix:=20si=20falla=20el=20reporte=20marc?= =?UTF-8?q?arlo=20para=20reenv=C3=ADo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/remote_flag_job.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/jobs/activity_pub/remote_flag_job.rb b/app/jobs/activity_pub/remote_flag_job.rb index f586a92e..211f46fc 100644 --- a/app/jobs/activity_pub/remote_flag_job.rb +++ b/app/jobs/activity_pub/remote_flag_job.rb @@ -30,6 +30,7 @@ class ActivityPub remote_flag.report! rescue Exception => e ExceptionNotifier.notify_exception(e, data: { remote_flag: remote_flag.id, response: response.parsed_response }) + remote_flag.resend! raise end end From 0ed702992b509cbd6d435474db050228cb88354f Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 12:39:41 -0300 Subject: [PATCH 260/297] fix: prevenir objetos multiplicados! parece que la forma en que estabamos creando indices unicos ya no funciona (??) asi que a veces estabamos creando objetos duplicados en threads. de paso actorfetchjob ya no es necesario. closes #15621 closes #15622 closes #15623 closes #15729 closes #15730 closes #15731 --- app/jobs/activity_pub/actor_fetch_job.rb | 26 ---------- app/jobs/activity_pub/fetch_job.rb | 2 +- app/jobs/activity_pub/process_job.rb | 17 +++---- app/models/activity_pub/actor.rb | 2 +- ...240319144735_add_missing_unique_indexes.rb | 48 +++++++++++++++++++ db/structure.sql | 28 +++++++++-- 6 files changed, 82 insertions(+), 41 deletions(-) delete mode 100644 app/jobs/activity_pub/actor_fetch_job.rb create mode 100644 db/migrate/20240319144735_add_missing_unique_indexes.rb diff --git a/app/jobs/activity_pub/actor_fetch_job.rb b/app/jobs/activity_pub/actor_fetch_job.rb deleted file mode 100644 index 598ecd83..00000000 --- a/app/jobs/activity_pub/actor_fetch_job.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -# Obtiene o actualiza el contenido de un objeto, usando las credenciales -# del sitio. -# -# XXX: Esto usa las credenciales del sitio para volver el objeto -# disponible para todo el CMS. Asumimos que el objeto devuelto es el -# mismo para todo el mundo y las credenciales solo son para -# autenticación. -class ActivityPub - class ActorFetchJob < ApplicationJob - self.priority = 50 - - def perform(site:, actor:) - ActivityPub::Actor.transaction do - response = site.social_inbox.dereferencer.get(uri: actor.uri) - - # @todo Fallar cuando la respuesta no funcione? - return unless response.success? - return if response.miss? && actor.content.present? - - actor.object.update(content: FastJsonparser.parse(response.body)) - end - end - end -end diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index beb585ff..e0033439 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -30,7 +30,7 @@ class ActivityPub current_type = object.type content = FastJsonparser.parse(response.body) - object.update!(content: content, type: ActivityPub::Object.type_from(content).name) + object.lock.update!(content: content, type: ActivityPub::Object.type_from(content).name) object = ::ActivityPub::Object.find(object_id) # Actualiza la mención diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index 9b72be43..4e278797 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -63,7 +63,7 @@ class ActivityPub # # @return [ActivityPub::Object] def object - @object ||= ::ActivityPub::Object.find_or_initialize_by(uri: object_uri).tap do |o| + @object ||= ::ActivityPub::Object.lock.find_or_initialize_by(uri: object_uri).tap do |o| o.content = original_object if object_embedded? o.save! @@ -80,8 +80,8 @@ class ActivityPub # # @return [ActivityPub] def activity_pub - @activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, actor: actor, instance: instance, - object_id: object.id, object_type: object.type) + @activity_pub ||= site.activity_pubs.lock.find_or_create_by!(site: site, actor: actor, instance: instance, + object_id: object.id, object_type: object.type) end # Crea la actividad y la vincula con el estado @@ -91,6 +91,7 @@ class ActivityPub @activity ||= ::ActivityPub::Activity .type_from(original_activity) + .lock .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| a.content = original_activity.dup a.content[:object] = object.uri @@ -103,20 +104,20 @@ class ActivityPub # # @return [Actor] def actor - @actor ||= ::ActivityPub::Actor.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| + @actor ||= ::ActivityPub::Actor.lock.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| unless a.instance - a.instance = ::ActivityPub::Instance.find_or_create_by(hostname: URI.parse(a.uri).hostname) + a.instance = ::ActivityPub::Instance.lock.find_or_create_by(hostname: URI.parse(a.uri).hostname) ::ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance) end - site.instance_moderations.find_or_create_by(instance: a.instance) + site.instance_moderations.lock.find_or_create_by(instance: a.instance) a.save! - site.actor_moderations.find_or_create_by(actor: a) + site.actor_moderations.lock.find_or_create_by(actor: a) - ::ActivityPub::ActorFetchJob.perform_later(site: site, actor: a) + ::ActivityPub::FetchJob.perform_later(site: site, actor: a.object) end end diff --git a/app/models/activity_pub/actor.rb b/app/models/activity_pub/actor.rb index a8cc0d5d..6a284025 100644 --- a/app/models/activity_pub/actor.rb +++ b/app/models/activity_pub/actor.rb @@ -33,7 +33,7 @@ class ActivityPub end def object - @object ||= ActivityPub::Object.find_or_initialize_by(uri: uri) + @object ||= ActivityPub::Object.lock.find_or_create_by(uri: uri) end def content diff --git a/db/migrate/20240319144735_add_missing_unique_indexes.rb b/db/migrate/20240319144735_add_missing_unique_indexes.rb new file mode 100644 index 00000000..7d18c8e8 --- /dev/null +++ b/db/migrate/20240319144735_add_missing_unique_indexes.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# Parece que la sintaxis que veníamos usando para los índices únicos ya +# no es válida y por eso teníamos objetos duplicados. +class AddMissingUniqueIndexes < ActiveRecord::Migration[6.1] + def up + ActivityPub::Object.group(:uri).count.select { |_, v| v > 1 }.keys.each do |uri| + objects = ActivityPub::Object.where(uri: uri) + deleted_ids = objects[1..].map(&:delete).map(&:id) + + ActivityPub.where(object_id: deleted_ids).update_all(object_id: objects.first.id, updated_at: Time.now) + end + + ActivityPub::Actor.group(:uri).count.select { |_, v| v > 1 }.keys.each do |uri| + objects = ActivityPub::Actor.where(uri: uri) + deleted_ids = objects[1..].map(&:delete).map(&:id) + + ActivityPub.where(actor_id: deleted_ids).update_all(actor_id: objects.first.id, updated_at: Time.now) + ActorModeration.where(actor_id: deleted_ids).update_all(actor_id: objects.first.id, updated_at: Time.now) + ActivityPub::Activity.where(actor_id: deleted_ids).update_all(actor_id: objects.first.id, updated_at: Time.now) + ActivityPub::RemoteFlag.where(actor_id: deleted_ids).update_all(actor_id: objects.first.id, updated_at: Time.now) + end + + ActivityPub::Instance.group(:hostname).count.select { |_, v| v > 1 }.keys.each do |hostname| + objects = ActivityPub::Instance.where(hostname: hostname) + deleted_ids = objects[1..].map(&:delete).map(&:id) + + ActivityPub.where(instance_id: deleted_ids).update_all(instance_id: objects.first.id, updated_at: Time.now) + InstanceModeration.where(instance_id: deleted_ids).update_all(instance_id: objects.first.id, updated_at: Time.now) + ActivityPub::Actor.where(instance_id: deleted_ids).update_all(instance_id: objects.first.id, updated_at: Time.now) + end + + remove_index :activity_pub_instances, :hostname + remove_index :activity_pub_actors, :uri + add_index :activity_pub_instances, :hostname, unique: true + add_index :activity_pub_objects, :uri, unique: true + add_index :activity_pub_actors, :uri, unique: true + end + + def down + remove_index :activity_pub_instances, :hostname, unique: true + remove_index :activity_pub_objects, :uri, unique: true + remove_index :activity_pub_actors, :uri, unique: true + add_index :activity_pub_instances, :hostname + add_index :activity_pub_objects, :uri + add_index :activity_pub_actors, :uri + end +end diff --git a/db/structure.sql b/db/structure.sql index ed58ebec..21cf04d0 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -498,7 +498,8 @@ CREATE TABLE public.activity_pub_actors ( created_at timestamp(6) without time zone NOT NULL, updated_at timestamp(6) without time zone NOT NULL, instance_id uuid NOT NULL, - uri character varying NOT NULL + uri character varying NOT NULL, + mention character varying ); @@ -512,7 +513,7 @@ CREATE TABLE public.activity_pub_fediblocks ( updated_at timestamp(6) without time zone NOT NULL, title character varying NOT NULL, url character varying NOT NULL, - download_url character varying NOT NULL, + download_url character varying, format character varying NOT NULL, hostnames jsonb DEFAULT '[]'::jsonb ); @@ -557,6 +558,7 @@ CREATE TABLE public.activity_pub_remote_flags ( site_id bigint, actor_id uuid, message text, + content jsonb, aasm_state character varying DEFAULT 'waiting'::character varying NOT NULL ); @@ -2138,14 +2140,21 @@ CREATE INDEX index_activity_pub_actors_on_instance_id ON public.activity_pub_act -- Name: index_activity_pub_actors_on_uri; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_activity_pub_actors_on_uri ON public.activity_pub_actors USING btree (uri); +CREATE UNIQUE INDEX index_activity_pub_actors_on_uri ON public.activity_pub_actors USING btree (uri); -- -- Name: index_activity_pub_instances_on_hostname; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_activity_pub_instances_on_hostname ON public.activity_pub_instances USING btree (hostname); +CREATE UNIQUE INDEX index_activity_pub_instances_on_hostname ON public.activity_pub_instances USING btree (hostname); + + +-- +-- Name: index_activity_pub_objects_on_uri; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_activity_pub_objects_on_uri ON public.activity_pub_objects USING btree (uri); -- @@ -2701,6 +2710,15 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240305164653'), ('20240305184854'), ('20240307201510'), -('20240307203039'); +('20240307203039'), +('20240313192134'), +('20240313204105'), +('20240314141536'), +('20240314153017'), +('20240314205923'), +('20240316203721'), +('20240318183846'), +('20240319124212'), +('20240319144735'); From 029d4e7c974914e9b17b871362be599440d1bc0b Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 13:01:26 -0300 Subject: [PATCH 261/297] fix: object_id closes #15739 closes #15738 closes #15737 --- app/jobs/activity_pub/process_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index 4e278797..f3aeebb4 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -117,7 +117,7 @@ class ActivityPub site.actor_moderations.lock.find_or_create_by(actor: a) - ::ActivityPub::FetchJob.perform_later(site: site, actor: a.object) + ::ActivityPub::FetchJob.perform_later(site: site, object_id: a.object.id) end end From d33e7b37a14c4636a2dab745d40831ffc091448a Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 13:16:28 -0300 Subject: [PATCH 262/297] fix: lock closes #15740 closes #15741 closes #15742 closes #15743 closes #15744 closes #15745 closes #15746 closes #15747 closes #15748 closes #15749 closes #15750 closes #15751 closes #15752 closes #15753 closes #15754 closes #15755 closes #15756 closes #15757 closes #15758 closes #15759 --- app/jobs/activity_pub/fetch_job.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index e0033439..73fc1c9f 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -30,7 +30,9 @@ class ActivityPub current_type = object.type content = FastJsonparser.parse(response.body) - object.lock.update!(content: content, type: ActivityPub::Object.type_from(content).name) + object.with_lock do + object.update!(content: content, type: ActivityPub::Object.type_from(content).name) + end object = ::ActivityPub::Object.find(object_id) # Actualiza la mención From 1ef7996ce90eacea7448293e7f0a29192ee1f1b6 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 13:21:41 -0300 Subject: [PATCH 263/297] fix: agrupar errores para que no nos inunden --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 5e089ff9..bc7cecd7 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -142,7 +142,7 @@ Rails.application.configure do } config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") } - config.middleware.use ExceptionNotification::Rack, gitlab: {}, ignore_exceptions: ['DeployJob::DeployAlreadyRunningException'] + config.middleware.use ExceptionNotification::Rack, gitlab: {}, error_grouping: true, ignore_exceptions: ['DeployJob::DeployAlreadyRunningException'] Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}" Rails.application.routes.default_url_options[:protocol] = 'https' From eafa6fad37cdb15a973f8fc0f3751044c2bab9cc Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Mar 2024 17:02:19 -0300 Subject: [PATCH 264/297] fix: cargar el objeto por id y modificarlo closes #15775 closes #15773 closes #15772 closes #15771 closes #15770 closes #15767 closes #15766 closes #15765 closes #15764 --- app/jobs/activity_pub/fetch_job.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 73fc1c9f..b19d5e41 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -30,9 +30,8 @@ class ActivityPub current_type = object.type content = FastJsonparser.parse(response.body) - object.with_lock do - object.update!(content: content, type: ActivityPub::Object.type_from(content).name) - end + # Modificar atómicamente + ::ActivityPub::Object.lock.find(object_id).update!(content: content, type: ActivityPub::Object.type_from(content).name) object = ::ActivityPub::Object.find(object_id) # Actualiza la mención From 28053b2a5676427deed9097717b69ed7bbd7f682 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Mar 2024 16:18:01 -0300 Subject: [PATCH 265/297] =?UTF-8?q?fix:=20las=20cuentas=20reportadas=20tam?= =?UTF-8?q?bi=C3=A9n=20est=C3=A1n=20bloqueadas=20#15649?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/sync_lists_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/sync_lists_job.rb b/app/jobs/activity_pub/sync_lists_job.rb index cc500bee..de71fe64 100644 --- a/app/jobs/activity_pub/sync_lists_job.rb +++ b/app/jobs/activity_pub/sync_lists_job.rb @@ -18,7 +18,7 @@ class ActivityPub instance_scope = site.instance_moderations.joins(:instance) actor_scope = site.actor_moderations.joins(:actor) - blocklist = wildcardize(instance_scope.blocked.pluck(:hostname)) + actor_scope.blocked.distinct.pluck(:mention).compact + blocklist = wildcardize(instance_scope.blocked.pluck(:hostname)) + actor_scope.blocked.distinct.pluck(:mention).compact + actor_scope.reported.distinct.pluck(:mention).compact allowlist = wildcardize(instance_scope.allowed.pluck(:hostname)) + actor_scope.allowed.distinct.pluck(:mention).compact pauselist = wildcardize(instance_scope.paused.pluck(:hostname)) + actor_scope.paused.distinct.pluck(:mention).compact From 0a1b86c11195af3a5ecaa2002173caa95bd0954e Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Mar 2024 17:49:09 -0300 Subject: [PATCH 266/297] fix: se pueden rechazar comentarios luego de aprobarlos #15600 --- app/models/activity_pub.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 913ac67d..335121d2 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -128,7 +128,7 @@ class ActivityPub < ApplicationRecord # La actividad fue rechazada event :reject do - transitions from: %i[paused], to: :rejected + transitions from: %i[paused approved], to: :rejected after do ActivityPub::InboxJob.perform_later(site: site, activity: activities.first.uri, action: :reject) From 0578b03ee76f8094c1b7a9fa6ee4fbb491b2503c Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Mar 2024 17:50:39 -0300 Subject: [PATCH 267/297] fix: reportar implicar bloquear #15600 --- app/models/activity_pub.rb | 5 +++-- app/models/actor_moderation.rb | 2 +- config/locales/en.yml | 4 ++-- config/locales/es.yml | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index 335121d2..cd893406 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -135,11 +135,12 @@ class ActivityPub < ApplicationRecord end end - # Solo podemos reportarla luego de rechazarla + # Reportarla implica rechazarla event :report do - transitions from: :rejected, to: :reported + transitions from: %i[paused approved rejected], to: :reported after do + ActivityPub::InboxJob.perform_later(site: site, activity: activities.first.uri, action: :reject) ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting? end end diff --git a/app/models/actor_moderation.rb b/app/models/actor_moderation.rb index 18149be4..1c3cf83a 100644 --- a/app/models/actor_moderation.rb +++ b/app/models/actor_moderation.rb @@ -43,7 +43,7 @@ class ActorModeration < ApplicationRecord # Al reportar, necesitamos asociar una RemoteFlag para poder # enviarla. event :report do - transitions from: %i[blocked], to: :reported + transitions from: %i[pause allowed blocked], to: :reported, after: :synchronize! after do ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting? diff --git a/config/locales/en.yml b/config/locales/en.yml index 35b857e6..643effaf 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -106,7 +106,7 @@ en: text_reject: Reject text_reply: Reply text_report: Report - confirm_report: "Send report to the remote instance?" + confirm_report: "Send report to the remote instance? This action will also reject the comment." instances_btn_box: text_pause: Check case by case text_allow: Allow everything @@ -116,7 +116,7 @@ en: text_allow: Always approve text_block: Block text_report: Report - confirm_report: "Send report to the remote instance?" + confirm_report: "Send report to the remote instance? This action will also block the account." remote_flags: report_message: "Hi! Someone using Sutty CMS reported this account on your instance. We don't have support for customized report messages yet, but we will soon. You can reach us at %{panel_actor_mention}." activity_pubs: diff --git a/config/locales/es.yml b/config/locales/es.yml index 77e611ed..b8bfc524 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -105,7 +105,7 @@ es: text_approve: Aceptar text_reject: Rechazar text_report: Reportar - confirm_report: "¿Enviar el reporte a la instancia remota?" + confirm_report: "¿Enviar el reporte a la instancia remota? Esta acción también rechazará el comentario." instances_btn_box: text_pause: Moderar caso por caso text_allow: Permitir todo @@ -115,7 +115,7 @@ es: text_allow: Aprobar siempre text_block: Bloquear text_report: Reportar - confirm_report: "¿Enviar el reporte a la instancia remota?" + confirm_report: "¿Enviar el reporte a la instancia remota? Esta acción también bloqueará la cuenta." remote_flags: report_message: "¡Hola! Une usuarie de Sutty CMS reportó esta cuenta en tu instancia. Todavía no tenemos soporte para mensajes personalizados. Podés contactarnos en %{panel_actor_mention}." activity_pubs: From e2fdce91f3b2cbfa110559ac2e0b01ef0333ab21 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Mar 2024 17:51:23 -0300 Subject: [PATCH 268/297] fix: agrupar espacialmente acciones #15600 --- app/views/components/_comments_filters.haml | 2 +- app/views/components/_instances_filters.haml | 2 +- app/views/components/_profiles_filters.haml | 2 +- app/views/moderation_queue/_comment.haml | 2 +- config/locales/en.yml | 6 +++--- config/locales/es.yml | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/components/_comments_filters.haml b/app/views/components/_comments_filters.haml index cf8c1aa2..b2870c5a 100644 --- a/app/views/components/_comments_filters.haml +++ b/app/views/components/_comments_filters.haml @@ -3,7 +3,7 @@ - current_state = params[:activity_pub_state]&.to_sym || ActivityPub.states.first -.d-flex.py-2 +.d-flex.flex-row.justify-content-between.py-2 - if ActivityPub.transitionable_events(current_state).present? = render 'components/dropdown', text: t('.text_checked') do = render 'components/comments_checked_submenu', form: form diff --git a/app/views/components/_instances_filters.haml b/app/views/components/_instances_filters.haml index 730184bd..f2296c7b 100644 --- a/app/views/components/_instances_filters.haml +++ b/app/views/components/_instances_filters.haml @@ -3,7 +3,7 @@ - current_state = params[:state]&.to_sym || InstanceModeration.states.first -.d-flex.py-2 +.d-flex.flex-row.justify-content-between.py-2 - if InstanceModeration.transitionable_events(current_state).present? = render 'components/dropdown', text: t('.text_checked') do = render 'components/instances_checked_submenu', form: form, current_state: current_state diff --git a/app/views/components/_profiles_filters.haml b/app/views/components/_profiles_filters.haml index 3f830ec8..c2670944 100644 --- a/app/views/components/_profiles_filters.haml +++ b/app/views/components/_profiles_filters.haml @@ -3,7 +3,7 @@ - current_state = params[:actor_state]&.to_sym || ActorModeration.states.first -.d-flex.py-2 +.d-flex.flex-row.justify-content-between.py-2 - if ActorModeration.transitionable_events(current_state).present? = render 'components/dropdown', text: t('.text_checked') do = render 'components/profiles_checked_submenu', form: form, current_state: current_state diff --git a/app/views/moderation_queue/_comment.haml b/app/views/moderation_queue/_comment.haml index 10f09106..a80bd27c 100644 --- a/app/views/moderation_queue/_comment.haml +++ b/app/views/moderation_queue/_comment.haml @@ -38,7 +38,7 @@ %dd.d-inline %small %a{ href: in_reply_to }= in_reply_to - .content + .content.mb-3 - if summary.present? = render 'layouts/details', summary: summary, summary_class: 'h5' do = sanitize comment['content'] diff --git a/config/locales/en.yml b/config/locales/en.yml index 643effaf..8059b6db 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -63,7 +63,7 @@ en: instances_blocked: Instances blocked instances_filters: text_show: Show - text_checked: With selected + text_checked: With selected... instances_checked_submenu: submenu_pause: Moderate submenu_allow: Allow @@ -74,7 +74,7 @@ en: submenu_blocked: "Blocked (%{count})" comments_filters: text_show: Show - text_checked: With selected + text_checked: With selected... comments_checked_submenu: submenu_pause: Pause submenu_approve: Approve @@ -87,7 +87,7 @@ en: submenu_reported: "Reported (%{count})" profiles_filters: text_show: Show - text_checked: With selected + text_checked: With selected... profiles_checked_submenu: submenu_pause: Pause submenu_allow: Allow diff --git a/config/locales/es.yml b/config/locales/es.yml index b8bfc524..06b933bc 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -63,7 +63,7 @@ es: instances_blocked: Instancias bloqueadas instances_filters: text_show: Ver - text_checked: Con los marcados + text_checked: Con los marcados... instances_checked_submenu: submenu_pause: Moderar caso por caso submenu_allow: Permitir todo @@ -74,7 +74,7 @@ es: submenu_blocked: "Bloqueadas (%{count})" comments_filters: text_show: Ver - text_checked: Con los marcados + text_checked: Con los marcados... comments_checked_submenu: submenu_pause: Pausar submenu_approve: Aprobar @@ -87,7 +87,7 @@ es: submenu_reported: "Reportados (%{count})" profiles_filters: text_show: Ver - text_checked: Con los marcados + text_checked: Con los marcados... profiles_checked_submenu: submenu_pause: Pausar submenu_allow: Aceptar From eda075fd4b889707328c2c62bcc6ac47984944ee Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Mar 2024 17:52:31 -0300 Subject: [PATCH 269/297] =?UTF-8?q?fix:=20la=20acci=C3=B3n=20no=20se=20pue?= =?UTF-8?q?de=20hacer=20porque=20es=20el=20estado=20actual=20#15600?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/components/_btn_base.haml | 1 - app/views/components/_comments_btn_box.haml | 4 +++- app/views/components/_instances_btn_box.haml | 5 +++-- app/views/components/_profiles_btn_box.haml | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/views/components/_btn_base.haml b/app/views/components/_btn_base.haml index faa5c85f..fed3254c 100644 --- a/app/views/components/_btn_base.haml +++ b/app/views/components/_btn_base.haml @@ -1,7 +1,6 @@ -# Componente Botón general Moderación - local_assigns[:method] ||= 'patch' -- local_assigns[:class] ||= 'btn-secondary' - local_assigns[:class] = "btn #{local_assigns[:class]}" - local_assigns.delete(:text) diff --git a/app/views/components/_comments_btn_box.haml b/app/views/components/_comments_btn_box.haml index 1993e5cb..ffc773a7 100644 --- a/app/views/components/_comments_btn_box.haml +++ b/app/views/components/_comments_btn_box.haml @@ -4,8 +4,10 @@ .d-flex.flex-row - ActivityPub.events.each do |event| + - possible = activity_pub.public_send(:"may_#{event}?") = render 'components/btn_base', text: t(".text_#{event}"), path: public_send(:"site_activity_pub_#{event}_path", activity_pub_id: activity_pub), - disabled: !activity_pub.public_send(:"may_#{event}?"), + class: ('btn-secondary' if possible), + disabled: !possible, data: local_data[event] diff --git a/app/views/components/_instances_btn_box.haml b/app/views/components/_instances_btn_box.haml index 15c6c040..8c3a5f88 100644 --- a/app/views/components/_instances_btn_box.haml +++ b/app/views/components/_instances_btn_box.haml @@ -2,9 +2,10 @@ - local_data = {} - InstanceModeration.events.each do |event| + - possible = instance_moderation.public_send(:"may_#{event}?") = render 'components/btn_base', path: public_send(:"site_instance_moderation_#{event}_path", instance_moderation_id: instance_moderation), text: t(".text_#{event}"), - class: 'btn btn-secondary', - disabled: !instance_moderation.public_send(:"may_#{event}?"), + class: ('btn-secondary' if possible), + disabled: !possible, data: local_data[event] diff --git a/app/views/components/_profiles_btn_box.haml b/app/views/components/_profiles_btn_box.haml index 488373b9..9414c178 100644 --- a/app/views/components/_profiles_btn_box.haml +++ b/app/views/components/_profiles_btn_box.haml @@ -2,9 +2,10 @@ .d-flex.flex-row - local_data = { report: { confirm: t('.confirm_report') } } - ActorModeration.events.each do |actor_event| + - possible = !actor_moderation.public_send(:"may_#{actor_event}?") = render 'components/btn_base', text: t(".text_#{actor_event}"), path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), - class: 'btn-secondary', - disabled: !actor_moderation.public_send(:"may_#{actor_event}?"), + class: ('btn-secondary' if possible), + disabled: !possible, data: local_data[actor_event] From 8dae40cb0ebef8c9851b03467962d2bb6bffb724 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Mar 2024 17:53:00 -0300 Subject: [PATCH 270/297] fix: confirmar al rechazar #15600 --- app/views/components/_comments_btn_box.haml | 2 +- config/locales/en.yml | 1 + config/locales/es.yml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/components/_comments_btn_box.haml b/app/views/components/_comments_btn_box.haml index ffc773a7..19ea169c 100644 --- a/app/views/components/_comments_btn_box.haml +++ b/app/views/components/_comments_btn_box.haml @@ -1,6 +1,6 @@ -# Componente Botonera de Comentarios -- local_data = { report: { confirm: t('.confirm_report') } } +- local_data = { reject: { confirm: t('.confirm_reject') }, report: { confirm: t('.confirm_report') } } .d-flex.flex-row - ActivityPub.events.each do |event| diff --git a/config/locales/en.yml b/config/locales/en.yml index 8059b6db..c88f7b26 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -107,6 +107,7 @@ en: text_reply: Reply text_report: Report confirm_report: "Send report to the remote instance? This action will also reject the comment." + confirm_reject: "Reject this comment? Please notice we can't undo this action at this moment." instances_btn_box: text_pause: Check case by case text_allow: Allow everything diff --git a/config/locales/es.yml b/config/locales/es.yml index 06b933bc..695b4e60 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -106,6 +106,7 @@ es: text_reject: Rechazar text_report: Reportar confirm_report: "¿Enviar el reporte a la instancia remota? Esta acción también rechazará el comentario." + confirm_reject: "¿Rechazar este comentario? Tené en cuenta que por el momento no es posible deshacer esta acción." instances_btn_box: text_pause: Moderar caso por caso text_allow: Permitir todo From e05a8aafda806adadea1af6b95c5ed13b8d0565b Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Mar 2024 11:14:36 -0300 Subject: [PATCH 271/297] fix: enviar los botones de reporte a la derecha #15600 --- app/views/components/_comments_btn_box.haml | 15 ++++++++------- app/views/components/_profiles_btn_box.haml | 17 +++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/views/components/_comments_btn_box.haml b/app/views/components/_comments_btn_box.haml index 19ea169c..578f6662 100644 --- a/app/views/components/_comments_btn_box.haml +++ b/app/views/components/_comments_btn_box.haml @@ -1,13 +1,14 @@ -# Componente Botonera de Comentarios -- local_data = { reject: { confirm: t('.confirm_reject') }, report: { confirm: t('.confirm_report') } } +- local = { reject: { data: { confirm: t('.confirm_reject') } }, report: { class: 'ml-auto', data: { confirm: t('.confirm_report') } } } .d-flex.flex-row - ActivityPub.events.each do |event| - possible = activity_pub.public_send(:"may_#{event}?") - = render 'components/btn_base', - text: t(".text_#{event}"), - path: public_send(:"site_activity_pub_#{event}_path", activity_pub_id: activity_pub), - class: ('btn-secondary' if possible), - disabled: !possible, - data: local_data[event] + %div{ class: local.dig(event, :class) } + = render 'components/btn_base', + text: t(".text_#{event}"), + path: public_send(:"site_activity_pub_#{event}_path", activity_pub_id: activity_pub), + class: ('btn-secondary' if possible), + disabled: !possible, + data: local.dig(event, :data) diff --git a/app/views/components/_profiles_btn_box.haml b/app/views/components/_profiles_btn_box.haml index 9414c178..8fc8dd39 100644 --- a/app/views/components/_profiles_btn_box.haml +++ b/app/views/components/_profiles_btn_box.haml @@ -1,11 +1,12 @@ -# Componente Botonera de Moderación de Cuentas (Remote_profile) -.d-flex.flex-row - - local_data = { report: { confirm: t('.confirm_report') } } +.d-flex.flex-row.w-100 + - local = { report: { class: 'ml-auto', data: { confirm: t('.confirm_report') } } } - ActorModeration.events.each do |actor_event| - possible = !actor_moderation.public_send(:"may_#{actor_event}?") - = render 'components/btn_base', - text: t(".text_#{actor_event}"), - path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), - class: ('btn-secondary' if possible), - disabled: !possible, - data: local_data[actor_event] + %div{ class: local.dig(actor_event, :class) } + = render 'components/btn_base', + text: t(".text_#{actor_event}"), + path: public_send(:"site_actor_moderation_#{actor_event}_path", actor_moderation_id: actor_moderation), + class: ('btn-secondary' if possible), + disabled: !possible, + data: local.dig(actor_event, :data) From 2aa2b9f3bb5e0aecd5f7b256a3248adadbd25087 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Mar 2024 18:12:12 -0300 Subject: [PATCH 272/297] =?UTF-8?q?fix:=20poder=20ir=20a=20la=20cola=20de?= =?UTF-8?q?=20moderaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/stylesheets/application.scss | 16 ++++ .../moderation_queue_controller.rb | 3 + app/models/moderation_queue.rb | 3 + app/models/site/social_distributed_press.rb | 23 ++++++ app/policies/moderation_queue_policy.rb | 9 +++ app/views/posts/index.haml | 10 +-- app/views/sites/_moderation_queue.haml | 9 +++ app/views/sites/index.haml | 74 +++++++++---------- 8 files changed, 103 insertions(+), 44 deletions(-) create mode 100644 app/models/moderation_queue.rb create mode 100644 app/policies/moderation_queue_policy.rb create mode 100644 app/views/sites/_moderation_queue.haml diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index cdf97b5b..11c44d90 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -32,6 +32,22 @@ $sizes: ( @import "bootstrap"; @import "editor"; +@each $color, $rgb in $theme-colors { + .#{$color} { + color: var(--#{$color}); + + &:focus { + color: var(--#{$color}); + } + + ::-moz-selection, + ::selection { + background: var(--#{$color}); + color: white; + } + } +} + .editor { .editor-content { figure { diff --git a/app/controllers/moderation_queue_controller.rb b/app/controllers/moderation_queue_controller.rb index 4bd61e38..ef830c41 100644 --- a/app/controllers/moderation_queue_controller.rb +++ b/app/controllers/moderation_queue_controller.rb @@ -11,9 +11,12 @@ class ModerationQueueController < ApplicationController # Cola de moderación viendo todo el sitio def index + authorize ModerationQueue.new(site) breadcrumb site.title, site_posts_path(site) breadcrumb I18n.t('moderation_queue.index.title'), '' + site.moderation_checked! + # @todo cambiar el estado por query @activity_pubs = site.activity_pubs @instance_moderations = rubanok_process(site.instance_moderations, with: InstanceModerationProcessor) diff --git a/app/models/moderation_queue.rb b/app/models/moderation_queue.rb new file mode 100644 index 00000000..31ca3c9b --- /dev/null +++ b/app/models/moderation_queue.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +ModerationQueue = Struct.new(:site) diff --git a/app/models/site/social_distributed_press.rb b/app/models/site/social_distributed_press.rb index 0716a670..8d8d60d4 100644 --- a/app/models/site/social_distributed_press.rb +++ b/app/models/site/social_distributed_press.rb @@ -19,6 +19,29 @@ class Site before_save :generate_private_key_pem!, unless: :private_key_pem? + def moderation_enabled? + deploy_social_inbox.present? + end + + def deploy_social_inbox + @deploy_social_inbox ||= deploys.find_by(type: 'DeploySocialDistributedPress') + end + + def moderation_checked! + deploy_social_inbox.touch + end + + # @return [Bool] + def moderation_needed? + return false unless moderation_enabled? + + last_activity_pub = activity_pubs.order(updated_at: :desc).first&.updated_at + + return false if last_activity_pub.blank? + + last_activity_pub > deploy_social_inbox.updated_at + end + # @return [SocialInbox] def social_inbox @social_inbox ||= SocialInbox.new(site: self) diff --git a/app/policies/moderation_queue_policy.rb b/app/policies/moderation_queue_policy.rb new file mode 100644 index 00000000..75a4c45a --- /dev/null +++ b/app/policies/moderation_queue_policy.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Si la cola de moderación está activada y le usuarie tiene permisos de +# usuarie. +ModerationQueuePolicy = Struct.new(:usuarie, :moderation_queue) do + def index? + moderation_queue.site.moderation_enabled? && moderation_queue.site.usuarie?(usuarie) + end +end diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 69fb2d8f..6d77b75d 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -1,10 +1,10 @@ %main.row %aside.menu.col-md-3 - = render 'sites/header', site: @site - - = render 'sites/status', site: @site - - = render 'sites/build', site: @site, class: 'btn-block' + .mb-3 + = render 'sites/header', site: @site + = render 'sites/status', site: @site + = render 'sites/build', site: @site, class: 'btn-block' + = render 'sites/moderation_queue', site: @site, class: 'btn-block' %h3= t('posts.new') %table.table.table-sm.mb-3 diff --git a/app/views/sites/_moderation_queue.haml b/app/views/sites/_moderation_queue.haml new file mode 100644 index 00000000..6b39d797 --- /dev/null +++ b/app/views/sites/_moderation_queue.haml @@ -0,0 +1,9 @@ +- if policy(ModerationQueue.new(site)).index? + - moderation_needed = site.moderation_needed? + + - local_assigns[:class] = "btn btn-secondary #{local_assigns[:class]}" + = link_to site_moderation_queue_path(site), class: local_assigns[:class], title: (t('.moderation_needed') if moderation_needed) do + = t('moderation_queue.index.title') + - if moderation_needed + %span.primary ⏺ + %span.sr-only= t('.moderation_needed') diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index ed87180a..fc8184e1 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -15,43 +15,39 @@ %tbody - @sites.each do |site| - next unless site.jekyll? - - rol = current_usuarie.rol_for_site(site) - -# - TODO: Solo les usuaries cachean porque tenemos que separar - les botones por permisos. - - cache_if (rol.usuarie? && !rol.temporal), [site, I18n.locale] do - %tr - %td - %h2 - - if policy(site).show? - = link_to site.title, site_posts_path(site, locale: site.default_locale) - - else - = site.title - %p.lead= site.description - %br - = link_to t('.visit'), site.url, class: 'btn btn-secondary' - - if rol.temporal - = button_to t('sites.invitations.accept'), - site_usuaries_accept_invitation_path(site), - method: :patch, - title: t('help.sites.invitations.accept'), - class: 'btn btn-secondary' - = button_to t('sites.invitations.reject'), - site_usuaries_reject_invitation_path(site), - method: :patch, - title: t('help.sites.invitations.reject'), - class: 'btn btn-secondary' + %tr + %td + %h2 + - if policy(site).show? + = link_to site.title, site_posts_path(site, locale: site.default_locale) - else - - if policy(site).show? - = render 'layouts/btn_with_tooltip', - tooltip: t('help.sites.edit_posts'), - type: 'success', - link: site_path(site), - text: t('sites.posts') - - if policy(SiteUsuarie.new(site, current_usuarie)).index? - = render 'layouts/btn_with_tooltip', - tooltip: t('usuaries.index.help.self'), - text: t('usuaries.index.title'), - type: 'info', - link: site_usuaries_path(site) - = render 'sites/build', site: site + = site.title + %p.lead= site.description + %br + = link_to t('.visit'), site.url, class: 'btn btn-secondary' + - if current_usuarie.rol_for_site(site).temporal? + = render 'components/btn_base', + text: t('sites.invitations.accept'), + path: site_usuaries_accept_invitation_path(site), + title: t('help.sites.invitations.accept'), + class: 'btn-secondary' + = render 'components/btn_base', + text: t('sites.invitations.reject'), + path: site_usuaries_reject_invitation_path(site), + title: t('help.sites.invitations.reject'), + class: 'btn-secondary' + - else + - if policy(site).show? + = render 'layouts/btn_with_tooltip', + tooltip: t('help.sites.edit_posts'), + type: 'success', + link: site_path(site), + text: t('sites.posts') + = render 'sites/build', site: site + = render 'sites/moderation_queue', site: site + - if policy(SiteUsuarie.new(site, current_usuarie)).index? + = render 'layouts/btn_with_tooltip', + tooltip: t('usuaries.index.help.self'), + text: t('usuaries.index.title'), + type: 'info', + link: site_usuaries_path(site) From ad637189b542a386ba6c09908b0cb59d20a2b91f Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Mar 2024 18:25:36 -0300 Subject: [PATCH 273/297] =?UTF-8?q?fixup!=20fix:=20poder=20ir=20a=20la=20c?= =?UTF-8?q?ola=20de=20moderaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/en.yml | 2 ++ config/locales/es.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index c88f7b26..f024fa98 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -573,6 +573,8 @@ en: column: "Country" empty: "(couldn't detect country)" sites: + moderation_queue: + moderation_needed: "There are new activities pending revision since the last time you moderated." donations: url: 'https://donaciones.sutty.nl/en/' text: 'Support us' diff --git a/config/locales/es.yml b/config/locales/es.yml index 695b4e60..204a3587 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -577,6 +577,8 @@ es: column: "País" empty: "(no se pudo detectar el país)" sites: + moderation_queue: + moderation_needed: "Hay actividades pendientes de revisión desde la última vez que moderaste." donations: url: 'https://donaciones.sutty.nl/' text: 'Apoyá nuestro trabajo' From 48532633740028bf406d2bd7ca97ab85f9c076bd Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 11:53:23 -0300 Subject: [PATCH 274/297] fix: pedir locks antes de guardar closes #15621 closes #15622 closes #15623 closes #15729 closes #15730 closes #15731 closes #15735 closes #15736 --- app/jobs/activity_pub/process_job.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index f3aeebb4..bd010613 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -66,6 +66,7 @@ class ActivityPub @object ||= ::ActivityPub::Object.lock.find_or_initialize_by(uri: object_uri).tap do |o| o.content = original_object if object_embedded? + o.lock! o.save! # XXX: el objeto necesita ser guardado antes de poder @@ -95,6 +96,7 @@ class ActivityPub .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| a.content = original_activity.dup a.content[:object] = object.uri + a.lock! a.save! end end @@ -113,6 +115,7 @@ class ActivityPub site.instance_moderations.lock.find_or_create_by(instance: a.instance) + a.lock! a.save! site.actor_moderations.lock.find_or_create_by(actor: a) From 0561b022de3eb01d747c4379590904087385f8c2 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 11:57:22 -0300 Subject: [PATCH 275/297] fix: no fallar si no hay registros sobre los que actuar #15725 --- app/controllers/activity_pubs_controller.rb | 6 ++++-- app/controllers/actor_moderations_controller.rb | 6 ++++-- app/controllers/instance_moderations_controller.rb | 6 ++++-- app/policies/instance_moderation_policy.rb | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/controllers/activity_pubs_controller.rb b/app/controllers/activity_pubs_controller.rb index d5041a84..428d5cb1 100644 --- a/app/controllers/activity_pubs_controller.rb +++ b/app/controllers/activity_pubs_controller.rb @@ -29,16 +29,18 @@ class ActivityPubsController < ApplicationController end def action_on_several + redirect_to_moderation_queue! + activity_pubs = site.activity_pubs.where(id: params[:activity_pub]) + return if activity_pubs.count.zero? + authorize activity_pubs action = params[:activity_pub_action].to_sym method = :"#{action}_all!" may = :"may_#{action}?" - redirect_to_moderation_queue! - return unless ActivityPub.events.include? action # Crear una sola remote flag por autore diff --git a/app/controllers/actor_moderations_controller.rb b/app/controllers/actor_moderations_controller.rb index 739b1f46..04d2603b 100644 --- a/app/controllers/actor_moderations_controller.rb +++ b/app/controllers/actor_moderations_controller.rb @@ -48,16 +48,18 @@ class ActorModerationsController < ApplicationController end def action_on_several + redirect_to_moderation_queue! + actor_moderations = site.actor_moderations.where(id: params[:actor_moderation]) + return if actor_moderations.count.zero? + authorize actor_moderations action = params[:actor_moderation_action].to_sym method = :"#{action}_all!" may = :"may_#{action}?" - redirect_to_moderation_queue! - return unless ActorModeration.events.include? action ActorModeration.transaction do diff --git a/app/controllers/instance_moderations_controller.rb b/app/controllers/instance_moderations_controller.rb index 13d7f428..de990eb1 100644 --- a/app/controllers/instance_moderations_controller.rb +++ b/app/controllers/instance_moderations_controller.rb @@ -22,15 +22,17 @@ class InstanceModerationsController < ApplicationController end def action_on_several + redirect_to_moderation_queue! + instance_moderations = site.instance_moderations.where(id: params[:instance_moderation]) + return if instance_moderations.count.zero? + authorize instance_moderations action = params[:instance_moderation_action].to_sym method = :"#{action}_all!" - redirect_to_moderation_queue! - return unless InstanceModeration.events.include? action InstanceModeration.transaction do diff --git a/app/policies/instance_moderation_policy.rb b/app/policies/instance_moderation_policy.rb index 13ebfeca..c54eb4b8 100644 --- a/app/policies/instance_moderation_policy.rb +++ b/app/policies/instance_moderation_policy.rb @@ -11,6 +11,6 @@ InstanceModerationPolicy = Struct.new(:usuarie, :instance_moderation) do # En este paso tenemos varias instancias por moderar pero todas son # del mismo sitio. def action_on_several? - instance_moderation.first.site.usuarie? usuarie + instance_moderation.first.presence && instance_moderation.first.site.usuarie? usuarie end end From cc654be121986126642308d68c0b9fdbb6cdb4a3 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 12:01:08 -0300 Subject: [PATCH 276/297] fixup! fix: no fallar si no hay registros sobre los que actuar #15725 --- app/policies/instance_moderation_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/policies/instance_moderation_policy.rb b/app/policies/instance_moderation_policy.rb index c54eb4b8..c6a229d3 100644 --- a/app/policies/instance_moderation_policy.rb +++ b/app/policies/instance_moderation_policy.rb @@ -11,6 +11,6 @@ InstanceModerationPolicy = Struct.new(:usuarie, :instance_moderation) do # En este paso tenemos varias instancias por moderar pero todas son # del mismo sitio. def action_on_several? - instance_moderation.first.presence && instance_moderation.first.site.usuarie? usuarie + instance_moderation.first.presence && instance_moderation.first.site.usuarie?(usuarie) end end From b6fdc8a5801ebacee50eb0b522a374ca4ac3aa52 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 12:38:54 -0300 Subject: [PATCH 277/297] =?UTF-8?q?fix:=20permitir=20hostnames=20num=C3=A9?= =?UTF-8?q?ricos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #15789 --- app/models/activity_pub/instance.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/activity_pub/instance.rb b/app/models/activity_pub/instance.rb index 749d98ac..cd14ef23 100644 --- a/app/models/activity_pub/instance.rb +++ b/app/models/activity_pub/instance.rb @@ -9,7 +9,7 @@ class ActivityPub include AASM validates :aasm_state, presence: true, inclusion: { in: %w[paused allowed blocked] } - validates :hostname, uniqueness: true, hostname: true + validates :hostname, uniqueness: true, hostname: { allow_numeric_hostname: true } has_many :activity_pubs has_many :actors From 5db263ef6c65450ae997e26a9ba9951f7ea859aa Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 12:54:27 -0300 Subject: [PATCH 278/297] fix: recordar que hay que volver a correr la tarea manualmente #15789 --- app/jobs/activity_pub/process_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index bd010613..94799735 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -28,7 +28,7 @@ class ActivityPub # Al generar una excepción, en lugar de seguir intentando, enviamos # el reporte. rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, activity: original_activity }) + ExceptionNotifier.notify_exception(e, data: { site: site.name, body: body, initial_state: initial_state, activity: original_activity, message: 'Esta acción se canceló automáticamente, para regenerarla, volver a correr el proceso con los mismos parámetros.' }) end private From dad5f5f00ce99d9b724853b6eaaa1ea0a0be70b8 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 13:06:58 -0300 Subject: [PATCH 279/297] =?UTF-8?q?fix:=20solo=20bloquear=20cuando=20ya=20?= =?UTF-8?q?est=C3=A1=20en=20la=20base=20de=20datos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #15791 --- app/jobs/activity_pub/process_job.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index 94799735..9b7c30fc 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -66,7 +66,7 @@ class ActivityPub @object ||= ::ActivityPub::Object.lock.find_or_initialize_by(uri: object_uri).tap do |o| o.content = original_object if object_embedded? - o.lock! + o.lock! if o.persisted? o.save! # XXX: el objeto necesita ser guardado antes de poder @@ -96,7 +96,7 @@ class ActivityPub .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| a.content = original_activity.dup a.content[:object] = object.uri - a.lock! + a.lock! if o.persisted? a.save! end end @@ -115,7 +115,7 @@ class ActivityPub site.instance_moderations.lock.find_or_create_by(instance: a.instance) - a.lock! + a.lock! if o.persisted? a.save! site.actor_moderations.lock.find_or_create_by(actor: a) From 071f20762c3ed2d435ed5351d5b9b5cd937865e5 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 13:09:13 -0300 Subject: [PATCH 280/297] fix: bloquear antes de empezar a modificar closes #15791 --- app/jobs/activity_pub/process_job.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index 9b7c30fc..a79abf5d 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -64,9 +64,9 @@ class ActivityPub # @return [ActivityPub::Object] def object @object ||= ::ActivityPub::Object.lock.find_or_initialize_by(uri: object_uri).tap do |o| + o.lock! if o.persisted? o.content = original_object if object_embedded? - o.lock! if o.persisted? o.save! # XXX: el objeto necesita ser guardado antes de poder @@ -94,9 +94,9 @@ class ActivityPub .type_from(original_activity) .lock .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| + a.lock! if o.persisted? a.content = original_activity.dup a.content[:object] = object.uri - a.lock! if o.persisted? a.save! end end @@ -107,6 +107,8 @@ class ActivityPub # @return [Actor] def actor @actor ||= ::ActivityPub::Actor.lock.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| + a.lock! if o.persisted? + unless a.instance a.instance = ::ActivityPub::Instance.lock.find_or_create_by(hostname: URI.parse(a.uri).hostname) @@ -114,8 +116,6 @@ class ActivityPub end site.instance_moderations.lock.find_or_create_by(instance: a.instance) - - a.lock! if o.persisted? a.save! site.actor_moderations.lock.find_or_create_by(actor: a) From dbf15397e53de6ce46f148a1be61ef98ab6fefb8 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 15:44:28 -0300 Subject: [PATCH 281/297] fix: mostrar los desplegables como accionables #15600 --- app/views/components/_dropdown.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/components/_dropdown.haml b/app/views/components/_dropdown.haml index 54ddcffb..6f34950b 100644 --- a/app/views/components/_dropdown.haml +++ b/app/views/components/_dropdown.haml @@ -11,7 +11,7 @@ controller: 'dropdown' } } - %button.btn.dropdown-toggle{ + %button.btn.btn-outline-secondary.dropdown-toggle{ type: 'button', class: button_classes, data: { From 07fd2cff930d26e33e8e3ac3fb3307554a3166b0 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 17:31:53 -0300 Subject: [PATCH 282/297] fix: usar locks en moderaciones de instancia closes #15788 --- app/jobs/activity_pub/instance_moderation_job.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/jobs/activity_pub/instance_moderation_job.rb b/app/jobs/activity_pub/instance_moderation_job.rb index 00100abf..9da0627f 100644 --- a/app/jobs/activity_pub/instance_moderation_job.rb +++ b/app/jobs/activity_pub/instance_moderation_job.rb @@ -9,7 +9,7 @@ class ActivityPub def perform(site:, hostnames:, perform_remotely: true) # Crear las instancias que no existan todavía hostnames.each do |hostname| - ActivityPub::Instance.find_or_create_by(hostname: hostname) + ActivityPub::Instance.lock.find_or_create_by(hostname: hostname) end instances = ActivityPub::Instance.where(hostname: hostnames) @@ -20,7 +20,7 @@ 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) + site.instance_moderations.lock.find_or_create_by(instance: instance) end scope = site.instance_moderations.where(instance_id: instances.ids) From 366494b61535358b6fecaa13482ff3dde10358af Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Mar 2024 18:28:34 -0300 Subject: [PATCH 283/297] fix: typo closes #15792 closes #15793 closes #15794 closes #15795 closes #15796 closes #15797 closes #15798 closes #15799 closes #15800 closes #15801 closes #15802 closes #15803 closes #15804 closes #15805 closes #15806 closes #15807 closes #15808 closes #15809 closes #15810 closes #15811 closes #15812 closes #15813 closes #15814 closes #15815 closes #15816 closes #15817 closes #15818 --- app/jobs/activity_pub/process_job.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index a79abf5d..af686dbd 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -94,7 +94,7 @@ class ActivityPub .type_from(original_activity) .lock .find_or_initialize_by(uri: original_activity[:id], activity_pub: activity_pub, actor: actor).tap do |a| - a.lock! if o.persisted? + a.lock! if a.persisted? a.content = original_activity.dup a.content[:object] = object.uri a.save! @@ -107,7 +107,7 @@ class ActivityPub # @return [Actor] def actor @actor ||= ::ActivityPub::Actor.lock.find_or_initialize_by(uri: original_activity[:actor]).tap do |a| - a.lock! if o.persisted? + a.lock! if a.persisted? unless a.instance a.instance = ::ActivityPub::Instance.lock.find_or_create_by(hostname: URI.parse(a.uri).hostname) From d7de351e1af36ab1383d2cf0c4e8f040fef6a686 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 25 Mar 2024 13:32:08 -0300 Subject: [PATCH 284/297] fix: espaciar el procesamiento de tareas closes #15621 closes #15622 closes #15623 closes #15729 closes #15730 closes #15731 closes #15735 closes #15736 closes #15824 --- .../api/v1/webhooks/social_inbox_controller.rb | 4 +++- app/jobs/application_job.rb | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb index 6ac91a51..9d215812 100644 --- a/app/controllers/api/v1/webhooks/social_inbox_controller.rb +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -49,7 +49,9 @@ module Api # # @param initial_state [Symbol] def process!(initial_state) - ::ActivityPub::ProcessJob.perform_later(site: site, body: request.raw_post, initial_state: initial_state) + ::ActivityPub::ProcessJob + .set(wait: ApplicationJob.random_wait) + .perform_later(site: site, body: request.raw_post, initial_state: initial_state) end end end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb index 06690c53..1bf20345 100644 --- a/app/jobs/application_job.rb +++ b/app/jobs/application_job.rb @@ -4,6 +4,17 @@ class ApplicationJob < ActiveJob::Base include Que::ActiveJob::JobExtensions + # Esperar una cantidad random de segundos primos, para que no se + # superpongan tareas + # + # @return [Array] + RANDOM_WAIT = [3, 5, 7, 11, 13].seconds + + # @return [ActiveSupport::Duration] + def self.random_wait + RANDOM_WAIT.sample + end + private def site From 718b8643562f2f1d3202ed3c0a7ded93a115ceb5 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 25 Mar 2024 13:33:52 -0300 Subject: [PATCH 285/297] fixup! fix: espaciar el procesamiento de tareas --- app/jobs/application_job.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb index 1bf20345..dc6d0478 100644 --- a/app/jobs/application_job.rb +++ b/app/jobs/application_job.rb @@ -7,12 +7,12 @@ class ApplicationJob < ActiveJob::Base # Esperar una cantidad random de segundos primos, para que no se # superpongan tareas # - # @return [Array] - RANDOM_WAIT = [3, 5, 7, 11, 13].seconds + # @return [Array] + RANDOM_WAIT = [3, 5, 7, 11, 13] # @return [ActiveSupport::Duration] def self.random_wait - RANDOM_WAIT.sample + RANDOM_WAIT.sample.seconds end private From 88cc5f9cb0adee2eca01b5b905d69dd6f160fd37 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 25 Mar 2024 14:17:26 -0300 Subject: [PATCH 286/297] fix: reintentar antes de reportar el error #15824 --- app/jobs/activity_pub/process_job.rb | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index af686dbd..d94c31e2 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -3,15 +3,16 @@ class ActivityPub # Procesar las actividades a medida que llegan class ProcessJob < ApplicationJob - attr_reader :body + attr_reader :body, :initial_state # Procesa la actividad en segundo plano # # @param :body [String] # @param :initial_state [Symbol,String] def perform(site:, body:, initial_state: :paused) - @body = body @site = site + @body = body + @initial_state = initial_state ActiveRecord::Base.connection_pool.with_connection do ::ActivityPub.transaction do @@ -25,10 +26,24 @@ class ActivityPub activity.update_activity_pub_state! end end + end + # Al generar una excepción, en lugar de seguir intentando, enviamos # el reporte. - rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, body: body, initial_state: initial_state, activity: original_activity, message: 'Esta acción se canceló automáticamente, para regenerarla, volver a correr el proceso con los mismos parámetros.' }) + def handle_error(error) + case error + when ActiveRecord::RecordInvalid then retry_in(ApplicationJob.random_wait) + else + ExceptionNotifier.notify_exception( + error, + data: { + site: site.name, + body: body, + initial_state: initial_state, + activity: original_activity, + message: 'Esta acción se canceló automáticamente, para regenerarla, volver a correr el proceso con los mismos parámetros.' + }) + end end private From 0d28748b8b65192f59af90869e02cef44fd0a50c Mon Sep 17 00:00:00 2001 From: f Date: Tue, 26 Mar 2024 11:42:29 -0300 Subject: [PATCH 287/297] fix: i18n --- config/locales/en.yml | 5 +---- config/locales/es.yml | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index f024fa98..1c9f4576 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -771,10 +771,7 @@ en: categories: 'Everything' index: search: 'Search' - edit_post: 'Edit' - edit: - moderation_queue: Moderation Queue - post: Post + edit: Edit preview: btn: 'Preliminary version' alert: 'Not every article type has a preliminary version' diff --git a/config/locales/es.yml b/config/locales/es.yml index 204a3587..a96d5e60 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -712,9 +712,7 @@ es: en: 'inglés' ar: 'árabe' posts: - edit: - moderation_queue: Comentarios - post: Contenido + edit: Editar prev: Página anterior next: Página siguiente empty: No hay artículos con estos parámetros de búsqueda. From b10eef7644a23e8104b9a97019f0f0ec793976bb Mon Sep 17 00:00:00 2001 From: f Date: Tue, 26 Mar 2024 13:03:46 -0300 Subject: [PATCH 288/297] =?UTF-8?q?fix:=20botones=20en=20una=20sola=20l?= =?UTF-8?q?=C3=ADnea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/index.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 6d77b75d..9def84b0 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -125,10 +125,11 @@ %br/ = post.order %td.text-nowrap - - if @usuarie || policy(post).edit? - = link_to t('posts.edit_post'), edit_site_post_path(@site, post.path), class: 'btn btn-secondary btn-block' - - if @usuarie || policy(post).destroy? - = link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-secondary btn-block', method: :delete, data: { confirm: t('posts.confirm_destroy') } + .d-flex.flex-row.align-items-start + - if @usuarie || policy(post).edit? + = link_to t('posts.edit_post'), edit_site_post_path(@site, post.path), class: 'btn btn-secondary' + - if @usuarie || policy(post).destroy? + = link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-secondary', method: :delete, data: { confirm: t('posts.confirm_destroy') } #footnotes{ hidden: true } - @filter_params.each do |param, value| From 34a858f26a20fa0095bfb3efb02ef7252944918b Mon Sep 17 00:00:00 2001 From: f Date: Tue, 26 Mar 2024 13:08:11 -0300 Subject: [PATCH 289/297] fix: botones apaisados --- app/views/layouts/_breadcrumb.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_breadcrumb.haml b/app/views/layouts/_breadcrumb.haml index 137f086e..a946243a 100644 --- a/app/views/layouts/_breadcrumb.haml +++ b/app/views/layouts/_breadcrumb.haml @@ -13,7 +13,7 @@ %span.line-clamp-1= link_to crumb.name, crumb.url - if @current_usuarie || current_usuarie - %ul.navbar-nav + %ul.navbar-nav.flex-row - if @site&.tienda? %li.nav-item = link_to t('.tienda'), @site.tienda_url, From c501107856ff21909196a024889cfae0237d047d Mon Sep 17 00:00:00 2001 From: f Date: Wed, 27 Mar 2024 12:56:54 -0300 Subject: [PATCH 290/297] fix: indicar que no hay que reportar el error closes #15621 closes #15622 closes #15623 closes #15729 closes #15730 closes #15731 closes #15735 closes #15736 closes #15824 closes #15838 closes #15839 --- app/jobs/activity_pub/process_job.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index d94c31e2..4d9c6869 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -30,6 +30,9 @@ class ActivityPub # Al generar una excepción, en lugar de seguir intentando, enviamos # el reporte. + # + # @param error [Exception] + # @return [Bool] def handle_error(error) case error when ActiveRecord::RecordInvalid then retry_in(ApplicationJob.random_wait) @@ -44,6 +47,8 @@ class ActivityPub message: 'Esta acción se canceló automáticamente, para regenerarla, volver a correr el proceso con los mismos parámetros.' }) end + + false end private From 142962160a9f4a9f25fcc6f3dc5c842c9fe2f177 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 27 Mar 2024 14:06:23 -0300 Subject: [PATCH 291/297] fix: reintentar en algunos errores closes #15351 closes #15352 closes #15612 closes #15618 closes #15621 closes #15622 closes #15623 closes #15729 closes #15730 closes #15731 closes #15735 closes #15736 closes #15776 closes #15824 closes #15827 closes #15828 closes #15829 closes #15830 closes #15831 closes #15832 closes #15838 closes #15839 closes #15882 --- app/jobs/activity_pub/fetch_job.rb | 34 ++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index b19d5e41..54641dbf 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -11,14 +11,42 @@ class ActivityPub class FetchJob < ApplicationJob self.priority = 50 + attr_reader :site, :object, :response + + # Notificar errores de JSON con el contenido, tomar los errores de + # validación y conexión como errores temporales y notificar todo lo + # demás sin reintentar. + # + # @param error [Exception] + # @return [Bool] + def handle_error(error) + case error + when FastJsonparser::ParseError + expire + + ExceptionNotifier.notify_exception(error, data: { site: site.name, object: object.uri, body: response.body }) + + false + when ActiveRecord::RecordInvalid, SocketError, SystemCallError, Net::OpenTimeout, OpenSSL::OpenSSLError + retry_in(ApplicationJob.random_wait) + + false + else + expire + + true + end + end + def perform(site:, object_id:) ActivityPub::Object.transaction do - object = ::ActivityPub::Object.find(object_id) + @site = site + @object = ::ActivityPub::Object.find(object_id) return if object.blank? return if object.activity_pubs.where(aasm_state: 'removed').count.positive? - response = site.social_inbox.dereferencer.get(uri: object.uri) + @response = site.social_inbox.dereferencer.get(uri: object.uri) # @todo Fallar cuando la respuesta no funcione? # @todo Eliminar en 410 Gone @@ -39,8 +67,6 @@ class ActivityPub # Arreglar las relaciones con actividades también ActivityPub.where(object_id: object.id).update_all(object_type: object.type, updated_at: Time.now) - rescue FastJsonparser::ParseError => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, object: object.uri, body: response.body }) end end end From ca4017dbd0ff732b3ca6374103f04a5c178f506c Mon Sep 17 00:00:00 2001 From: f Date: Thu, 28 Mar 2024 10:09:20 -0300 Subject: [PATCH 292/297] =?UTF-8?q?fix:=20activejob=20usa=20su=20propio=20?= =?UTF-8?q?m=C3=A9todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #15621 closes #15622 closes #15623 closes #15729 closes #15730 closes #15731 closes #15735 closes #15736 closes #15824 closes #15838 closes #15839 closes #15882 --- app/jobs/activity_pub/process_job.rb | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index 4d9c6869..dd76a928 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -3,7 +3,9 @@ class ActivityPub # Procesar las actividades a medida que llegan class ProcessJob < ApplicationJob - attr_reader :body, :initial_state + attr_reader :body + + retry_on ActivityPub::RecordInvalid # Procesa la actividad en segundo plano # @@ -12,7 +14,6 @@ class ActivityPub def perform(site:, body:, initial_state: :paused) @site = site @body = body - @initial_state = initial_state ActiveRecord::Base.connection_pool.with_connection do ::ActivityPub.transaction do @@ -28,29 +29,6 @@ class ActivityPub end end - # Al generar una excepción, en lugar de seguir intentando, enviamos - # el reporte. - # - # @param error [Exception] - # @return [Bool] - def handle_error(error) - case error - when ActiveRecord::RecordInvalid then retry_in(ApplicationJob.random_wait) - else - ExceptionNotifier.notify_exception( - error, - data: { - site: site.name, - body: body, - initial_state: initial_state, - activity: original_activity, - message: 'Esta acción se canceló automáticamente, para regenerarla, volver a correr el proceso con los mismos parámetros.' - }) - end - - false - end - private # Si el objeto ya viene incorporado en la actividad o lo tenemos From 3f7f8878417bb034f97738de38d5e094bad0e65e Mon Sep 17 00:00:00 2001 From: f Date: Thu, 28 Mar 2024 10:13:13 -0300 Subject: [PATCH 293/297] fix: activejob gestiona errores por su cuenta closes #15351 closes #15352 closes #15612 closes #15618 closes #15621 closes #15622 closes #15623 closes #15729 closes #15730 closes #15731 closes #15735 closes #15736 closes #15776 closes #15824 closes #15827 closes #15828 closes #15829 closes #15830 closes #15831 closes #15832 closes #15838 closes #15839 closes #15882 --- app/jobs/activity_pub/fetch_job.rb | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 54641dbf..0a3ebf03 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -19,25 +19,16 @@ class ActivityPub # # @param error [Exception] # @return [Bool] - def handle_error(error) - case error - when FastJsonparser::ParseError - expire - - ExceptionNotifier.notify_exception(error, data: { site: site.name, object: object.uri, body: response.body }) - - false - when ActiveRecord::RecordInvalid, SocketError, SystemCallError, Net::OpenTimeout, OpenSSL::OpenSSLError - retry_in(ApplicationJob.random_wait) - - false - else - expire - - true - end + discard_on(FastJsonparser::ParseError) do |error| + ExceptionNotifier.notify_exception(error, data: { site: site.name, object: object.uri, body: response.body }) end + retry_on ActiveRecord::RecordInvalid + retry_on SocketError, wait: ApplicationJob.random_wait + retry_on SystemCallError, wait: ApplicationJob.random_wait + retry_on Net::OpenTimeout, wait: ApplicationJob.random_wait + retry_on OpenSSL::OpenSSLError, wait: ApplicationJob.random_wait + def perform(site:, object_id:) ActivityPub::Object.transaction do @site = site From fe51ef2193ef18e1cfcbc25bb4faef7a1a70643a Mon Sep 17 00:00:00 2001 From: f Date: Thu, 28 Mar 2024 10:15:21 -0300 Subject: [PATCH 294/297] =?UTF-8?q?fixup!=20fix:=20activejob=20usa=20su=20?= =?UTF-8?q?propio=20m=C3=A9todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/activity_pub/process_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/process_job.rb b/app/jobs/activity_pub/process_job.rb index dd76a928..69c83e33 100644 --- a/app/jobs/activity_pub/process_job.rb +++ b/app/jobs/activity_pub/process_job.rb @@ -5,7 +5,7 @@ class ActivityPub class ProcessJob < ApplicationJob attr_reader :body - retry_on ActivityPub::RecordInvalid + retry_on ActiveRecord::RecordInvalid # Procesa la actividad en segundo plano # From 452ca435081af9d29af1b1a03e2d99a221588942 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 19 Apr 2024 13:07:49 -0300 Subject: [PATCH 295/297] =?UTF-8?q?fix:=20doble=20negaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/components/_profiles_btn_box.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/components/_profiles_btn_box.haml b/app/views/components/_profiles_btn_box.haml index 8fc8dd39..2023de96 100644 --- a/app/views/components/_profiles_btn_box.haml +++ b/app/views/components/_profiles_btn_box.haml @@ -2,7 +2,7 @@ .d-flex.flex-row.w-100 - local = { report: { class: 'ml-auto', data: { confirm: t('.confirm_report') } } } - ActorModeration.events.each do |actor_event| - - possible = !actor_moderation.public_send(:"may_#{actor_event}?") + - possible = actor_moderation.public_send(:"may_#{actor_event}?") %div{ class: local.dig(actor_event, :class) } = render 'components/btn_base', text: t(".text_#{actor_event}"), From b11282997b8574c093e0742b6409b4378e966df3 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 2 May 2024 16:02:34 -0300 Subject: [PATCH 296/297] fix: redundante --- app/jobs/activity_pub/fetch_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 0a3ebf03..5d9e02de 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -11,7 +11,7 @@ class ActivityPub class FetchJob < ApplicationJob self.priority = 50 - attr_reader :site, :object, :response + attr_reader :object, :response # Notificar errores de JSON con el contenido, tomar los errores de # validación y conexión como errores temporales y notificar todo lo From c2cc08007e6d3018a929e61d7727f226380c074c Mon Sep 17 00:00:00 2001 From: f Date: Thu, 2 May 2024 16:05:35 -0300 Subject: [PATCH 297/297] chore: rubocop --- app/controllers/application_controller.rb | 9 +++------ app/helpers/application_helper.rb | 4 ++-- app/jobs/activity_pub/fetch_job.rb | 3 ++- app/jobs/activity_pub/sync_lists_job.rb | 4 +++- app/jobs/application_job.rb | 4 +--- app/models/activity_pub.rb | 5 +++-- app/models/activity_pub/fediblock.rb | 4 ++-- app/models/activity_pub/object.rb | 5 +++-- app/models/activity_pub/remote_flag.rb | 5 +++-- app/models/concerns/tienda.rb | 2 +- app/models/fediblock_state.rb | 3 ++- app/models/instance_moderation.rb | 4 +++- app/models/metadata_template.rb | 2 +- app/models/social_inbox.rb | 4 ++-- app/services/site_service.rb | 2 -- config/environments/production.rb | 9 +++++---- config/initializers/que_web.rb | 2 +- db/migrate/20240227142019_create_fediblock_states.rb | 4 +--- ...0313204105_brs_decompressor_corrupted_source_error.rb | 6 ++++-- db/migrate/20240314205923_fix_activity_type.rb | 4 +++- db/migrate/20240318183846_fix_duplicate_objects.rb | 2 +- db/migrate/20240319124212_add_fedipact_to_fediblocks.rb | 4 +--- db/migrate/20240319144735_add_missing_unique_indexes.rb | 6 +++--- db/seeds.rb | 4 ++-- 24 files changed, 52 insertions(+), 49 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b76238a0..117be995 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,7 +14,7 @@ class ApplicationController < ActionController::Base after_action :store_location! before_action do - Rack::MiniProfiler.authorize_request if current_usuarie&.email&.ends_with?('@' + ENV.fetch('SUTTY', 'sutty.nl')) + Rack::MiniProfiler.authorize_request if current_usuarie&.email&.ends_with?("@#{ENV.fetch('SUTTY', 'sutty.nl')}") end # No tenemos índice de sutty, vamos directamente a ver el listado de @@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base end private - + def notify_unconfirmed_email return unless current_usuarie return if current_usuarie.confirmed? @@ -58,9 +58,7 @@ class ApplicationController < ActionController::Base def current_locale locale = params[:change_locale_to] - if locale.present? && I18n.locale_available?(locale) - session[:locale] = params[:change_locale_to] - end + session[:locale] = params[:change_locale_to] if locale.present? && I18n.locale_available?(locale) session[:locale] || current_usuarie&.lang || I18n.locale end @@ -119,5 +117,4 @@ class ApplicationController < ActionController::Base session[:usuarie_return_to] = request.fullpath end - end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 146846f0..fcbd4074 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -13,7 +13,7 @@ module ApplicationHelper root = names.shift names.each do |n| - root += '[' + n.to_s + ']' + root += "[#{n}]" end [root, name] @@ -22,7 +22,7 @@ module ApplicationHelper def plain_field_name_for(*names) root, name = field_name_for(*names) - root + '[' + name.to_s + ']' + "#{root}[#{name}]" end def distance_of_time_in_words_if_more_than_a_minute(seconds) diff --git a/app/jobs/activity_pub/fetch_job.rb b/app/jobs/activity_pub/fetch_job.rb index 5d9e02de..07190c35 100644 --- a/app/jobs/activity_pub/fetch_job.rb +++ b/app/jobs/activity_pub/fetch_job.rb @@ -50,7 +50,8 @@ class ActivityPub content = FastJsonparser.parse(response.body) # Modificar atómicamente - ::ActivityPub::Object.lock.find(object_id).update!(content: content, type: ActivityPub::Object.type_from(content).name) + ::ActivityPub::Object.lock.find(object_id).update!(content: content, + type: ActivityPub::Object.type_from(content).name) object = ::ActivityPub::Object.find(object_id) # Actualiza la mención diff --git a/app/jobs/activity_pub/sync_lists_job.rb b/app/jobs/activity_pub/sync_lists_job.rb index de71fe64..e37e15be 100644 --- a/app/jobs/activity_pub/sync_lists_job.rb +++ b/app/jobs/activity_pub/sync_lists_job.rb @@ -43,7 +43,9 @@ class ActivityPub # Si alguna falló, reintentar raise if logs.present? rescue Exception => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, logs: logs, blocklist: blocklist, allowlist: allowlist, pauselist: pauselist }) + ExceptionNotifier.notify_exception(e, + data: { site: site.name, logs: logs, blocklist: blocklist, + allowlist: allowlist, pauselist: pauselist }) raise end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb index 4bb68359..ee4e3b2c 100644 --- a/app/jobs/application_job.rb +++ b/app/jobs/application_job.rb @@ -8,7 +8,7 @@ class ApplicationJob < ActiveJob::Base # superpongan tareas # # @return [Array] - RANDOM_WAIT = [3, 5, 7, 11, 13] + RANDOM_WAIT = [3, 5, 7, 11, 13].freeze # @return [ActiveSupport::Duration] def self.random_wait @@ -17,8 +17,6 @@ class ApplicationJob < ActiveJob::Base attr_reader :site - private - # Si falla por cualquier cosa informar y descartar discard_on(Exception) do |job, error| ExceptionNotifier.notify_exception(error, data: { job: job }) diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb index cd893406..7f8155cd 100644 --- a/app/models/activity_pub.rb +++ b/app/models/activity_pub.rb @@ -64,7 +64,7 @@ class ActivityPub < ApplicationRecord # Array o mezcla y obtener el que más nos convenga o # adivinar uno. when Array - links = object['url'].map.with_index do |link, i| + links = object['url'].map.with_index do |link, _i| case link when Hash then link else { 'href' => link.to_s } @@ -93,7 +93,8 @@ class ActivityPub < ApplicationRecord # Gestionar todos los errores error_on_all_events do |e| - ExceptionNotifier.notify_exception(e, data: { site: site.name, activity_pub: self.id, activity: activities.first.uri }) + ExceptionNotifier.notify_exception(e, + data: { site: site.name, activity_pub: id, activity: activities.first.uri }) end # Se puede volver a pausa en caso de actualización remota, para diff --git a/app/models/activity_pub/fediblock.rb b/app/models/activity_pub/fediblock.rb index 17897d79..e66e6e60 100644 --- a/app/models/activity_pub/fediblock.rb +++ b/app/models/activity_pub/fediblock.rb @@ -35,9 +35,9 @@ class ActivityPub validates_inclusion_of :format, in: %w[mastodon fediblock none] HOSTNAME_HEADERS = { - 'mastodon' => '#domain', + 'mastodon' => '#domain', 'fediblock' => 'domain' - } + }.freeze def client @client ||= Client.new diff --git a/app/models/activity_pub/object.rb b/app/models/activity_pub/object.rb index d37c9b88..b10b4431 100644 --- a/app/models/activity_pub/object.rb +++ b/app/models/activity_pub/object.rb @@ -39,13 +39,14 @@ class ActivityPub def referenced(site) require 'distributed_press/v1/social/referenced_object' - @referenced ||= DistributedPress::V1::Social::ReferencedObject.new(object: content, dereferencer: site.social_inbox.dereferencer) + @referenced ||= DistributedPress::V1::Social::ReferencedObject.new(object: content, + dereferencer: site.social_inbox.dereferencer) end private def uri_is_content_id? - return if self.uri == content['id'] + return if uri == content['id'] errors.add(:activity_pub_objects, 'El ID del objeto no coincide con su URI') end diff --git a/app/models/activity_pub/remote_flag.rb b/app/models/activity_pub/remote_flag.rb index c3cc0fb0..d6348650 100644 --- a/app/models/activity_pub/remote_flag.rb +++ b/app/models/activity_pub/remote_flag.rb @@ -40,7 +40,8 @@ class ActivityPub def content { '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => Rails.application.routes.url_helpers.v1_activity_pub_remote_flag_url(self, host: site.social_inbox_hostname), + 'id' => Rails.application.routes.url_helpers.v1_activity_pub_remote_flag_url(self, + host: site.social_inbox_hostname), 'type' => 'Flag', 'actor' => main_site.social_inbox.actor_id, 'content' => message.to_s, @@ -53,7 +54,7 @@ class ActivityPub # # @return [Site] def main_site - @main_site ||= Site.find(ENV.fetch('PANEL_ACTOR_SITE_ID') { 1 }) + @main_site ||= Site.find(ENV.fetch('PANEL_ACTOR_SITE_ID', 1)) end end end diff --git a/app/models/concerns/tienda.rb b/app/models/concerns/tienda.rb index 86174c9a..a3e6007a 100644 --- a/app/models/concerns/tienda.rb +++ b/app/models/concerns/tienda.rb @@ -17,7 +17,7 @@ module Tienda return t if new_record? - t.blank? ? 'https://' + name + '.' + ENV.fetch('TIENDA', 'tienda.sutty.nl') : t + t.blank? ? "https://#{name}.#{ENV.fetch('TIENDA', 'tienda.sutty.nl')}" : t end end end diff --git a/app/models/fediblock_state.rb b/app/models/fediblock_state.rb index 82912f76..02dee2d8 100644 --- a/app/models/fediblock_state.rb +++ b/app/models/fediblock_state.rb @@ -45,7 +45,8 @@ class FediblockState < ApplicationRecord private def block_instances! - ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: fediblock.hostnames, perform_remotely: false) + ActivityPub::InstanceModerationJob.perform_later(site: site, hostnames: fediblock.hostnames, + perform_remotely: false) end # Pausar todas las moderaciones de las instancias que no estén diff --git a/app/models/instance_moderation.rb b/app/models/instance_moderation.rb index 5a1a5ed6..c1192615 100644 --- a/app/models/instance_moderation.rb +++ b/app/models/instance_moderation.rb @@ -16,7 +16,9 @@ class InstanceModeration < ApplicationRecord state :blocked error_on_all_events do |e| - ExceptionNotifier.notify_exception(e, data: { site: site.name, instance: instance.hostname, instance_moderation: id }) + ExceptionNotifier.notify_exception(e, + data: { site: site.name, instance: instance.hostname, + instance_moderation: id }) end after_all_events do diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index a9765918..78989e15 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -134,7 +134,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, # En caso de que algún campo necesite realizar acciones antes de ser # guardado def save - if !changed? + unless changed? self[:value] = document_value if private? return true diff --git a/app/models/social_inbox.rb b/app/models/social_inbox.rb index 183ebfb0..adeedffc 100644 --- a/app/models/social_inbox.rb +++ b/app/models/social_inbox.rb @@ -45,7 +45,7 @@ class SocialInbox # @param url [String] # @return [DistributedPress::V1::Social::Client] def client_for(url) - raise "Falló generar un cliente" if url.blank? + raise 'Falló generar un cliente' if url.blank? @client_for ||= {} @client_for[url] ||= @@ -54,7 +54,7 @@ class SocialInbox public_key_url: public_key_url, private_key_pem: site.private_key_pem, logger: Rails.logger, - cache_store: HTTParty::Cache::Store::Redis.new(redis_url: ENV['REDIS_SERVER']) + cache_store: HTTParty::Cache::Store::Redis.new(redis_url: ENV.fetch('REDIS_SERVER', nil)) ) end diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 53e953f3..1a85d401 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -222,8 +222,6 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do end end - private - # Asignar un rol a cada deploy si no lo tenía ya def add_role_to_deploys!(role = current_role) site.deploys.each do |deploy| diff --git a/config/environments/production.rb b/config/environments/production.rb index bc7cecd7..5b0667a5 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -62,7 +62,7 @@ Rails.application.configure do config.log_tags = %i[request_id] # Use a different cache store in production. - config.cache_store = :redis_cache_store, { url: ENV['REDIS_SERVER'] } + config.cache_store = :redis_cache_store, { url: ENV.fetch('REDIS_SERVER', nil) } config.action_mailer.perform_caching = false @@ -87,7 +87,7 @@ Rails.application.configure do config.lograge.enabled = true # Use default logging formatter so that PID and timestamp are not # suppressed. - config.log_formatter = ::Logger::Formatter.new + config.log_formatter = Logger::Formatter.new # Use a different logger for distributed setups. require 'syslog/logger' @@ -140,9 +140,10 @@ Rails.application.configure do domain: ENV.fetch('SUTTY', 'sutty.nl'), enable_starttls_auto: false } - config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") } + config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', 'noreply@sutty.nl') } - config.middleware.use ExceptionNotification::Rack, gitlab: {}, error_grouping: true, ignore_exceptions: ['DeployJob::DeployAlreadyRunningException'] + config.middleware.use ExceptionNotification::Rack, gitlab: {}, error_grouping: true, + ignore_exceptions: ['DeployJob::DeployAlreadyRunningException'] Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}" Rails.application.routes.default_url_options[:protocol] = 'https' diff --git a/config/initializers/que_web.rb b/config/initializers/que_web.rb index 192256db..a6b87cf8 100644 --- a/config/initializers/que_web.rb +++ b/config/initializers/que_web.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true Que::Web.use(Rack::Auth::Basic) do |user, password| - [user, password] == [ENV['HTTP_BASIC_USER'], ENV['HTTP_BASIC_PASSWORD']] + [user, password] == [ENV.fetch('HTTP_BASIC_USER', nil), ENV.fetch('HTTP_BASIC_PASSWORD', nil)] end diff --git a/db/migrate/20240227142019_create_fediblock_states.rb b/db/migrate/20240227142019_create_fediblock_states.rb index c99cf63d..1e718343 100644 --- a/db/migrate/20240227142019_create_fediblock_states.rb +++ b/db/migrate/20240227142019_create_fediblock_states.rb @@ -16,9 +16,7 @@ class CreateFediblockStates < ActiveRecord::Migration[6.1] # Todas las listas están activas por defecto DeploySocialDistributedPress.find_each do |deploy| ActivityPub::Fediblock.find_each do |fediblock| - FediblockState.create(site: deploy.site, fediblock: fediblock, aasm_state: 'disabled').tap do |f| - f.enable! - end + FediblockState.create(site: deploy.site, fediblock: fediblock, aasm_state: 'disabled').tap(&:enable!) end end end diff --git a/db/migrate/20240313204105_brs_decompressor_corrupted_source_error.rb b/db/migrate/20240313204105_brs_decompressor_corrupted_source_error.rb index a0c29311..e22d759b 100644 --- a/db/migrate/20240313204105_brs_decompressor_corrupted_source_error.rb +++ b/db/migrate/20240313204105_brs_decompressor_corrupted_source_error.rb @@ -4,9 +4,11 @@ # decompresión class BrsDecompressorCorruptedSourceError < ActiveRecord::Migration[6.1] def up - raise unless HTTParty.get("https://mas.to/api/v2/instance", headers: { "Accept-Encoding": "br;q=1.0,gzip;q=1.0,deflate;q=0.6,identity;q=0.3" }).ok? + raise unless HTTParty.get('https://mas.to/api/v2/instance', + headers: { 'Accept-Encoding': 'br;q=1.0,gzip;q=1.0,deflate;q=0.6,identity;q=0.3' }).ok? - QueJob.where("last_error_message like '%BRS::DecompressorCorruptedSourceError%'").update_all(error_count: 0, run_at: Time.now) + QueJob.where("last_error_message like '%BRS::DecompressorCorruptedSourceError%'").update_all(error_count: 0, + run_at: Time.now) end def down; end diff --git a/db/migrate/20240314205923_fix_activity_type.rb b/db/migrate/20240314205923_fix_activity_type.rb index 042de8eb..e6640ff8 100644 --- a/db/migrate/20240314205923_fix_activity_type.rb +++ b/db/migrate/20240314205923_fix_activity_type.rb @@ -4,7 +4,9 @@ class FixActivityType < ActiveRecord::Migration[6.1] def up %w[Like Announce].each do |type| - ActivityPub::Activity.where(Arel.sql("content->>'type' = '#{type}'")).update_all(type: "ActivityPub::Activity::#{type}", updated_at: Time.now) + ActivityPub::Activity.where(Arel.sql("content->>'type' = '#{type}'")).update_all( + type: "ActivityPub::Activity::#{type}", updated_at: Time.now + ) end end diff --git a/db/migrate/20240318183846_fix_duplicate_objects.rb b/db/migrate/20240318183846_fix_duplicate_objects.rb index 88d23c6f..9f02c3db 100644 --- a/db/migrate/20240318183846_fix_duplicate_objects.rb +++ b/db/migrate/20240318183846_fix_duplicate_objects.rb @@ -3,7 +3,7 @@ # De alguna forma se guardaron objetos duplicados! class FixDuplicateObjects < ActiveRecord::Migration[6.1] def up - ActivityPub::Object.group(:uri).count.select { |_, v| v > 1 }.keys.each do |uri| + ActivityPub::Object.group(:uri).count.select { |_, v| v > 1 }.each_key do |uri| objects = ActivityPub::Object.where(uri: uri) deleted_ids = objects[1..].map(&:delete).map(&:id) diff --git a/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb b/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb index d78439b2..f751123a 100644 --- a/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb +++ b/db/migrate/20240319124212_add_fedipact_to_fediblocks.rb @@ -14,9 +14,7 @@ class AddFedipactToFediblocks < ActiveRecord::Migration[6.1] ) DeploySocialDistributedPress.find_each do |deploy| - FediblockState.create(site: deploy.site, fediblock: fedipact, aasm_state: 'disabled').tap do |f| - f.enable! - end + FediblockState.create(site: deploy.site, fediblock: fedipact, aasm_state: 'disabled').tap(&:enable!) end end diff --git a/db/migrate/20240319144735_add_missing_unique_indexes.rb b/db/migrate/20240319144735_add_missing_unique_indexes.rb index 7d18c8e8..2f6ef1aa 100644 --- a/db/migrate/20240319144735_add_missing_unique_indexes.rb +++ b/db/migrate/20240319144735_add_missing_unique_indexes.rb @@ -4,14 +4,14 @@ # no es válida y por eso teníamos objetos duplicados. class AddMissingUniqueIndexes < ActiveRecord::Migration[6.1] def up - ActivityPub::Object.group(:uri).count.select { |_, v| v > 1 }.keys.each do |uri| + ActivityPub::Object.group(:uri).count.select { |_, v| v > 1 }.each_key do |uri| objects = ActivityPub::Object.where(uri: uri) deleted_ids = objects[1..].map(&:delete).map(&:id) ActivityPub.where(object_id: deleted_ids).update_all(object_id: objects.first.id, updated_at: Time.now) end - ActivityPub::Actor.group(:uri).count.select { |_, v| v > 1 }.keys.each do |uri| + ActivityPub::Actor.group(:uri).count.select { |_, v| v > 1 }.each_key do |uri| objects = ActivityPub::Actor.where(uri: uri) deleted_ids = objects[1..].map(&:delete).map(&:id) @@ -21,7 +21,7 @@ class AddMissingUniqueIndexes < ActiveRecord::Migration[6.1] ActivityPub::RemoteFlag.where(actor_id: deleted_ids).update_all(actor_id: objects.first.id, updated_at: Time.now) end - ActivityPub::Instance.group(:hostname).count.select { |_, v| v > 1 }.keys.each do |hostname| + ActivityPub::Instance.group(:hostname).count.select { |_, v| v > 1 }.each_key do |hostname| objects = ActivityPub::Instance.where(hostname: hostname) deleted_ids = objects[1..].map(&:delete).map(&:id) diff --git a/db/seeds.rb b/db/seeds.rb index 8e8c291f..41474883 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -20,13 +20,13 @@ if CodeOfConduct.count.zero? YAML.safe_load(File.read('db/seeds/codes_of_conduct.yml')).each do |coc| CodeOfConduct.new(**coc).save! end -end +end if PrivacyPolicy.count.zero? YAML.safe_load(File.read('db/seeds/privacy_policies.yml')).each do |pp| PrivacyPolicy.new(**pp).save! end -end +end YAML.safe_load(File.read('db/seeds/activity_pub/fediblocks.yml')).each do |fediblock| ActivityPub::Fediblock.find_or_create_by(id: fediblock['id']).tap do |f|