mobilizon/lib/federation/activity_pub/activity_pub.ex
Thomas Citharel e73fd9b370
fix(activitypub): make relay outbox events ordered by desc publication date
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2023-12-20 09:24:59 +01:00

180 lines
5.7 KiB
Elixir

# Portions of this file are derived from Pleroma:
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social>
# SPDX-License-Identifier: AGPL-3.0-only
# Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/web/activity_pub/activity_pub.ex
defmodule Mobilizon.Federation.ActivityPub do
@moduledoc """
The ActivityPub context.
"""
import Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.{
Actors,
Discussions,
Events,
Posts,
Resources
}
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Tombstone
alias Mobilizon.Federation.ActivityPub.{
Activity,
Fetcher,
Preloader,
Relay
}
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Storage.Page
alias Mobilizon.Web.Endpoint
require Logger
@public_ap_adress "https://www.w3.org/ns/activitystreams#Public"
@doc """
Fetch an object from an URL, from our local database of events and comments, then eventually remote
"""
# TODO: Make database calls parallel
@spec fetch_object_from_url(String.t(), Keyword.t()) ::
{:ok, struct()} | {:ok, atom(), struct()} | {:error, any()}
def fetch_object_from_url(url, options \\ []) do
Logger.info("Fetching object from url #{url}")
if String.starts_with?(url, "http") do
with {:existing, nil} <-
{:existing, Tombstone.find_tombstone(url)},
{:existing, nil} <- {:existing, Events.get_event_by_url(url)},
{:existing, nil} <-
{:existing, Discussions.get_discussion_by_url(url)},
{:existing, nil} <- {:existing, Discussions.get_comment_from_url(url)},
{:existing, nil} <- {:existing, Resources.get_resource_by_url(url)},
{:existing, nil} <- {:existing, Posts.get_post_by_url(url)},
{:existing, nil} <-
{:existing, Actors.get_actor_by_url_2(url)},
{:existing, nil} <- {:existing, Actors.get_member_by_url(url)},
:ok <- Logger.info("Data for URL not found anywhere, going to fetch it"),
{:ok, _activity, entity} <- Fetcher.fetch_and_create(url, options) do
Logger.debug("Going to preload the new entity")
Preloader.maybe_preload(entity)
else
{:existing, entity} ->
handle_existing_entity(url, entity, options)
{:error, e} ->
Logger.warning("Something failed while fetching url #{url} #{inspect(e)}")
{:error, e}
end
else
{:error, :url_not_http}
end
end
@spec handle_existing_entity(String.t(), struct(), Keyword.t()) ::
{:ok, struct()}
| {:ok, atom(), struct()}
| {:error, String.t(), struct()}
| {:error, String.t()}
defp handle_existing_entity(url, entity, options) do
Logger.debug("Entity is already existing")
Logger.debug("Going to preload an existing entity")
case refresh_entity(url, entity, options) do
{:ok, entity} ->
Preloader.maybe_preload(entity)
{:error, status, entity} ->
{:ok, entity} = Preloader.maybe_preload(entity)
{:error, status, entity}
{:error, err} ->
{:error, err}
end
end
@spec refresh_entity(String.t(), struct(), Keyword.t()) ::
{:ok, struct()} | {:error, atom(), struct()} | {:error, atom()}
defp refresh_entity(url, entity, options) do
force_fetch = Keyword.get(options, :force, false)
if force_fetch and not are_same_origin?(url, Endpoint.url()) do
Logger.debug("Entity is external and we want a force fetch")
case Fetcher.fetch_and_update(url, options) do
{:ok, _activity, entity} ->
{:ok, entity}
{:error, :http_gone} ->
{:error, :http_gone, entity}
{:error, :http_not_found} ->
{:error, :http_not_found, entity}
{:error, err} ->
{:error, err}
end
else
{:ok, entity}
end
end
@doc """
Return all public activities (events & comments) for an actor
"""
@spec fetch_public_activities_for_actor(Actor.t(), pos_integer(), pos_integer()) :: map()
def fetch_public_activities_for_actor(%Actor{id: actor_id} = actor, page \\ 1, limit \\ 10) do
%Actor{id: relay_actor_id} = Relay.get_actor()
%Page{total: total_events, elements: events} =
if actor_id == relay_actor_id do
Events.list_public_local_events(page, limit, :publish_at, :desc)
else
Events.list_public_events_for_actor(actor, page, limit)
end
%Page{total: total_comments, elements: comments} =
if actor_id == relay_actor_id do
Discussions.list_local_comments(page, limit)
else
Discussions.list_public_comments_for_actor(actor, page, limit)
end
event_activities = Enum.map(events, &event_to_activity/1)
comment_activities = Enum.map(comments, &comment_to_activity/1)
activities = event_activities ++ comment_activities
%{elements: activities, total: total_events + total_comments}
end
# Create an activity from an event
@spec event_to_activity(Event.t(), boolean()) :: Activity.t()
defp event_to_activity(%Event{} = event, local \\ true) do
%Activity{
recipients: [@public_ap_adress],
actor: event.organizer_actor.url,
data: event |> Convertible.model_to_as() |> make_create_data(%{"to" => @public_ap_adress}),
local: local
}
end
# Create an activity from a comment
@spec comment_to_activity(Comment.t(), boolean()) :: Activity.t()
defp comment_to_activity(%Comment{} = comment, local \\ true) do
%Activity{
recipients: [@public_ap_adress],
actor: comment.actor.url,
data:
comment |> Convertible.model_to_as() |> make_create_data(%{"to" => @public_ap_adress}),
local: local
}
end
end