# 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 # 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 # 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 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 end