# 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. # # # @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 IGNORED_STATES = %i[removed].freeze include AASM belongs_to :instance belongs_to :site belongs_to :object, polymorphic: true belongs_to :actor belongs_to :remote_flag, optional: true, 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] 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'] || object[:id]) 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 # 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 :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 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. event :remove do transitions to: :removed after do next if object.blank? object.update(content: {}) unless object.content.empty? end 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], 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 event :report do transitions from: :rejected, to: :reported after do ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting? end end end # Definir eventos en masa include AasmEventsConcern end