diff --git a/lib/federation/activity_pub/activity_pub.ex b/lib/federation/activity_pub/activity_pub.ex index 6696123da..b9208c700 100644 --- a/lib/federation/activity_pub/activity_pub.ex +++ b/lib/federation/activity_pub/activity_pub.ex @@ -480,7 +480,7 @@ defmodule Mobilizon.Federation.ActivityPub do "to" => [group_members_url], "type" => "Remove", "actor" => moderator_url, - "object" => member.actor.url, + "object" => member.url, "origin" => group_url }, {:ok, activity} <- create_activity(remove_data, local), diff --git a/lib/federation/activity_pub/transmogrifier.ex b/lib/federation/activity_pub/transmogrifier.ex index e53a672b5..ddeec0af1 100644 --- a/lib/federation/activity_pub/transmogrifier.ex +++ b/lib/federation/activity_pub/transmogrifier.ex @@ -686,8 +686,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do with {:ok, %Actor{id: moderator_id} = moderator} <- data |> Utils.get_actor() |> ActivityPub.get_or_fetch_actor_by_url(), - {:ok, %Actor{id: person_id}} <- - object |> Utils.get_url() |> ActivityPub.get_or_fetch_actor_by_url(), + {:ok, person_id} <- get_remove_object(object), {:ok, %Actor{type: :Group, id: group_id} = group} <- origin |> Utils.get_url() |> ActivityPub.get_or_fetch_actor_by_url(), {:is_admin, {:ok, %Member{role: role}}} @@ -1084,4 +1083,16 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do err end end + + # Before 1.0.4 the object of a "Remove" activity was an actor's URL + # instead of the member's URL. + # TODO: Remove in 1.2 + @spec get_remove_object(map() | String.t()) :: {:ok, String.t() | integer()} + defp get_remove_object(object) do + case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do + {:ok, %Member{actor: %Actor{id: person_id}}} -> {:ok, person_id} + {:ok, %Actor{id: person_id}} -> {:ok, person_id} + _ -> {:error, :remove_object_not_found} + end + end end diff --git a/lib/mobilizon/discussions/discussions.ex b/lib/mobilizon/discussions/discussions.ex index 132fb647b..9ecddffc0 100644 --- a/lib/mobilizon/discussions/discussions.ex +++ b/lib/mobilizon/discussions/discussions.ex @@ -55,6 +55,10 @@ defmodule Mobilizon.Discussions do @public_visibility [:public, :unlisted] + @doc """ + Callback for Absinthe Ecto Dataloader + """ + @spec data :: Dataloader.Ecto.t() def data do Dataloader.Ecto.new(Repo, query: &query/2) end @@ -65,6 +69,7 @@ defmodule Mobilizon.Discussions do We only get first comment of thread, and count replies. Read: https://hexdocs.pm/absinthe/ecto.html#dataloader """ + @spec query(atom(), map()) :: Ecto.Queryable.t() def query(Comment, _params) do Comment |> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id) @@ -94,6 +99,9 @@ defmodule Mobilizon.Discussions do @spec get_comment!(integer | String.t()) :: Comment.t() def get_comment!(id), do: Repo.get!(Comment, id) + @doc """ + Get a single comment by it's ID and all associations preloaded + """ @spec get_comment_with_preload(String.t() | integer() | nil) :: Comment.t() | nil def get_comment_with_preload(nil), do: nil @@ -160,6 +168,10 @@ defmodule Mobilizon.Discussions do |> Repo.preload(@comment_preloads) end + @doc """ + Get all comment threads under an event + """ + @spec get_threads(String.t() | integer()) :: [Comment.t()] def get_threads(event_id) do Comment |> where([c, _], c.event_id == ^event_id and is_nil(c.origin_comment_id)) @@ -179,6 +191,10 @@ defmodule Mobilizon.Discussions do |> Repo.all() end + @doc """ + Get a comment or create it + """ + @spec get_or_create_comment(map()) :: {:ok, Comment.t()} def get_or_create_comment(%{"url" => url} = attrs) do case Repo.get_by(Comment, url: url) do %Comment{} = comment -> {:ok, Repo.preload(comment, @comment_preloads)} @@ -239,6 +255,9 @@ defmodule Mobilizon.Discussions do Repo.all(from(c in Comment, where: c.visibility == ^:public)) end + @doc """ + Returns a paginated list of local comments + """ @spec list_local_comments(integer | nil, integer | nil) :: Page.t() def list_local_comments(page \\ nil, limit \\ nil) do Comment @@ -274,6 +293,9 @@ defmodule Mobilizon.Discussions do |> Repo.all() end + @doc """ + Get all the comments contained into a discussion + """ @spec get_comments_for_discussion(integer, integer | nil, integer | nil) :: Page.t() def get_comments_for_discussion(discussion_id, page \\ nil, limit \\ nil) do Comment @@ -302,12 +324,19 @@ defmodule Mobilizon.Discussions do |> Repo.one() end + @doc """ + Get a discussion by it's ID + """ + @spec get_discussion(String.t() | integer()) :: Discussion.t() def get_discussion(discussion_id) do Discussion |> Repo.get(discussion_id) |> Repo.preload(@discussion_preloads) end + @doc """ + Get a discussion by it's URL + """ @spec get_discussion_by_url(String.t() | nil) :: Discussion.t() | nil def get_discussion_by_url(nil), do: nil @@ -317,12 +346,19 @@ defmodule Mobilizon.Discussions do |> Repo.preload(@discussion_preloads) end + @doc """ + Get a discussion by it's slug + """ + @spec get_discussion_by_slug(String.t()) :: Discussion.t() def get_discussion_by_slug(discussion_slug) do Discussion |> Repo.get_by(slug: discussion_slug) |> Repo.preload(@discussion_preloads) end + @doc """ + Get a paginated list of discussions for a group actor + """ @spec find_discussions_for_actor(Actor.t(), integer | nil, integer | nil) :: Page.t() def find_discussions_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do Discussion @@ -366,6 +402,10 @@ defmodule Mobilizon.Discussions do end end + @doc """ + Create a response to a discussion + """ + @spec reply_to_discussion(Discussion.t(), map()) :: {:ok, Discussion.t()} def reply_to_discussion(%Discussion{id: discussion_id} = discussion, attrs \\ %{}) do with {:ok, %{comment: %Comment{} = comment, discussion: %Discussion{} = discussion}} <- Multi.new() @@ -415,6 +455,7 @@ defmodule Mobilizon.Discussions do |> Repo.transaction() end + @spec public_comments_for_actor_query(String.t() | integer()) :: [Comment.t()] defp public_comments_for_actor_query(actor_id) do Comment |> where([c], c.actor_id == ^actor_id and c.visibility in ^@public_visibility) @@ -422,6 +463,7 @@ defmodule Mobilizon.Discussions do |> preload_for_comment() end + @spec public_replies_for_thread_query(String.t() | integer()) :: [Comment.t()] defp public_replies_for_thread_query(comment_id) do Comment |> where([c], c.origin_comment_id == ^comment_id and c.visibility in ^@public_visibility) @@ -444,6 +486,7 @@ defmodule Mobilizon.Discussions do ) end + @spec filter_comments_under_events(Ecto.Query.t()) :: Ecto.Query.t() defp filter_comments_under_events(query) do where(query, [c], is_nil(c.discussion_id) and not is_nil(c.event_id)) end diff --git a/mix.exs b/mix.exs index 64316feca..c2d3e0129 100644 --- a/mix.exs +++ b/mix.exs @@ -103,8 +103,8 @@ defmodule Mobilizon.Mixfile do {:bamboo_smtp, "~> 3.0"}, {:geolix, "~> 2.0"}, {:geolix_adapter_mmdb2, "~> 0.6.0"}, - {:absinthe, "~> 1.5.5"}, - {:absinthe_phoenix, "~> 2.0.0"}, + {:absinthe, "~> 1.6"}, + {:absinthe_phoenix, github: "tcitworld/absinthe_phoenix", branch: "patch-1"}, {:absinthe_plug, "~> 1.5.0"}, {:dataloader, "~> 1.0.6"}, {:plug_cowboy, "~> 2.0"}, diff --git a/mix.lock b/mix.lock index 3861d9403..57ebf8f3e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,8 @@ %{ - "absinthe": {:hex, :absinthe, "1.5.5", "22b26228f56dc6a1074c52cea9c64e869a0cb2427403bcf9056c422d36c66292", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "41e79ed4bffbab4986493ff4120c948d59871fd08ad5e31195129ce3c01aad58"}, + "absinthe": {:hex, :absinthe, "1.6.0", "7cb42eebbb9cbf5077541d73c189e205ebe12caf1c78372fc5b9e706fc8ac298", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "99915841495522332b3af8ff10c9cbb51e256b28d9b19c0dfaac5f044b6bfb66"}, "absinthe_ecto": {:hex, :absinthe_ecto, "0.1.3", "420b68129e79fe4571a4838904ba03e282330d335da47729ad52ffd7b8c5fcb1", [:mix], [{:absinthe, "~> 1.3.0 or ~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "355b9db34abfab96ae1e025434b66e11002babcf4fe6b7144d26ff7548985f52"}, - "absinthe_phoenix": {:hex, :absinthe_phoenix, "2.0.0", "01c6a90af0ca12ee08d0fb93e23f9890d75bb6d3027f49ee4383bc03058ef5c3", [:mix], [{:absinthe, "~> 1.5.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.5.0", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "7ffbfe9fb82a14cafb78885cc2cef4f9d454bbbe2c95eec12b5463f5a20d1020"}, - "absinthe_plug": {:hex, :absinthe_plug, "1.5.2", "995de73f0afa763e0aae17e4e0e4affbb5d79e33199bf7e075aeb07f1fb6b7d1", [:mix], [{:absinthe, "~> 1.5.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e03549bce5bb77d7b4a9bd68c69f442b0f4409b723b9ce41683ac5c1c70b5958"}, + "absinthe_phoenix": {:git, "https://github.com/tcitworld/absinthe_phoenix.git", "4759d4141fb6254f5e7726ad6a00d7618116fb9e", [branch: "patch-1"]}, + "absinthe_plug": {:hex, :absinthe_plug, "1.5.3", "40b62edddc7db94098ab2f4a3011c17189862a3dbc50580fc92e3cb7953e9b36", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8378204b9b0c1f37bd7a52ecde8fa627e553a8bf04610d20e1bce2dbde71fdb2"}, "argon2_elixir": {:hex, :argon2_elixir, "2.4.0", "2a22ea06e979f524c53b42b598fc6ba38cdcbc977a155e33e057732cfb1fb311", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "4ea82e183cf8e7f66dab1f767fedcfe6a195e140357ef2b0423146b72e0a551d"}, "atomex": {:hex, :atomex, "0.3.0", "19b5d1a2aef8706dbd307385f7d5d9f6f273869226d317492c396c7bacf26402", [:mix], [{:xml_builder, "~> 2.0.0", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "025dbc3a3e99380894791a093019f535d0ef6cf1916f6ec1b778ac107fcfc3e4"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, diff --git a/test/federation/activity_pub/activity_pub_test.exs b/test/federation/activity_pub/activity_pub_test.exs index aa548d546..ac4f697bf 100644 --- a/test/federation/activity_pub/activity_pub_test.exs +++ b/test/federation/activity_pub/activity_pub_test.exs @@ -17,7 +17,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do alias Mobilizon.Todos.{Todo, TodoList} alias Mobilizon.Federation.ActivityPub - alias Mobilizon.Federation.ActivityPub.Utils + alias Mobilizon.Federation.ActivityPub.{Relay, Utils} alias Mobilizon.Federation.HTTPSignatures.Signature alias Mobilizon.Service.HTTP.ActivityPub.Mock @@ -125,6 +125,11 @@ defmodule Mobilizon.Federation.ActivityPubTest do assert_called(ActivityPub.make_actor_from_url(@actor_url, false)) end end + + @public_url "https://www.w3.org/ns/activitystreams#Public" + test "activitystreams#Public uri returns Relay actor" do + assert ActivityPub.get_or_fetch_actor_by_url(@public_url) == {:ok, Relay.get_actor()} + end end describe "create activities" do @@ -301,6 +306,19 @@ defmodule Mobilizon.Federation.ActivityPubTest do end end + describe "remove a member" do + test "it creates an remove activity" do + group = insert(:group) + member = insert(:member, parent: group) + moderator = insert(:actor) + {:ok, activity, _member} = ActivityPub.remove(member, group, moderator, true) + assert activity.data["type"] == "Remove" + assert activity.data["actor"] == moderator.url + assert activity.data["to"] == [group.members_url] + assert activity.data["object"] == member.url + end + end + describe "create a todo list" do @todo_list_title "My TODO-list"