5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-23 08:56:21 +00:00
panel/app/controllers/api/v1/webhooks/social_inbox_controller.rb

196 lines
6.4 KiB
Ruby
Raw Normal View History

2024-02-20 20:13:42 +00:00
# frozen_string_literal: true
module Api
module V1
module Webhooks
# Recibe webhooks de la Social Inbox
#
2024-02-28 20:52:24 +00:00
# @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.
2024-02-20 20:13:42 +00:00
# @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
2024-02-20 20:15:43 +00:00
# Devuelve un error si el token no es válido
usuarie.present?
ActivityPub.transaction do
# Crea todos los registros necesarios y actualiza el estado
actor.present?
instance.present?
object.present?
activity_pub.present?
2024-02-28 20:52:24 +00:00
2024-02-20 20:15:43 +00:00
activity.update_activity_pub_state!
end
rescue ActiveRecord::RecordInvalid => e
2024-02-21 15:46:38 +00:00
ExceptionNotifier.notify_exception(e,
data: { site: site.name, usuarie: usuarie.email,
activity: original_activity })
2024-02-20 20:15:43 +00:00
ensure
2024-02-20 20:13:42 +00:00
head :accepted
end
# Cuando la Social Inbox acepta una actividad, la recibimos
# igual y la guardamos por si cambiamos de idea.
#
# @todo DRY
2024-02-20 20:13:42 +00:00
def onapproved
ActivityPub.transaction do
actor.present?
instance.present?
object.present?
activity.present?
activity_pub.approve!
end
2024-02-20 20:13:42 +00:00
head :accepted
end
# Cuando la Social Inbox rechaza una actividad, la recibimos
# igual y la guardamos por si cambiamos de idea.
#
# @todo DRY
2024-02-20 20:13:42 +00:00
def onrejected
ActivityPub.transaction do
actor.present?
instance.present?
object.present?
activity.present?
activity_pub.reject!
end
2024-02-20 20:13:42 +00:00
head :accepted
end
2024-02-20 20:15:43 +00:00
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 ||=
2024-02-21 15:46:38 +00:00
case original_activity[:object]
when String then original_activity[:object]
when Hash then original_activity.dig(:object, :id)
2024-02-20 20:15:43 +00:00
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. Si el objeto no viene
# incorporado, obtenemos el contenido más tarde.
2024-02-20 20:15:43 +00:00
#
# @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'
o.content = original_object if object_embedded?
2024-02-20 20:15:43 +00:00
o.save!
# XXX: el objeto necesita ser guardado antes de poder
# procesarlo
ActivityPub::FetchJob.perform_later(site: site, object: o) unless object_embedded?
2024-02-20 20:15:43 +00:00
end
end
# Genera el seguimiento del estado del objeto con respecto al
# sitio.
#
# @return [ActivityPub]
def activity_pub
2024-02-28 20:52:24 +00:00
@activity_pub ||= site.activity_pubs.find_or_create_by!(site: site, instance: instance, object: object)
2024-02-20 20:15:43 +00:00
end
# Crea la actividad y la vincula con el estado
#
# @return [ActivityPub::Activity]
def activity
@activity ||=
ActivityPub::Activity
.type_from(original_activity)
2024-02-28 20:52:24 +00:00
.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
2024-02-20 20:15:43 +00:00
end
# Actor, si no hay instancia, la crea en el momento, junto con
# su estado de moderación.
2024-02-20 20:15:43 +00:00
#
# @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)
2024-02-20 20:15:43 +00:00
site.instance_moderations.find_or_create_by(instance: a.instance)
ActivityPub::InstanceFetchJob.perform_later(site: site, instance: a.instance)
end
2024-02-20 20:15:43 +00:00
a.save!
site.actor_moderations.find_or_create_by(actor: a)
ActivityPub::ActorFetchJob.perform_later(site: site, actor: a)
2024-02-20 20:15:43 +00:00
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
2024-02-20 20:15:43 +00:00
end
2024-02-20 20:13:42 +00:00
end
end
end
end