Merge branch 'develop' into global-status-expiration
authorEgor Kislitsyn <egor@kislitsyn.com>
Mon, 8 Jun 2020 13:21:47 +0000 (17:21 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Mon, 8 Jun 2020 13:21:47 +0000 (17:21 +0400)
82 files changed:
benchmarks/load_testing/activities.ex
benchmarks/load_testing/fetcher.ex
benchmarks/load_testing/users.ex
benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
config/config.exs
docs/API/admin_api.md
docs/clients.md
lib/pleroma/bbs/handler.ex
lib/pleroma/constants.ex
lib/pleroma/conversation/participation.ex
lib/pleroma/following_relationship.ex
lib/pleroma/helpers/uri_helper.ex
lib/pleroma/maps.ex [new file with mode: 0644]
lib/pleroma/pagination.ex
lib/pleroma/user.ex
lib/pleroma/user/query.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/activity_pub/views/user_view.ex
lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
lib/pleroma/web/admin_api/controllers/config_controller.ex [new file with mode: 0644]
lib/pleroma/web/admin_api/controllers/invite_controller.ex [new file with mode: 0644]
lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex [new file with mode: 0644]
lib/pleroma/web/admin_api/controllers/relay_controller.ex [new file with mode: 0644]
lib/pleroma/web/admin_api/controllers/report_controller.ex [new file with mode: 0644]
lib/pleroma/web/admin_api/controllers/status_controller.ex
lib/pleroma/web/admin_api/search.ex
lib/pleroma/web/admin_api/views/account_view.ex
lib/pleroma/web/admin_api/views/invite_view.ex [new file with mode: 0644]
lib/pleroma/web/admin_api/views/report_view.ex
lib/pleroma/web/api_spec/operations/admin/config_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/admin/invite_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/admin/relay_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/admin/report_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/admin/status_operation.ex
lib/pleroma/web/api_spec/operations/instance_operation.ex
lib/pleroma/web/controller_helper.ex
lib/pleroma/web/embed_controller.ex [new file with mode: 0644]
lib/pleroma/web/feed/tag_controller.ex
lib/pleroma/web/feed/user_controller.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
lib/pleroma/web/mastodon_api/controllers/search_controller.ex
lib/pleroma/web/mastodon_api/controllers/status_controller.ex
lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
lib/pleroma/web/mastodon_api/views/app_view.ex
lib/pleroma/web/mastodon_api/views/conversation_view.ex
lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex
lib/pleroma/web/oauth/app.ex
lib/pleroma/web/oauth/oauth_controller.ex
lib/pleroma/web/pleroma_api/controllers/account_controller.ex
lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
lib/pleroma/web/router.ex
lib/pleroma/web/static_fe/static_fe_controller.ex
lib/pleroma/web/templates/embed/_attachment.html.eex [new file with mode: 0644]
lib/pleroma/web/templates/embed/show.html.eex [new file with mode: 0644]
lib/pleroma/web/templates/layout/embed.html.eex [new file with mode: 0644]
lib/pleroma/web/views/embed_view.ex [new file with mode: 0644]
priv/gettext/nl/LC_MESSAGES/errors.po
priv/repo/migrations/20200520155351_add_recipients_contain_blocked_domains_function.exs [new file with mode: 0644]
priv/static/embed.css [new file with mode: 0644]
priv/static/embed.js [new file with mode: 0644]
test/pagination_test.exs
test/tasks/relay_test.exs
test/user_test.exs
test/web/activity_pub/activity_pub_controller_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/views/user_view_test.exs
test/web/admin_api/controllers/admin_api_controller_test.exs
test/web/admin_api/controllers/config_controller_test.exs [new file with mode: 0644]
test/web/admin_api/controllers/invite_controller_test.exs [new file with mode: 0644]
test/web/admin_api/controllers/oauth_app_controller_test.exs [new file with mode: 0644]
test/web/admin_api/controllers/relay_controller_test.exs [new file with mode: 0644]
test/web/admin_api/controllers/report_controller_test.exs [new file with mode: 0644]
test/web/admin_api/controllers/status_controller_test.exs
test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
test/web/mastodon_api/controllers/search_controller_test.exs
test/web/mastodon_api/controllers/timeline_controller_test.exs

index ff0d481a81a0fa71bfa618837649b14540e7b6d3..074ded45709139da3896ec503412eefc6a45e011 100644 (file)
@@ -22,8 +22,21 @@ defmodule Pleroma.LoadTesting.Activities do
   @max_concurrency 10
 
   @visibility ~w(public private direct unlisted)
-  @types ~w(simple emoji mentions hell_thread attachment tag like reblog simple_thread remote)
-  @groups ~w(user friends non_friends)
+  @types [
+    :simple,
+    :emoji,
+    :mentions,
+    :hell_thread,
+    :attachment,
+    :tag,
+    :like,
+    :reblog,
+    :simple_thread
+  ]
+  @groups [:friends_local, :friends_remote, :non_friends_local, :non_friends_local]
+  @remote_groups [:friends_remote, :non_friends_remote]
+  @friends_groups [:friends_local, :friends_remote]
+  @non_friends_groups [:non_friends_local, :non_friends_remote]
 
   @spec generate(User.t(), keyword()) :: :ok
   def generate(user, opts \\ []) do
@@ -34,33 +47,24 @@ defmodule Pleroma.LoadTesting.Activities do
 
     opts = Keyword.merge(@defaults, opts)
 
-    friends =
-      user
-      |> Users.get_users(limit: opts[:friends_used], local: :local, friends?: true)
-      |> Enum.shuffle()
+    users = Users.prepare_users(user, opts)
 
-    non_friends =
-      user
-      |> Users.get_users(limit: opts[:non_friends_used], local: :local, friends?: false)
-      |> Enum.shuffle()
+    {:ok, _} = Agent.start_link(fn -> users[:non_friends_remote] end, name: :non_friends_remote)
 
     task_data =
       for visibility <- @visibility,
           type <- @types,
-          group <- @groups,
+          group <- [:user | @groups],
           do: {visibility, type, group}
 
     IO.puts("Starting generating #{opts[:iterations]} iterations of activities...")
 
-    friends_thread = Enum.take(friends, 5)
-    non_friends_thread = Enum.take(friends, 5)
-
     public_long_thread = fn ->
-      generate_long_thread("public", user, friends_thread, non_friends_thread, opts)
+      generate_long_thread("public", users, opts)
     end
 
     private_long_thread = fn ->
-      generate_long_thread("private", user, friends_thread, non_friends_thread, opts)
+      generate_long_thread("private", users, opts)
     end
 
     iterations = opts[:iterations]
@@ -73,10 +77,10 @@ defmodule Pleroma.LoadTesting.Activities do
             i when i == iterations - 2 ->
               spawn(public_long_thread)
               spawn(private_long_thread)
-              generate_activities(user, friends, non_friends, Enum.shuffle(task_data), opts)
+              generate_activities(users, Enum.shuffle(task_data), opts)
 
             _ ->
-              generate_activities(user, friends, non_friends, Enum.shuffle(task_data), opts)
+              generate_activities(users, Enum.shuffle(task_data), opts)
           end
         )
       end)
@@ -127,16 +131,16 @@ defmodule Pleroma.LoadTesting.Activities do
     end)
   end
 
-  defp generate_long_thread(visibility, user, friends, non_friends, _opts) do
+  defp generate_long_thread(visibility, users, _opts) do
     group =
       if visibility == "public",
-        do: "friends",
-        else: "user"
+        do: :friends_local,
+        else: :user
 
     tasks = get_reply_tasks(visibility, group) |> Stream.cycle() |> Enum.take(50)
 
     {:ok, activity} =
-      CommonAPI.post(user, %{
+      CommonAPI.post(users[:user], %{
         status: "Start of #{visibility} long thread",
         visibility: visibility
       })
@@ -150,31 +154,28 @@ defmodule Pleroma.LoadTesting.Activities do
       Map.put(state, key, activity)
     end)
 
-    acc = {activity.id, ["@" <> user.nickname, "reply to long thread"]}
-    insert_replies_for_long_thread(tasks, visibility, user, friends, non_friends, acc)
+    acc = {activity.id, ["@" <> users[:user].nickname, "reply to long thread"]}
+    insert_replies_for_long_thread(tasks, visibility, users, acc)
     IO.puts("Generating #{visibility} long thread ended\n")
   end
 
-  defp insert_replies_for_long_thread(tasks, visibility, user, friends, non_friends, acc) do
+  defp insert_replies_for_long_thread(tasks, visibility, users, acc) do
     Enum.reduce(tasks, acc, fn
-      "friend", {id, data} ->
-        friend = Enum.random(friends)
-        insert_reply(friend, List.delete(data, "@" <> friend.nickname), id, visibility)
-
-      "non_friend", {id, data} ->
-        non_friend = Enum.random(non_friends)
-        insert_reply(non_friend, List.delete(data, "@" <> non_friend.nickname), id, visibility)
-
-      "user", {id, data} ->
+      :user, {id, data} ->
+        user = users[:user]
         insert_reply(user, List.delete(data, "@" <> user.nickname), id, visibility)
+
+      group, {id, data} ->
+        replier = Enum.random(users[group])
+        insert_reply(replier, List.delete(data, "@" <> replier.nickname), id, visibility)
     end)
   end
 
-  defp generate_activities(user, friends, non_friends, task_data, opts) do
+  defp generate_activities(users, task_data, opts) do
     Task.async_stream(
       task_data,
       fn {visibility, type, group} ->
-        insert_activity(type, visibility, group, user, friends, non_friends, opts)
+        insert_activity(type, visibility, group, users, opts)
       end,
       max_concurrency: @max_concurrency,
       timeout: 30_000
@@ -182,67 +183,104 @@ defmodule Pleroma.LoadTesting.Activities do
     |> Stream.run()
   end
 
-  defp insert_activity("simple", visibility, group, user, friends, non_friends, _opts) do
-    {:ok, _activity} =
+  defp insert_local_activity(visibility, group, users, status) do
+    {:ok, _} =
       group
-      |> get_actor(user, friends, non_friends)
-      |> CommonAPI.post(%{status: "Simple status", visibility: visibility})
+      |> get_actor(users)
+      |> CommonAPI.post(%{status: status, visibility: visibility})
   end
 
-  defp insert_activity("emoji", visibility, group, user, friends, non_friends, _opts) do
-    {:ok, _activity} =
-      group
-      |> get_actor(user, friends, non_friends)
-      |> CommonAPI.post(%{
-        status: "Simple status with emoji :firefox:",
-        visibility: visibility
-      })
+  defp insert_remote_activity(visibility, group, users, status) do
+    actor = get_actor(group, users)
+    {act_data, obj_data} = prepare_activity_data(actor, visibility, users[:user])
+    {activity_data, object_data} = other_data(actor, status)
+
+    activity_data
+    |> Map.merge(act_data)
+    |> Map.put("object", Map.merge(object_data, obj_data))
+    |> Pleroma.Web.ActivityPub.ActivityPub.insert(false)
   end
 
-  defp insert_activity("mentions", visibility, group, user, friends, non_friends, _opts) do
+  defp user_mentions(users) do
     user_mentions =
-      get_random_mentions(friends, Enum.random(0..3)) ++
-        get_random_mentions(non_friends, Enum.random(0..3))
+      Enum.reduce(
+        @groups,
+        [],
+        fn group, acc ->
+          acc ++ get_random_mentions(users[group], Enum.random(0..2))
+        end
+      )
 
-    user_mentions =
-      if Enum.random([true, false]),
-        do: ["@" <> user.nickname | user_mentions],
-        else: user_mentions
+    if Enum.random([true, false]),
+      do: ["@" <> users[:user].nickname | user_mentions],
+      else: user_mentions
+  end
 
-    {:ok, _activity} =
-      group
-      |> get_actor(user, friends, non_friends)
-      |> CommonAPI.post(%{
-        status: Enum.join(user_mentions, ", ") <> " simple status with mentions",
-        visibility: visibility
-      })
+  defp hell_thread_mentions(users) do
+    with {:ok, nil} <- Cachex.get(:user_cache, "hell_thread_mentions") do
+      cached =
+        @groups
+        |> Enum.reduce([users[:user]], fn group, acc ->
+          acc ++ Enum.take(users[group], 5)
+        end)
+        |> Enum.map(&"@#{&1.nickname}")
+        |> Enum.join(", ")
+
+      Cachex.put(:user_cache, "hell_thread_mentions", cached)
+      cached
+    else
+      {:ok, cached} -> cached
+    end
   end
 
-  defp insert_activity("hell_thread", visibility, group, user, friends, non_friends, _opts) do
-    mentions =
-      with {:ok, nil} <- Cachex.get(:user_cache, "hell_thread_mentions") do
-        cached =
-          ([user | Enum.take(friends, 10)] ++ Enum.take(non_friends, 10))
-          |> Enum.map(&"@#{&1.nickname}")
-          |> Enum.join(", ")
+  defp insert_activity(:simple, visibility, group, users, _opts)
+       when group in @remote_groups do
+    insert_remote_activity(visibility, group, users, "Remote status")
+  end
 
-        Cachex.put(:user_cache, "hell_thread_mentions", cached)
-        cached
-      else
-        {:ok, cached} -> cached
-      end
+  defp insert_activity(:simple, visibility, group, users, _opts) do
+    insert_local_activity(visibility, group, users, "Simple status")
+  end
 
-    {:ok, _activity} =
-      group
-      |> get_actor(user, friends, non_friends)
-      |> CommonAPI.post(%{
-        status: mentions <> " hell thread status",
-        visibility: visibility
-      })
+  defp insert_activity(:emoji, visibility, group, users, _opts)
+       when group in @remote_groups do
+    insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:")
+  end
+
+  defp insert_activity(:emoji, visibility, group, users, _opts) do
+    insert_local_activity(visibility, group, users, "Simple status with emoji :firefox:")
+  end
+
+  defp insert_activity(:mentions, visibility, group, users, _opts)
+       when group in @remote_groups do
+    mentions = user_mentions(users)
+
+    status = Enum.join(mentions, ", ") <> " remote status with mentions"
+
+    insert_remote_activity(visibility, group, users, status)
+  end
+
+  defp insert_activity(:mentions, visibility, group, users, _opts) do
+    mentions = user_mentions(users)
+
+    status = Enum.join(mentions, ", ") <> " simple status with mentions"
+    insert_remote_activity(visibility, group, users, status)
+  end
+
+  defp insert_activity(:hell_thread, visibility, group, users, _)
+       when group in @remote_groups do
+    mentions = hell_thread_mentions(users)
+    insert_remote_activity(visibility, group, users, mentions <> " remote hell thread status")
+  end
+
+  defp insert_activity(:hell_thread, visibility, group, users, _opts) do
+    mentions = hell_thread_mentions(users)
+
+    insert_local_activity(visibility, group, users, mentions <> " hell thread status")
   end
 
-  defp insert_activity("attachment", visibility, group, user, friends, non_friends, _opts) do
-    actor = get_actor(group, user, friends, non_friends)
+  defp insert_activity(:attachment, visibility, group, users, _opts) do
+    actor = get_actor(group, users)
 
     obj_data = %{
       "actor" => actor.ap_id,
@@ -268,67 +306,54 @@ defmodule Pleroma.LoadTesting.Activities do
       })
   end
 
-  defp insert_activity("tag", visibility, group, user, friends, non_friends, _opts) do
-    {:ok, _activity} =
-      group
-      |> get_actor(user, friends, non_friends)
-      |> CommonAPI.post(%{status: "Status with #tag", visibility: visibility})
+  defp insert_activity(:tag, visibility, group, users, _opts) do
+    insert_local_activity(visibility, group, users, "Status with #tag")
   end
 
-  defp insert_activity("like", visibility, group, user, friends, non_friends, opts) do
-    actor = get_actor(group, user, friends, non_friends)
+  defp insert_activity(:like, visibility, group, users, opts) do
+    actor = get_actor(group, users)
 
     with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(),
          {:ok, _activity} <- CommonAPI.favorite(actor, activity_id) do
       :ok
     else
       {:error, _} ->
-        insert_activity("like", visibility, group, user, friends, non_friends, opts)
+        insert_activity(:like, visibility, group, users, opts)
 
       nil ->
         Process.sleep(15)
-        insert_activity("like", visibility, group, user, friends, non_friends, opts)
+        insert_activity(:like, visibility, group, users, opts)
     end
   end
 
-  defp insert_activity("reblog", visibility, group, user, friends, non_friends, opts) do
-    actor = get_actor(group, user, friends, non_friends)
+  defp insert_activity(:reblog, visibility, group, users, opts) do
+    actor = get_actor(group, users)
 
     with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(),
-         {:ok, _activity, _object} <- CommonAPI.repeat(activity_id, actor) do
+         {:ok, _activity} <- CommonAPI.repeat(activity_id, actor) do
       :ok
     else
       {:error, _} ->
-        insert_activity("reblog", visibility, group, user, friends, non_friends, opts)
+        insert_activity(:reblog, visibility, group, users, opts)
 
       nil ->
         Process.sleep(15)
-        insert_activity("reblog", visibility, group, user, friends, non_friends, opts)
+        insert_activity(:reblog, visibility, group, users, opts)
     end
   end
 
-  defp insert_activity("simple_thread", visibility, group, user, friends, non_friends, _opts)
-       when visibility in ["public", "unlisted", "private"] do
-    actor = get_actor(group, user, friends, non_friends)
-    tasks = get_reply_tasks(visibility, group)
-
-    {:ok, activity} = CommonAPI.post(user, %{status: "Simple status", visibility: visibility})
-
-    acc = {activity.id, ["@" <> actor.nickname, "reply to status"]}
-    insert_replies(tasks, visibility, user, friends, non_friends, acc)
-  end
-
-  defp insert_activity("simple_thread", "direct", group, user, friends, non_friends, _opts) do
-    actor = get_actor(group, user, friends, non_friends)
+  defp insert_activity(:simple_thread, "direct", group, users, _opts) do
+    actor = get_actor(group, users)
     tasks = get_reply_tasks("direct", group)
 
     list =
       case group do
-        "non_friends" ->
-          Enum.take(non_friends, 3)
+        :user ->
+          group = Enum.random(@friends_groups)
+          Enum.take(users[group], 3)
 
         _ ->
-          Enum.take(friends, 3)
+          Enum.take(users[group], 3)
       end
 
     data = Enum.map(list, &("@" <> &1.nickname))
@@ -339,40 +364,30 @@ defmodule Pleroma.LoadTesting.Activities do
         visibility: "direct"
       })
 
-    acc = {activity.id, ["@" <> user.nickname | data] ++ ["reply to status"]}
-    insert_direct_replies(tasks, user, list, acc)
+    acc = {activity.id, ["@" <> users[:user].nickname | data] ++ ["reply to status"]}
+    insert_direct_replies(tasks, users[:user], list, acc)
   end
 
-  defp insert_activity("remote", _, "user", _, _, _, _), do: :ok
-
-  defp insert_activity("remote", visibility, group, user, _friends, _non_friends, opts) do
-    remote_friends =
-      Users.get_users(user, limit: opts[:friends_used], local: :external, friends?: true)
-
-    remote_non_friends =
-      Users.get_users(user, limit: opts[:non_friends_used], local: :external, friends?: false)
-
-    actor = get_actor(group, user, remote_friends, remote_non_friends)
+  defp insert_activity(:simple_thread, visibility, group, users, _opts) do
+    actor = get_actor(group, users)
+    tasks = get_reply_tasks(visibility, group)
 
-    {act_data, obj_data} = prepare_activity_data(actor, visibility, user)
-    {activity_data, object_data} = other_data(actor)
+    {:ok, activity} =
+      CommonAPI.post(users[:user], %{status: "Simple status", visibility: visibility})
 
-    activity_data
-    |> Map.merge(act_data)
-    |> Map.put("object", Map.merge(object_data, obj_data))
-    |> Pleroma.Web.ActivityPub.ActivityPub.insert(false)
+    acc = {activity.id, ["@" <> actor.nickname, "reply to status"]}
+    insert_replies(tasks, visibility, users, acc)
   end
 
-  defp get_actor("user", user, _friends, _non_friends), do: user
-  defp get_actor("friends", _user, friends, _non_friends), do: Enum.random(friends)
-  defp get_actor("non_friends", _user, _friends, non_friends), do: Enum.random(non_friends)
+  defp get_actor(:user, %{user: user}), do: user
+  defp get_actor(group, users), do: Enum.random(users[group])
 
-  defp other_data(actor) do
+  defp other_data(actor, content) do
     %{host: host} = URI.parse(actor.ap_id)
     datetime = DateTime.utc_now()
-    context_id = "http://#{host}:4000/contexts/#{UUID.generate()}"
-    activity_id = "http://#{host}:4000/activities/#{UUID.generate()}"
-    object_id = "http://#{host}:4000/objects/#{UUID.generate()}"
+    context_id = "https://#{host}/contexts/#{UUID.generate()}"
+    activity_id = "https://#{host}/activities/#{UUID.generate()}"
+    object_id = "https://#{host}/objects/#{UUID.generate()}"
 
     activity_data = %{
       "actor" => actor.ap_id,
@@ -389,7 +404,7 @@ defmodule Pleroma.LoadTesting.Activities do
       "attributedTo" => actor.ap_id,
       "bcc" => [],
       "bto" => [],
-      "content" => "Remote post",
+      "content" => content,
       "context" => context_id,
       "conversation" => context_id,
       "emoji" => %{},
@@ -475,51 +490,65 @@ defmodule Pleroma.LoadTesting.Activities do
     {act_data, obj_data}
   end
 
-  defp get_reply_tasks("public", "user"), do: ~w(friend non_friend user)
-  defp get_reply_tasks("public", "friends"), do: ~w(non_friend user friend)
-  defp get_reply_tasks("public", "non_friends"), do: ~w(user friend non_friend)
+  defp get_reply_tasks("public", :user) do
+    [:friends_local, :friends_remote, :non_friends_local, :non_friends_remote, :user]
+  end
+
+  defp get_reply_tasks("public", group) when group in @friends_groups do
+    [:non_friends_local, :non_friends_remote, :user, :friends_local, :friends_remote]
+  end
 
-  defp get_reply_tasks(visibility, "user") when visibility in ["unlisted", "private"],
-    do: ~w(friend user friend)
+  defp get_reply_tasks("public", group) when group in @non_friends_groups do
+    [:user, :friends_local, :friends_remote, :non_friends_local, :non_friends_remote]
+  end
 
-  defp get_reply_tasks(visibility, "friends") when visibility in ["unlisted", "private"],
-    do: ~w(user friend user)
+  defp get_reply_tasks(visibility, :user) when visibility in ["unlisted", "private"] do
+    [:friends_local, :friends_remote, :user, :friends_local, :friends_remote]
+  end
 
-  defp get_reply_tasks(visibility, "non_friends") when visibility in ["unlisted", "private"],
-    do: []
+  defp get_reply_tasks(visibility, group)
+       when visibility in ["unlisted", "private"] and group in @friends_groups do
+    [:user, :friends_remote, :friends_local, :user]
+  end
 
-  defp get_reply_tasks("direct", "user"), do: ~w(friend user friend)
-  defp get_reply_tasks("direct", "friends"), do: ~w(user friend user)
-  defp get_reply_tasks("direct", "non_friends"), do: ~w(user non_friend user)
+  defp get_reply_tasks(visibility, group)
+       when visibility in ["unlisted", "private"] and
+              group in @non_friends_groups,
+       do: []
 
-  defp insert_replies(tasks, visibility, user, friends, non_friends, acc) do
-    Enum.reduce(tasks, acc, fn
-      "friend", {id, data} ->
-        friend = Enum.random(friends)
-        insert_reply(friend, data, id, visibility)
+  defp get_reply_tasks("direct", :user), do: [:friends_local, :user, :friends_remote]
 
-      "non_friend", {id, data} ->
-        non_friend = Enum.random(non_friends)
-        insert_reply(non_friend, data, id, visibility)
+  defp get_reply_tasks("direct", group) when group in @friends_groups,
+    do: [:user, group, :user]
 
-      "user", {id, data} ->
-        insert_reply(user, data, id, visibility)
+  defp get_reply_tasks("direct", group) when group in @non_friends_groups do
+    [:user, :non_friends_remote, :user, :non_friends_local]
+  end
+
+  defp insert_replies(tasks, visibility, users, acc) do
+    Enum.reduce(tasks, acc, fn
+      :user, {id, data} ->
+        insert_reply(users[:user], data, id, visibility)
+
+      group, {id, data} ->
+        replier = Enum.random(users[group])
+        insert_reply(replier, data, id, visibility)
     end)
   end
 
   defp insert_direct_replies(tasks, user, list, acc) do
     Enum.reduce(tasks, acc, fn
-      group, {id, data} when group in ["friend", "non_friend"] ->
+      :user, {id, data} ->
+        {reply_id, _} = insert_reply(user, List.delete(data, "@" <> user.nickname), id, "direct")
+        {reply_id, data}
+
+      _, {id, data} ->
         actor = Enum.random(list)
 
         {reply_id, _} =
           insert_reply(actor, List.delete(data, "@" <> actor.nickname), id, "direct")
 
         {reply_id, data}
-
-      "user", {id, data} ->
-        {reply_id, _} = insert_reply(user, List.delete(data, "@" <> user.nickname), id, "direct")
-        {reply_id, data}
     end)
   end
 
index 0de4924bcac30d141f001e728b4debc12b1fc737..15fd06c3d262a0189f7ed473025989799a817dd0 100644 (file)
@@ -36,6 +36,7 @@ defmodule Pleroma.LoadTesting.Fetcher do
     fetch_home_timeline(user)
     fetch_direct_timeline(user)
     fetch_public_timeline(user)
+    fetch_public_timeline(user, :with_blocks)
     fetch_public_timeline(user, :local)
     fetch_public_timeline(user, :tag)
     fetch_notifications(user)
@@ -51,12 +52,12 @@ defmodule Pleroma.LoadTesting.Fetcher do
 
   defp opts_for_home_timeline(user) do
     %{
-      "blocking_user" => user,
-      "count" => "20",
-      "muting_user" => user,
-      "type" => ["Create", "Announce"],
-      "user" => user,
-      "with_muted" => "true"
+      blocking_user: user,
+      count: "20",
+      muting_user: user,
+      type: ["Create", "Announce"],
+      user: user,
+      with_muted: true
     }
   end
 
@@ -69,17 +70,17 @@ defmodule Pleroma.LoadTesting.Fetcher do
       ActivityPub.fetch_activities(recipients, opts) |> Enum.reverse() |> List.last()
 
     second_page_last =
-      ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", first_page_last.id))
+      ActivityPub.fetch_activities(recipients, Map.put(opts, :max_id, first_page_last.id))
       |> Enum.reverse()
       |> List.last()
 
     third_page_last =
-      ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", second_page_last.id))
+      ActivityPub.fetch_activities(recipients, Map.put(opts, :max_id, second_page_last.id))
       |> Enum.reverse()
       |> List.last()
 
     forth_page_last =
-      ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", third_page_last.id))
+      ActivityPub.fetch_activities(recipients, Map.put(opts, :max_id, third_page_last.id))
       |> Enum.reverse()
       |> List.last()
 
@@ -89,19 +90,19 @@ defmodule Pleroma.LoadTesting.Fetcher do
       },
       inputs: %{
         "1 page" => opts,
-        "2 page" => Map.put(opts, "max_id", first_page_last.id),
-        "3 page" => Map.put(opts, "max_id", second_page_last.id),
-        "4 page" => Map.put(opts, "max_id", third_page_last.id),
-        "5 page" => Map.put(opts, "max_id", forth_page_last.id),
-        "1 page only media" => Map.put(opts, "only_media", "true"),
+        "2 page" => Map.put(opts, :max_id, first_page_last.id),
+        "3 page" => Map.put(opts, :max_id, second_page_last.id),
+        "4 page" => Map.put(opts, :max_id, third_page_last.id),
+        "5 page" => Map.put(opts, :max_id, forth_page_last.id),
+        "1 page only media" => Map.put(opts, :only_media, true),
         "2 page only media" =>
-          Map.put(opts, "max_id", first_page_last.id) |> Map.put("only_media", "true"),
+          Map.put(opts, :max_id, first_page_last.id) |> Map.put(:only_media, true),
         "3 page only media" =>
-          Map.put(opts, "max_id", second_page_last.id) |> Map.put("only_media", "true"),
+          Map.put(opts, :max_id, second_page_last.id) |> Map.put(:only_media, true),
         "4 page only media" =>
-          Map.put(opts, "max_id", third_page_last.id) |> Map.put("only_media", "true"),
+          Map.put(opts, :max_id, third_page_last.id) |> Map.put(:only_media, true),
         "5 page only media" =>
-          Map.put(opts, "max_id", forth_page_last.id) |> Map.put("only_media", "true")
+          Map.put(opts, :max_id, forth_page_last.id) |> Map.put(:only_media, true)
       },
       formatters: formatters()
     )
@@ -109,12 +110,12 @@ defmodule Pleroma.LoadTesting.Fetcher do
 
   defp opts_for_direct_timeline(user) do
     %{
-      :visibility => "direct",
-      "blocking_user" => user,
-      "count" => "20",
-      "type" => "Create",
-      "user" => user,
-      "with_muted" => "true"
+      visibility: "direct",
+      blocking_user: user,
+      count: "20",
+      type: "Create",
+      user: user,
+      with_muted: true
     }
   end
 
@@ -129,7 +130,7 @@ defmodule Pleroma.LoadTesting.Fetcher do
       |> Pagination.fetch_paginated(opts)
       |> List.last()
 
-    opts2 = Map.put(opts, "max_id", first_page_last.id)
+    opts2 = Map.put(opts, :max_id, first_page_last.id)
 
     second_page_last =
       recipients
@@ -137,7 +138,7 @@ defmodule Pleroma.LoadTesting.Fetcher do
       |> Pagination.fetch_paginated(opts2)
       |> List.last()
 
-    opts3 = Map.put(opts, "max_id", second_page_last.id)
+    opts3 = Map.put(opts, :max_id, second_page_last.id)
 
     third_page_last =
       recipients
@@ -145,7 +146,7 @@ defmodule Pleroma.LoadTesting.Fetcher do
       |> Pagination.fetch_paginated(opts3)
       |> List.last()
 
-    opts4 = Map.put(opts, "max_id", third_page_last.id)
+    opts4 = Map.put(opts, :max_id, third_page_last.id)
 
     forth_page_last =
       recipients
@@ -164,7 +165,7 @@ defmodule Pleroma.LoadTesting.Fetcher do
         "2 page" => opts2,
         "3 page" => opts3,
         "4 page" => opts4,
-        "5 page" => Map.put(opts4, "max_id", forth_page_last.id)
+        "5 page" => Map.put(opts4, :max_id, forth_page_last.id)
       },
       formatters: formatters()
     )
@@ -172,34 +173,34 @@ defmodule Pleroma.LoadTesting.Fetcher do
 
   defp opts_for_public_timeline(user) do
     %{
-      "type" => ["Create", "Announce"],
-      "local_only" => false,
-      "blocking_user" => user,
-      "muting_user" => user
+      type: ["Create", "Announce"],
+      local_only: false,
+      blocking_user: user,
+      muting_user: user
     }
   end
 
   defp opts_for_public_timeline(user, :local) do
     %{
-      "type" => ["Create", "Announce"],
-      "local_only" => true,
-      "blocking_user" => user,
-      "muting_user" => user
+      type: ["Create", "Announce"],
+      local_only: true,
+      blocking_user: user,
+      muting_user: user
     }
   end
 
   defp opts_for_public_timeline(user, :tag) do
     %{
-      "blocking_user" => user,
-      "count" => "20",
-      "local_only" => nil,
-      "muting_user" => user,
-      "tag" => ["tag"],
-      "tag_all" => [],
-      "tag_reject" => [],
-      "type" => "Create",
-      "user" => user,
-      "with_muted" => "true"
+      blocking_user: user,
+      count: "20",
+      local_only: nil,
+      muting_user: user,
+      tag: ["tag"],
+      tag_all: [],
+      tag_reject: [],
+      type: "Create",
+      user: user,
+      with_muted: true
     }
   end
 
@@ -222,24 +223,72 @@ defmodule Pleroma.LoadTesting.Fetcher do
   end
 
   defp fetch_public_timeline(user, :only_media) do
-    opts = opts_for_public_timeline(user) |> Map.put("only_media", "true")
+    opts = opts_for_public_timeline(user) |> Map.put(:only_media, true)
 
     fetch_public_timeline(opts, "public timeline only media")
   end
 
+  defp fetch_public_timeline(user, :with_blocks) do
+    opts = opts_for_public_timeline(user)
+
+    remote_non_friends = Agent.get(:non_friends_remote, & &1)
+
+    Benchee.run(%{
+      "public timeline without blocks" => fn ->
+        ActivityPub.fetch_public_activities(opts)
+      end
+    })
+
+    Enum.each(remote_non_friends, fn non_friend ->
+      {:ok, _} = User.block(user, non_friend)
+    end)
+
+    user = User.get_by_id(user.id)
+
+    opts = Map.put(opts, :blocking_user, user)
+
+    Benchee.run(%{
+      "public timeline with user block" => fn ->
+        ActivityPub.fetch_public_activities(opts)
+      end
+    })
+
+    domains =
+      Enum.reduce(remote_non_friends, [], fn non_friend, domains ->
+        {:ok, _user} = User.unblock(user, non_friend)
+        %{host: host} = URI.parse(non_friend.ap_id)
+        [host | domains]
+      end)
+
+    domains = Enum.uniq(domains)
+
+    Enum.each(domains, fn domain ->
+      {:ok, _} = User.block_domain(user, domain)
+    end)
+
+    user = User.get_by_id(user.id)
+    opts = Map.put(opts, :blocking_user, user)
+
+    Benchee.run(%{
+      "public timeline with domain block" => fn ->
+        ActivityPub.fetch_public_activities(opts)
+      end
+    })
+  end
+
   defp fetch_public_timeline(opts, title) when is_binary(title) do
     first_page_last = ActivityPub.fetch_public_activities(opts) |> List.last()
 
     second_page_last =
-      ActivityPub.fetch_public_activities(Map.put(opts, "max_id", first_page_last.id))
+      ActivityPub.fetch_public_activities(Map.put(opts, :max_id, first_page_last.id))
       |> List.last()
 
     third_page_last =
-      ActivityPub.fetch_public_activities(Map.put(opts, "max_id", second_page_last.id))
+      ActivityPub.fetch_public_activities(Map.put(opts, :max_id, second_page_last.id))
       |> List.last()
 
     forth_page_last =
-      ActivityPub.fetch_public_activities(Map.put(opts, "max_id", third_page_last.id))
+      ActivityPub.fetch_public_activities(Map.put(opts, :max_id, third_page_last.id))
       |> List.last()
 
     Benchee.run(
@@ -250,17 +299,17 @@ defmodule Pleroma.LoadTesting.Fetcher do
       },
       inputs: %{
         "1 page" => opts,
-        "2 page" => Map.put(opts, "max_id", first_page_last.id),
-        "3 page" => Map.put(opts, "max_id", second_page_last.id),
-        "4 page" => Map.put(opts, "max_id", third_page_last.id),
-        "5 page" => Map.put(opts, "max_id", forth_page_last.id)
+        "2 page" => Map.put(opts, :max_id, first_page_last.id),
+        "3 page" => Map.put(opts, :max_id, second_page_last.id),
+        "4 page" => Map.put(opts, :max_id, third_page_last.id),
+        "5 page" => Map.put(opts, :max_id, forth_page_last.id)
       },
       formatters: formatters()
     )
   end
 
   defp opts_for_notifications do
-    %{"count" => "20", "with_muted" => "true"}
+    %{count: "20", with_muted: true}
   end
 
   defp fetch_notifications(user) do
@@ -269,15 +318,15 @@ defmodule Pleroma.LoadTesting.Fetcher do
     first_page_last = MastodonAPI.get_notifications(user, opts) |> List.last()
 
     second_page_last =
-      MastodonAPI.get_notifications(user, Map.put(opts, "max_id", first_page_last.id))
+      MastodonAPI.get_notifications(user, Map.put(opts, :max_id, first_page_last.id))
       |> List.last()
 
     third_page_last =
-      MastodonAPI.get_notifications(user, Map.put(opts, "max_id", second_page_last.id))
+      MastodonAPI.get_notifications(user, Map.put(opts, :max_id, second_page_last.id))
       |> List.last()
 
     forth_page_last =
-      MastodonAPI.get_notifications(user, Map.put(opts, "max_id", third_page_last.id))
+      MastodonAPI.get_notifications(user, Map.put(opts, :max_id, third_page_last.id))
       |> List.last()
 
     Benchee.run(
@@ -288,10 +337,10 @@ defmodule Pleroma.LoadTesting.Fetcher do
       },
       inputs: %{
         "1 page" => opts,
-        "2 page" => Map.put(opts, "max_id", first_page_last.id),
-        "3 page" => Map.put(opts, "max_id", second_page_last.id),
-        "4 page" => Map.put(opts, "max_id", third_page_last.id),
-        "5 page" => Map.put(opts, "max_id", forth_page_last.id)
+        "2 page" => Map.put(opts, :max_id, first_page_last.id),
+        "3 page" => Map.put(opts, :max_id, second_page_last.id),
+        "4 page" => Map.put(opts, :max_id, third_page_last.id),
+        "5 page" => Map.put(opts, :max_id, forth_page_last.id)
       },
       formatters: formatters()
     )
@@ -301,13 +350,13 @@ defmodule Pleroma.LoadTesting.Fetcher do
     first_page_last = ActivityPub.fetch_favourites(user) |> List.last()
 
     second_page_last =
-      ActivityPub.fetch_favourites(user, %{"max_id" => first_page_last.id}) |> List.last()
+      ActivityPub.fetch_favourites(user, %{:max_id => first_page_last.id}) |> List.last()
 
     third_page_last =
-      ActivityPub.fetch_favourites(user, %{"max_id" => second_page_last.id}) |> List.last()
+      ActivityPub.fetch_favourites(user, %{:max_id => second_page_last.id}) |> List.last()
 
     forth_page_last =
-      ActivityPub.fetch_favourites(user, %{"max_id" => third_page_last.id}) |> List.last()
+      ActivityPub.fetch_favourites(user, %{:max_id => third_page_last.id}) |> List.last()
 
     Benchee.run(
       %{
@@ -317,10 +366,10 @@ defmodule Pleroma.LoadTesting.Fetcher do
       },
       inputs: %{
         "1 page" => %{},
-        "2 page" => %{"max_id" => first_page_last.id},
-        "3 page" => %{"max_id" => second_page_last.id},
-        "4 page" => %{"max_id" => third_page_last.id},
-        "5 page" => %{"max_id" => forth_page_last.id}
+        "2 page" => %{:max_id => first_page_last.id},
+        "3 page" => %{:max_id => second_page_last.id},
+        "4 page" => %{:max_id => third_page_last.id},
+        "5 page" => %{:max_id => forth_page_last.id}
       },
       formatters: formatters()
     )
@@ -328,8 +377,8 @@ defmodule Pleroma.LoadTesting.Fetcher do
 
   defp opts_for_long_thread(user) do
     %{
-      "blocking_user" => user,
-      "user" => user
+      blocking_user: user,
+      user: user
     }
   end
 
@@ -339,9 +388,9 @@ defmodule Pleroma.LoadTesting.Fetcher do
 
     opts = opts_for_long_thread(user)
 
-    private_input = {private.data["context"], Map.put(opts, "exclude_id", private.id)}
+    private_input = {private.data["context"], Map.put(opts, :exclude_id, private.id)}
 
-    public_input = {public.data["context"], Map.put(opts, "exclude_id", public.id)}
+    public_input = {public.data["context"], Map.put(opts, :exclude_id, public.id)}
 
     Benchee.run(
       %{
@@ -461,13 +510,13 @@ defmodule Pleroma.LoadTesting.Fetcher do
     public_context =
       ActivityPub.fetch_activities_for_context(
         public.data["context"],
-        Map.put(fetch_opts, "exclude_id", public.id)
+        Map.put(fetch_opts, :exclude_id, public.id)
       )
 
     private_context =
       ActivityPub.fetch_activities_for_context(
         private.data["context"],
-        Map.put(fetch_opts, "exclude_id", private.id)
+        Map.put(fetch_opts, :exclude_id, private.id)
       )
 
     Benchee.run(
@@ -498,14 +547,14 @@ defmodule Pleroma.LoadTesting.Fetcher do
         end,
         "Public timeline with reply filtering - following" => fn ->
           public_params
-          |> Map.put("reply_visibility", "following")
-          |> Map.put("reply_filtering_user", user)
+          |> Map.put(:reply_visibility, "following")
+          |> Map.put(:reply_filtering_user, user)
           |> ActivityPub.fetch_public_activities()
         end,
         "Public timeline with reply filtering - self" => fn ->
           public_params
-          |> Map.put("reply_visibility", "self")
-          |> Map.put("reply_filtering_user", user)
+          |> Map.put(:reply_visibility, "self")
+          |> Map.put(:reply_filtering_user, user)
           |> ActivityPub.fetch_public_activities()
         end
       },
@@ -524,16 +573,16 @@ defmodule Pleroma.LoadTesting.Fetcher do
         "Home timeline with reply filtering - following" => fn ->
           private_params =
             private_params
-            |> Map.put("reply_filtering_user", user)
-            |> Map.put("reply_visibility", "following")
+            |> Map.put(:reply_filtering_user, user)
+            |> Map.put(:reply_visibility, "following")
 
           ActivityPub.fetch_activities(recipients, private_params)
         end,
         "Home timeline with reply filtering - self" => fn ->
           private_params =
             private_params
-            |> Map.put("reply_filtering_user", user)
-            |> Map.put("reply_visibility", "self")
+            |> Map.put(:reply_filtering_user, user)
+            |> Map.put(:reply_visibility, "self")
 
           ActivityPub.fetch_activities(recipients, private_params)
         end
index e4d0b22ffd52669e4f1a99a2b2e9b2234c581f9c..6cf3958c14c6674c3c57b53478d8a0cce5d779fa 100644 (file)
@@ -27,7 +27,7 @@ defmodule Pleroma.LoadTesting.Users do
 
     make_friends(main_user, opts[:friends])
 
-    Repo.get(User, main_user.id)
+    User.get_by_id(main_user.id)
   end
 
   def generate_users(max) do
@@ -166,4 +166,24 @@ defmodule Pleroma.LoadTesting.Users do
     )
     |> Stream.run()
   end
+
+  @spec prepare_users(User.t(), keyword()) :: map()
+  def prepare_users(user, opts) do
+    friends_limit = opts[:friends_used]
+    non_friends_limit = opts[:non_friends_used]
+
+    %{
+      user: user,
+      friends_local: fetch_users(user, friends_limit, :local, true),
+      friends_remote: fetch_users(user, friends_limit, :external, true),
+      non_friends_local: fetch_users(user, non_friends_limit, :local, false),
+      non_friends_remote: fetch_users(user, non_friends_limit, :external, false)
+    }
+  end
+
+  defp fetch_users(user, limit, local, friends?) do
+    user
+    |> get_users(limit: limit, local: local, friends?: friends?)
+    |> Enum.shuffle()
+  end
 end
index 65740320272c8a01e1c6d98be317de37569b7158..c051335a5ab644ee738673a347877223d668a128 100644 (file)
@@ -5,7 +5,6 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
   import Ecto.Query
 
   alias Pleroma.Repo
-  alias Pleroma.Web.MastodonAPI.TimelineController
 
   def run(_args) do
     Mix.Pleroma.start_pleroma()
@@ -37,7 +36,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
     Benchee.run(
       %{
         "Hashtag fetching, any" => fn tags ->
-          TimelineController.hashtag_fetching(
+          hashtag_fetching(
             %{
               "any" => tags
             },
@@ -47,7 +46,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
         end,
         # Will always return zero results because no overlapping hashtags are generated.
         "Hashtag fetching, all" => fn tags ->
-          TimelineController.hashtag_fetching(
+          hashtag_fetching(
             %{
               "all" => tags
             },
@@ -67,7 +66,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
     Benchee.run(
       %{
         "Hashtag fetching" => fn tag ->
-          TimelineController.hashtag_fetching(
+          hashtag_fetching(
             %{
               "tag" => tag
             },
@@ -80,4 +79,35 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
       time: 5
     )
   end
+
+  defp hashtag_fetching(params, user, local_only) do
+    tags =
+      [params["tag"], params["any"]]
+      |> List.flatten()
+      |> Enum.uniq()
+      |> Enum.filter(& &1)
+      |> Enum.map(&String.downcase(&1))
+
+    tag_all =
+      params
+      |> Map.get("all", [])
+      |> Enum.map(&String.downcase(&1))
+
+    tag_reject =
+      params
+      |> Map.get("none", [])
+      |> Enum.map(&String.downcase(&1))
+
+    _activities =
+      params
+      |> Map.put(:type, "Create")
+      |> Map.put(:local_only, local_only)
+      |> Map.put(:blocking_user, user)
+      |> Map.put(:muting_user, user)
+      |> Map.put(:user, user)
+      |> Map.put(:tag, tags)
+      |> Map.put(:tag_all, tag_all)
+      |> Map.put(:tag_reject, tag_reject)
+      |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
+  end
 end
index 9940fcfe9c8e1fe90d0c1b13581b5f95cb2b7946..7a70164f3c2b0d7cb4d047d4db8c4d6a8d4531ed 100644 (file)
@@ -171,7 +171,8 @@ config :mime, :types, %{
   "application/ld+json" => ["activity+json"]
 }
 
-config :tesla, adapter: Tesla.Adapter.Gun
+config :tesla, adapter: Tesla.Adapter.Hackney
+
 # Configures http settings, upstream proxy etc.
 config :pleroma, :http,
   proxy_url: nil,
@@ -183,7 +184,7 @@ config :pleroma, :instance,
   name: "Pleroma",
   email: "example@example.com",
   notify_email: "noreply@example.com",
-  description: "A Pleroma instance, an alternative fediverse server",
+  description: "Pleroma: An efficient and flexible fediverse server",
   background_image: "/images/city.jpg",
   limit: 5_000,
   chat_limit: 5_000,
index 639c3224ddb7853b7c0d1cbe58e40e7c20bd792b..92816baf9cd6014db6f16ddb37a27410c56b1b42 100644 (file)
@@ -547,7 +547,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 
 ```json
 {
-  "totalReports" : 1,
+  "total" : 1,
   "reports": [
     {
       "account": {
@@ -768,7 +768,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
     - 400 Bad Request `"Invalid parameters"` when `status` is missing
   - On success: `204`, empty response
 
-## `POST /api/pleroma/admin/reports/:report_id/notes/:id`
+## `DELETE /api/pleroma/admin/reports/:report_id/notes/:id`
 
 ### Delete report note
 
index 7f98dc7b130539ea70050f7a46f851fc1263a360..ea751637ec8051d097851f0ed1a85654ba75f0eb 100644 (file)
@@ -42,6 +42,12 @@ Feel free to contact us to be added to this list!
 - Platforms: SailfishOS
 - Features: No Streaming
 
+### Husky
+- Source code: <https://git.mentality.rip/FWGS/Husky>
+- Contact: [@Husky@enigmatic.observer](https://enigmatic.observer/users/Husky)
+- Platforms: Android
+- Features: No Streaming, Emoji Reactions, Text Formatting, FE Stickers
+
 ### Nekonium
 - Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/)
 - Source: <https://gogs.gdgd.jp.net/lin/nekonium>
index 12d64c2fe2c982f2c09800bb9d5cb5871b5661bb..cd523cf7d9855476ec578679d4f1102f578b618d 100644 (file)
@@ -92,10 +92,10 @@ defmodule Pleroma.BBS.Handler do
 
     params =
       %{}
-      |> Map.put("type", ["Create"])
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> Map.put("user", user)
+      |> Map.put(:type, ["Create"])
+      |> Map.put(:blocking_user, user)
+      |> Map.put(:muting_user, user)
+      |> Map.put(:user, user)
 
     activities =
       [user.ap_id | Pleroma.User.following(user)]
index 06174f624a6d4d80a1c457a74f5e888122a5b516..13eeaa96b4762fbb91ced797cd93c7ad2dcf9255 100644 (file)
@@ -24,6 +24,6 @@ defmodule Pleroma.Constants do
 
   const(static_only_files,
     do:
-      ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
+      ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
   )
 end
index 51bb1bda94d43e85043d3c930896f4462565a2ca..ce7bd23961f2df83c6a2cb38faedf221b2f2d93a 100644 (file)
@@ -163,8 +163,8 @@ defmodule Pleroma.Conversation.Participation do
     |> Enum.map(fn participation ->
       activity_id =
         ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
-          "user" => user,
-          "blocking_user" => user
+          user: user,
+          blocking_user: user
         })
 
       %{
index 3a3082e728037e265bc56a55883f8df7f5e36fe9..093b1f4050d62a94f70c865bc27262f2cb70b6ca 100644 (file)
@@ -141,6 +141,12 @@ defmodule Pleroma.FollowingRelationship do
     |> where([r], r.state == ^:follow_accept)
   end
 
+  def outgoing_pending_follow_requests_query(%User{} = follower) do
+    __MODULE__
+    |> where([r], r.follower_id == ^follower.id)
+    |> where([r], r.state == ^:follow_pending)
+  end
+
   def following(%User{} = user) do
     following =
       following_query(user)
index 69d8c8fe018dfb399764dec2eee25ad16bc8e0ef..6d205a636bb9fbe1fec59b4e7ae2cfa31baf7af8 100644 (file)
@@ -17,14 +17,6 @@ defmodule Pleroma.Helpers.UriHelper do
     |> URI.to_string()
   end
 
-  def append_param_if_present(%{} = params, param_name, param_value) do
-    if param_value do
-      Map.put(params, param_name, param_value)
-    else
-      params
-    end
-  end
-
   def maybe_add_base("/" <> uri, base), do: Path.join([base, uri])
   def maybe_add_base(uri, _base), do: uri
 end
diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex
new file mode 100644 (file)
index 0000000..ab2e32e
--- /dev/null
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Maps do
+  def put_if_present(map, key, value, value_function \\ &{:ok, &1}) when is_map(map) do
+    with false <- is_nil(key),
+         false <- is_nil(value),
+         {:ok, new_value} <- value_function.(value) do
+      Map.put(map, key, new_value)
+    else
+      _ -> map
+    end
+  end
+end
index d43a96cd2ee4bcfef30d9570722c59e7fd730b89..1b99e44f9304210b811ecd9668097847be2c395e 100644 (file)
@@ -23,12 +23,12 @@ defmodule Pleroma.Pagination do
   @spec fetch_paginated(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]
   def fetch_paginated(query, params, type \\ :keyset, table_binding \\ nil)
 
-  def fetch_paginated(query, %{"total" => true} = params, :keyset, table_binding) do
+  def fetch_paginated(query, %{total: true} = params, :keyset, table_binding) do
     total = Repo.aggregate(query, :count, :id)
 
     %{
       total: total,
-      items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset, table_binding)
+      items: fetch_paginated(query, Map.drop(params, [:total]), :keyset, table_binding)
     }
   end
 
@@ -41,7 +41,7 @@ defmodule Pleroma.Pagination do
     |> enforce_order(options)
   end
 
-  def fetch_paginated(query, %{"total" => true} = params, :offset, table_binding) do
+  def fetch_paginated(query, %{total: true} = params, :offset, table_binding) do
     total =
       query
       |> Ecto.Query.exclude(:left_join)
@@ -49,7 +49,7 @@ defmodule Pleroma.Pagination do
 
     %{
       total: total,
-      items: fetch_paginated(query, Map.drop(params, ["total"]), :offset, table_binding)
+      items: fetch_paginated(query, Map.drop(params, [:total]), :offset, table_binding)
     }
   end
 
@@ -90,12 +90,6 @@ defmodule Pleroma.Pagination do
       skip_order: :boolean
     }
 
-    params =
-      Enum.reduce(params, %{}, fn
-        {key, _value}, acc when is_atom(key) -> Map.drop(acc, [key])
-        {key, value}, acc -> Map.put(acc, key, value)
-      end)
-
     changeset = cast({%{}, param_types}, params, Map.keys(param_types))
     changeset.changes
   end
index 72ee2d58e2951a615e247e574faf480c8e1180e2..c5c74d132038825cd10d4763e3ff7fd8123dfc4e 100644 (file)
@@ -1489,6 +1489,8 @@ defmodule Pleroma.User do
 
     delete_user_activities(user)
 
+    delete_outgoing_pending_follow_requests(user)
+
     delete_or_deactivate(user)
   end
 
@@ -1611,6 +1613,12 @@ defmodule Pleroma.User do
 
   defp delete_activity(_activity, _user), do: "Doing nothing"
 
+  defp delete_outgoing_pending_follow_requests(user) do
+    user
+    |> FollowingRelationship.outgoing_pending_follow_requests_query()
+    |> Repo.delete_all()
+  end
+
   def html_filter_policy(%User{no_rich_text: true}) do
     Pleroma.HTML.Scrubber.TwitterText
   end
index 293bbc0827b4fad317ad1facf2095ed6d10583be..66ffe909031637f284f66c2450a8701ffc4e8add 100644 (file)
@@ -45,7 +45,7 @@ defmodule Pleroma.User.Query do
             is_admin: boolean(),
             is_moderator: boolean(),
             super_users: boolean(),
-            exclude_service_users: boolean(),
+            invisible: boolean(),
             followers: User.t(),
             friends: User.t(),
             recipients_from_activity: [String.t()],
@@ -89,8 +89,8 @@ defmodule Pleroma.User.Query do
     where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
   end
 
-  defp compose_query({:exclude_service_users, _}, query) do
-    where(query, [u], not like(u.ap_id, "%/relay") and not like(u.ap_id, "%/internal/fetch"))
+  defp compose_query({:invisible, bool}, query) when is_boolean(bool) do
+    where(query, [u], u.invisible == ^bool)
   end
 
   defp compose_query({key, value}, query)
index 7f6b59cc625ffe96d3b7abe9040284062a0f1342..5b519033eac9232db801e71a0091fbf7ccfbaee8 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   alias Pleroma.Constants
   alias Pleroma.Conversation
   alias Pleroma.Conversation.Participation
+  alias Pleroma.Maps
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Object.Containment
@@ -20,7 +21,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.MRF
   alias Pleroma.Web.ActivityPub.Transmogrifier
-  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.Streamer
   alias Pleroma.Web.WebFinger
   alias Pleroma.Workers.BackgroundWorker
@@ -68,16 +68,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     {recipients, to, cc}
   end
 
-  defp check_actor_is_active(actor) do
-    if not is_nil(actor) do
-      with user <- User.get_cached_by_ap_id(actor),
-           false <- user.deactivated do
-        true
-      else
-        _e -> false
-      end
-    else
-      true
+  defp check_actor_is_active(nil), do: true
+
+  defp check_actor_is_active(actor) when is_binary(actor) do
+    case User.get_cached_by_ap_id(actor) do
+      %User{deactivated: deactivated} -> not deactivated
+      _ -> false
     end
   end
 
@@ -88,7 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp check_remote_limit(_), do: true
 
-  def increase_note_count_if_public(actor, object) do
+  defp increase_note_count_if_public(actor, object) do
     if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
   end
 
@@ -96,36 +92,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
   end
 
-  def increase_replies_count_if_reply(%{
-        "object" => %{"inReplyTo" => reply_ap_id} = object,
-        "type" => "Create"
-      }) do
+  defp increase_replies_count_if_reply(%{
+         "object" => %{"inReplyTo" => reply_ap_id} = object,
+         "type" => "Create"
+       }) do
     if is_public?(object) do
       Object.increase_replies_count(reply_ap_id)
     end
   end
 
-  def increase_replies_count_if_reply(_create_data), do: :noop
+  defp increase_replies_count_if_reply(_create_data), do: :noop
 
-  def decrease_replies_count_if_reply(%Object{
-        data: %{"inReplyTo" => reply_ap_id} = object
-      }) do
-    if is_public?(object) do
-      Object.decrease_replies_count(reply_ap_id)
-    end
-  end
-
-  def decrease_replies_count_if_reply(_object), do: :noop
-
-  def increase_poll_votes_if_vote(%{
-        "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
-        "type" => "Create",
-        "actor" => actor
-      }) do
+  defp increase_poll_votes_if_vote(%{
+         "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
+         "type" => "Create",
+         "actor" => actor
+       }) do
     Object.increase_vote_count(reply_ap_id, name, actor)
   end
 
-  def increase_poll_votes_if_vote(_create_data), do: :noop
+  defp increase_poll_votes_if_vote(_create_data), do: :noop
 
   @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
   def persist(object, meta) do
@@ -164,12 +150,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
         |> maybe_create_activity_expiration()
 
       # Splice in the child object if we have one.
-      activity =
-        if not is_nil(object) do
-          Map.put(activity, :object, object)
-        else
-          activity
-        end
+      activity = Maps.put_if_present(activity, :object, object)
 
       BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
 
@@ -214,8 +195,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp create_or_bump_conversation(activity, actor) do
     with {:ok, conversation} <- Conversation.create_or_bump_for(activity),
-         %User{} = user <- User.get_cached_by_ap_id(actor),
-         Participation.mark_as_read(user, conversation) do
+         %User{} = user <- User.get_cached_by_ap_id(actor) do
+      Participation.mark_as_read(user, conversation)
       {:ok, conversation}
     end
   end
@@ -237,13 +218,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   def stream_out_participations(%Object{data: %{"context" => context}}, user) do
-    with %Conversation{} = conversation <- Conversation.get_for_ap_id(context),
-         conversation = Repo.preload(conversation, :participations),
-         last_activity_id =
-           fetch_latest_activity_id_for_context(conversation.ap_id, %{
-             "user" => user,
-             "blocking_user" => user
-           }) do
+    with %Conversation{} = conversation <- Conversation.get_for_ap_id(context) do
+      conversation = Repo.preload(conversation, :participations)
+
+      last_activity_id =
+        fetch_latest_activity_id_for_context(conversation.ap_id, %{
+          user: user,
+          blocking_user: user
+        })
+
       if last_activity_id do
         stream_out_participations(conversation.participations)
       end
@@ -277,12 +260,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     published = params[:published]
     quick_insert? = Config.get([:env]) == :benchmark
 
-    with create_data <-
-           make_create_data(
-             %{to: to, actor: actor, published: published, context: context, object: object},
-             additional
-           ),
-         {:ok, activity} <- insert(create_data, local, fake),
+    create_data =
+      make_create_data(
+        %{to: to, actor: actor, published: published, context: context, object: object},
+        additional
+      )
+
+    with {:ok, activity} <- insert(create_data, local, fake),
          {:fake, false, activity} <- {:fake, fake, activity},
          _ <- increase_replies_count_if_reply(create_data),
          _ <- increase_poll_votes_if_vote(create_data),
@@ -310,12 +294,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     local = !(params[:local] == false)
     published = params[:published]
 
-    with listen_data <-
-           make_listen_data(
-             %{to: to, actor: actor, published: published, context: context, object: object},
-             additional
-           ),
-         {:ok, activity} <- insert(listen_data, local),
+    listen_data =
+      make_listen_data(
+        %{to: to, actor: actor, published: published, context: context, object: object},
+        additional
+      )
+
+    with {:ok, activity} <- insert(listen_data, local),
          _ <- notify_and_stream(activity),
          :ok <- maybe_federate(activity) do
       {:ok, activity}
@@ -333,14 +318,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   @spec accept_or_reject(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
-  def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
+  defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
     local = Map.get(params, :local, true)
     activity_id = Map.get(params, :activity_id, nil)
 
-    with data <-
-           %{"to" => to, "type" => type, "actor" => actor.ap_id, "object" => object}
-           |> Utils.maybe_put("id", activity_id),
-         {:ok, activity} <- insert(data, local),
+    data =
+      %{"to" => to, "type" => type, "actor" => actor.ap_id, "object" => object}
+      |> Maps.put_if_present("id", activity_id)
+
+    with {:ok, activity} <- insert(data, local),
          _ <- notify_and_stream(activity),
          :ok <- maybe_federate(activity) do
       {:ok, activity}
@@ -352,15 +338,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     local = !(params[:local] == false)
     activity_id = params[:activity_id]
 
-    with data <- %{
-           "to" => to,
-           "cc" => cc,
-           "type" => "Update",
-           "actor" => actor,
-           "object" => object
-         },
-         data <- Utils.maybe_put(data, "id", activity_id),
-         {:ok, activity} <- insert(data, local),
+    data =
+      %{
+        "to" => to,
+        "cc" => cc,
+        "type" => "Update",
+        "actor" => actor,
+        "object" => object
+      }
+      |> Maps.put_if_present("id", activity_id)
+
+    with {:ok, activity} <- insert(data, local),
          _ <- notify_and_stream(activity),
          :ok <- maybe_federate(activity) do
       {:ok, activity}
@@ -377,8 +365,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp do_follow(follower, followed, activity_id, local) do
-    with data <- make_follow_data(follower, followed, activity_id),
-         {:ok, activity} <- insert(data, local),
+    data = make_follow_data(follower, followed, activity_id)
+
+    with {:ok, activity} <- insert(data, local),
          _ <- notify_and_stream(activity),
          :ok <- maybe_federate(activity) do
       {:ok, activity}
@@ -422,13 +411,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp do_block(blocker, blocked, activity_id, local) do
     unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
 
-    if unfollow_blocked do
-      follow_activity = fetch_latest_follow(blocker, blocked)
-      if follow_activity, do: unfollow(blocker, blocked, nil, local)
+    if unfollow_blocked and fetch_latest_follow(blocker, blocked) do
+      unfollow(blocker, blocked, nil, local)
     end
 
-    with block_data <- make_block_data(blocker, blocked, activity_id),
-         {:ok, activity} <- insert(block_data, local),
+    block_data = make_block_data(blocker, blocked, activity_id)
+
+    with {:ok, activity} <- insert(block_data, local),
          _ <- notify_and_stream(activity),
          :ok <- maybe_federate(activity) do
       {:ok, activity}
@@ -507,8 +496,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     public = [Constants.as_public()]
 
     recipients =
-      if opts["user"],
-        do: [opts["user"].ap_id | User.following(opts["user"])] ++ public,
+      if opts[:user],
+        do: [opts[:user].ap_id | User.following(opts[:user])] ++ public,
         else: public
 
     from(activity in Activity)
@@ -516,7 +505,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> maybe_preload_bookmarks(opts)
     |> maybe_set_thread_muted_field(opts)
     |> restrict_blocked(opts)
-    |> restrict_recipients(recipients, opts["user"])
+    |> restrict_recipients(recipients, opts[:user])
     |> where(
       [activity],
       fragment(
@@ -543,7 +532,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
           FlakeId.Ecto.CompatType.t() | nil
   def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
     context
-    |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
+    |> fetch_activities_for_context_query(Map.merge(%{skip_preload: true}, opts))
     |> limit(1)
     |> select([a], a.id)
     |> Repo.one()
@@ -551,24 +540,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   @spec fetch_public_or_unlisted_activities(map(), Pagination.type()) :: [Activity.t()]
   def fetch_public_or_unlisted_activities(opts \\ %{}, pagination \\ :keyset) do
-    opts = Map.drop(opts, ["user"])
+    opts = Map.delete(opts, :user)
 
-    query = fetch_activities_query([Constants.as_public()], opts)
-
-    query =
-      if opts["restrict_unlisted"] do
-        restrict_unlisted(query)
-      else
-        query
-      end
-
-    Pagination.fetch_paginated(query, opts, pagination)
+    [Constants.as_public()]
+    |> fetch_activities_query(opts)
+    |> restrict_unlisted(opts)
+    |> Pagination.fetch_paginated(opts, pagination)
   end
 
   @spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
   def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do
     opts
-    |> Map.put("restrict_unlisted", true)
+    |> Map.put(:restrict_unlisted, true)
     |> fetch_public_or_unlisted_activities(pagination)
   end
 
@@ -577,20 +560,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp restrict_visibility(query, %{visibility: visibility})
        when is_list(visibility) do
     if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
-      query =
-        from(
-          a in query,
-          where:
-            fragment(
-              "activity_visibility(?, ?, ?) = ANY (?)",
-              a.actor,
-              a.recipients,
-              a.data,
-              ^visibility
-            )
-        )
-
-      query
+      from(
+        a in query,
+        where:
+          fragment(
+            "activity_visibility(?, ?, ?) = ANY (?)",
+            a.actor,
+            a.recipients,
+            a.data,
+            ^visibility
+          )
+      )
     else
       Logger.error("Could not restrict visibility to #{visibility}")
     end
@@ -612,7 +592,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_visibility(query, _visibility), do: query
 
-  defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
+  defp exclude_visibility(query, %{exclude_visibilities: visibility})
        when is_list(visibility) do
     if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
       from(
@@ -632,7 +612,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
+  defp exclude_visibility(query, %{exclude_visibilities: visibility})
        when visibility in @valid_visibilities do
     from(
       a in query,
@@ -647,7 +627,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     )
   end
 
-  defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
+  defp exclude_visibility(query, %{exclude_visibilities: visibility})
        when visibility not in [nil | @valid_visibilities] do
     Logger.error("Could not exclude visibility to #{visibility}")
     query
@@ -658,14 +638,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
     do: query
 
-  defp restrict_thread_visibility(
-         query,
-         %{"user" => %User{skip_thread_containment: true}},
-         _
-       ),
-       do: query
+  defp restrict_thread_visibility(query, %{user: %User{skip_thread_containment: true}}, _),
+    do: query
 
-  defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
+  defp restrict_thread_visibility(query, %{user: %User{ap_id: ap_id}}, _) do
     from(
       a in query,
       where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
@@ -677,87 +653,79 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   def fetch_user_abstract_activities(user, reading_user, params \\ %{}) do
     params =
       params
-      |> Map.put("user", reading_user)
-      |> Map.put("actor_id", user.ap_id)
-
-    recipients =
-      user_activities_recipients(%{
-        "godmode" => params["godmode"],
-        "reading_user" => reading_user
-      })
+      |> Map.put(:user, reading_user)
+      |> Map.put(:actor_id, user.ap_id)
 
-    fetch_activities(recipients, params)
+    %{
+      godmode: params[:godmode],
+      reading_user: reading_user
+    }
+    |> user_activities_recipients()
+    |> fetch_activities(params)
     |> Enum.reverse()
   end
 
   def fetch_user_activities(user, reading_user, params \\ %{}) do
     params =
       params
-      |> Map.put("type", ["Create", "Announce"])
-      |> Map.put("user", reading_user)
-      |> Map.put("actor_id", user.ap_id)
-      |> Map.put("pinned_activity_ids", user.pinned_activities)
+      |> Map.put(:type, ["Create", "Announce"])
+      |> Map.put(:user, reading_user)
+      |> Map.put(:actor_id, user.ap_id)
+      |> Map.put(:pinned_activity_ids, user.pinned_activities)
 
     params =
       if User.blocks?(reading_user, user) do
         params
       else
         params
-        |> Map.put("blocking_user", reading_user)
-        |> Map.put("muting_user", reading_user)
+        |> Map.put(:blocking_user, reading_user)
+        |> Map.put(:muting_user, reading_user)
       end
 
-    recipients =
-      user_activities_recipients(%{
-        "godmode" => params["godmode"],
-        "reading_user" => reading_user
-      })
-
-    fetch_activities(recipients, params)
+    %{
+      godmode: params[:godmode],
+      reading_user: reading_user
+    }
+    |> user_activities_recipients()
+    |> fetch_activities(params)
     |> Enum.reverse()
   end
 
   def fetch_statuses(reading_user, params) do
-    params =
-      params
-      |> Map.put("type", ["Create", "Announce"])
+    params = Map.put(params, :type, ["Create", "Announce"])
 
-    recipients =
-      user_activities_recipients(%{
-        "godmode" => params["godmode"],
-        "reading_user" => reading_user
-      })
-
-    fetch_activities(recipients, params, :offset)
+    %{
+      godmode: params[:godmode],
+      reading_user: reading_user
+    }
+    |> user_activities_recipients()
+    |> fetch_activities(params, :offset)
     |> Enum.reverse()
   end
 
-  defp user_activities_recipients(%{"godmode" => true}) do
-    []
-  end
+  defp user_activities_recipients(%{godmode: true}), do: []
 
-  defp user_activities_recipients(%{"reading_user" => reading_user}) do
+  defp user_activities_recipients(%{reading_user: reading_user}) do
     if reading_user do
-      [Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)]
+      [Constants.as_public()reading_user.ap_id | User.following(reading_user)]
     else
       [Constants.as_public()]
     end
   end
 
-  defp restrict_since(query, %{"since_id" => ""}), do: query
+  defp restrict_since(query, %{since_id: ""}), do: query
 
-  defp restrict_since(query, %{"since_id" => since_id}) do
+  defp restrict_since(query, %{since_id: since_id}) do
     from(activity in query, where: activity.id > ^since_id)
   end
 
   defp restrict_since(query, _), do: query
 
-  defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do
+  defp restrict_tag_reject(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
     raise "Can't use the child object without preloading!"
   end
 
-  defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
-       when is_list(tag_reject) and tag_reject != [] do
+  defp restrict_tag_reject(query, %{tag_reject: [_ | _] = tag_reject}) do
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
@@ -766,12 +734,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_tag_reject(query, _), do: query
 
-  defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do
+  defp restrict_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
     raise "Can't use the child object without preloading!"
   end
 
-  defp restrict_tag_all(query, %{"tag_all" => tag_all})
-       when is_list(tag_all) and tag_all != [] do
+  defp restrict_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
@@ -780,18 +747,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_tag_all(query, _), do: query
 
-  defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do
+  defp restrict_tag(_query, %{tag: _tag, skip_preload: true}) do
     raise "Can't use the child object without preloading!"
   end
 
-  defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
+  defp restrict_tag(query, %{tag: tag}) when is_list(tag) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
     )
   end
 
-  defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
+  defp restrict_tag(query, %{tag: tag}) when is_binary(tag) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
@@ -814,35 +781,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     )
   end
 
-  defp restrict_local(query, %{"local_only" => true}) do
+  defp restrict_local(query, %{local_only: true}) do
     from(activity in query, where: activity.local == true)
   end
 
   defp restrict_local(query, _), do: query
 
-  defp restrict_actor(query, %{"actor_id" => actor_id}) do
+  defp restrict_actor(query, %{actor_id: actor_id}) do
     from(activity in query, where: activity.actor == ^actor_id)
   end
 
   defp restrict_actor(query, _), do: query
 
-  defp restrict_type(query, %{"type" => type}) when is_binary(type) do
+  defp restrict_type(query, %{type: type}) when is_binary(type) do
     from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
   end
 
-  defp restrict_type(query, %{"type" => type}) do
+  defp restrict_type(query, %{type: type}) do
     from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
   end
 
   defp restrict_type(query, _), do: query
 
-  defp restrict_state(query, %{"state" => state}) do
+  defp restrict_state(query, %{state: state}) do
     from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
   end
 
   defp restrict_state(query, _), do: query
 
-  defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
+  defp restrict_favorited_by(query, %{favorited_by: ap_id}) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
@@ -851,11 +818,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_favorited_by(query, _), do: query
 
-  defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do
+  defp restrict_media(_query, %{only_media: _val, skip_preload: true}) do
     raise "Can't use the child object without preloading!"
   end
 
-  defp restrict_media(query, %{"only_media" => val}) when val in [true, "true", "1"] do
+  defp restrict_media(query, %{only_media: true}) do
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
@@ -864,7 +831,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_media(query, _), do: query
 
-  defp restrict_replies(query, %{"exclude_replies" => val}) when val in [true, "true", "1"] do
+  defp restrict_replies(query, %{exclude_replies: true}) do
     from(
       [_activity, object] in query,
       where: fragment("?->>'inReplyTo' is null", object.data)
@@ -872,8 +839,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp restrict_replies(query, %{
-         "reply_filtering_user" => user,
-         "reply_visibility" => "self"
+         reply_filtering_user: user,
+         reply_visibility: "self"
        }) do
     from(
       [activity, object] in query,
@@ -888,8 +855,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp restrict_replies(query, %{
-         "reply_filtering_user" => user,
-         "reply_visibility" => "following"
+         reply_filtering_user: user,
+         reply_visibility: "following"
        }) do
     from(
       [activity, object] in query,
@@ -908,16 +875,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_replies(query, _), do: query
 
-  defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val in [true, "true", "1"] do
+  defp restrict_reblogs(query, %{exclude_reblogs: true}) do
     from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
   end
 
   defp restrict_reblogs(query, _), do: query
 
-  defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
+  defp restrict_muted(query, %{with_muted: true}), do: query
 
-  defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
-    mutes = opts["muted_users_ap_ids"] || User.muted_users_ap_ids(user)
+  defp restrict_muted(query, %{muting_user: %User{} = user} = opts) do
+    mutes = opts[:muted_users_ap_ids] || User.muted_users_ap_ids(user)
 
     query =
       from([activity] in query,
@@ -925,7 +892,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
         where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
       )
 
-    unless opts["skip_preload"] do
+    unless opts[:skip_preload] do
       from([thread_mute: tm] in query, where: is_nil(tm.user_id))
     else
       query
@@ -934,8 +901,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_muted(query, _), do: query
 
-  defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do
-    blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user)
+  defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
+    blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
     domain_blocks = user.domain_blocks || []
 
     following_ap_ids = User.get_friends_ap_ids(user)
@@ -947,6 +914,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       [activity, object: o] in query,
       where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
       where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
+      where:
+        fragment(
+          "recipients_contain_blocked_domains(?, ?) = false",
+          activity.recipients,
+          ^domain_blocks
+        ),
       where:
         fragment(
           "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
@@ -975,7 +948,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_blocked(query, _), do: query
 
-  defp restrict_unlisted(query) do
+  defp restrict_unlisted(query, %{restrict_unlisted: true}) do
     from(
       activity in query,
       where:
@@ -987,19 +960,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     )
   end
 
-  # TODO: when all endpoints migrated to OpenAPI compare `pinned` with `true` (boolean) only,
-  # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2'
-  # and `restrict_muted/2`
+  defp restrict_unlisted(query, _), do: query
 
-  defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids})
-       when pinned in [true, "true", "1"] do
+  defp restrict_pinned(query, %{pinned: true, pinned_activity_ids: ids}) do
     from(activity in query, where: activity.id in ^ids)
   end
 
   defp restrict_pinned(query, _), do: query
 
-  defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user} = opts) do
-    muted_reblogs = opts["reblog_muted_users_ap_ids"] || User.reblog_muted_users_ap_ids(user)
+  defp restrict_muted_reblogs(query, %{muting_user: %User{} = user} = opts) do
+    muted_reblogs = opts[:reblog_muted_users_ap_ids] || User.reblog_muted_users_ap_ids(user)
 
     from(
       activity in query,
@@ -1015,7 +985,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_muted_reblogs(query, _), do: query
 
-  defp restrict_instance(query, %{"instance" => instance}) do
+  defp restrict_instance(query, %{instance: instance}) do
     users =
       from(
         u in User,
@@ -1029,7 +999,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_instance(query, _), do: query
 
-  defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
+  defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
 
   defp exclude_poll_votes(query, _) do
     if has_named_binding?(query, :object) do
@@ -1041,38 +1011,49 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
+  defp exclude_invisible_actors(query, %{invisible_actors: true}), do: query
+
+  defp exclude_invisible_actors(query, _opts) do
+    invisible_ap_ids =
+      User.Query.build(%{invisible: true, select: [:ap_id]})
+      |> Repo.all()
+      |> Enum.map(fn %{ap_id: ap_id} -> ap_id end)
+
+    from([activity] in query, where: activity.actor not in ^invisible_ap_ids)
+  end
+
+  defp exclude_id(query, %{exclude_id: id}) when is_binary(id) do
     from(activity in query, where: activity.id != ^id)
   end
 
   defp exclude_id(query, _), do: query
 
-  defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
+  defp maybe_preload_objects(query, %{skip_preload: true}), do: query
 
   defp maybe_preload_objects(query, _) do
     query
     |> Activity.with_preloaded_object()
   end
 
-  defp maybe_preload_bookmarks(query, %{"skip_preload" => true}), do: query
+  defp maybe_preload_bookmarks(query, %{skip_preload: true}), do: query
 
   defp maybe_preload_bookmarks(query, opts) do
     query
-    |> Activity.with_preloaded_bookmark(opts["user"])
+    |> Activity.with_preloaded_bookmark(opts[:user])
   end
 
-  defp maybe_preload_report_notes(query, %{"preload_report_notes" => true}) do
+  defp maybe_preload_report_notes(query, %{preload_report_notes: true}) do
     query
     |> Activity.with_preloaded_report_notes()
   end
 
   defp maybe_preload_report_notes(query, _), do: query
 
-  defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
+  defp maybe_set_thread_muted_field(query, %{skip_preload: true}), do: query
 
   defp maybe_set_thread_muted_field(query, opts) do
     query
-    |> Activity.with_set_thread_muted_field(opts["muting_user"] || opts["user"])
+    |> Activity.with_set_thread_muted_field(opts[:muting_user] || opts[:user])
   end
 
   defp maybe_order(query, %{order: :desc}) do
@@ -1088,24 +1069,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp maybe_order(query, _), do: query
 
   defp fetch_activities_query_ap_ids_ops(opts) do
-    source_user = opts["muting_user"]
+    source_user = opts[:muting_user]
     ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
 
     ap_id_relationships =
-      ap_id_relationships ++
-        if opts["blocking_user"] && opts["blocking_user"] == source_user do
-          [:block]
-        else
-          []
-        end
+      if opts[:blocking_user] && opts[:blocking_user] == source_user do
+        [:block | ap_id_relationships]
+      else
+        ap_id_relationships
+      end
 
     preloaded_ap_ids = User.outgoing_relationships_ap_ids(source_user, ap_id_relationships)
 
-    restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
-    restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
+    restrict_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
+    restrict_muted_opts = Map.merge(%{muted_users_ap_ids: preloaded_ap_ids[:mute]}, opts)
 
     restrict_muted_reblogs_opts =
-      Map.merge(%{"reblog_muted_users_ap_ids" => preloaded_ap_ids[:reblog_mute]}, opts)
+      Map.merge(%{reblog_muted_users_ap_ids: preloaded_ap_ids[:reblog_mute]}, opts)
 
     {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts}
   end
@@ -1124,7 +1104,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> maybe_preload_report_notes(opts)
     |> maybe_set_thread_muted_field(opts)
     |> maybe_order(opts)
-    |> restrict_recipients(recipients, opts["user"])
+    |> restrict_recipients(recipients, opts[:user])
     |> restrict_replies(opts)
     |> restrict_tag(opts)
     |> restrict_tag_reject(opts)
@@ -1146,16 +1126,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_instance(opts)
     |> Activity.restrict_deactivated_users()
     |> exclude_poll_votes(opts)
+    |> exclude_invisible_actors(opts)
     |> exclude_visibility(opts)
   end
 
   def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
-    list_memberships = Pleroma.List.memberships(opts["user"])
+    list_memberships = Pleroma.List.memberships(opts[:user])
 
     fetch_activities_query(recipients ++ list_memberships, opts)
     |> Pagination.fetch_paginated(opts, pagination)
     |> Enum.reverse()
-    |> maybe_update_cc(list_memberships, opts["user"])
+    |> maybe_update_cc(list_memberships, opts[:user])
   end
 
   @doc """
@@ -1171,16 +1152,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> select([_like, object, activity], %{activity | object: object})
     |> order_by([like, _, _], desc_nulls_last: like.id)
     |> Pagination.fetch_paginated(
-      Map.merge(params, %{"skip_order" => true}),
+      Map.merge(params, %{skip_order: true}),
       pagination,
       :object_activity
     )
   end
 
-  defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
-       when is_list(list_memberships) and length(list_memberships) > 0 do
+  defp maybe_update_cc(activities, [_ | _] = list_memberships, %User{ap_id: user_ap_id}) do
     Enum.map(activities, fn
-      %{data: %{"bcc" => bcc}} = activity when is_list(bcc) and length(bcc) > 0 ->
+      %{data: %{"bcc" => [_ | _] = bcc}} = activity ->
         if Enum.any?(bcc, &(&1 in list_memberships)) do
           update_in(activity.data["cc"], &[user_ap_id | &1])
         else
@@ -1194,7 +1174,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp maybe_update_cc(activities, _, _), do: activities
 
-  def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
+  defp fetch_activities_bounded_query(query, recipients, recipients_with_public) do
     from(activity in query,
       where:
         fragment("? && ?", activity.recipients, ^recipients) or
@@ -1218,12 +1198,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()}
   def upload(file, opts \\ []) do
     with {:ok, data} <- Upload.store(file, opts) do
-      obj_data =
-        if opts[:actor] do
-          Map.put(data, "actor", opts[:actor])
-        else
-          data
-        end
+      obj_data = Maps.put_if_present(data, "actor", opts[:actor])
 
       Repo.insert(%Object{data: obj_data})
     end
@@ -1269,8 +1244,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
         %{"type" => "Emoji"} -> true
         _ -> false
       end)
-      |> Enum.reduce(%{}, fn %{"icon" => %{"url" => url}, "name" => name}, acc ->
-        Map.put(acc, String.trim(name, ":"), url)
+      |> Map.new(fn %{"icon" => %{"url" => url}, "name" => name} ->
+        {String.trim(name, ":"), url}
       end)
 
     locked = data["manuallyApprovesFollowers"] || false
@@ -1316,18 +1291,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     }
 
     # nickname can be nil because of virtual actors
-    user_data =
-      if data["preferredUsername"] do
-        Map.put(
-          user_data,
-          :nickname,
-          "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
-        )
-      else
-        Map.put(user_data, :nickname, nil)
-      end
-
-    {:ok, user_data}
+    if data["preferredUsername"] do
+      Map.put(
+        user_data,
+        :nickname,
+        "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
+      )
+    else
+      Map.put(user_data, :nickname, nil)
+    end
   end
 
   def fetch_follow_information_for_user(user) do
@@ -1402,9 +1374,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp collection_private(_data), do: {:ok, true}
 
   def user_data_from_user_object(data) do
-    with {:ok, data} <- MRF.filter(data),
-         {:ok, data} <- object_to_user_data(data) do
-      {:ok, data}
+    with {:ok, data} <- MRF.filter(data) do
+      {:ok, object_to_user_data(data)}
     else
       e -> {:error, e}
     end
@@ -1412,15 +1383,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   def fetch_and_prepare_user_from_ap_id(ap_id) do
     with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
-         {:ok, data} <- user_data_from_user_object(data),
-         data <- maybe_update_follow_information(data) do
-      {:ok, data}
+         {:ok, data} <- user_data_from_user_object(data) do
+      {:ok, maybe_update_follow_information(data)}
     else
-      {:error, "Object has been deleted"} = e ->
+      {:error, "Object has been deleted" = e} ->
         Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
         {:error, e}
 
-      e ->
+      {:error, e} ->
         Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
         {:error, e}
     end
@@ -1443,8 +1413,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
           |> Repo.insert()
           |> User.set_cache()
         end
-      else
-        e -> {:error, e}
       end
     end
   end
@@ -1458,7 +1426,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   # filter out broken threads
-  def contain_broken_threads(%Activity{} = activity, %User{} = user) do
+  defp contain_broken_threads(%Activity{} = activity, %User{} = user) do
     entire_thread_visible_for_user?(activity, user)
   end
 
@@ -1469,7 +1437,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   def fetch_direct_messages_query do
     Activity
-    |> restrict_type(%{"type" => "Create"})
+    |> restrict_type(%{type: "Create"})
     |> restrict_visibility(%{visibility: "direct"})
     |> order_by([activity], asc: activity.id)
   end
index 28727d619ea3d9180ecb811cf4ca6f0db2186103..f0b5c6e935eca254b6238f3d8fb44be2bd303ac2 100644 (file)
@@ -21,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
   alias Pleroma.Web.ActivityPub.UserView
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
+  alias Pleroma.Web.ControllerHelper
   alias Pleroma.Web.Endpoint
   alias Pleroma.Web.FederatingPlug
   alias Pleroma.Web.Federator
@@ -230,27 +231,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
       when page? in [true, "true"] do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
          {:ok, user} <- User.ensure_keys_present(user) do
-      activities =
-        if params["max_id"] do
-          ActivityPub.fetch_user_activities(user, for_user, %{
-            "max_id" => params["max_id"],
-            # This is a hack because postgres generates inefficient queries when filtering by
-            # 'Answer', poll votes will be hidden by the visibility filter in this case anyway
-            "include_poll_votes" => true,
-            "limit" => 10
-          })
-        else
-          ActivityPub.fetch_user_activities(user, for_user, %{
-            "limit" => 10,
-            "include_poll_votes" => true
-          })
-        end
+      # "include_poll_votes" is a hack because postgres generates inefficient
+      # queries when filtering by 'Answer', poll votes will be hidden by the
+      # visibility filter in this case anyway
+      params =
+        params
+        |> Map.drop(["nickname", "page"])
+        |> Map.put("include_poll_votes", true)
+        |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
+
+      activities = ActivityPub.fetch_user_activities(user, for_user, params)
 
       conn
       |> put_resp_content_type("application/activity+json")
       |> put_view(UserView)
       |> render("activity_collection_page.json", %{
         activities: activities,
+        pagination: ControllerHelper.get_pagination_fields(conn, activities),
         iri: "#{user.ap_id}/outbox"
       })
     end
@@ -353,21 +350,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
         %{"nickname" => nickname, "page" => page?} = params
       )
       when page? in [true, "true"] do
+    params =
+      params
+      |> Map.drop(["nickname", "page"])
+      |> Map.put("blocking_user", user)
+      |> Map.put("user", user)
+      |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
+
     activities =
-      if params["max_id"] do
-        ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{
-          "max_id" => params["max_id"],
-          "limit" => 10
-        })
-      else
-        ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{"limit" => 10})
-      end
+      [user.ap_id | User.following(user)]
+      |> ActivityPub.fetch_activities(params)
+      |> Enum.reverse()
 
     conn
     |> put_resp_content_type("application/activity+json")
     |> put_view(UserView)
     |> render("activity_collection_page.json", %{
       activities: activities,
+      pagination: ControllerHelper.get_pagination_fields(conn, activities),
       iri: "#{user.ap_id}/inbox"
     })
   end
index 8443c284c11a8584d9d376c1e80d5a2e991ad7f6..fda1c71df1e13244e329466903db3fb506b22781 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Activity
   alias Pleroma.EarmarkRenderer
   alias Pleroma.FollowingRelationship
+  alias Pleroma.Maps
   alias Pleroma.Object
   alias Pleroma.Object.Containment
   alias Pleroma.Repo
@@ -208,12 +209,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("conversation", context)
   end
 
-  defp add_if_present(map, _key, nil), do: map
-
-  defp add_if_present(map, key, value) do
-    Map.put(map, key, value)
-  end
-
   def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
     attachments =
       Enum.map(attachment, fn data ->
@@ -241,13 +236,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
         attachment_url =
           %{"href" => href}
-          |> add_if_present("mediaType", media_type)
-          |> add_if_present("type", Map.get(url || %{}, "type"))
+          |> Maps.put_if_present("mediaType", media_type)
+          |> Maps.put_if_present("type", Map.get(url || %{}, "type"))
 
         %{"url" => [attachment_url]}
-        |> add_if_present("mediaType", media_type)
-        |> add_if_present("type", data["type"])
-        |> add_if_present("name", data["name"])
+        |> Maps.put_if_present("mediaType", media_type)
+        |> Maps.put_if_present("type", data["type"])
+        |> Maps.put_if_present("name", data["name"])
       end)
 
     Map.put(object, "attachment", attachments)
index f2375bcc4f7d1119cf923361b58fd920bfdab4f7..dfae602dfea61a6c01496f54c51fe289cb40ce05 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   alias Ecto.UUID
   alias Pleroma.Activity
   alias Pleroma.Config
+  alias Pleroma.Maps
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Repo
@@ -244,7 +245,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   Inserts a full object if it is contained in an activity.
   """
   def insert_full_object(%{"object" => %{"type" => type} = object_data} = map)
-      when is_map(object_data) and type in @supported_object_types do
+      when type in @supported_object_types do
     with {:ok, object} <- Object.create(object_data) do
       map = Map.put(map, "object", object.data["id"])
 
@@ -307,7 +308,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "cc" => cc,
       "context" => object.data["context"]
     }
-    |> maybe_put("id", activity_id)
+    |> Maps.put_if_present("id", activity_id)
   end
 
   def make_emoji_reaction_data(user, object, emoji, activity_id) do
@@ -477,7 +478,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "object" => followed_id,
       "state" => "pending"
     }
-    |> maybe_put("id", activity_id)
+    |> Maps.put_if_present("id", activity_id)
   end
 
   def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
@@ -546,7 +547,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "cc" => [],
       "context" => object.data["context"]
     }
-    |> maybe_put("id", activity_id)
+    |> Maps.put_if_present("id", activity_id)
   end
 
   def make_announce_data(
@@ -563,7 +564,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "cc" => [Pleroma.Constants.as_public()],
       "context" => object.data["context"]
     }
-    |> maybe_put("id", activity_id)
+    |> Maps.put_if_present("id", activity_id)
   end
 
   def make_undo_data(
@@ -582,7 +583,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "cc" => [Pleroma.Constants.as_public()],
       "context" => context
     }
-    |> maybe_put("id", activity_id)
+    |> Maps.put_if_present("id", activity_id)
   end
 
   @spec add_announce_to_object(Activity.t(), Object.t()) ::
@@ -627,7 +628,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "to" => [followed.ap_id],
       "object" => follow_activity.data
     }
-    |> maybe_put("id", activity_id)
+    |> Maps.put_if_present("id", activity_id)
   end
 
   #### Block-related helpers
@@ -650,7 +651,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "to" => [blocked.ap_id],
       "object" => blocked.ap_id
     }
-    |> maybe_put("id", activity_id)
+    |> Maps.put_if_present("id", activity_id)
   end
 
   #### Create-related helpers
@@ -740,12 +741,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   def get_reports(params, page, page_size) do
     params =
       params
-      |> Map.put("type", "Flag")
-      |> Map.put("skip_preload", true)
-      |> Map.put("preload_report_notes", true)
-      |> Map.put("total", true)
-      |> Map.put("limit", page_size)
-      |> Map.put("offset", (page - 1) * page_size)
+      |> Map.put(:type, "Flag")
+      |> Map.put(:skip_preload, true)
+      |> Map.put(:preload_report_notes, true)
+      |> Map.put(:total, true)
+      |> Map.put(:limit, page_size)
+      |> Map.put(:offset, (page - 1) * page_size)
 
     ActivityPub.fetch_activities([], params, :offset)
   end
@@ -870,7 +871,4 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     |> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data))
     |> Repo.all()
   end
-
-  def maybe_put(map, _key, nil), do: map
-  def maybe_put(map, key, value), do: Map.put(map, key, value)
 end
index 34590b16d8f1950e5935dfa3b4efbcdc6ea80ca1..4a02b09a17dd3f731edf5d74daf62b8bcea839a6 100644 (file)
@@ -213,34 +213,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     |> Map.merge(Utils.make_json_ld_header())
   end
 
-  def render("activity_collection_page.json", %{activities: activities, iri: iri}) do
-    # this is sorted chronologically, so first activity is the newest (max)
-    {max_id, min_id, collection} =
-      if length(activities) > 0 do
-        {
-          Enum.at(activities, 0).id,
-          Enum.at(Enum.reverse(activities), 0).id,
-          Enum.map(activities, fn act ->
-            {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
-            data
-          end)
-        }
-      else
-        {
-          0,
-          0,
-          []
-        }
-      end
+  def render("activity_collection_page.json", %{
+        activities: activities,
+        iri: iri,
+        pagination: pagination
+      }) do
+    collection =
+      Enum.map(activities, fn activity ->
+        {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+        data
+      end)
 
     %{
-      "id" => "#{iri}?max_id=#{max_id}&page=true",
       "type" => "OrderedCollectionPage",
       "partOf" => iri,
-      "orderedItems" => collection,
-      "next" => "#{iri}?max_id=#{min_id}&page=true"
+      "orderedItems" => collection
     }
     |> Map.merge(Utils.make_json_ld_header())
+    |> Map.merge(pagination)
   end
 
   defp maybe_put_total_items(map, false, _total), do: map
index 783203c071028fcb885bb5f3b056e9237b8300e0..5cbf0dd4fceaa74e2f2843ca3cee6eb0e5ca2855 100644 (file)
@@ -7,38 +7,24 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
   import Pleroma.Web.ControllerHelper, only: [json_response: 3]
 
-  alias Pleroma.Activity
   alias Pleroma.Config
-  alias Pleroma.ConfigDB
   alias Pleroma.MFA
   alias Pleroma.ModerationLog
   alias Pleroma.Plugs.OAuthScopesPlug
-  alias Pleroma.ReportNote
   alias Pleroma.Stats
   alias Pleroma.User
-  alias Pleroma.UserInviteToken
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.Pipeline
-  alias Pleroma.Web.ActivityPub.Relay
-  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.AdminAPI
   alias Pleroma.Web.AdminAPI.AccountView
-  alias Pleroma.Web.AdminAPI.ConfigView
   alias Pleroma.Web.AdminAPI.ModerationLogView
-  alias Pleroma.Web.AdminAPI.Report
-  alias Pleroma.Web.AdminAPI.ReportView
   alias Pleroma.Web.AdminAPI.Search
-  alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Endpoint
-  alias Pleroma.Web.MastodonAPI
-  alias Pleroma.Web.MastodonAPI.AppView
-  alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.Router
 
   require Logger
 
-  @descriptions Pleroma.Docs.JSON.compile()
   @users_page_size 50
 
   plug(
@@ -69,30 +55,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
          ]
   )
 
-  plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
-
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["write:invites"], admin: true}
-    when action in [:create_invite_token, :revoke_invite, :email_invite]
-  )
-
   plug(
     OAuthScopesPlug,
     %{scopes: ["write:follows"], admin: true}
-    when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
-  )
-
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["read:reports"], admin: true}
-    when action in [:list_reports, :report_show]
-  )
-
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["write:reports"], admin: true}
-    when action in [:reports_update, :report_notes_create, :report_notes_delete]
+    when action in [:user_follow, :user_unfollow]
   )
 
   plug(
@@ -105,11 +71,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     OAuthScopesPlug,
     %{scopes: ["read"], admin: true}
     when action in [
-           :config_show,
            :list_log,
            :stats,
-           :relay_list,
-           :config_descriptions,
            :need_reboot
          ]
   )
@@ -119,13 +82,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     %{scopes: ["write"], admin: true}
     when action in [
            :restart,
-           :config_update,
            :resend_confirmation_email,
            :confirm_email,
-           :oauth_app_create,
-           :oauth_app_list,
-           :oauth_app_update,
-           :oauth_app_delete,
            :reload_emoji
          ]
   )
@@ -268,10 +226,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
     activities =
       ActivityPub.fetch_statuses(nil, %{
-        "instance" => instance,
-        "limit" => page_size,
-        "offset" => (page - 1) * page_size,
-        "exclude_reblogs" => !with_reblogs && "true"
+        instance: instance,
+        limit: page_size,
+        offset: (page - 1) * page_size,
+        exclude_reblogs: not with_reblogs
       })
 
     conn
@@ -288,13 +246,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
       activities =
         ActivityPub.fetch_user_activities(user, nil, %{
-          "limit" => page_size,
-          "godmode" => godmode,
-          "exclude_reblogs" => !with_reblogs && "true"
+          limit: page_size,
+          godmode: godmode,
+          exclude_reblogs: not with_reblogs
         })
 
       conn
-      |> put_view(MastodonAPI.StatusView)
+      |> put_view(AdminAPI.StatusView)
       |> render("index.json", %{activities: activities, as: :activity})
     else
       _ -> {:error, :not_found}
@@ -531,113 +489,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     render_error(conn, :forbidden, "You can't revoke your own admin status.")
   end
 
-  def relay_list(conn, _params) do
-    with {:ok, list} <- Relay.list() do
-      json(conn, %{relays: list})
-    else
-      _ ->
-        conn
-        |> put_status(500)
-    end
-  end
-
-  def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
-    with {:ok, _message} <- Relay.follow(target) do
-      ModerationLog.insert_log(%{
-        action: "relay_follow",
-        actor: admin,
-        target: target
-      })
-
-      json(conn, target)
-    else
-      _ ->
-        conn
-        |> put_status(500)
-        |> json(target)
-    end
-  end
-
-  def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
-    with {:ok, _message} <- Relay.unfollow(target) do
-      ModerationLog.insert_log(%{
-        action: "relay_unfollow",
-        actor: admin,
-        target: target
-      })
-
-      json(conn, target)
-    else
-      _ ->
-        conn
-        |> put_status(500)
-        |> json(target)
-    end
-  end
-
-  @doc "Sends registration invite via email"
-  def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
-    with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
-         {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
-         {:ok, invite_token} <- UserInviteToken.create_invite(),
-         email <-
-           Pleroma.Emails.UserEmail.user_invitation_email(
-             user,
-             invite_token,
-             email,
-             params["name"]
-           ),
-         {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
-      json_response(conn, :no_content, "")
-    else
-      {:registrations_open, _} ->
-        {:error, "To send invites you need to set the `registrations_open` option to false."}
-
-      {:invites_enabled, _} ->
-        {:error, "To send invites you need to set the `invites_enabled` option to true."}
-    end
-  end
-
-  @doc "Create an account registration invite token"
-  def create_invite_token(conn, params) do
-    opts = %{}
-
-    opts =
-      if params["max_use"],
-        do: Map.put(opts, :max_use, params["max_use"]),
-        else: opts
-
-    opts =
-      if params["expires_at"],
-        do: Map.put(opts, :expires_at, params["expires_at"]),
-        else: opts
-
-    {:ok, invite} = UserInviteToken.create_invite(opts)
-
-    json(conn, AccountView.render("invite.json", %{invite: invite}))
-  end
-
-  @doc "Get list of created invites"
-  def invites(conn, _params) do
-    invites = UserInviteToken.list_invites()
-
-    conn
-    |> put_view(AccountView)
-    |> render("invites.json", %{invites: invites})
-  end
-
-  @doc "Revokes invite by token"
-  def revoke_invite(conn, %{"token" => token}) do
-    with {:ok, invite} <- UserInviteToken.find_by_token(token),
-         {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
-      conn
-      |> put_view(AccountView)
-      |> render("invite.json", %{invite: updated_invite})
-    else
-      nil -> {:error, :not_found}
-    end
-  end
-
   @doc "Get a password reset token (base64 string) for given nickname"
   def get_password_reset(conn, %{"nickname" => nickname}) do
     (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
@@ -724,85 +575,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  def list_reports(conn, params) do
-    {page, page_size} = page_params(params)
-
-    reports = Utils.get_reports(params, page, page_size)
-
-    conn
-    |> put_view(ReportView)
-    |> render("index.json", %{reports: reports})
-  end
-
-  def report_show(conn, %{"id" => id}) do
-    with %Activity{} = report <- Activity.get_by_id(id) do
-      conn
-      |> put_view(ReportView)
-      |> render("show.json", Report.extract_report_info(report))
-    else
-      _ -> {:error, :not_found}
-    end
-  end
-
-  def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
-    result =
-      reports
-      |> Enum.map(fn report ->
-        with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
-          ModerationLog.insert_log(%{
-            action: "report_update",
-            actor: admin,
-            subject: activity
-          })
-
-          activity
-        else
-          {:error, message} -> %{id: report["id"], error: message}
-        end
-      end)
-
-    case Enum.any?(result, &Map.has_key?(&1, :error)) do
-      true -> json_response(conn, :bad_request, result)
-      false -> json_response(conn, :no_content, "")
-    end
-  end
-
-  def report_notes_create(%{assigns: %{user: user}} = conn, %{
-        "id" => report_id,
-        "content" => content
-      }) do
-    with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
-      ModerationLog.insert_log(%{
-        action: "report_note",
-        actor: user,
-        subject: Activity.get_by_id(report_id),
-        text: content
-      })
-
-      json_response(conn, :no_content, "")
-    else
-      _ -> json_response(conn, :bad_request, "")
-    end
-  end
-
-  def report_notes_delete(%{assigns: %{user: user}} = conn, %{
-        "id" => note_id,
-        "report_id" => report_id
-      }) do
-    with {:ok, note} <- ReportNote.destroy(note_id) do
-      ModerationLog.insert_log(%{
-        action: "report_note_delete",
-        actor: user,
-        subject: Activity.get_by_id(report_id),
-        text: note.content
-      })
-
-      json_response(conn, :no_content, "")
-    else
-      _ -> json_response(conn, :bad_request, "")
-    end
-  end
-
   def list_log(conn, params) do
     {page, page_size} = page_params(params)
 
@@ -821,105 +593,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> render("index.json", %{log: log})
   end
 
-  def config_descriptions(conn, _params) do
-    descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
-
-    json(conn, descriptions)
-  end
-
-  def config_show(conn, %{"only_db" => true}) do
-    with :ok <- configurable_from_database() do
-      configs = Pleroma.Repo.all(ConfigDB)
-
-      conn
-      |> put_view(ConfigView)
-      |> render("index.json", %{configs: configs})
-    end
-  end
-
-  def config_show(conn, _params) do
-    with :ok <- configurable_from_database() do
-      configs = ConfigDB.get_all_as_keyword()
-
-      merged =
-        Config.Holder.default_config()
-        |> ConfigDB.merge(configs)
-        |> Enum.map(fn {group, values} ->
-          Enum.map(values, fn {key, value} ->
-            db =
-              if configs[group][key] do
-                ConfigDB.get_db_keys(configs[group][key], key)
-              end
-
-            db_value = configs[group][key]
-
-            merged_value =
-              if !is_nil(db_value) and Keyword.keyword?(db_value) and
-                   ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
-                ConfigDB.merge_group(group, key, value, db_value)
-              else
-                value
-              end
-
-            setting = %{
-              group: ConfigDB.convert(group),
-              key: ConfigDB.convert(key),
-              value: ConfigDB.convert(merged_value)
-            }
-
-            if db, do: Map.put(setting, :db, db), else: setting
-          end)
-        end)
-        |> List.flatten()
-
-      json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
-    end
-  end
-
-  def config_update(conn, %{"configs" => configs}) do
-    with :ok <- configurable_from_database() do
-      {_errors, results} =
-        configs
-        |> Enum.filter(&whitelisted_config?/1)
-        |> Enum.map(fn
-          %{"group" => group, "key" => key, "delete" => true} = params ->
-            ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]})
-
-          %{"group" => group, "key" => key, "value" => value} ->
-            ConfigDB.update_or_create(%{group: group, key: key, value: value})
-        end)
-        |> Enum.split_with(fn result -> elem(result, 0) == :error end)
-
-      {deleted, updated} =
-        results
-        |> Enum.map(fn {:ok, config} ->
-          Map.put(config, :db, ConfigDB.get_db_keys(config))
-        end)
-        |> Enum.split_with(fn config ->
-          Ecto.get_meta(config, :state) == :deleted
-        end)
-
-      Config.TransferTask.load_and_update_env(deleted, false)
-
-      if !Restarter.Pleroma.need_reboot?() do
-        changed_reboot_settings? =
-          (updated ++ deleted)
-          |> Enum.any?(fn config ->
-            group = ConfigDB.from_string(config.group)
-            key = ConfigDB.from_string(config.key)
-            value = ConfigDB.from_binary(config.value)
-            Config.TransferTask.pleroma_need_restart?(group, key, value)
-          end)
-
-        if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
-      end
-
-      conn
-      |> put_view(ConfigView)
-      |> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()})
-    end
-  end
-
   def restart(conn, _params) do
     with :ok <- configurable_from_database() do
       Restarter.Pleroma.restart(Config.get(:env), 50)
@@ -940,28 +613,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  defp whitelisted_config?(group, key) do
-    if whitelisted_configs = Config.get(:database_config_whitelist) do
-      Enum.any?(whitelisted_configs, fn
-        {whitelisted_group} ->
-          group == inspect(whitelisted_group)
-
-        {whitelisted_group, whitelisted_key} ->
-          group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
-      end)
-    else
-      true
-    end
-  end
-
-  defp whitelisted_config?(%{"group" => group, "key" => key}) do
-    whitelisted_config?(group, key)
-  end
-
-  defp whitelisted_config?(%{:group => group} = config) do
-    whitelisted_config?(group, config[:key])
-  end
-
   def reload_emoji(conn, _params) do
     Pleroma.Emoji.reload()
 
@@ -996,83 +647,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     conn |> json("")
   end
 
-  def oauth_app_create(conn, params) do
-    params =
-      if params["name"] do
-        Map.put(params, "client_name", params["name"])
-      else
-        params
-      end
-
-    result =
-      case App.create(params) do
-        {:ok, app} ->
-          AppView.render("show.json", %{app: app, admin: true})
-
-        {:error, changeset} ->
-          App.errors(changeset)
-      end
-
-    json(conn, result)
-  end
-
-  def oauth_app_update(conn, params) do
-    params =
-      if params["name"] do
-        Map.put(params, "client_name", params["name"])
-      else
-        params
-      end
-
-    with {:ok, app} <- App.update(params) do
-      json(conn, AppView.render("show.json", %{app: app, admin: true}))
-    else
-      {:error, changeset} ->
-        json(conn, App.errors(changeset))
-
-      nil ->
-        json_response(conn, :bad_request, "")
-    end
-  end
-
-  def oauth_app_list(conn, params) do
-    {page, page_size} = page_params(params)
-
-    search_params = %{
-      client_name: params["name"],
-      client_id: params["client_id"],
-      page: page,
-      page_size: page_size
-    }
-
-    search_params =
-      if Map.has_key?(params, "trusted") do
-        Map.put(search_params, :trusted, params["trusted"])
-      else
-        search_params
-      end
-
-    with {:ok, apps, count} <- App.search(search_params) do
-      json(
-        conn,
-        AppView.render("index.json",
-          apps: apps,
-          count: count,
-          page_size: page_size,
-          admin: true
-        )
-      )
-    end
-  end
-
-  def oauth_app_delete(conn, params) do
-    with {:ok, _app} <- App.destroy(params["id"]) do
-      json_response(conn, :no_content, "")
-    else
-      _ -> json_response(conn, :bad_request, "")
-    end
-  end
-
   def stats(conn, _) do
     count = Stats.get_status_visibility_count()
 
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
new file mode 100644 (file)
index 0000000..d6e2019
--- /dev/null
@@ -0,0 +1,152 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ConfigController do
+  use Pleroma.Web, :controller
+
+  alias Pleroma.Config
+  alias Pleroma.ConfigDB
+  alias Pleroma.Plugs.OAuthScopesPlug
+
+  @descriptions Pleroma.Docs.JSON.compile()
+
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+  plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["read"], admin: true}
+    when action in [:show, :descriptions]
+  )
+
+  action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
+
+  def descriptions(conn, _params) do
+    descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
+
+    json(conn, descriptions)
+  end
+
+  def show(conn, %{only_db: true}) do
+    with :ok <- configurable_from_database() do
+      configs = Pleroma.Repo.all(ConfigDB)
+      render(conn, "index.json", %{configs: configs})
+    end
+  end
+
+  def show(conn, _params) do
+    with :ok <- configurable_from_database() do
+      configs = ConfigDB.get_all_as_keyword()
+
+      merged =
+        Config.Holder.default_config()
+        |> ConfigDB.merge(configs)
+        |> Enum.map(fn {group, values} ->
+          Enum.map(values, fn {key, value} ->
+            db =
+              if configs[group][key] do
+                ConfigDB.get_db_keys(configs[group][key], key)
+              end
+
+            db_value = configs[group][key]
+
+            merged_value =
+              if not is_nil(db_value) and Keyword.keyword?(db_value) and
+                   ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
+                ConfigDB.merge_group(group, key, value, db_value)
+              else
+                value
+              end
+
+            %{
+              group: ConfigDB.convert(group),
+              key: ConfigDB.convert(key),
+              value: ConfigDB.convert(merged_value)
+            }
+            |> Pleroma.Maps.put_if_present(:db, db)
+          end)
+        end)
+        |> List.flatten()
+
+      json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
+    end
+  end
+
+  def update(%{body_params: %{configs: configs}} = conn, _) do
+    with :ok <- configurable_from_database() do
+      results =
+        configs
+        |> Enum.filter(&whitelisted_config?/1)
+        |> Enum.map(fn
+          %{group: group, key: key, delete: true} = params ->
+            ConfigDB.delete(%{group: group, key: key, subkeys: params[:subkeys]})
+
+          %{group: group, key: key, value: value} ->
+            ConfigDB.update_or_create(%{group: group, key: key, value: value})
+        end)
+        |> Enum.reject(fn {result, _} -> result == :error end)
+
+      {deleted, updated} =
+        results
+        |> Enum.map(fn {:ok, config} ->
+          Map.put(config, :db, ConfigDB.get_db_keys(config))
+        end)
+        |> Enum.split_with(fn config ->
+          Ecto.get_meta(config, :state) == :deleted
+        end)
+
+      Config.TransferTask.load_and_update_env(deleted, false)
+
+      if not Restarter.Pleroma.need_reboot?() do
+        changed_reboot_settings? =
+          (updated ++ deleted)
+          |> Enum.any?(fn config ->
+            group = ConfigDB.from_string(config.group)
+            key = ConfigDB.from_string(config.key)
+            value = ConfigDB.from_binary(config.value)
+            Config.TransferTask.pleroma_need_restart?(group, key, value)
+          end)
+
+        if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
+      end
+
+      render(conn, "index.json", %{
+        configs: updated,
+        need_reboot: Restarter.Pleroma.need_reboot?()
+      })
+    end
+  end
+
+  defp configurable_from_database do
+    if Config.get(:configurable_from_database) do
+      :ok
+    else
+      {:error, "To use this endpoint you need to enable configuration from database."}
+    end
+  end
+
+  defp whitelisted_config?(group, key) do
+    if whitelisted_configs = Config.get(:database_config_whitelist) do
+      Enum.any?(whitelisted_configs, fn
+        {whitelisted_group} ->
+          group == inspect(whitelisted_group)
+
+        {whitelisted_group, whitelisted_key} ->
+          group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
+      end)
+    else
+      true
+    end
+  end
+
+  defp whitelisted_config?(%{group: group, key: key}) do
+    whitelisted_config?(group, key)
+  end
+
+  defp whitelisted_config?(%{group: group} = config) do
+    whitelisted_config?(group, config[:key])
+  end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/invite_controller.ex b/lib/pleroma/web/admin_api/controllers/invite_controller.ex
new file mode 100644 (file)
index 0000000..7d169b8
--- /dev/null
@@ -0,0 +1,78 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.InviteController do
+  use Pleroma.Web, :controller
+
+  import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
+  alias Pleroma.Config
+  alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.UserInviteToken
+
+  require Logger
+
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+  plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :index)
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["write:invites"], admin: true} when action in [:create, :revoke, :email]
+  )
+
+  action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InviteOperation
+
+  @doc "Get list of created invites"
+  def index(conn, _params) do
+    invites = UserInviteToken.list_invites()
+
+    render(conn, "index.json", invites: invites)
+  end
+
+  @doc "Create an account registration invite token"
+  def create(%{body_params: params} = conn, _) do
+    {:ok, invite} = UserInviteToken.create_invite(params)
+
+    render(conn, "show.json", invite: invite)
+  end
+
+  @doc "Revokes invite by token"
+  def revoke(%{body_params: %{token: token}} = conn, _) do
+    with {:ok, invite} <- UserInviteToken.find_by_token(token),
+         {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
+      render(conn, "show.json", invite: updated_invite)
+    else
+      nil -> {:error, :not_found}
+      error -> error
+    end
+  end
+
+  @doc "Sends registration invite via email"
+  def email(%{assigns: %{user: user}, body_params: %{email: email} = params} = conn, _) do
+    with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
+         {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
+         {:ok, invite_token} <- UserInviteToken.create_invite(),
+         {:ok, _} <-
+           user
+           |> Pleroma.Emails.UserEmail.user_invitation_email(
+             invite_token,
+             email,
+             params[:name]
+           )
+           |> Pleroma.Emails.Mailer.deliver() do
+      json_response(conn, :no_content, "")
+    else
+      {:registrations_open, _} ->
+        {:error, "To send invites you need to set the `registrations_open` option to false."}
+
+      {:invites_enabled, _} ->
+        {:error, "To send invites you need to set the `invites_enabled` option to true."}
+
+      {:error, error} ->
+        {:error, error}
+    end
+  end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex
new file mode 100644 (file)
index 0000000..dca23ea
--- /dev/null
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.OAuthAppController do
+  use Pleroma.Web, :controller
+
+  import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
+  alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.Web.OAuth.App
+
+  require Logger
+
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+  plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["write"], admin: true}
+    when action in [:create, :index, :update, :delete]
+  )
+
+  action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation
+
+  def index(conn, params) do
+    search_params =
+      params
+      |> Map.take([:client_id, :page, :page_size, :trusted])
+      |> Map.put(:client_name, params[:name])
+
+    with {:ok, apps, count} <- App.search(search_params) do
+      render(conn, "index.json",
+        apps: apps,
+        count: count,
+        page_size: params.page_size,
+        admin: true
+      )
+    end
+  end
+
+  def create(%{body_params: params} = conn, _) do
+    params = Pleroma.Maps.put_if_present(params, :client_name, params[:name])
+
+    case App.create(params) do
+      {:ok, app} ->
+        render(conn, "show.json", app: app, admin: true)
+
+      {:error, changeset} ->
+        json(conn, App.errors(changeset))
+    end
+  end
+
+  def update(%{body_params: params} = conn, %{id: id}) do
+    params = Pleroma.Maps.put_if_present(params, :client_name, params[:name])
+
+    with {:ok, app} <- App.update(id, params) do
+      render(conn, "show.json", app: app, admin: true)
+    else
+      {:error, changeset} ->
+        json(conn, App.errors(changeset))
+
+      nil ->
+        json_response(conn, :bad_request, "")
+    end
+  end
+
+  def delete(conn, params) do
+    with {:ok, _app} <- App.destroy(params.id) do
+      json_response(conn, :no_content, "")
+    else
+      _ -> json_response(conn, :bad_request, "")
+    end
+  end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
new file mode 100644 (file)
index 0000000..cf9f3a1
--- /dev/null
@@ -0,0 +1,67 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.RelayController do
+  use Pleroma.Web, :controller
+
+  alias Pleroma.ModerationLog
+  alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.Web.ActivityPub.Relay
+
+  require Logger
+
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["write:follows"], admin: true}
+    when action in [:follow, :unfollow]
+  )
+
+  plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :index)
+
+  action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.RelayOperation
+
+  def index(conn, _params) do
+    with {:ok, list} <- Relay.list() do
+      json(conn, %{relays: list})
+    end
+  end
+
+  def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
+    with {:ok, _message} <- Relay.follow(target) do
+      ModerationLog.insert_log(%{
+        action: "relay_follow",
+        actor: admin,
+        target: target
+      })
+
+      json(conn, target)
+    else
+      _ ->
+        conn
+        |> put_status(500)
+        |> json(target)
+    end
+  end
+
+  def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
+    with {:ok, _message} <- Relay.unfollow(target) do
+      ModerationLog.insert_log(%{
+        action: "relay_unfollow",
+        actor: admin,
+        target: target
+      })
+
+      json(conn, target)
+    else
+      _ ->
+        conn
+        |> put_status(500)
+        |> json(target)
+    end
+  end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex
new file mode 100644 (file)
index 0000000..4c011e1
--- /dev/null
@@ -0,0 +1,107 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ReportController do
+  use Pleroma.Web, :controller
+
+  import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
+  alias Pleroma.Activity
+  alias Pleroma.ModerationLog
+  alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.ReportNote
+  alias Pleroma.Web.ActivityPub.Utils
+  alias Pleroma.Web.AdminAPI
+  alias Pleroma.Web.AdminAPI.Report
+  alias Pleroma.Web.CommonAPI
+
+  require Logger
+
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+  plug(OAuthScopesPlug, %{scopes: ["read:reports"], admin: true} when action in [:index, :show])
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["write:reports"], admin: true}
+    when action in [:update, :notes_create, :notes_delete]
+  )
+
+  action_fallback(AdminAPI.FallbackController)
+
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ReportOperation
+
+  def index(conn, params) do
+    reports = Utils.get_reports(params, params.page, params.page_size)
+
+    render(conn, "index.json", reports: reports)
+  end
+
+  def show(conn, %{id: id}) do
+    with %Activity{} = report <- Activity.get_by_id(id) do
+      render(conn, "show.json", Report.extract_report_info(report))
+    else
+      _ -> {:error, :not_found}
+    end
+  end
+
+  def update(%{assigns: %{user: admin}, body_params: %{reports: reports}} = conn, _) do
+    result =
+      Enum.map(reports, fn report ->
+        case CommonAPI.update_report_state(report.id, report.state) do
+          {:ok, activity} ->
+            ModerationLog.insert_log(%{
+              action: "report_update",
+              actor: admin,
+              subject: activity
+            })
+
+            activity
+
+          {:error, message} ->
+            %{id: report.id, error: message}
+        end
+      end)
+
+    if Enum.any?(result, &Map.has_key?(&1, :error)) do
+      json_response(conn, :bad_request, result)
+    else
+      json_response(conn, :no_content, "")
+    end
+  end
+
+  def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{
+        id: report_id
+      }) do
+    with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
+      ModerationLog.insert_log(%{
+        action: "report_note",
+        actor: user,
+        subject: Activity.get_by_id(report_id),
+        text: content
+      })
+
+      json_response(conn, :no_content, "")
+    else
+      _ -> json_response(conn, :bad_request, "")
+    end
+  end
+
+  def notes_delete(%{assigns: %{user: user}} = conn, %{
+        id: note_id,
+        report_id: report_id
+      }) do
+    with {:ok, note} <- ReportNote.destroy(note_id) do
+      ModerationLog.insert_log(%{
+        action: "report_note_delete",
+        actor: user,
+        subject: Activity.get_by_id(report_id),
+        text: note.content
+      })
+
+      json_response(conn, :no_content, "")
+    else
+      _ -> json_response(conn, :bad_request, "")
+    end
+  end
+end
index 08cb9c10b3da0a696eeb7a31992728ef194c86dc..bc48cc5278997ca9add04f1162ffe2a81fa9d2fc 100644 (file)
@@ -29,11 +29,11 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
   def index(%{assigns: %{user: _admin}} = conn, params) do
     activities =
       ActivityPub.fetch_statuses(nil, %{
-        "godmode" => params.godmode,
-        "local_only" => params.local_only,
-        "limit" => params.page_size,
-        "offset" => (params.page - 1) * params.page_size,
-        "exclude_reblogs" => not params.with_reblogs
+        godmode: params.godmode,
+        local_only: params.local_only,
+        limit: params.page_size,
+        offset: (params.page - 1) * params.page_size,
+        exclude_reblogs: not params.with_reblogs
       })
 
     render(conn, "index.json", activities: activities, as: :activity)
@@ -41,9 +41,7 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
 
   def show(conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id(id) do
-      conn
-      |> put_view(MastodonAPI.StatusView)
-      |> render("show.json", %{activity: activity})
+      render(conn, "show.json", %{activity: activity})
     else
       nil -> {:error, :not_found}
     end
index c28efadd566e7a1905d6d00d9b29be7ebe78ce44..0bfb8f02261ec953ca675edc00cfb601de46986d 100644 (file)
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.AdminAPI.Search do
     query =
       params
       |> Map.drop([:page, :page_size])
-      |> Map.put(:exclude_service_users, true)
+      |> Map.put(:invisible, false)
       |> User.Query.build()
       |> order_by([u], u.nickname)
 
@@ -31,7 +31,6 @@ defmodule Pleroma.Web.AdminAPI.Search do
     count = Repo.aggregate(query, :count, :id)
 
     results = Repo.all(paginated_query)
-
     {:ok, results, count}
   end
 end
index 46dadb5ee546084698ab92819800e35c66ea76bf..120159527abefe8259b52ff4624a09ecacd23137 100644 (file)
@@ -80,24 +80,6 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
     }
   end
 
-  def render("invite.json", %{invite: invite}) do
-    %{
-      "id" => invite.id,
-      "token" => invite.token,
-      "used" => invite.used,
-      "expires_at" => invite.expires_at,
-      "uses" => invite.uses,
-      "max_use" => invite.max_use,
-      "invite_type" => invite.invite_type
-    }
-  end
-
-  def render("invites.json", %{invites: invites}) do
-    %{
-      invites: render_many(invites, AccountView, "invite.json", as: :invite)
-    }
-  end
-
   def render("created.json", %{user: user}) do
     %{
       type: "success",
diff --git a/lib/pleroma/web/admin_api/views/invite_view.ex b/lib/pleroma/web/admin_api/views/invite_view.ex
new file mode 100644 (file)
index 0000000..f93cb69
--- /dev/null
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.InviteView do
+  use Pleroma.Web, :view
+
+  def render("index.json", %{invites: invites}) do
+    %{
+      invites: render_many(invites, __MODULE__, "show.json", as: :invite)
+    }
+  end
+
+  def render("show.json", %{invite: invite}) do
+    %{
+      "id" => invite.id,
+      "token" => invite.token,
+      "used" => invite.used,
+      "expires_at" => invite.expires_at,
+      "uses" => invite.uses,
+      "max_use" => invite.max_use,
+      "invite_type" => invite.invite_type
+    }
+  end
+end
index f432b8c2c9e20da80512dd5e823ebf6e190232a2..773f798fe4a5010aa12acd35955f559514824a5c 100644 (file)
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
     %{
       reports:
         reports[:items]
-        |> Enum.map(&Report.extract_report_info(&1))
+        |> Enum.map(&Report.extract_report_info/1)
         |> Enum.map(&render(__MODULE__, "show.json", &1))
         |> Enum.reverse(),
       total: reports[:total]
diff --git a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex
new file mode 100644 (file)
index 0000000..7b38a2e
--- /dev/null
@@ -0,0 +1,142 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def show_operation do
+    %Operation{
+      tags: ["Admin", "Config"],
+      summary: "Get list of merged default settings with saved in database",
+      operationId: "AdminAPI.ConfigController.show",
+      parameters: [
+        Operation.parameter(
+          :only_db,
+          :query,
+          %Schema{type: :boolean, default: false},
+          "Get only saved in database settings"
+        )
+      ],
+      security: [%{"oAuth" => ["read"]}],
+      responses: %{
+        200 => Operation.response("Config", "application/json", config_response()),
+        400 => Operation.response("Bad Request", "application/json", ApiError)
+      }
+    }
+  end
+
+  def update_operation do
+    %Operation{
+      tags: ["Admin", "Config"],
+      summary: "Update config settings",
+      operationId: "AdminAPI.ConfigController.update",
+      security: [%{"oAuth" => ["write"]}],
+      requestBody:
+        request_body("Parameters", %Schema{
+          type: :object,
+          properties: %{
+            configs: %Schema{
+              type: :array,
+              items: %Schema{
+                type: :object,
+                properties: %{
+                  group: %Schema{type: :string},
+                  key: %Schema{type: :string},
+                  value: any(),
+                  delete: %Schema{type: :boolean},
+                  subkeys: %Schema{type: :array, items: %Schema{type: :string}}
+                }
+              }
+            }
+          }
+        }),
+      responses: %{
+        200 => Operation.response("Config", "application/json", config_response()),
+        400 => Operation.response("Bad Request", "application/json", ApiError)
+      }
+    }
+  end
+
+  def descriptions_operation do
+    %Operation{
+      tags: ["Admin", "Config"],
+      summary: "Get JSON with config descriptions.",
+      operationId: "AdminAPI.ConfigController.descriptions",
+      security: [%{"oAuth" => ["read"]}],
+      responses: %{
+        200 =>
+          Operation.response("Config Descriptions", "application/json", %Schema{
+            type: :array,
+            items: %Schema{
+              type: :object,
+              properties: %{
+                group: %Schema{type: :string},
+                key: %Schema{type: :string},
+                type: %Schema{oneOf: [%Schema{type: :string}, %Schema{type: :array}]},
+                description: %Schema{type: :string},
+                children: %Schema{
+                  type: :array,
+                  items: %Schema{
+                    type: :object,
+                    properties: %{
+                      key: %Schema{type: :string},
+                      type: %Schema{oneOf: [%Schema{type: :string}, %Schema{type: :array}]},
+                      description: %Schema{type: :string},
+                      suggestions: %Schema{type: :array}
+                    }
+                  }
+                }
+              }
+            }
+          }),
+        400 => Operation.response("Bad Request", "application/json", ApiError)
+      }
+    }
+  end
+
+  defp any do
+    %Schema{
+      oneOf: [
+        %Schema{type: :array},
+        %Schema{type: :object},
+        %Schema{type: :string},
+        %Schema{type: :integer},
+        %Schema{type: :boolean}
+      ]
+    }
+  end
+
+  defp config_response do
+    %Schema{
+      type: :object,
+      properties: %{
+        configs: %Schema{
+          type: :array,
+          items: %Schema{
+            type: :object,
+            properties: %{
+              group: %Schema{type: :string},
+              key: %Schema{type: :string},
+              value: any()
+            }
+          }
+        },
+        need_reboot: %Schema{
+          type: :boolean,
+          description:
+            "If `need_reboot` is `true`, instance must be restarted, so reboot time settings can take effect"
+        }
+      }
+    }
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
new file mode 100644 (file)
index 0000000..d3af9db
--- /dev/null
@@ -0,0 +1,148 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      tags: ["Admin", "Invites"],
+      summary: "Get a list of generated invites",
+      operationId: "AdminAPI.InviteController.index",
+      security: [%{"oAuth" => ["read:invites"]}],
+      responses: %{
+        200 =>
+          Operation.response("Invites", "application/json", %Schema{
+            type: :object,
+            properties: %{
+              invites: %Schema{type: :array, items: invite()}
+            },
+            example: %{
+              "invites" => [
+                %{
+                  "id" => 123,
+                  "token" => "kSQtDj_GNy2NZsL9AQDFIsHN5qdbguB6qRg3WHw6K1U=",
+                  "used" => true,
+                  "expires_at" => nil,
+                  "uses" => 0,
+                  "max_use" => nil,
+                  "invite_type" => "one_time"
+                }
+              ]
+            }
+          })
+      }
+    }
+  end
+
+  def create_operation do
+    %Operation{
+      tags: ["Admin", "Invites"],
+      summary: "Create an account registration invite token",
+      operationId: "AdminAPI.InviteController.create",
+      security: [%{"oAuth" => ["write:invites"]}],
+      requestBody:
+        request_body("Parameters", %Schema{
+          type: :object,
+          properties: %{
+            max_use: %Schema{type: :integer},
+            expires_at: %Schema{type: :string, format: :date, example: "2020-04-20"}
+          }
+        }),
+      responses: %{
+        200 => Operation.response("Invite", "application/json", invite())
+      }
+    }
+  end
+
+  def revoke_operation do
+    %Operation{
+      tags: ["Admin", "Invites"],
+      summary: "Revoke invite by token",
+      operationId: "AdminAPI.InviteController.revoke",
+      security: [%{"oAuth" => ["write:invites"]}],
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            type: :object,
+            required: [:token],
+            properties: %{
+              token: %Schema{type: :string}
+            }
+          },
+          required: true
+        ),
+      responses: %{
+        200 => Operation.response("Invite", "application/json", invite()),
+        400 => Operation.response("Bad Request", "application/json", ApiError),
+        404 => Operation.response("Not Found", "application/json", ApiError)
+      }
+    }
+  end
+
+  def email_operation do
+    %Operation{
+      tags: ["Admin", "Invites"],
+      summary: "Sends registration invite via email",
+      operationId: "AdminAPI.InviteController.email",
+      security: [%{"oAuth" => ["write:invites"]}],
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            type: :object,
+            required: [:email],
+            properties: %{
+              email: %Schema{type: :string, format: :email},
+              name: %Schema{type: :string}
+            }
+          },
+          required: true
+        ),
+      responses: %{
+        204 => no_content_response(),
+        400 => Operation.response("Bad Request", "application/json", ApiError),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  defp invite do
+    %Schema{
+      title: "Invite",
+      type: :object,
+      properties: %{
+        id: %Schema{type: :integer},
+        token: %Schema{type: :string},
+        used: %Schema{type: :boolean},
+        expires_at: %Schema{type: :string, format: :date, nullable: true},
+        uses: %Schema{type: :integer},
+        max_use: %Schema{type: :integer, nullable: true},
+        invite_type: %Schema{
+          type: :string,
+          enum: ["one_time", "reusable", "date_limited", "reusable_date_limited"]
+        }
+      },
+      example: %{
+        "id" => 123,
+        "token" => "kSQtDj_GNy2NZsL9AQDFIsHN5qdbguB6qRg3WHw6K1U=",
+        "used" => true,
+        "expires_at" => nil,
+        "uses" => 0,
+        "max_use" => nil,
+        "invite_type" => "one_time"
+      }
+    }
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
new file mode 100644 (file)
index 0000000..fbc9f80
--- /dev/null
@@ -0,0 +1,215 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      summary: "List OAuth apps",
+      tags: ["Admin", "oAuth Apps"],
+      operationId: "AdminAPI.OAuthAppController.index",
+      security: [%{"oAuth" => ["write"]}],
+      parameters: [
+        Operation.parameter(:name, :query, %Schema{type: :string}, "App name"),
+        Operation.parameter(:client_id, :query, %Schema{type: :string}, "Client ID"),
+        Operation.parameter(:page, :query, %Schema{type: :integer, default: 1}, "Page"),
+        Operation.parameter(
+          :trusted,
+          :query,
+          %Schema{type: :boolean, default: false},
+          "Trusted apps"
+        ),
+        Operation.parameter(
+          :page_size,
+          :query,
+          %Schema{type: :integer, default: 50},
+          "Number of apps to return"
+        )
+      ],
+      responses: %{
+        200 =>
+          Operation.response("List of apps", "application/json", %Schema{
+            type: :object,
+            properties: %{
+              apps: %Schema{type: :array, items: oauth_app()},
+              count: %Schema{type: :integer},
+              page_size: %Schema{type: :integer}
+            },
+            example: %{
+              "apps" => [
+                %{
+                  "id" => 1,
+                  "name" => "App name",
+                  "client_id" => "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
+                  "client_secret" => "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
+                  "redirect_uri" => "https://example.com/oauth-callback",
+                  "website" => "https://example.com",
+                  "trusted" => true
+                }
+              ],
+              "count" => 1,
+              "page_size" => 50
+            }
+          })
+      }
+    }
+  end
+
+  def create_operation do
+    %Operation{
+      tags: ["Admin", "oAuth Apps"],
+      summary: "Create OAuth App",
+      operationId: "AdminAPI.OAuthAppController.create",
+      requestBody: request_body("Parameters", create_request()),
+      security: [%{"oAuth" => ["write"]}],
+      responses: %{
+        200 => Operation.response("App", "application/json", oauth_app()),
+        400 => Operation.response("Bad Request", "application/json", ApiError)
+      }
+    }
+  end
+
+  def update_operation do
+    %Operation{
+      tags: ["Admin", "oAuth Apps"],
+      summary: "Update OAuth App",
+      operationId: "AdminAPI.OAuthAppController.update",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["write"]}],
+      requestBody: request_body("Parameters", update_request()),
+      responses: %{
+        200 => Operation.response("App", "application/json", oauth_app()),
+        400 =>
+          Operation.response("Bad Request", "application/json", %Schema{
+            oneOf: [ApiError, %Schema{type: :string}]
+          })
+      }
+    }
+  end
+
+  def delete_operation do
+    %Operation{
+      tags: ["Admin", "oAuth Apps"],
+      summary: "Delete OAuth App",
+      operationId: "AdminAPI.OAuthAppController.delete",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["write"]}],
+      responses: %{
+        204 => no_content_response(),
+        400 => no_content_response()
+      }
+    }
+  end
+
+  defp create_request do
+    %Schema{
+      title: "oAuthAppCreateRequest",
+      type: :object,
+      required: [:name, :redirect_uris],
+      properties: %{
+        name: %Schema{type: :string, description: "Application Name"},
+        scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"},
+        redirect_uris: %Schema{
+          type: :string,
+          description:
+            "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
+        },
+        website: %Schema{
+          type: :string,
+          nullable: true,
+          description: "A URL to the homepage of the app"
+        },
+        trusted: %Schema{
+          type: :boolean,
+          nullable: true,
+          default: false,
+          description: "Is the app trusted?"
+        }
+      },
+      example: %{
+        "name" => "My App",
+        "redirect_uris" => "https://myapp.com/auth/callback",
+        "website" => "https://myapp.com/",
+        "scopes" => ["read", "write"],
+        "trusted" => true
+      }
+    }
+  end
+
+  defp update_request do
+    %Schema{
+      title: "oAuthAppUpdateRequest",
+      type: :object,
+      properties: %{
+        name: %Schema{type: :string, description: "Application Name"},
+        scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"},
+        redirect_uris: %Schema{
+          type: :string,
+          description:
+            "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
+        },
+        website: %Schema{
+          type: :string,
+          nullable: true,
+          description: "A URL to the homepage of the app"
+        },
+        trusted: %Schema{
+          type: :boolean,
+          nullable: true,
+          default: false,
+          description: "Is the app trusted?"
+        }
+      },
+      example: %{
+        "name" => "My App",
+        "redirect_uris" => "https://myapp.com/auth/callback",
+        "website" => "https://myapp.com/",
+        "scopes" => ["read", "write"],
+        "trusted" => true
+      }
+    }
+  end
+
+  defp oauth_app do
+    %Schema{
+      title: "oAuthApp",
+      type: :object,
+      properties: %{
+        id: %Schema{type: :integer},
+        name: %Schema{type: :string},
+        client_id: %Schema{type: :string},
+        client_secret: %Schema{type: :string},
+        redirect_uri: %Schema{type: :string},
+        website: %Schema{type: :string, nullable: true},
+        trusted: %Schema{type: :boolean}
+      },
+      example: %{
+        "id" => 123,
+        "name" => "My App",
+        "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
+        "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
+        "redirect_uri" => "https://myapp.com/oauth-callback",
+        "website" => "https://myapp.com/",
+        "trusted" => false
+      }
+    }
+  end
+
+  def id_param do
+    Operation.parameter(:id, :path, :integer, "App ID",
+      example: 1337,
+      required: true
+    )
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
new file mode 100644 (file)
index 0000000..7672cb4
--- /dev/null
@@ -0,0 +1,83 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      tags: ["Admin", "Relays"],
+      summary: "List Relays",
+      operationId: "AdminAPI.RelayController.index",
+      security: [%{"oAuth" => ["read"]}],
+      responses: %{
+        200 =>
+          Operation.response("Response", "application/json", %Schema{
+            type: :object,
+            properties: %{
+              relays: %Schema{
+                type: :array,
+                items: %Schema{type: :string},
+                example: ["lain.com", "mstdn.io"]
+              }
+            }
+          })
+      }
+    }
+  end
+
+  def follow_operation do
+    %Operation{
+      tags: ["Admin", "Relays"],
+      summary: "Follow a Relay",
+      operationId: "AdminAPI.RelayController.follow",
+      security: [%{"oAuth" => ["write:follows"]}],
+      requestBody:
+        request_body("Parameters", %Schema{
+          type: :object,
+          properties: %{
+            relay_url: %Schema{type: :string, format: :uri}
+          }
+        }),
+      responses: %{
+        200 =>
+          Operation.response("Status", "application/json", %Schema{
+            type: :string,
+            example: "http://mastodon.example.org/users/admin"
+          })
+      }
+    }
+  end
+
+  def unfollow_operation do
+    %Operation{
+      tags: ["Admin", "Relays"],
+      summary: "Unfollow a Relay",
+      operationId: "AdminAPI.RelayController.unfollow",
+      security: [%{"oAuth" => ["write:follows"]}],
+      requestBody:
+        request_body("Parameters", %Schema{
+          type: :object,
+          properties: %{
+            relay_url: %Schema{type: :string, format: :uri}
+          }
+        }),
+      responses: %{
+        200 =>
+          Operation.response("Status", "application/json", %Schema{
+            type: :string,
+            example: "http://mastodon.example.org/users/admin"
+          })
+      }
+    }
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex
new file mode 100644 (file)
index 0000000..15e78bf
--- /dev/null
@@ -0,0 +1,237 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+  alias Pleroma.Web.ApiSpec.Schemas.Status
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Get a list of reports",
+      operationId: "AdminAPI.ReportController.index",
+      security: [%{"oAuth" => ["read:reports"]}],
+      parameters: [
+        Operation.parameter(
+          :state,
+          :query,
+          report_state(),
+          "Filter by report state"
+        ),
+        Operation.parameter(
+          :limit,
+          :query,
+          %Schema{type: :integer},
+          "The number of records to retrieve"
+        ),
+        Operation.parameter(
+          :page,
+          :query,
+          %Schema{type: :integer, default: 1},
+          "Page number"
+        ),
+        Operation.parameter(
+          :page_size,
+          :query,
+          %Schema{type: :integer, default: 50},
+          "Number number of log entries per page"
+        )
+      ],
+      responses: %{
+        200 =>
+          Operation.response("Response", "application/json", %Schema{
+            type: :object,
+            properties: %{
+              total: %Schema{type: :integer},
+              reports: %Schema{
+                type: :array,
+                items: report()
+              }
+            }
+          }),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def show_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Get an individual report",
+      operationId: "AdminAPI.ReportController.show",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["read:reports"]}],
+      responses: %{
+        200 => Operation.response("Report", "application/json", report()),
+        404 => Operation.response("Not Found", "application/json", ApiError)
+      }
+    }
+  end
+
+  def update_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Change the state of one or multiple reports",
+      operationId: "AdminAPI.ReportController.update",
+      security: [%{"oAuth" => ["write:reports"]}],
+      requestBody: request_body("Parameters", update_request(), required: true),
+      responses: %{
+        204 => no_content_response(),
+        400 => Operation.response("Bad Request", "application/json", update_400_response()),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def notes_create_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Create report note",
+      operationId: "AdminAPI.ReportController.notes_create",
+      parameters: [id_param()],
+      requestBody:
+        request_body("Parameters", %Schema{
+          type: :object,
+          properties: %{
+            content: %Schema{type: :string, description: "The message"}
+          }
+        }),
+      security: [%{"oAuth" => ["write:reports"]}],
+      responses: %{
+        204 => no_content_response(),
+        404 => Operation.response("Not Found", "application/json", ApiError)
+      }
+    }
+  end
+
+  def notes_delete_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Delete report note",
+      operationId: "AdminAPI.ReportController.notes_delete",
+      parameters: [
+        Operation.parameter(:report_id, :path, :string, "Report ID"),
+        Operation.parameter(:id, :path, :string, "Note ID")
+      ],
+      security: [%{"oAuth" => ["write:reports"]}],
+      responses: %{
+        204 => no_content_response(),
+        404 => Operation.response("Not Found", "application/json", ApiError)
+      }
+    }
+  end
+
+  defp report_state do
+    %Schema{type: :string, enum: ["open", "closed", "resolved"]}
+  end
+
+  defp id_param do
+    Operation.parameter(:id, :path, FlakeID, "Report ID",
+      example: "9umDrYheeY451cQnEe",
+      required: true
+    )
+  end
+
+  defp report do
+    %Schema{
+      type: :object,
+      properties: %{
+        id: FlakeID,
+        state: report_state(),
+        account: account_admin(),
+        actor: account_admin(),
+        content: %Schema{type: :string},
+        created_at: %Schema{type: :string, format: :"date-time"},
+        statuses: %Schema{type: :array, items: Status},
+        notes: %Schema{
+          type: :array,
+          items: %Schema{
+            type: :object,
+            properties: %{
+              id: %Schema{type: :integer},
+              user_id: FlakeID,
+              content: %Schema{type: :string},
+              inserted_at: %Schema{type: :string, format: :"date-time"}
+            }
+          }
+        }
+      }
+    }
+  end
+
+  defp account_admin do
+    %Schema{
+      title: "Account",
+      description: "Account view for admins",
+      type: :object,
+      properties:
+        Map.merge(Account.schema().properties, %{
+          nickname: %Schema{type: :string},
+          deactivated: %Schema{type: :boolean},
+          local: %Schema{type: :boolean},
+          roles: %Schema{
+            type: :object,
+            properties: %{
+              admin: %Schema{type: :boolean},
+              moderator: %Schema{type: :boolean}
+            }
+          },
+          confirmation_pending: %Schema{type: :boolean}
+        })
+    }
+  end
+
+  defp update_request do
+    %Schema{
+      type: :object,
+      required: [:reports],
+      properties: %{
+        reports: %Schema{
+          type: :array,
+          items: %Schema{
+            type: :object,
+            properties: %{
+              id: %Schema{allOf: [FlakeID], description: "Required, report ID"},
+              state: %Schema{
+                type: :string,
+                description:
+                  "Required, the new state. Valid values are `open`, `closed` and `resolved`"
+              }
+            }
+          },
+          example: %{
+            "reports" => [
+              %{"id" => "123", "state" => "closed"},
+              %{"id" => "1337", "state" => "resolved"}
+            ]
+          }
+        }
+      }
+    }
+  end
+
+  defp update_400_response do
+    %Schema{
+      type: :array,
+      items: %Schema{
+        type: :object,
+        properties: %{
+          id: %Schema{allOf: [FlakeID], description: "Report ID"},
+          error: %Schema{type: :string, description: "Error message"}
+        }
+      }
+    }
+  end
+end
index 0b138dc797e7ab1ffeed5578d4ae4bd883479f3e..745399b4b08bcda7472e9589f28e46b3c5038472 100644 (file)
@@ -74,7 +74,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
       parameters: [id_param()],
       security: [%{"oAuth" => ["read:statuses"]}],
       responses: %{
-        200 => Operation.response("Status", "application/json", Status),
+        200 => Operation.response("Status", "application/json", status()),
         404 => Operation.response("Not Found", "application/json", ApiError)
       }
     }
@@ -123,7 +123,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
     }
   end
 
-  defp admin_account do
+  def admin_account do
     %Schema{
       type: :object,
       properties: %{
index d5c335d0c19bb312b41bff9bf1f3eff9e8a05901..bf39ae643267594cd9614881fbfd96b5fc1ea86b 100644 (file)
@@ -137,7 +137,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
         "background_upload_limit" => 4_000_000,
         "background_image" => "/static/image.png",
         "banner_upload_limit" => 4_000_000,
-        "description" => "A Pleroma instance, an alternative fediverse server",
+        "description" => "Pleroma: An efficient and flexible fediverse server",
         "email" => "lain@lain.com",
         "languages" => ["en"],
         "max_toot_chars" => 5000,
index 5a1316a5f166f5871be0cb75e059de9299f96735..5d67d75b5fd09bdb5d0cf793e9be5c3abfeccc3d 100644 (file)
@@ -5,6 +5,8 @@
 defmodule Pleroma.Web.ControllerHelper do
   use Pleroma.Web, :controller
 
+  alias Pleroma.Pagination
+
   # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
   @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
 
@@ -46,43 +48,52 @@ defmodule Pleroma.Web.ControllerHelper do
     do: conn
 
   def add_link_headers(conn, activities, extra_params) do
+    case get_pagination_fields(conn, activities, extra_params) do
+      %{"next" => next_url, "prev" => prev_url} ->
+        put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
+
+      _ ->
+        conn
+    end
+  end
+
+  def get_pagination_fields(conn, activities, extra_params \\ %{}) do
     case List.last(activities) do
       %{id: max_id} ->
         params =
           conn.params
           |> Map.drop(Map.keys(conn.path_params))
-          |> Map.drop(["since_id", "max_id", "min_id"])
           |> Map.merge(extra_params)
-
-        limit =
-          params
-          |> Map.get("limit", "20")
-          |> String.to_integer()
+          |> Map.drop(Pagination.page_keys() -- ["limit", "order"])
 
         min_id =
-          if length(activities) <= limit do
-            activities
-            |> List.first()
-            |> Map.get(:id)
-          else
-            activities
-            |> Enum.at(limit * -1)
-            |> Map.get(:id)
-          end
-
-        next_url = current_url(conn, Map.merge(params, %{max_id: max_id}))
-        prev_url = current_url(conn, Map.merge(params, %{min_id: min_id}))
-
-        put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
+          activities
+          |> List.first()
+          |> Map.get(:id)
+
+        fields = %{
+          "next" => current_url(conn, Map.put(params, :max_id, max_id)),
+          "prev" => current_url(conn, Map.put(params, :min_id, min_id))
+        }
+
+        #  Generating an `id` without already present pagination keys would
+        # need a query-restriction with an `q.id >= ^id` or `q.id <= ^id`
+        # instead of the `q.id > ^min_id` and `q.id < ^max_id`.
+        #  This is because we only have ids present inside of the page, while
+        # `min_id`, `since_id` and `max_id` requires to know one outside of it.
+        if Map.take(conn.params, Pagination.page_keys() -- ["limit", "order"]) != [] do
+          Map.put(fields, "id", current_url(conn, conn.params))
+        else
+          fields
+        end
 
       _ ->
-        conn
+        %{}
     end
   end
 
   def assign_account_by_id(conn, _) do
-    # TODO: use `conn.params[:id]` only after moving to OpenAPI
-    case Pleroma.User.get_cached_by_id(conn.params[:id] || conn.params["id"]) do
+    case Pleroma.User.get_cached_by_id(conn.params.id) do
       %Pleroma.User{} = account -> assign(conn, :account, account)
       nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
     end
@@ -99,11 +110,6 @@ defmodule Pleroma.Web.ControllerHelper do
     render_error(conn, :not_implemented, "Can't display this activity")
   end
 
-  @spec put_if_exist(map(), atom() | String.t(), any) :: map()
-  def put_if_exist(map, _key, nil), do: map
-
-  def put_if_exist(map, key, value), do: Map.put(map, key, value)
-
   @doc """
   Returns true if request specifies to include embedded relationships in account objects.
   May only be used in selected account-related endpoints; has no effect for status- or
diff --git a/lib/pleroma/web/embed_controller.ex b/lib/pleroma/web/embed_controller.ex
new file mode 100644 (file)
index 0000000..f6b8a5e
--- /dev/null
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.EmbedController do
+  use Pleroma.Web, :controller
+
+  alias Pleroma.Activity
+  alias Pleroma.Object
+  alias Pleroma.User
+
+  alias Pleroma.Web.ActivityPub.Visibility
+
+  plug(:put_layout, :embed)
+
+  def show(conn, %{"id" => id}) do
+    with %Activity{local: true} = activity <-
+           Activity.get_by_id_with_object(id),
+         true <- Visibility.is_public?(activity.object) do
+      {:ok, author} = User.get_or_fetch(activity.object.data["actor"])
+
+      conn
+      |> delete_resp_header("x-frame-options")
+      |> delete_resp_header("content-security-policy")
+      |> render("show.html",
+        activity: activity,
+        author: User.sanitize_html(author),
+        counts: get_counts(activity)
+      )
+    end
+  end
+
+  defp get_counts(%Activity{} = activity) do
+    %Object{data: data} = Object.normalize(activity)
+
+    %{
+      likes: Map.get(data, "like_count", 0),
+      replies: Map.get(data, "repliesCount", 0),
+      announces: Map.get(data, "announcement_count", 0)
+    }
+  end
+end
index 8133f8480366bc2c14c6fa36538b0435e3d928d4..39b2a766a503cc25ff8715e435d18ec6917f1c82 100644 (file)
@@ -9,14 +9,12 @@ defmodule Pleroma.Web.Feed.TagController do
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.Feed.FeedView
 
-  import Pleroma.Web.ControllerHelper, only: [put_if_exist: 3]
-
   def feed(conn, %{"tag" => raw_tag} = params) do
     {format, tag} = parse_tag(raw_tag)
 
     activities =
-      %{"type" => ["Create"], "tag" => tag}
-      |> put_if_exist("max_id", params["max_id"])
+      %{type: ["Create"], tag: tag}
+      |> Pleroma.Maps.put_if_present(:max_id, params["max_id"])
       |> ActivityPub.fetch_public_activities()
 
     conn
index 5a6fc9de03b6635ec2a09143768238f83bd0a75e..d56f438184fa920457ad4f8d6ceaf5f5b6b932a4 100644 (file)
@@ -11,8 +11,6 @@ defmodule Pleroma.Web.Feed.UserController do
   alias Pleroma.Web.ActivityPub.ActivityPubController
   alias Pleroma.Web.Feed.FeedView
 
-  import Pleroma.Web.ControllerHelper, only: [put_if_exist: 3]
-
   plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
 
   action_fallback(:errors)
@@ -52,10 +50,10 @@ defmodule Pleroma.Web.Feed.UserController do
     with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
       activities =
         %{
-          "type" => ["Create"],
-          "actor_id" => user.ap_id
+          type: ["Create"],
+          actor_id: user.ap_id
         }
-        |> put_if_exist("max_id", params["max_id"])
+        |> Pleroma.Maps.put_if_present(:max_id, params["max_id"])
         |> ActivityPub.fetch_public_or_unlisted_activities()
 
       conn
index 47649d41dff3e7da67771b06728d8867d9091f7c..7cdd8f458734a95a77ff35a30a9114b396f8c5a0 100644 (file)
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       json_response: 3
     ]
 
+  alias Pleroma.Maps
   alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Plugs.OAuthScopesPlug
   alias Pleroma.Plugs.RateLimiter
@@ -139,9 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   end
 
   @doc "PATCH /api/v1/accounts/update_credentials"
-  def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do
-    user = original_user
-
+  def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _params) do
     params =
       params
       |> Enum.filter(fn {_, value} -> not is_nil(value) end)
@@ -162,41 +161,49 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
         :discoverable
       ]
       |> Enum.reduce(%{}, fn key, acc ->
-        add_if_present(acc, params, key, key, &{:ok, truthy_param?(&1)})
+        Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)})
       end)
-      |> add_if_present(params, :display_name, :name)
-      |> add_if_present(params, :note, :bio)
-      |> add_if_present(params, :avatar, :avatar)
-      |> add_if_present(params, :header, :banner)
-      |> add_if_present(params, :pleroma_background_image, :background)
-      |> add_if_present(
-        params,
-        :fields_attributes,
+      |> Maps.put_if_present(:name, params[:display_name])
+      |> Maps.put_if_present(:bio, params[:note])
+      |> Maps.put_if_present(:avatar, params[:avatar])
+      |> Maps.put_if_present(:banner, params[:header])
+      |> Maps.put_if_present(:background, params[:pleroma_background_image])
+      |> Maps.put_if_present(
         :raw_fields,
+        params[:fields_attributes],
         &{:ok, normalize_fields_attributes(&1)}
       )
-      |> add_if_present(params, :pleroma_settings_store, :pleroma_settings_store)
-      |> add_if_present(params, :default_scope, :default_scope)
-      |> add_if_present(params["source"], "privacy", :default_scope)
-      |> add_if_present(params, :actor_type, :actor_type)
+      |> Maps.put_if_present(:pleroma_settings_store, params[:pleroma_settings_store])
+      |> Maps.put_if_present(:default_scope, params[:default_scope])
+      |> Maps.put_if_present(:default_scope, params["source"]["privacy"])
+      |> Maps.put_if_present(:actor_type, params[:actor_type])
 
     changeset = User.update_changeset(user, user_params)
 
     with {:ok, user} <- User.update_and_set_cache(changeset) do
+      user
+      |> build_update_activity_params()
+      |> ActivityPub.update()
+
       render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
     else
       _e -> render_error(conn, :forbidden, "Invalid request")
     end
   end
 
-  defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
-    with true <- is_map(params),
-         true <- Map.has_key?(params, params_field),
-         {:ok, new_value} <- value_function.(Map.get(params, params_field)) do
-      Map.put(map, map_field, new_value)
-    else
-      _ -> map
-    end
+  # Hotfix, handling will be redone with the pipeline
+  defp build_update_activity_params(user) do
+    object =
+      Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
+      |> Map.delete("@context")
+
+    %{
+      local: true,
+      to: [user.follower_address],
+      cc: [],
+      object: object,
+      actor: user.ap_id
+    }
   end
 
   defp normalize_fields_attributes(fields) do
@@ -237,9 +244,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       params =
         params
         |> Map.delete(:tagged)
-        |> Enum.filter(&(not is_nil(&1)))
-        |> Map.new(fn {key, value} -> {to_string(key), value} end)
-        |> Map.put("tag", params[:tagged])
+        |> Map.put(:tag, params[:tagged])
 
       activities = ActivityPub.fetch_user_activities(user, reading_user, params)
 
index 69f0e384600ae80b9698638da7c4767d141cb6b2..f35ec3596589c902da4e5a79d06ad09291c9dbf6 100644 (file)
@@ -21,7 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
 
   @doc "GET /api/v1/conversations"
   def index(%{assigns: %{user: user}} = conn, params) do
-    params = stringify_pagination_params(params)
     participations = Participation.for_user_with_last_activity_id(user, params)
 
     conn
@@ -37,20 +36,4 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
       render(conn, "participation.json", participation: participation, for: user)
     end
   end
-
-  defp stringify_pagination_params(params) do
-    atom_keys =
-      Pleroma.Pagination.page_keys()
-      |> Enum.map(&String.to_atom(&1))
-
-    str_keys =
-      params
-      |> Map.take(atom_keys)
-      |> Enum.map(fn {key, value} -> {to_string(key), value} end)
-      |> Enum.into(%{})
-
-    params
-    |> Map.delete(atom_keys)
-    |> Map.merge(str_keys)
-  end
 end
index 77e2224e43ec3477a02a22390dfbb5e89a5ef5bc..8840fc19ce790d7c11695035705f11c037687465 100644 (file)
@@ -113,22 +113,44 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
     query
     |> prepare_tags()
     |> Enum.map(fn tag ->
-      tag = String.trim_leading(tag, "#")
       %{name: tag, url: tags_path <> tag}
     end)
   end
 
   defp resource_search(:v1, "hashtags", query, _options) do
-    query
-    |> prepare_tags()
-    |> Enum.map(fn tag -> String.trim_leading(tag, "#") end)
+    prepare_tags(query)
   end
 
-  defp prepare_tags(query) do
-    query
-    |> String.split()
-    |> Enum.uniq()
-    |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
+  defp prepare_tags(query, add_joined_tag \\ true) do
+    tags =
+      query
+      |> String.split(~r/[^#\w]+/u, trim: true)
+      |> Enum.uniq_by(&String.downcase/1)
+
+    explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end)
+
+    tags =
+      if Enum.any?(explicit_tags) do
+        explicit_tags
+      else
+        tags
+      end
+
+    tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
+
+    if Enum.empty?(explicit_tags) && add_joined_tag do
+      tags
+      |> Kernel.++([joined_tag(tags)])
+      |> Enum.uniq_by(&String.downcase/1)
+    else
+      tags
+    end
+  end
+
+  defp joined_tag(tags) do
+    tags
+    |> Enum.map(fn tag -> String.capitalize(tag) end)
+    |> Enum.join()
   end
 
   defp with_fallback(f, fallback \\ []) do
index f20157a5f36f47067c9f158ef5dd63a6eb3d39d1..468b44b6758ea43f89d67fb7603d6c19a1dabb2b 100644 (file)
@@ -359,9 +359,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
     with %Activity{} = activity <- Activity.get_by_id(id) do
       activities =
         ActivityPub.fetch_activities_for_context(activity.data["context"], %{
-          "blocking_user" => user,
-          "user" => user,
-          "exclude_id" => activity.id
+          blocking_user: user,
+          user: user,
+          exclude_id: activity.id
         })
 
       render(conn, "context.json", activity: activity, activities: activities, user: user)
@@ -370,11 +370,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
   @doc "GET /api/v1/favourites"
   def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do
-    params =
-      params
-      |> Map.new(fn {key, value} -> {to_string(key), value} end)
-      |> Map.take(Pleroma.Pagination.page_keys())
-
     activities = ActivityPub.fetch_favourites(user, params)
 
     conn
index 958567510bcc22d26e24c9c8bc456d6e28463476..9270ca267a75f27704646f261a3a5c2150dd1191 100644 (file)
@@ -44,17 +44,14 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
   def home(%{assigns: %{user: user}} = conn, params) do
     params =
       params
-      |> Map.new(fn {key, value} -> {to_string(key), value} end)
-      |> Map.put("type", ["Create", "Announce"])
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> Map.put("reply_filtering_user", user)
-      |> Map.put("user", user)
-
-    recipients = [user.ap_id | User.following(user)]
+      |> Map.put(:type, ["Create", "Announce"])
+      |> Map.put(:blocking_user, user)
+      |> Map.put(:muting_user, user)
+      |> Map.put(:reply_filtering_user, user)
+      |> Map.put(:user, user)
 
     activities =
-      recipients
+      [user.ap_id | User.following(user)]
       |> ActivityPub.fetch_activities(params)
       |> Enum.reverse()
 
@@ -71,10 +68,9 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
   def direct(%{assigns: %{user: user}} = conn, params) do
     params =
       params
-      |> Map.new(fn {key, value} -> {to_string(key), value} end)
-      |> Map.put("type", "Create")
-      |> Map.put("blocking_user", user)
-      |> Map.put("user", user)
+      |> Map.put(:type, "Create")
+      |> Map.put(:blocking_user, user)
+      |> Map.put(:user, user)
       |> Map.put(:visibility, "direct")
 
     activities =
@@ -93,9 +89,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
   # GET /api/v1/timelines/public
   def public(%{assigns: %{user: user}} = conn, params) do
-    params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
-
-    local_only = params["local"]
+    local_only = params[:local]
 
     cfg_key =
       if local_only do
@@ -111,11 +105,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
     else
       activities =
         params
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("local_only", local_only)
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create"])
+        |> Map.put(:local_only, local_only)
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:reply_filtering_user, user)
         |> ActivityPub.fetch_public_activities()
 
       conn
@@ -130,39 +124,38 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
   defp hashtag_fetching(params, user, local_only) do
     tags =
-      [params["tag"], params["any"]]
+      [params[:tag], params[:any]]
       |> List.flatten()
       |> Enum.uniq()
-      |> Enum.filter(& &1)
-      |> Enum.map(&String.downcase(&1))
+      |> Enum.reject(&is_nil/1)
+      |> Enum.map(&String.downcase/1)
 
     tag_all =
       params
-      |> Map.get("all", [])
-      |> Enum.map(&String.downcase(&1))
+      |> Map.get(:all, [])
+      |> Enum.map(&String.downcase/1)
 
     tag_reject =
       params
-      |> Map.get("none", [])
-      |> Enum.map(&String.downcase(&1))
+      |> Map.get(:none, [])
+      |> Enum.map(&String.downcase/1)
 
     _activities =
       params
-      |> Map.put("type", "Create")
-      |> Map.put("local_only", local_only)
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> Map.put("user", user)
-      |> Map.put("tag", tags)
-      |> Map.put("tag_all", tag_all)
-      |> Map.put("tag_reject", tag_reject)
+      |> Map.put(:type, "Create")
+      |> Map.put(:local_only, local_only)
+      |> Map.put(:blocking_user, user)
+      |> Map.put(:muting_user, user)
+      |> Map.put(:user, user)
+      |> Map.put(:tag, tags)
+      |> Map.put(:tag_all, tag_all)
+      |> Map.put(:tag_reject, tag_reject)
       |> ActivityPub.fetch_public_activities()
   end
 
   # GET /api/v1/timelines/tag/:tag
   def hashtag(%{assigns: %{user: user}} = conn, params) do
-    params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
-    local_only = params["local"]
+    local_only = params[:local]
     activities = hashtag_fetching(params, user, local_only)
 
     conn
index 36071cd25dbaa70672bc6cec69335923f16769a7..e44272c6f6309d50ab98cc7f3a05335996528d9b 100644 (file)
@@ -45,10 +45,6 @@ defmodule Pleroma.Web.MastodonAPI.AppView do
   defp with_vapid_key(data) do
     vapid_key = Application.get_env(:web_push_encryption, :vapid_details, [])[:public_key]
 
-    if vapid_key do
-      Map.put(data, "vapid_key", vapid_key)
-    else
-      data
-    end
+    Pleroma.Maps.put_if_present(data, "vapid_key", vapid_key)
   end
 end
index 2b6f84c72bbea703236178cb8c78f2f93eb13c05..fbe61837739162ed5e48562f34cf100c456b4c73 100644 (file)
@@ -24,8 +24,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
     last_activity_id =
       with nil <- participation.last_activity_id do
         ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
-          "user" => user,
-          "blocking_user" => user
+          user: user,
+          blocking_user: user
         })
       end
 
index 458f6bc78636de7ece1bf98c94459631feec6b90..5b896bf3bef532b36de47f551ce81be119c0cf6b 100644 (file)
@@ -30,7 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
   defp with_media_attachments(data, _), do: data
 
   defp status_params(params) do
-    data = %{
+    %{
       text: params["status"],
       sensitive: params["sensitive"],
       spoiler_text: params["spoiler_text"],
@@ -39,10 +39,6 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
       poll: params["poll"],
       in_reply_to_id: params["in_reply_to_id"]
     }
-
-    case params["media_ids"] do
-      nil -> data
-      media_ids -> Map.put(data, :media_ids, media_ids)
-    end
+    |> Pleroma.Maps.put_if_present(:media_ids, params["media_ids"])
   end
 end
index 6a6d5f2e2a40a1dce242af3c3e811e0ac23f8136..df99472e19092d7175c245134ae5d7e6aa8acbfb 100644 (file)
@@ -25,12 +25,12 @@ defmodule Pleroma.Web.OAuth.App do
     timestamps()
   end
 
-  @spec changeset(App.t(), map()) :: Ecto.Changeset.t()
+  @spec changeset(t(), map()) :: Ecto.Changeset.t()
   def changeset(struct, params) do
     cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
   end
 
-  @spec register_changeset(App.t(), map()) :: Ecto.Changeset.t()
+  @spec register_changeset(t(), map()) :: Ecto.Changeset.t()
   def register_changeset(struct, params \\ %{}) do
     changeset =
       struct
@@ -52,18 +52,19 @@ defmodule Pleroma.Web.OAuth.App do
     end
   end
 
-  @spec create(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
   def create(params) do
-    with changeset <- __MODULE__.register_changeset(%__MODULE__{}, params) do
-      Repo.insert(changeset)
-    end
+    %__MODULE__{}
+    |> register_changeset(params)
+    |> Repo.insert()
   end
 
-  @spec update(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
-  def update(params) do
-    with %__MODULE__{} = app <- Repo.get(__MODULE__, params["id"]),
-         changeset <- changeset(app, params) do
-      Repo.update(changeset)
+  @spec update(pos_integer(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+  def update(id, params) do
+    with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
+      app
+      |> changeset(params)
+      |> Repo.update()
     end
   end
 
@@ -71,7 +72,7 @@ defmodule Pleroma.Web.OAuth.App do
   Gets app by attrs or create new  with attrs.
   And updates the scopes if need.
   """
-  @spec get_or_make(map(), list(String.t())) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  @spec get_or_make(map(), list(String.t())) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
   def get_or_make(attrs, scopes) do
     with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do
       update_scopes(app, scopes)
@@ -92,7 +93,7 @@ defmodule Pleroma.Web.OAuth.App do
     |> Repo.update()
   end
 
-  @spec search(map()) :: {:ok, [App.t()], non_neg_integer()}
+  @spec search(map()) :: {:ok, [t()], non_neg_integer()}
   def search(params) do
     query = from(a in __MODULE__)
 
@@ -128,7 +129,7 @@ defmodule Pleroma.Web.OAuth.App do
     {:ok, Repo.all(query), count}
   end
 
-  @spec destroy(pos_integer()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  @spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
   def destroy(id) do
     with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
       Repo.delete(app)
index 7c804233c4460249b83b4d60ded37210b5fc42e8..c557778ca14d3d8f05c80537e94cedf3221ebd62 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   use Pleroma.Web, :controller
 
   alias Pleroma.Helpers.UriHelper
+  alias Pleroma.Maps
   alias Pleroma.MFA
   alias Pleroma.Plugs.RateLimiter
   alias Pleroma.Registration
@@ -108,7 +109,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     if redirect_uri in String.split(app.redirect_uris) do
       redirect_uri = redirect_uri(conn, redirect_uri)
       url_params = %{access_token: token.token}
-      url_params = UriHelper.append_param_if_present(url_params, :state, params["state"])
+      url_params = Maps.put_if_present(url_params, :state, params["state"])
       url = UriHelper.append_uri_params(redirect_uri, url_params)
       redirect(conn, external: url)
     else
@@ -147,7 +148,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     if redirect_uri in String.split(app.redirect_uris) do
       redirect_uri = redirect_uri(conn, redirect_uri)
       url_params = %{code: auth.token}
-      url_params = UriHelper.append_param_if_present(url_params, :state, auth_attrs["state"])
+      url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"])
       url = UriHelper.append_uri_params(redirect_uri, url_params)
       redirect(conn, external: url)
     else
index 0a3f45620581558c01c2378cad3f125671d8d96e..f3554d919990613cc04a1d77d0310ae588916d9d 100644 (file)
@@ -126,10 +126,9 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
   def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
     params =
       params
-      |> Map.new(fn {key, value} -> {to_string(key), value} end)
-      |> Map.put("type", "Create")
-      |> Map.put("favorited_by", user.ap_id)
-      |> Map.put("blocking_user", for_user)
+      |> Map.put(:type, "Create")
+      |> Map.put(:favorited_by, user.ap_id)
+      |> Map.put(:blocking_user, for_user)
 
     recipients =
       if for_user do
index 21d5eb8d5cae109309293500918bb6a4424570ec..3d007f3245136e31e604f530c2bbd0e647889774 100644 (file)
@@ -42,15 +42,14 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do
            Participation.get(participation_id, preload: [:conversation]) do
       params =
         params
-        |> Map.new(fn {key, value} -> {to_string(key), value} end)
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("user", user)
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:user, user)
 
       activities =
         participation.conversation.ap_id
         |> ActivityPub.fetch_activities_for_context_query(params)
-        |> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
+        |> Pleroma.Pagination.fetch_paginated(Map.put(params, :total, false))
         |> Enum.reverse()
 
       conn
index 8665ca56ca667b5525c6cf288478d65d862d9c97..e9a4fba92df552c55179dd32950d32f73a51fce3 100644 (file)
@@ -36,10 +36,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
 
   def index(%{assigns: %{user: reading_user}} = conn, %{id: id} = params) do
     with %User{} = user <- User.get_cached_by_nickname_or_id(id, for: reading_user) do
-      params =
-        params
-        |> Map.new(fn {key, value} -> {to_string(key), value} end)
-        |> Map.put("type", ["Listen"])
+      params = Map.put(params, :type, ["Listen"])
 
       activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params)
 
index e493a41534bb0f8b3a0a11e0eb6ec7714e4fd803..aa272540dc4b7ed0a6d3606b70ba00be25584c8c 100644 (file)
@@ -160,14 +160,14 @@ defmodule Pleroma.Web.Router do
       :right_delete_multiple
     )
 
-    get("/relay", AdminAPIController, :relay_list)
-    post("/relay", AdminAPIController, :relay_follow)
-    delete("/relay", AdminAPIController, :relay_unfollow)
+    get("/relay", RelayController, :index)
+    post("/relay", RelayController, :follow)
+    delete("/relay", RelayController, :unfollow)
 
-    post("/users/invite_token", AdminAPIController, :create_invite_token)
-    get("/users/invites", AdminAPIController, :invites)
-    post("/users/revoke_invite", AdminAPIController, :revoke_invite)
-    post("/users/email_invite", AdminAPIController, :email_invite)
+    post("/users/invite_token", InviteController, :create)
+    get("/users/invites", InviteController, :index)
+    post("/users/revoke_invite", InviteController, :revoke)
+    post("/users/email_invite", InviteController, :email)
 
     get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
     patch("/users/force_password_reset", AdminAPIController, :force_password_reset)
@@ -183,20 +183,20 @@ defmodule Pleroma.Web.Router do
     patch("/users/confirm_email", AdminAPIController, :confirm_email)
     patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
 
-    get("/reports", AdminAPIController, :list_reports)
-    get("/reports/:id", AdminAPIController, :report_show)
-    patch("/reports", AdminAPIController, :reports_update)
-    post("/reports/:id/notes", AdminAPIController, :report_notes_create)
-    delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete)
+    get("/reports", ReportController, :index)
+    get("/reports/:id", ReportController, :show)
+    patch("/reports", ReportController, :update)
+    post("/reports/:id/notes", ReportController, :notes_create)
+    delete("/reports/:report_id/notes/:id", ReportController, :notes_delete)
 
     get("/statuses/:id", StatusController, :show)
     put("/statuses/:id", StatusController, :update)
     delete("/statuses/:id", StatusController, :delete)
     get("/statuses", StatusController, :index)
 
-    get("/config", AdminAPIController, :config_show)
-    post("/config", AdminAPIController, :config_update)
-    get("/config/descriptions", AdminAPIController, :config_descriptions)
+    get("/config", ConfigController, :show)
+    post("/config", ConfigController, :update)
+    get("/config/descriptions", ConfigController, :descriptions)
     get("/need_reboot", AdminAPIController, :need_reboot)
     get("/restart", AdminAPIController, :restart)
 
@@ -205,10 +205,10 @@ defmodule Pleroma.Web.Router do
     post("/reload_emoji", AdminAPIController, :reload_emoji)
     get("/stats", AdminAPIController, :stats)
 
-    get("/oauth_app", AdminAPIController, :oauth_app_list)
-    post("/oauth_app", AdminAPIController, :oauth_app_create)
-    patch("/oauth_app/:id", AdminAPIController, :oauth_app_update)
-    delete("/oauth_app/:id", AdminAPIController, :oauth_app_delete)
+    get("/oauth_app", OAuthAppController, :index)
+    post("/oauth_app", OAuthAppController, :create)
+    patch("/oauth_app/:id", OAuthAppController, :update)
+    delete("/oauth_app/:id", OAuthAppController, :delete)
   end
 
   scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
@@ -571,13 +571,6 @@ defmodule Pleroma.Web.Router do
     get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
   end
 
-  scope "/", Pleroma.Web.ActivityPub do
-    # XXX: not really ostatus
-    pipe_through(:ostatus)
-
-    get("/users/:nickname/outbox", ActivityPubController, :outbox)
-  end
-
   pipeline :ap_service_actor do
     plug(:accepts, ["activity+json", "json"])
   end
@@ -602,6 +595,7 @@ defmodule Pleroma.Web.Router do
     get("/api/ap/whoami", ActivityPubController, :whoami)
     get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
 
+    get("/users/:nickname/outbox", ActivityPubController, :outbox)
     post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
     post("/api/ap/upload_media", ActivityPubController, :upload_media)
 
@@ -664,6 +658,8 @@ defmodule Pleroma.Web.Router do
     post("/auth/password", MastodonAPI.AuthController, :password_reset)
 
     get("/web/*path", MastoFEController, :index)
+
+    get("/embed/:id", EmbedController, :show)
   end
 
   scope "/proxy/", Pleroma.Web.MediaProxy do
index c3efb66513304ac4b254e6d6cc445419a06929f5..a7a891b133ea6e8fd28b7601cb7d31385803f7f0 100644 (file)
@@ -111,8 +111,14 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
       %User{} = user ->
         meta = Metadata.build_tags(%{user: user})
 
+        params =
+          params
+          |> Map.take(@page_keys)
+          |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
+
         timeline =
-          ActivityPub.fetch_user_activities(user, nil, Map.take(params, @page_keys))
+          user
+          |> ActivityPub.fetch_user_activities(nil, params)
           |> Enum.map(&represent/1)
 
         prev_page_id =
diff --git a/lib/pleroma/web/templates/embed/_attachment.html.eex b/lib/pleroma/web/templates/embed/_attachment.html.eex
new file mode 100644 (file)
index 0000000..7e04e95
--- /dev/null
@@ -0,0 +1,8 @@
+<%= case @mediaType do %>
+<% "audio" -> %>
+<audio src="<%= @url %>" controls="controls"></audio>
+<% "video" -> %>
+<video src="<%= @url %>" controls="controls"></video>
+<% _ -> %>
+<img src="<%= @url %>" alt="<%= @name %>" title="<%= @name %>">
+<% end %>
diff --git a/lib/pleroma/web/templates/embed/show.html.eex b/lib/pleroma/web/templates/embed/show.html.eex
new file mode 100644 (file)
index 0000000..05a3f0e
--- /dev/null
@@ -0,0 +1,76 @@
+<div>
+  <div class="p-author h-card">
+    <a class="u-url" rel="author noopener" href="<%= @author.ap_id %>">
+      <div class="avatar">
+        <img src="<%= User.avatar_url(@author) |> MediaProxy.url %>" width="48" height="48" alt="">
+      </div>
+      <span class="display-name" style="padding-left: 0.5em;">
+        <bdi><%= raw (@author.name |> Formatter.emojify(@author.emoji)) %></bdi>
+        <span class="nickname"><%= full_nickname(@author) %></span>
+      </span>
+    </a>
+  </div>
+
+  <div class="activity-content" >
+    <%= if status_title(@activity) != "" do %>
+      <details <%= if open_content?() do %>open<% end %>>
+        <summary><%= raw status_title(@activity) %></summary>
+        <div><%= activity_content(@activity) %></div>
+      </details>
+    <% else %>
+      <div><%= activity_content(@activity) %></div>
+    <% end %>
+    <%= for %{"name" => name, "url" => [url | _]} <- attachments(@activity) do %>
+      <div class="attachment">
+      <%= if sensitive?(@activity) do %>
+        <details class="nsfw">
+          <summary onClick="updateHeight()"><%= Gettext.gettext("sensitive media") %></summary>
+          <div class="nsfw-content">
+            <%= render("_attachment.html", %{name: name, url: url["href"],
+                                             mediaType: fetch_media_type(url)}) %>
+          </div>
+        </details>
+      <% else %>
+        <%= render("_attachment.html", %{name: name, url: url["href"],
+                                         mediaType: fetch_media_type(url)}) %>
+      <% end %>
+      </div>
+    <% end %>
+  </div>
+
+  <dl class="counts pull-right">
+    <dt><%= Gettext.gettext("replies") %></dt><dd><%= @counts.replies %></dd>
+    <dt><%= Gettext.gettext("announces") %></dt><dd><%= @counts.announces %></dd>
+    <dt><%= Gettext.gettext("likes") %></dt><dd><%= @counts.likes %></dd>
+  </dl>
+
+  <p class="date pull-left">
+    <%= link published(@activity), to: activity_url(@author, @activity) %>
+  </p>
+</div>
+
+<script>
+function updateHeight() {
+  window.requestAnimationFrame(function(){
+    var height = document.getElementsByTagName('html')[0].scrollHeight;
+
+    window.parent.postMessage({
+      type: 'setHeightPleromaEmbed',
+      id: window.parentId,
+      height: height,
+    }, '*');
+  })
+}
+
+window.addEventListener('message', function(e){
+  var data = e.data || {};
+
+  if (!window.parent || data.type !== 'setHeightPleromaEmbed') {
+    return;
+  }
+
+  window.parentId = data.id
+
+  updateHeight()
+});
+</script>
diff --git a/lib/pleroma/web/templates/layout/embed.html.eex b/lib/pleroma/web/templates/layout/embed.html.eex
new file mode 100644 (file)
index 0000000..8b905f0
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
+    <title><%= Pleroma.Config.get([:instance, :name]) %></title>
+    <meta content='noindex' name='robots'>
+    <%= Phoenix.HTML.raw(assigns[:meta] || "") %>
+    <link rel="stylesheet" href="/embed.css">
+    <base target="_parent">
+  </head>
+  <body>
+    <%= render @view_module, @view_template, assigns %>
+  </body>
+</html>
diff --git a/lib/pleroma/web/views/embed_view.ex b/lib/pleroma/web/views/embed_view.ex
new file mode 100644 (file)
index 0000000..5f50bd1
--- /dev/null
@@ -0,0 +1,74 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.EmbedView do
+  use Pleroma.Web, :view
+
+  alias Calendar.Strftime
+  alias Pleroma.Activity
+  alias Pleroma.Emoji.Formatter
+  alias Pleroma.Object
+  alias Pleroma.User
+  alias Pleroma.Web.Gettext
+  alias Pleroma.Web.MediaProxy
+  alias Pleroma.Web.Metadata.Utils
+  alias Pleroma.Web.Router.Helpers
+
+  use Phoenix.HTML
+
+  @media_types ["image", "audio", "video"]
+
+  defp fetch_media_type(%{"mediaType" => mediaType}) do
+    Utils.fetch_media_type(@media_types, mediaType)
+  end
+
+  defp open_content? do
+    Pleroma.Config.get(
+      [:frontend_configurations, :collapse_message_with_subjects],
+      true
+    )
+  end
+
+  defp full_nickname(user) do
+    %{host: host} = URI.parse(user.ap_id)
+    "@" <> user.nickname <> "@" <> host
+  end
+
+  defp status_title(%Activity{object: %Object{data: %{"name" => name}}}) when is_binary(name),
+    do: name
+
+  defp status_title(%Activity{object: %Object{data: %{"summary" => summary}}})
+       when is_binary(summary),
+       do: summary
+
+  defp status_title(_), do: nil
+
+  defp activity_content(%Activity{object: %Object{data: %{"content" => content}}}) do
+    content |> Pleroma.HTML.filter_tags() |> raw()
+  end
+
+  defp activity_content(_), do: nil
+
+  defp activity_url(%User{local: true}, activity) do
+    Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
+  end
+
+  defp activity_url(%User{local: false}, %Activity{object: %Object{data: data}}) do
+    data["url"] || data["external_url"] || data["id"]
+  end
+
+  defp attachments(%Activity{object: %Object{data: %{"attachment" => attachments}}}) do
+    attachments
+  end
+
+  defp sensitive?(%Activity{object: %Object{data: %{"sensitive" => sensitive}}}) do
+    sensitive
+  end
+
+  defp published(%Activity{object: %Object{data: %{"published" => published}}}) do
+    published
+    |> NaiveDateTime.from_iso8601!()
+    |> Strftime.strftime!("%B %d, %Y, %l:%M %p")
+  end
+end
index 7e12ff96cf437b405ae10f470c9c0047cda02479..3118f6b5d9eb749145d1c870954d8467be006459 100644 (file)
@@ -3,14 +3,16 @@ msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2020-05-15 09:37+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Automatically generated\n"
-"Language-Team: none\n"
+"PO-Revision-Date: 2020-06-02 07:36+0000\n"
+"Last-Translator: Fristi <fristi@subcon.town>\n"
+"Language-Team: Dutch <https://translate.pleroma.social/projects/pleroma/"
+"pleroma/nl/>\n"
 "Language: nl\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 2.5.1\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.0.4\n"
 
 ## This file is a PO Template file.
 ##
@@ -23,142 +25,142 @@ msgstr ""
 ## effect: edit them in PO (`.po`) files instead.
 ## From Ecto.Changeset.cast/4
 msgid "can't be blank"
-msgstr ""
+msgstr "kan niet leeg zijn"
 
 ## From Ecto.Changeset.unique_constraint/3
 msgid "has already been taken"
-msgstr ""
+msgstr "is al bezet"
 
 ## From Ecto.Changeset.put_change/3
 msgid "is invalid"
-msgstr ""
+msgstr "is ongeldig"
 
 ## From Ecto.Changeset.validate_format/3
 msgid "has invalid format"
-msgstr ""
+msgstr "heeft een ongeldig formaat"
 
 ## From Ecto.Changeset.validate_subset/3
 msgid "has an invalid entry"
-msgstr ""
+msgstr "heeft een ongeldige entry"
 
 ## From Ecto.Changeset.validate_exclusion/3
 msgid "is reserved"
-msgstr ""
+msgstr "is gereserveerd"
 
 ## From Ecto.Changeset.validate_confirmation/3
 msgid "does not match confirmation"
-msgstr ""
+msgstr "komt niet overeen met bevestiging"
 
 ## From Ecto.Changeset.no_assoc_constraint/3
 msgid "is still associated with this entry"
-msgstr ""
+msgstr "is nog geassocieerd met deze entry"
 
 msgid "are still associated with this entry"
-msgstr ""
+msgstr "zijn nog geassocieerd met deze entry"
 
 ## From Ecto.Changeset.validate_length/3
 msgid "should be %{count} character(s)"
 msgid_plural "should be %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dient %{count} karakter te bevatten"
+msgstr[1] "dient %{count} karakters te bevatten"
 
 msgid "should have %{count} item(s)"
 msgid_plural "should have %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dient %{count} item te bevatten"
+msgstr[1] "dient %{count} items te bevatten"
 
 msgid "should be at least %{count} character(s)"
 msgid_plural "should be at least %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dient ten minste %{count} karakter te bevatten"
+msgstr[1] "dient ten minste %{count} karakters te bevatten"
 
 msgid "should have at least %{count} item(s)"
 msgid_plural "should have at least %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dient ten minste %{count} item te bevatten"
+msgstr[1] "dient ten minste %{count} items te bevatten"
 
 msgid "should be at most %{count} character(s)"
 msgid_plural "should be at most %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dient niet meer dan %{count} karakter te bevatten"
+msgstr[1] "dient niet meer dan %{count} karakters te bevatten"
 
 msgid "should have at most %{count} item(s)"
 msgid_plural "should have at most %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dient niet meer dan %{count} item te bevatten"
+msgstr[1] "dient niet meer dan %{count} items te bevatten"
 
 ## From Ecto.Changeset.validate_number/3
 msgid "must be less than %{number}"
-msgstr ""
+msgstr "dient kleiner te zijn dan %{number}"
 
 msgid "must be greater than %{number}"
-msgstr ""
+msgstr "dient groter te zijn dan %{number}"
 
 msgid "must be less than or equal to %{number}"
-msgstr ""
+msgstr "dient kleiner dan of gelijk te zijn aan %{number}"
 
 msgid "must be greater than or equal to %{number}"
-msgstr ""
+msgstr "dient groter dan of gelijk te zijn aan %{number}"
 
 msgid "must be equal to %{number}"
-msgstr ""
+msgstr "dient gelijk te zijn aan %{number}"
 
 #: lib/pleroma/web/common_api/common_api.ex:421
 #, elixir-format
 msgid "Account not found"
-msgstr ""
+msgstr "Account niet gevonden"
 
 #: lib/pleroma/web/common_api/common_api.ex:249
 #, elixir-format
 msgid "Already voted"
-msgstr ""
+msgstr "Al gestemd"
 
 #: lib/pleroma/web/oauth/oauth_controller.ex:360
 #, elixir-format
 msgid "Bad request"
-msgstr ""
+msgstr "Bad request"
 
 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
 #, elixir-format
 msgid "Can't delete object"
-msgstr ""
+msgstr "Object kan niet verwijderd worden"
 
 #: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
 #, elixir-format
 msgid "Can't delete this post"
-msgstr ""
+msgstr "Bericht kan niet verwijderd worden"
 
 #: lib/pleroma/web/controller_helper.ex:95
 #: lib/pleroma/web/controller_helper.ex:101
 #, elixir-format
 msgid "Can't display this activity"
-msgstr ""
+msgstr "Activiteit kan niet worden getoond"
 
 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227
 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
 #, elixir-format
 msgid "Can't find user"
-msgstr ""
+msgstr "Gebruiker kan niet gevonden worden"
 
 #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
 #, elixir-format
 msgid "Can't get favorites"
-msgstr ""
+msgstr "Favorieten konden niet opgehaald worden"
 
 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
 #, elixir-format
 msgid "Can't like object"
-msgstr ""
+msgstr "Object kan niet geliked worden"
 
 #: lib/pleroma/web/common_api/utils.ex:556
 #, elixir-format
 msgid "Cannot post an empty status without attachments"
-msgstr ""
+msgstr "Status kan niet geplaatst worden zonder tekst of bijlagen"
 
 #: lib/pleroma/web/common_api/utils.ex:504
 #, elixir-format
 msgid "Comment must be up to %{max_size} characters"
-msgstr ""
+msgstr "Opmerking dient maximaal %{max_size} karakters te bevatten"
 
 #: lib/pleroma/config/config_db.ex:222
 #, elixir-format
diff --git a/priv/repo/migrations/20200520155351_add_recipients_contain_blocked_domains_function.exs b/priv/repo/migrations/20200520155351_add_recipients_contain_blocked_domains_function.exs
new file mode 100644 (file)
index 0000000..14e8731
--- /dev/null
@@ -0,0 +1,33 @@
+defmodule Pleroma.Repo.Migrations.AddRecipientsContainBlockedDomainsFunction do
+  use Ecto.Migration
+  @disable_ddl_transaction true
+
+  def up do
+    statement = """
+    CREATE OR REPLACE FUNCTION recipients_contain_blocked_domains(recipients varchar[], blocked_domains varchar[]) RETURNS boolean AS $$
+    DECLARE
+      recipient_domain varchar;
+      recipient varchar;
+    BEGIN
+      FOREACH recipient IN ARRAY recipients LOOP
+        recipient_domain = split_part(recipient, '/', 3)::varchar;
+
+        IF recipient_domain = ANY(blocked_domains) THEN
+          RETURN TRUE;
+        END IF;
+      END LOOP;
+
+      RETURN FALSE;
+    END;
+    $$ LANGUAGE plpgsql;
+    """
+
+    execute(statement)
+  end
+
+  def down do
+    execute(
+      "drop function if exists recipients_contain_blocked_domains(recipients varchar[], blocked_domains varchar[])"
+    )
+  end
+end
diff --git a/priv/static/embed.css b/priv/static/embed.css
new file mode 100644 (file)
index 0000000..cc79ee7
--- /dev/null
@@ -0,0 +1,115 @@
+body {
+  background-color: #282c37;
+  font-family: sans-serif;
+  color: white;
+  margin: 0;
+  padding: 1em;
+  padding-bottom: 0;
+}
+
+.avatar {
+  cursor: pointer;
+}
+
+.avatar img {
+  float: left;
+  border-radius: 4px;
+  margin-right: 4px;
+}
+
+.activity-content {
+  padding-top: 1em;
+}
+
+.attachment {
+  margin-top: 1em;
+}
+
+.attachment img {
+  max-width: 100%;
+}
+
+.date a {
+  text-decoration: none;
+}
+
+.date a:hover {
+  text-decoration: underline;
+}
+
+.date a,
+.counts {
+  color: #666;
+  font-size: 0.9em;
+}
+
+.counts dt,
+.counts dd {
+  float: left;
+  margin-left: 1em;
+}
+
+a {
+  color: white;
+}
+
+.h-card {
+  min-height: 48px;
+  margin-bottom: 8px;
+}
+
+.h-card a {
+  text-decoration: none;
+}
+
+.h-card a:hover {
+  text-decoration: underline;
+}
+
+.display-name {
+  padding-top: 4px;
+  display: block;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  color: white;
+}
+
+/* keep emoji from being hilariously huge */
+.display-name img {
+  max-height: 1em;
+}
+
+.display-name .nickname {
+  padding-top: 4px;
+  display: block;
+}
+
+.nickname:hover {
+  text-decoration: none;
+}
+
+.pull-right {
+  float: right;
+}
+
+.collapse {
+  margin: 0;
+  width: auto;
+}
+
+a.button {
+  box-sizing: border-box;
+  display: inline-block;
+  color: white;
+  background-color: #419bdd;
+  border-radius: 4px;
+  border: none;
+  padding: 10px;
+  font-weight: 500;
+  font-size: 0.9em;
+}
+
+a.button:hover {
+  text-decoration: none;
+  background-color: #61a6d9;
+}
diff --git a/priv/static/embed.js b/priv/static/embed.js
new file mode 100644 (file)
index 0000000..f675f64
--- /dev/null
@@ -0,0 +1,43 @@
+(function () {
+  'use strict'
+
+  var ready = function (loaded) {
+    if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {
+      loaded()
+    } else {
+      document.addEventListener('DOMContentLoaded', loaded)
+    }
+  }
+
+  ready(function () {
+    var iframes = []
+
+    window.addEventListener('message', function (e) {
+      var data = e.data || {}
+
+      if (data.type !== 'setHeightPleromaEmbed' || !iframes[data.id]) {
+        return
+      }
+
+      iframes[data.id].height = data.height
+    });
+
+    [].forEach.call(document.querySelectorAll('iframe.pleroma-embed'), function (iframe) {
+      iframe.scrolling = 'no'
+      iframe.style.overflow = 'hidden'
+
+      iframes.push(iframe)
+
+      var id = iframes.length - 1
+
+      iframe.onload = function () {
+        iframe.contentWindow.postMessage({
+          type: 'setHeightPleromaEmbed',
+          id: id
+        }, '*')
+      }
+
+      iframe.onload()
+    })
+  })
+})()
index d5b1b782db6c4be2196124348984bc42547fc234..9165427aee6030584eb4edb199091f86082bcedf 100644 (file)
@@ -21,7 +21,7 @@ defmodule Pleroma.PaginationTest do
       id = Enum.at(notes, 2).id |> Integer.to_string()
 
       %{total: total, items: paginated} =
-        Pagination.fetch_paginated(Object, %{"min_id" => id, "total" => true})
+        Pagination.fetch_paginated(Object, %{min_id: id, total: true})
 
       assert length(paginated) == 2
       assert total == 5
@@ -31,7 +31,7 @@ defmodule Pleroma.PaginationTest do
       id = Enum.at(notes, 2).id |> Integer.to_string()
 
       %{total: total, items: paginated} =
-        Pagination.fetch_paginated(Object, %{"since_id" => id, "total" => true})
+        Pagination.fetch_paginated(Object, %{since_id: id, total: true})
 
       assert length(paginated) == 2
       assert total == 5
@@ -41,7 +41,7 @@ defmodule Pleroma.PaginationTest do
       id = Enum.at(notes, 1).id |> Integer.to_string()
 
       %{total: total, items: paginated} =
-        Pagination.fetch_paginated(Object, %{"max_id" => id, "total" => true})
+        Pagination.fetch_paginated(Object, %{max_id: id, total: true})
 
       assert length(paginated) == 1
       assert total == 5
@@ -50,7 +50,7 @@ defmodule Pleroma.PaginationTest do
     test "paginates by min_id & limit", %{notes: notes} do
       id = Enum.at(notes, 2).id |> Integer.to_string()
 
-      paginated = Pagination.fetch_paginated(Object, %{"min_id" => id, "limit" => 1})
+      paginated = Pagination.fetch_paginated(Object, %{min_id: id, limit: 1})
 
       assert length(paginated) == 1
     end
@@ -64,13 +64,13 @@ defmodule Pleroma.PaginationTest do
     end
 
     test "paginates by limit" do
-      paginated = Pagination.fetch_paginated(Object, %{"limit" => 2}, :offset)
+      paginated = Pagination.fetch_paginated(Object, %{limit: 2}, :offset)
 
       assert length(paginated) == 2
     end
 
     test "paginates by limit & offset" do
-      paginated = Pagination.fetch_paginated(Object, %{"limit" => 2, "offset" => 4}, :offset)
+      paginated = Pagination.fetch_paginated(Object, %{limit: 2, offset: 4}, :offset)
 
       assert length(paginated) == 1
     end
index d3d88467d2cf5e5e15de6d1dee307353694bdbe3..a8ba0658d0d7d8abf46b80ac0c070dbe0b9da477 100644 (file)
@@ -62,10 +62,11 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
 
       [undo_activity] =
         ActivityPub.fetch_activities([], %{
-          "type" => "Undo",
-          "actor_id" => follower_id,
-          "limit" => 1,
-          "skip_preload" => true
+          type: "Undo",
+          actor_id: follower_id,
+          limit: 1,
+          skip_preload: true,
+          invisible_actors: true
         })
 
       assert undo_activity.data["type"] == "Undo"
index 6b344158d8de9cdd05f0bd9f3ff48f3f8709728e..98c79da4f642f7717e8ae12a08d5f27c98cb3b32 100644 (file)
@@ -1122,7 +1122,7 @@ defmodule Pleroma.UserTest do
 
       assert [%{activity | thread_muted?: CommonAPI.thread_muted?(user2, activity)}] ==
                ActivityPub.fetch_activities([user2.ap_id | User.following(user2)], %{
-                 "user" => user2
+                 user: user2
                })
 
       {:ok, _user} = User.deactivate(user)
@@ -1132,7 +1132,7 @@ defmodule Pleroma.UserTest do
 
       assert [] ==
                ActivityPub.fetch_activities([user2.ap_id | User.following(user2)], %{
-                 "user" => user2
+                 user: user2
                })
     end
   end
@@ -1159,6 +1159,9 @@ defmodule Pleroma.UserTest do
       follower = insert(:user)
       {:ok, follower} = User.follow(follower, user)
 
+      locked_user = insert(:user, name: "locked", locked: true)
+      {:ok, _} = User.follow(user, locked_user, :follow_pending)
+
       object = insert(:note, user: user)
       activity = insert(:note_activity, user: user, note: object)
 
@@ -1177,6 +1180,8 @@ defmodule Pleroma.UserTest do
       refute User.following?(follower, user)
       assert %{deactivated: true} = User.get_by_id(user.id)
 
+      assert [] == User.get_follow_requests(locked_user)
+
       user_activities =
         user.ap_id
         |> Activity.Queries.by_actor()
index 24edab41ae4f5595d6aa4baf8c1780dc8b392aad..e490a5744fc75dace9a97e5c9a622e46cad8fb68 100644 (file)
@@ -804,17 +804,63 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
   end
 
   describe "GET /users/:nickname/outbox" do
+    test "it paginates correctly", %{conn: conn} do
+      user = insert(:user)
+      conn = assign(conn, :user, user)
+      outbox_endpoint = user.ap_id <> "/outbox"
+
+      _posts =
+        for i <- 0..25 do
+          {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
+          activity
+        end
+
+      result =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get(outbox_endpoint <> "?page=true")
+        |> json_response(200)
+
+      result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
+      assert length(result["orderedItems"]) == 20
+      assert length(result_ids) == 20
+      assert result["next"]
+      assert String.starts_with?(result["next"], outbox_endpoint)
+
+      result_next =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get(result["next"])
+        |> json_response(200)
+
+      result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
+      assert length(result_next["orderedItems"]) == 6
+      assert length(result_next_ids) == 6
+      refute Enum.find(result_next_ids, fn x -> x in result_ids end)
+      refute Enum.find(result_ids, fn x -> x in result_next_ids end)
+      assert String.starts_with?(result["id"], outbox_endpoint)
+
+      result_next_again =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get(result_next["id"])
+        |> json_response(200)
+
+      assert result_next == result_next_again
+    end
+
     test "it returns 200 even if there're no activities", %{conn: conn} do
       user = insert(:user)
+      outbox_endpoint = user.ap_id <> "/outbox"
 
       conn =
         conn
         |> assign(:user, user)
         |> put_req_header("accept", "application/activity+json")
-        |> get("/users/#{user.nickname}/outbox")
+        |> get(outbox_endpoint)
 
       result = json_response(conn, 200)
-      assert user.ap_id <> "/outbox" == result["id"]
+      assert outbox_endpoint == result["id"]
     end
 
     test "it returns a note activity in a collection", %{conn: conn} do
index 4cd14aa4ab0e0dca8333546632fd69a6b82ac42d..8a21c6fcab3c57653588f75c5d8bab0da919a0dd 100644 (file)
@@ -82,30 +82,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
 
-      activities =
-        ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id})
+      activities = ActivityPub.fetch_activities([], %{visibility: "direct", actor_id: user.ap_id})
 
       assert activities == [direct_activity]
 
       activities =
-        ActivityPub.fetch_activities([], %{:visibility => "unlisted", "actor_id" => user.ap_id})
+        ActivityPub.fetch_activities([], %{visibility: "unlisted", actor_id: user.ap_id})
 
       assert activities == [unlisted_activity]
 
       activities =
-        ActivityPub.fetch_activities([], %{:visibility => "private", "actor_id" => user.ap_id})
+        ActivityPub.fetch_activities([], %{visibility: "private", actor_id: user.ap_id})
 
       assert activities == [private_activity]
 
-      activities =
-        ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})
+      activities = ActivityPub.fetch_activities([], %{visibility: "public", actor_id: user.ap_id})
 
       assert activities == [public_activity]
 
       activities =
         ActivityPub.fetch_activities([], %{
-          :visibility => ~w[private public],
-          "actor_id" => user.ap_id
+          visibility: ~w[private public],
+          actor_id: user.ap_id
         })
 
       assert activities == [public_activity, private_activity]
@@ -126,8 +124,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       activities =
         ActivityPub.fetch_activities([], %{
-          "exclude_visibilities" => "direct",
-          "actor_id" => user.ap_id
+          exclude_visibilities: "direct",
+          actor_id: user.ap_id
         })
 
       assert public_activity in activities
@@ -137,8 +135,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       activities =
         ActivityPub.fetch_activities([], %{
-          "exclude_visibilities" => "unlisted",
-          "actor_id" => user.ap_id
+          exclude_visibilities: "unlisted",
+          actor_id: user.ap_id
         })
 
       assert public_activity in activities
@@ -148,8 +146,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       activities =
         ActivityPub.fetch_activities([], %{
-          "exclude_visibilities" => "private",
-          "actor_id" => user.ap_id
+          exclude_visibilities: "private",
+          actor_id: user.ap_id
         })
 
       assert public_activity in activities
@@ -159,8 +157,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       activities =
         ActivityPub.fetch_activities([], %{
-          "exclude_visibilities" => "public",
-          "actor_id" => user.ap_id
+          exclude_visibilities: "public",
+          actor_id: user.ap_id
         })
 
       refute public_activity in activities
@@ -193,23 +191,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
       {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
 
-      fetch_one = ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => "test"})
+      fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
 
-      fetch_two =
-        ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => ["test", "essais"]})
+      fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
 
       fetch_three =
         ActivityPub.fetch_activities([], %{
-          "type" => "Create",
-          "tag" => ["test", "essais"],
-          "tag_reject" => ["reject"]
+          type: "Create",
+          tag: ["test", "essais"],
+          tag_reject: ["reject"]
         })
 
       fetch_four =
         ActivityPub.fetch_activities([], %{
-          "type" => "Create",
-          "tag" => ["test"],
-          "tag_all" => ["test", "reject"]
+          type: "Create",
+          tag: ["test"],
+          tag_all: ["test", "reject"]
         })
 
       assert fetch_one == [status_one, status_three]
@@ -375,7 +372,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       _listen_activity_2 = insert(:listen)
       _listen_activity_3 = insert(:listen)
 
-      timeline = ActivityPub.fetch_activities([], %{"type" => ["Listen"]})
+      timeline = ActivityPub.fetch_activities([], %{type: ["Listen"]})
 
       assert length(timeline) == 3
     end
@@ -507,7 +504,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
 
-      activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
+      activities = ActivityPub.fetch_activities_for_context("2hu", %{blocking_user: user})
       assert activities == [activity_two, activity]
     end
   end
@@ -520,8 +517,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     booster = insert(:user)
     {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
 
-    activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
 
     assert Enum.member?(activities, activity_two)
     assert Enum.member?(activities, activity_three)
@@ -529,8 +525,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
     {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
 
-    activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
 
     assert Enum.member?(activities, activity_two)
     assert Enum.member?(activities, activity_three)
@@ -541,16 +536,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
     activity_three = Activity.get_by_id(activity_three.id)
 
-    activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
 
     assert Enum.member?(activities, activity_two)
     refute Enum.member?(activities, activity_three)
     refute Enum.member?(activities, boost_activity)
     assert Enum.member?(activities, activity_one)
 
-    activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: nil, skip_preload: true})
 
     assert Enum.member?(activities, activity_two)
     assert Enum.member?(activities, activity_three)
@@ -573,7 +566,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
     {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
 
-    activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: blocker})
 
     assert Enum.member?(activities, activity_one)
     refute Enum.member?(activities, activity_two)
@@ -595,7 +588,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     {:ok, activity_three} = CommonAPI.repeat(activity_two.id, friend)
 
     activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
+      ActivityPub.fetch_activities([], %{blocking_user: blocker})
       |> Enum.map(fn act -> act.id end)
 
     assert Enum.member?(activities, activity_one.id)
@@ -611,8 +604,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     user = insert(:user)
     {:ok, user} = User.block_domain(user, domain)
 
-    activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
 
     refute activity in activities
 
@@ -620,8 +612,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     ActivityPub.follow(user, followed_user)
     {:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
 
-    activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
 
     refute repeat_activity in activities
   end
@@ -641,8 +632,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
     activity = insert(:note_activity, %{note: note})
 
-    activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
 
     assert activity in activities
 
@@ -653,8 +643,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     bad_activity = insert(:note_activity, %{note: bad_note})
     {:ok, repeat_activity} = CommonAPI.repeat(bad_activity.id, domain_user)
 
-    activities =
-      ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
 
     refute repeat_activity in activities
   end
@@ -669,8 +658,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
     {:ok, _user_relationships} = User.mute(user, activity_one_actor)
 
-    activities =
-      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
 
     assert Enum.member?(activities, activity_two)
     assert Enum.member?(activities, activity_three)
@@ -679,9 +667,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     # Calling with 'with_muted' will deliver muted activities, too.
     activities =
       ActivityPub.fetch_activities([], %{
-        "muting_user" => user,
-        "with_muted" => true,
-        "skip_preload" => true
+        muting_user: user,
+        with_muted: true,
+        skip_preload: true
       })
 
     assert Enum.member?(activities, activity_two)
@@ -690,8 +678,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
     {:ok, _user_mute} = User.unmute(user, activity_one_actor)
 
-    activities =
-      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
 
     assert Enum.member?(activities, activity_two)
     assert Enum.member?(activities, activity_three)
@@ -703,15 +690,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
     activity_three = Activity.get_by_id(activity_three.id)
 
-    activities =
-      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
 
     assert Enum.member?(activities, activity_two)
     refute Enum.member?(activities, activity_three)
     refute Enum.member?(activities, boost_activity)
     assert Enum.member?(activities, activity_one)
 
-    activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})
+    activities = ActivityPub.fetch_activities([], %{muting_user: nil, skip_preload: true})
 
     assert Enum.member?(activities, activity_two)
     assert Enum.member?(activities, activity_three)
@@ -727,7 +713,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
     {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
 
-    assert [_activity_one] = ActivityPub.fetch_activities([], %{"muting_user" => user})
+    assert [_activity_one] = ActivityPub.fetch_activities([], %{muting_user: user})
   end
 
   test "returns thread muted activities when with_muted is set" do
@@ -739,7 +725,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
 
     assert [_activity_two, _activity_one] =
-             ActivityPub.fetch_activities([], %{"muting_user" => user, "with_muted" => true})
+             ActivityPub.fetch_activities([], %{muting_user: user, with_muted: true})
   end
 
   test "does include announces on request" do
@@ -761,7 +747,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
     {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
 
-    [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
+    [activity] = ActivityPub.fetch_user_activities(user, nil, %{exclude_reblogs: true})
 
     assert activity == expected_activity
   end
@@ -804,7 +790,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       expected_activities = ActivityBuilder.insert_list(10)
       since_id = List.last(activities).id
 
-      activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id})
+      activities = ActivityPub.fetch_public_activities(%{since_id: since_id})
 
       assert collect_ids(activities) == collect_ids(expected_activities)
       assert length(activities) == 10
@@ -819,7 +805,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
         |> ActivityBuilder.insert_list()
         |> List.first()
 
-      activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id})
+      activities = ActivityPub.fetch_public_activities(%{max_id: max_id})
 
       assert length(activities) == 20
       assert collect_ids(activities) == collect_ids(expected_activities)
@@ -831,8 +817,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       later_activities = ActivityBuilder.insert_list(10)
 
-      activities =
-        ActivityPub.fetch_public_activities(%{"page" => "2", "page_size" => "20"}, :offset)
+      activities = ActivityPub.fetch_public_activities(%{page: "2", page_size: "20"}, :offset)
 
       assert length(activities) == 20
 
@@ -848,7 +833,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       {:ok, activity} = CommonAPI.repeat(activity.id, booster)
 
-      activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
+      activities = ActivityPub.fetch_activities([], %{muting_user: user})
 
       refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
     end
@@ -862,7 +847,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       {:ok, activity} = CommonAPI.repeat(activity.id, booster)
 
-      activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
+      activities = ActivityPub.fetch_activities([], %{muting_user: user})
 
       assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
     end
@@ -1066,7 +1051,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert length(activities) == 3
 
       activities =
-        ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1})
+        ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1})
         |> Enum.map(fn a -> a.id end)
 
       assert [public_activity.id, private_activity_1.id] == activities
@@ -1115,7 +1100,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     CommonAPI.pin(activity_three.id, user)
     user = refresh_record(user)
 
-    activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
+    activities = ActivityPub.fetch_user_activities(user, nil, %{pinned: true})
 
     assert 3 = length(activities)
   end
@@ -1226,7 +1211,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     activity = Repo.preload(activity, :bookmark)
     activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
 
-    assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
+    assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
   end
 
   def data_uri do
@@ -1400,7 +1385,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
       assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
 
-      result = ActivityPub.fetch_favourites(user, %{"limit" => 2})
+      result = ActivityPub.fetch_favourites(user, %{limit: 2})
       assert Enum.map(result, & &1.id) == [a1.id, a5.id]
     end
   end
@@ -1470,7 +1455,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
     {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
 
-    [result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"})
+    [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
 
     assert result.id == activity.id
 
@@ -1483,11 +1468,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     test "public timeline", %{users: %{u1: user}} do
       activities_ids =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("local_only", false)
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:local_only, false)
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:reply_filtering_user, user)
         |> ActivityPub.fetch_public_activities()
         |> Enum.map(& &1.id)
 
@@ -1504,12 +1489,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     } do
       activities_ids =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("local_only", false)
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("reply_visibility", "following")
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:local_only, false)
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:reply_visibility, "following")
+        |> Map.put(:reply_filtering_user, user)
         |> ActivityPub.fetch_public_activities()
         |> Enum.map(& &1.id)
 
@@ -1531,12 +1516,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     } do
       activities_ids =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("local_only", false)
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("reply_visibility", "self")
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:local_only, false)
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:reply_visibility, "self")
+        |> Map.put(:reply_filtering_user, user)
         |> ActivityPub.fetch_public_activities()
         |> Enum.map(& &1.id)
 
@@ -1555,11 +1540,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     } do
       params =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("user", user)
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:user, user)
+        |> Map.put(:reply_filtering_user, user)
 
       activities_ids =
         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
@@ -1593,12 +1578,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     } do
       params =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("user", user)
-        |> Map.put("reply_visibility", "following")
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:user, user)
+        |> Map.put(:reply_visibility, "following")
+        |> Map.put(:reply_filtering_user, user)
 
       activities_ids =
         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
@@ -1632,12 +1617,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     } do
       params =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("user", user)
-        |> Map.put("reply_visibility", "self")
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:user, user)
+        |> Map.put(:reply_visibility, "self")
+        |> Map.put(:reply_filtering_user, user)
 
       activities_ids =
         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
@@ -1666,11 +1651,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     test "public timeline", %{users: %{u1: user}} do
       activities_ids =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("local_only", false)
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:local_only, false)
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:user, user)
         |> ActivityPub.fetch_public_activities()
         |> Enum.map(& &1.id)
 
@@ -1680,13 +1665,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
       activities_ids =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("local_only", false)
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("reply_visibility", "following")
-        |> Map.put("reply_filtering_user", user)
-        |> Map.put("user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:local_only, false)
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:reply_visibility, "following")
+        |> Map.put(:reply_filtering_user, user)
+        |> Map.put(:user, user)
         |> ActivityPub.fetch_public_activities()
         |> Enum.map(& &1.id)
 
@@ -1696,13 +1681,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
       activities_ids =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("local_only", false)
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("reply_visibility", "self")
-        |> Map.put("reply_filtering_user", user)
-        |> Map.put("user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:local_only, false)
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:reply_visibility, "self")
+        |> Map.put(:reply_filtering_user, user)
+        |> Map.put(:user, user)
         |> ActivityPub.fetch_public_activities()
         |> Enum.map(& &1.id)
 
@@ -1712,10 +1697,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     test "home timeline", %{users: %{u1: user}} do
       params =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:user, user)
 
       activities_ids =
         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
@@ -1727,12 +1712,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
       params =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("user", user)
-        |> Map.put("reply_visibility", "following")
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:user, user)
+        |> Map.put(:reply_visibility, "following")
+        |> Map.put(:reply_filtering_user, user)
 
       activities_ids =
         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
@@ -1751,12 +1736,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     } do
       params =
         %{}
-        |> Map.put("type", ["Create", "Announce"])
-        |> Map.put("blocking_user", user)
-        |> Map.put("muting_user", user)
-        |> Map.put("user", user)
-        |> Map.put("reply_visibility", "self")
-        |> Map.put("reply_filtering_user", user)
+        |> Map.put(:type, ["Create", "Announce"])
+        |> Map.put(:blocking_user, user)
+        |> Map.put(:muting_user, user)
+        |> Map.put(:user, user)
+        |> Map.put(:reply_visibility, "self")
+        |> Map.put(:reply_filtering_user, user)
 
       activities_ids =
         ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
index 20b0f223c8b6506d4a14154a0a00eff3e933b3f6..bec15a996fd256ba56300402679a1cef60820b41 100644 (file)
@@ -158,35 +158,4 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
       assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
     end
   end
-
-  test "activity collection page aginates correctly" do
-    user = insert(:user)
-
-    posts =
-      for i <- 0..25 do
-        {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
-        activity
-      end
-
-    # outbox sorts chronologically, newest first, with ten per page
-    posts = Enum.reverse(posts)
-
-    %{"next" => next_url} =
-      UserView.render("activity_collection_page.json", %{
-        iri: "#{user.ap_id}/outbox",
-        activities: Enum.take(posts, 10)
-      })
-
-    next_id = Enum.at(posts, 9).id
-    assert next_url =~ next_id
-
-    %{"next" => next_url} =
-      UserView.render("activity_collection_page.json", %{
-        iri: "#{user.ap_id}/outbox",
-        activities: Enum.take(Enum.drop(posts, 10), 10)
-      })
-
-    next_id = Enum.at(posts, 19).id
-    assert next_url =~ next_id
-  end
 end
index ead840186beba08fca0f38276f48164401bbdcee..bea810c4ac7a4809265dd812ca14caa1720e9967 100644 (file)
@@ -12,15 +12,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   alias Pleroma.Activity
   alias Pleroma.Config
-  alias Pleroma.ConfigDB
   alias Pleroma.HTML
   alias Pleroma.MFA
   alias Pleroma.ModerationLog
   alias Pleroma.Repo
-  alias Pleroma.ReportNote
   alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
-  alias Pleroma.UserInviteToken
   alias Pleroma.Web
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.CommonAPI
@@ -588,122 +585,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "POST /api/pleroma/admin/email_invite, with valid config" do
-    setup do: clear_config([:instance, :registrations_open], false)
-    setup do: clear_config([:instance, :invites_enabled], true)
-
-    test "sends invitation and returns 204", %{admin: admin, conn: conn} do
-      recipient_email = "foo@bar.com"
-      recipient_name = "J. D."
-
-      conn =
-        post(
-          conn,
-          "/api/pleroma/admin/users/email_invite?email=#{recipient_email}&name=#{recipient_name}"
-        )
-
-      assert json_response(conn, :no_content)
-
-      token_record = List.last(Repo.all(Pleroma.UserInviteToken))
-      assert token_record
-      refute token_record.used
-
-      notify_email = Config.get([:instance, :notify_email])
-      instance_name = Config.get([:instance, :name])
-
-      email =
-        Pleroma.Emails.UserEmail.user_invitation_email(
-          admin,
-          token_record,
-          recipient_email,
-          recipient_name
-        )
-
-      Swoosh.TestAssertions.assert_email_sent(
-        from: {instance_name, notify_email},
-        to: {recipient_name, recipient_email},
-        html_body: email.html_body
-      )
-    end
-
-    test "it returns 403 if requested by a non-admin" do
-      non_admin_user = insert(:user)
-      token = insert(:oauth_token, user: non_admin_user)
-
-      conn =
-        build_conn()
-        |> assign(:user, non_admin_user)
-        |> assign(:token, token)
-        |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
-
-      assert json_response(conn, :forbidden)
-    end
-
-    test "email with +", %{conn: conn, admin: admin} do
-      recipient_email = "foo+bar@baz.com"
-
-      conn
-      |> put_req_header("content-type", "application/json;charset=utf-8")
-      |> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email})
-      |> json_response(:no_content)
-
-      token_record =
-        Pleroma.UserInviteToken
-        |> Repo.all()
-        |> List.last()
-
-      assert token_record
-      refute token_record.used
-
-      notify_email = Config.get([:instance, :notify_email])
-      instance_name = Config.get([:instance, :name])
-
-      email =
-        Pleroma.Emails.UserEmail.user_invitation_email(
-          admin,
-          token_record,
-          recipient_email
-        )
-
-      Swoosh.TestAssertions.assert_email_sent(
-        from: {instance_name, notify_email},
-        to: recipient_email,
-        html_body: email.html_body
-      )
-    end
-  end
-
-  describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
-    setup do: clear_config([:instance, :registrations_open])
-    setup do: clear_config([:instance, :invites_enabled])
-
-    test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do
-      Config.put([:instance, :registrations_open], false)
-      Config.put([:instance, :invites_enabled], false)
-
-      conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
-
-      assert json_response(conn, :bad_request) ==
-               %{
-                 "error" =>
-                   "To send invites you need to set the `invites_enabled` option to true."
-               }
-    end
-
-    test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
-      Config.put([:instance, :registrations_open], true)
-      Config.put([:instance, :invites_enabled], true)
-
-      conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
-
-      assert json_response(conn, :bad_request) ==
-               %{
-                 "error" =>
-                   "To send invites you need to set the `registrations_open` option to false."
-               }
-    end
-  end
-
   test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do
     user = insert(:user)
 
@@ -757,8 +638,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "pagination works correctly with service users", %{conn: conn} do
-      service1 = insert(:user, ap_id: Web.base_url() <> "/relay")
-      service2 = insert(:user, ap_id: Web.base_url() <> "/internal/fetch")
+      service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido")
+
       insert_list(25, :user)
 
       assert %{"count" => 26, "page_size" => 10, "users" => users1} =
@@ -767,8 +648,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                |> json_response(200)
 
       assert Enum.count(users1) == 10
-      assert service1 not in [users1]
-      assert service2 not in [users1]
+      assert service1 not in users1
 
       assert %{"count" => 26, "page_size" => 10, "users" => users2} =
                conn
@@ -776,8 +656,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                |> json_response(200)
 
       assert Enum.count(users2) == 10
-      assert service1 not in [users2]
-      assert service2 not in [users2]
+      assert service1 not in users2
 
       assert %{"count" => 26, "page_size" => 10, "users" => users3} =
                conn
@@ -785,8 +664,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                |> json_response(200)
 
       assert Enum.count(users3) == 6
-      assert service1 not in [users3]
-      assert service2 not in [users3]
+      assert service1 not in users3
     end
 
     test "renders empty array for the second page", %{conn: conn} do
@@ -1318,1755 +1196,200 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "POST /api/pleroma/admin/users/invite_token" do
-    test "without options", %{conn: conn} do
-      conn = post(conn, "/api/pleroma/admin/users/invite_token")
-
-      invite_json = json_response(conn, 200)
-      invite = UserInviteToken.find_by_token!(invite_json["token"])
-      refute invite.used
-      refute invite.expires_at
-      refute invite.max_use
-      assert invite.invite_type == "one_time"
-    end
-
-    test "with expires_at", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/users/invite_token", %{
-          "expires_at" => Date.to_string(Date.utc_today())
-        })
+  describe "GET /api/pleroma/admin/restart" do
+    setup do: clear_config(:configurable_from_database, true)
 
-      invite_json = json_response(conn, 200)
-      invite = UserInviteToken.find_by_token!(invite_json["token"])
+    test "pleroma restarts", %{conn: conn} do
+      capture_log(fn ->
+        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+      end) =~ "pleroma restarted"
 
-      refute invite.used
-      assert invite.expires_at == Date.utc_today()
-      refute invite.max_use
-      assert invite.invite_type == "date_limited"
+      refute Restarter.Pleroma.need_reboot?()
     end
+  end
 
-    test "with max_use", %{conn: conn} do
-      conn = post(conn, "/api/pleroma/admin/users/invite_token", %{"max_use" => 150})
+  test "need_reboot flag", %{conn: conn} do
+    assert conn
+           |> get("/api/pleroma/admin/need_reboot")
+           |> json_response(200) == %{"need_reboot" => false}
 
-      invite_json = json_response(conn, 200)
-      invite = UserInviteToken.find_by_token!(invite_json["token"])
-      refute invite.used
-      refute invite.expires_at
-      assert invite.max_use == 150
-      assert invite.invite_type == "reusable"
-    end
+    Restarter.Pleroma.need_reboot()
 
-    test "with max use and expires_at", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/users/invite_token", %{
-          "max_use" => 150,
-          "expires_at" => Date.to_string(Date.utc_today())
-        })
+    assert conn
+           |> get("/api/pleroma/admin/need_reboot")
+           |> json_response(200) == %{"need_reboot" => true}
 
-      invite_json = json_response(conn, 200)
-      invite = UserInviteToken.find_by_token!(invite_json["token"])
-      refute invite.used
-      assert invite.expires_at == Date.utc_today()
-      assert invite.max_use == 150
-      assert invite.invite_type == "reusable_date_limited"
-    end
+    on_exit(fn -> Restarter.Pleroma.refresh() end)
   end
 
-  describe "GET /api/pleroma/admin/users/invites" do
-    test "no invites", %{conn: conn} do
-      conn = get(conn, "/api/pleroma/admin/users/invites")
-
-      assert json_response(conn, 200) == %{"invites" => []}
-    end
+  describe "GET /api/pleroma/admin/users/:nickname/statuses" do
+    setup do
+      user = insert(:user)
 
-    test "with invite", %{conn: conn} do
-      {:ok, invite} = UserInviteToken.create_invite()
+      date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
+      date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
+      date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
 
-      conn = get(conn, "/api/pleroma/admin/users/invites")
+      insert(:note_activity, user: user, published: date1)
+      insert(:note_activity, user: user, published: date2)
+      insert(:note_activity, user: user, published: date3)
 
-      assert json_response(conn, 200) == %{
-               "invites" => [
-                 %{
-                   "expires_at" => nil,
-                   "id" => invite.id,
-                   "invite_type" => "one_time",
-                   "max_use" => nil,
-                   "token" => invite.token,
-                   "used" => false,
-                   "uses" => 0
-                 }
-               ]
-             }
+      %{user: user}
     end
-  end
 
-  describe "POST /api/pleroma/admin/users/revoke_invite" do
-    test "with token", %{conn: conn} do
-      {:ok, invite} = UserInviteToken.create_invite()
-
-      conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token})
+    test "renders user's statuses", %{conn: conn, user: user} do
+      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
 
-      assert json_response(conn, 200) == %{
-               "expires_at" => nil,
-               "id" => invite.id,
-               "invite_type" => "one_time",
-               "max_use" => nil,
-               "token" => invite.token,
-               "used" => true,
-               "uses" => 0
-             }
+      assert json_response(conn, 200) |> length() == 3
     end
 
-    test "with invalid token", %{conn: conn} do
-      conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"})
+    test "renders user's statuses with a limit", %{conn: conn, user: user} do
+      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2")
 
-      assert json_response(conn, :not_found) == %{"error" => "Not found"}
+      assert json_response(conn, 200) |> length() == 2
     end
-  end
 
-  describe "GET /api/pleroma/admin/reports/:id" do
-    test "returns report by its id", %{conn: conn} do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
+    test "doesn't return private statuses by default", %{conn: conn, user: user} do
+      {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
 
-      {:ok, %{id: report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
+      {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
 
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports/#{report_id}")
-        |> json_response(:ok)
+      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
 
-      assert response["id"] == report_id
+      assert json_response(conn, 200) |> length() == 4
     end
 
-    test "returns 404 when report id is invalid", %{conn: conn} do
-      conn = get(conn, "/api/pleroma/admin/reports/test")
-
-      assert json_response(conn, :not_found) == %{"error" => "Not found"}
-    end
-  end
+    test "returns private statuses with godmode on", %{conn: conn, user: user} do
+      {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
 
-  describe "PATCH /api/pleroma/admin/reports" do
-    setup do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
-
-      {:ok, %{id: report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
+      {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
 
-      {:ok, %{id: second_report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel very offended",
-          status_ids: [activity.id]
-        })
+      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
 
-      %{
-        id: report_id,
-        second_report_id: second_report_id
-      }
+      assert json_response(conn, 200) |> length() == 5
     end
 
-    test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
-      read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
-      write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
+    test "excludes reblogs by default", %{conn: conn, user: user} do
+      other_user = insert(:user)
+      {:ok, activity} = CommonAPI.post(user, %{status: "."})
+      {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user)
 
-      response =
-        conn
-        |> assign(:token, read_token)
-        |> patch("/api/pleroma/admin/reports", %{
-          "reports" => [%{"state" => "resolved", "id" => id}]
-        })
-        |> json_response(403)
+      conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses")
+      assert json_response(conn_res, 200) |> length() == 0
 
-      assert response == %{
-               "error" => "Insufficient permissions: admin:write:reports."
-             }
+      conn_res =
+        get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true")
 
-      conn
-      |> assign(:token, write_token)
-      |> patch("/api/pleroma/admin/reports", %{
-        "reports" => [%{"state" => "resolved", "id" => id}]
-      })
-      |> json_response(:no_content)
+      assert json_response(conn_res, 200) |> length() == 1
     end
+  end
 
-    test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
-      conn
-      |> patch("/api/pleroma/admin/reports", %{
-        "reports" => [
-          %{"state" => "resolved", "id" => id}
-        ]
-      })
-      |> json_response(:no_content)
-
-      activity = Activity.get_by_id(id)
-      assert activity.data["state"] == "resolved"
-
-      log_entry = Repo.one(ModerationLog)
+  describe "GET /api/pleroma/admin/moderation_log" do
+    setup do
+      moderator = insert(:user, is_moderator: true)
 
-      assert ModerationLog.get_log_entry_message(log_entry) ==
-               "@#{admin.nickname} updated report ##{id} with 'resolved' state"
+      %{moderator: moderator}
     end
 
-    test "closes report", %{conn: conn, id: id, admin: admin} do
-      conn
-      |> patch("/api/pleroma/admin/reports", %{
-        "reports" => [
-          %{"state" => "closed", "id" => id}
-        ]
+    test "returns the log", %{conn: conn, admin: admin} do
+      Repo.insert(%ModerationLog{
+        data: %{
+          actor: %{
+            "id" => admin.id,
+            "nickname" => admin.nickname,
+            "type" => "user"
+          },
+          action: "relay_follow",
+          target: "https://example.org/relay"
+        },
+        inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
       })
-      |> json_response(:no_content)
 
-      activity = Activity.get_by_id(id)
-      assert activity.data["state"] == "closed"
+      Repo.insert(%ModerationLog{
+        data: %{
+          actor: %{
+            "id" => admin.id,
+            "nickname" => admin.nickname,
+            "type" => "user"
+          },
+          action: "relay_unfollow",
+          target: "https://example.org/relay"
+        },
+        inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
+      })
 
-      log_entry = Repo.one(ModerationLog)
+      conn = get(conn, "/api/pleroma/admin/moderation_log")
 
-      assert ModerationLog.get_log_entry_message(log_entry) ==
-               "@#{admin.nickname} updated report ##{id} with 'closed' state"
-    end
+      response = json_response(conn, 200)
+      [first_entry, second_entry] = response["items"]
 
-    test "returns 400 when state is unknown", %{conn: conn, id: id} do
-      conn =
-        conn
-        |> patch("/api/pleroma/admin/reports", %{
-          "reports" => [
-            %{"state" => "test", "id" => id}
-          ]
-        })
+      assert response["total"] == 2
+      assert first_entry["data"]["action"] == "relay_unfollow"
 
-      assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state"
-    end
+      assert first_entry["message"] ==
+               "@#{admin.nickname} unfollowed relay: https://example.org/relay"
 
-    test "returns 404 when report is not exist", %{conn: conn} do
-      conn =
-        conn
-        |> patch("/api/pleroma/admin/reports", %{
-          "reports" => [
-            %{"state" => "closed", "id" => "test"}
-          ]
-        })
+      assert second_entry["data"]["action"] == "relay_follow"
 
-      assert hd(json_response(conn, :bad_request))["error"] == "not_found"
+      assert second_entry["message"] ==
+               "@#{admin.nickname} followed relay: https://example.org/relay"
     end
 
-    test "updates state of multiple reports", %{
-      conn: conn,
-      id: id,
-      admin: admin,
-      second_report_id: second_report_id
-    } do
-      conn
-      |> patch("/api/pleroma/admin/reports", %{
-        "reports" => [
-          %{"state" => "resolved", "id" => id},
-          %{"state" => "closed", "id" => second_report_id}
-        ]
+    test "returns the log with pagination", %{conn: conn, admin: admin} do
+      Repo.insert(%ModerationLog{
+        data: %{
+          actor: %{
+            "id" => admin.id,
+            "nickname" => admin.nickname,
+            "type" => "user"
+          },
+          action: "relay_follow",
+          target: "https://example.org/relay"
+        },
+        inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
       })
-      |> json_response(:no_content)
 
-      activity = Activity.get_by_id(id)
-      second_activity = Activity.get_by_id(second_report_id)
-      assert activity.data["state"] == "resolved"
-      assert second_activity.data["state"] == "closed"
-
-      [first_log_entry, second_log_entry] = Repo.all(ModerationLog)
-
-      assert ModerationLog.get_log_entry_message(first_log_entry) ==
-               "@#{admin.nickname} updated report ##{id} with 'resolved' state"
-
-      assert ModerationLog.get_log_entry_message(second_log_entry) ==
-               "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state"
-    end
-  end
+      Repo.insert(%ModerationLog{
+        data: %{
+          actor: %{
+            "id" => admin.id,
+            "nickname" => admin.nickname,
+            "type" => "user"
+          },
+          action: "relay_unfollow",
+          target: "https://example.org/relay"
+        },
+        inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
+      })
 
-  describe "GET /api/pleroma/admin/reports" do
-    test "returns empty response when no reports created", %{conn: conn} do
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports")
-        |> json_response(:ok)
+      conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1")
 
-      assert Enum.empty?(response["reports"])
-      assert response["total"] == 0
-    end
+      response1 = json_response(conn1, 200)
+      [first_entry] = response1["items"]
 
-    test "returns reports", %{conn: conn} do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
+      assert response1["total"] == 2
+      assert response1["items"] |> length() == 1
+      assert first_entry["data"]["action"] == "relay_unfollow"
 
-      {:ok, %{id: report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
+      assert first_entry["message"] ==
+               "@#{admin.nickname} unfollowed relay: https://example.org/relay"
 
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports")
-        |> json_response(:ok)
+      conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2")
 
-      [report] = response["reports"]
+      response2 = json_response(conn2, 200)
+      [second_entry] = response2["items"]
 
-      assert length(response["reports"]) == 1
-      assert report["id"] == report_id
+      assert response2["total"] == 2
+      assert response2["items"] |> length() == 1
+      assert second_entry["data"]["action"] == "relay_follow"
 
-      assert response["total"] == 1
+      assert second_entry["message"] ==
+               "@#{admin.nickname} followed relay: https://example.org/relay"
     end
 
-    test "returns reports with specified state", %{conn: conn} do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
-
-      {:ok, %{id: first_report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
-
-      {:ok, %{id: second_report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I don't like this user"
-        })
-
-      CommonAPI.update_report_state(second_report_id, "closed")
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports", %{
-          "state" => "open"
-        })
-        |> json_response(:ok)
-
-      [open_report] = response["reports"]
-
-      assert length(response["reports"]) == 1
-      assert open_report["id"] == first_report_id
-
-      assert response["total"] == 1
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports", %{
-          "state" => "closed"
-        })
-        |> json_response(:ok)
-
-      [closed_report] = response["reports"]
-
-      assert length(response["reports"]) == 1
-      assert closed_report["id"] == second_report_id
-
-      assert response["total"] == 1
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports", %{
-          "state" => "resolved"
-        })
-        |> json_response(:ok)
-
-      assert Enum.empty?(response["reports"])
-      assert response["total"] == 0
-    end
-
-    test "returns 403 when requested by a non-admin" do
-      user = insert(:user)
-      token = insert(:oauth_token, user: user)
-
-      conn =
-        build_conn()
-        |> assign(:user, user)
-        |> assign(:token, token)
-        |> get("/api/pleroma/admin/reports")
-
-      assert json_response(conn, :forbidden) ==
-               %{"error" => "User is not an admin or OAuth admin scope is not granted."}
-    end
-
-    test "returns 403 when requested by anonymous" do
-      conn = get(build_conn(), "/api/pleroma/admin/reports")
-
-      assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
-    end
-  end
-
-  describe "GET /api/pleroma/admin/config" do
-    setup do: clear_config(:configurable_from_database, true)
-
-    test "when configuration from database is off", %{conn: conn} do
-      Config.put(:configurable_from_database, false)
-      conn = get(conn, "/api/pleroma/admin/config")
-
-      assert json_response(conn, 400) ==
-               %{
-                 "error" => "To use this endpoint you need to enable configuration from database."
-               }
-    end
-
-    test "with settings only in db", %{conn: conn} do
-      config1 = insert(:config)
-      config2 = insert(:config)
-
-      conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true})
-
-      %{
-        "configs" => [
-          %{
-            "group" => ":pleroma",
-            "key" => key1,
-            "value" => _
-          },
-          %{
-            "group" => ":pleroma",
-            "key" => key2,
-            "value" => _
-          }
-        ]
-      } = json_response(conn, 200)
-
-      assert key1 == config1.key
-      assert key2 == config2.key
-    end
-
-    test "db is added to settings that are in db", %{conn: conn} do
-      _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
-
-      %{"configs" => configs} =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      [instance_config] =
-        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
-          group == ":pleroma" and key == ":instance"
-        end)
-
-      assert instance_config["db"] == [":name"]
-    end
-
-    test "merged default setting with db settings", %{conn: conn} do
-      config1 = insert(:config)
-      config2 = insert(:config)
-
-      config3 =
-        insert(:config,
-          value: ConfigDB.to_binary(k1: :v1, k2: :v2)
-        )
-
-      %{"configs" => configs} =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      assert length(configs) > 3
-
-      received_configs =
-        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
-          group == ":pleroma" and key in [config1.key, config2.key, config3.key]
-        end)
-
-      assert length(received_configs) == 3
-
-      db_keys =
-        config3.value
-        |> ConfigDB.from_binary()
-        |> Keyword.keys()
-        |> ConfigDB.convert()
-
-      Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
-        assert db in [[config1.key], [config2.key], db_keys]
-
-        assert value in [
-                 ConfigDB.from_binary_with_convert(config1.value),
-                 ConfigDB.from_binary_with_convert(config2.value),
-                 ConfigDB.from_binary_with_convert(config3.value)
-               ]
-      end)
-    end
-
-    test "subkeys with full update right merge", %{conn: conn} do
-      config1 =
-        insert(:config,
-          key: ":emoji",
-          value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
-        )
-
-      config2 =
-        insert(:config,
-          key: ":assets",
-          value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
-        )
-
-      %{"configs" => configs} =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      vals =
-        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
-          group == ":pleroma" and key in [config1.key, config2.key]
-        end)
-
-      emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
-      assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
-
-      emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
-      assets_val = ConfigDB.transform_with_out_binary(assets["value"])
-
-      assert emoji_val[:groups] == [a: 1, b: 2]
-      assert assets_val[:mascots] == [a: 1, b: 2]
-    end
-  end
-
-  test "POST /api/pleroma/admin/config error", %{conn: conn} do
-    conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []})
-
-    assert json_response(conn, 400) ==
-             %{"error" => "To use this endpoint you need to enable configuration from database."}
-  end
-
-  describe "POST /api/pleroma/admin/config" do
-    setup do
-      http = Application.get_env(:pleroma, :http)
-
-      on_exit(fn ->
-        Application.delete_env(:pleroma, :key1)
-        Application.delete_env(:pleroma, :key2)
-        Application.delete_env(:pleroma, :key3)
-        Application.delete_env(:pleroma, :key4)
-        Application.delete_env(:pleroma, :keyaa1)
-        Application.delete_env(:pleroma, :keyaa2)
-        Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
-        Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
-        Application.put_env(:pleroma, :http, http)
-        Application.put_env(:tesla, :adapter, Tesla.Mock)
-        Restarter.Pleroma.refresh()
-      end)
-    end
-
-    setup do: clear_config(:configurable_from_database, true)
-
-    @tag capture_log: true
-    test "create new config setting in db", %{conn: conn} do
-      ueberauth = Application.get_env(:ueberauth, Ueberauth)
-      on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{group: ":pleroma", key: ":key1", value: "value1"},
-            %{
-              group: ":ueberauth",
-              key: "Ueberauth",
-              value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
-            },
-            %{
-              group: ":pleroma",
-              key: ":key2",
-              value: %{
-                ":nested_1" => "nested_value1",
-                ":nested_2" => [
-                  %{":nested_22" => "nested_value222"},
-                  %{":nested_33" => %{":nested_44" => "nested_444"}}
-                ]
-              }
-            },
-            %{
-              group: ":pleroma",
-              key: ":key3",
-              value: [
-                %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
-                %{"nested_4" => true}
-              ]
-            },
-            %{
-              group: ":pleroma",
-              key: ":key4",
-              value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
-            },
-            %{
-              group: ":idna",
-              key: ":key5",
-              value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => "value1",
-                   "db" => [":key1"]
-                 },
-                 %{
-                   "group" => ":ueberauth",
-                   "key" => "Ueberauth",
-                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
-                   "db" => [":consumer_secret"]
-                 },
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key2",
-                   "value" => %{
-                     ":nested_1" => "nested_value1",
-                     ":nested_2" => [
-                       %{":nested_22" => "nested_value222"},
-                       %{":nested_33" => %{":nested_44" => "nested_444"}}
-                     ]
-                   },
-                   "db" => [":key2"]
-                 },
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key3",
-                   "value" => [
-                     %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
-                     %{"nested_4" => true}
-                   ],
-                   "db" => [":key3"]
-                 },
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key4",
-                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
-                   "db" => [":key4"]
-                 },
-                 %{
-                   "group" => ":idna",
-                   "key" => ":key5",
-                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
-                   "db" => [":key5"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:pleroma, :key1) == "value1"
-
-      assert Application.get_env(:pleroma, :key2) == %{
-               nested_1: "nested_value1",
-               nested_2: [
-                 %{nested_22: "nested_value222"},
-                 %{nested_33: %{nested_44: "nested_444"}}
-               ]
-             }
-
-      assert Application.get_env(:pleroma, :key3) == [
-               %{"nested_3" => :nested_3, "nested_33" => "nested_33"},
-               %{"nested_4" => true}
-             ]
-
-      assert Application.get_env(:pleroma, :key4) == %{
-               "endpoint" => "https://example.com",
-               nested_5: :upload
-             }
-
-      assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
-    end
-
-    test "save configs setting without explicit key", %{conn: conn} do
-      level = Application.get_env(:quack, :level)
-      meta = Application.get_env(:quack, :meta)
-      webhook_url = Application.get_env(:quack, :webhook_url)
-
-      on_exit(fn ->
-        Application.put_env(:quack, :level, level)
-        Application.put_env(:quack, :meta, meta)
-        Application.put_env(:quack, :webhook_url, webhook_url)
-      end)
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: ":quack",
-              key: ":level",
-              value: ":info"
-            },
-            %{
-              group: ":quack",
-              key: ":meta",
-              value: [":none"]
-            },
-            %{
-              group: ":quack",
-              key: ":webhook_url",
-              value: "https://hooks.slack.com/services/KEY"
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":quack",
-                   "key" => ":level",
-                   "value" => ":info",
-                   "db" => [":level"]
-                 },
-                 %{
-                   "group" => ":quack",
-                   "key" => ":meta",
-                   "value" => [":none"],
-                   "db" => [":meta"]
-                 },
-                 %{
-                   "group" => ":quack",
-                   "key" => ":webhook_url",
-                   "value" => "https://hooks.slack.com/services/KEY",
-                   "db" => [":webhook_url"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:quack, :level) == :info
-      assert Application.get_env(:quack, :meta) == [:none]
-      assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
-    end
-
-    test "saving config with partial update", %{conn: conn} do
-      config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => [
-                     %{"tuple" => [":key1", 1]},
-                     %{"tuple" => [":key2", 2]},
-                     %{"tuple" => [":key3", 3]}
-                   ],
-                   "db" => [":key1", ":key2", ":key3"]
-                 }
-               ]
-             }
-    end
-
-    test "saving config which need pleroma reboot", %{conn: conn} do
-      chat = Config.get(:chat)
-      on_exit(fn -> Config.put(:chat, chat) end)
-
-      assert post(
-               conn,
-               "/api/pleroma/admin/config",
-               %{
-                 configs: [
-                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
-                 ]
-               }
-             )
-             |> json_response(200) == %{
-               "configs" => [
-                 %{
-                   "db" => [":enabled"],
-                   "group" => ":pleroma",
-                   "key" => ":chat",
-                   "value" => [%{"tuple" => [":enabled", true]}]
-                 }
-               ],
-               "need_reboot" => true
-             }
-
-      configs =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      assert configs["need_reboot"]
-
-      capture_log(fn ->
-        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
-      end) =~ "pleroma restarted"
-
-      configs =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      assert configs["need_reboot"] == false
-    end
-
-    test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
-      chat = Config.get(:chat)
-      on_exit(fn -> Config.put(:chat, chat) end)
-
-      assert post(
-               conn,
-               "/api/pleroma/admin/config",
-               %{
-                 configs: [
-                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
-                 ]
-               }
-             )
-             |> json_response(200) == %{
-               "configs" => [
-                 %{
-                   "db" => [":enabled"],
-                   "group" => ":pleroma",
-                   "key" => ":chat",
-                   "value" => [%{"tuple" => [":enabled", true]}]
-                 }
-               ],
-               "need_reboot" => true
-             }
-
-      assert post(conn, "/api/pleroma/admin/config", %{
-               configs: [
-                 %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
-               ]
-             })
-             |> json_response(200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => [
-                     %{"tuple" => [":key3", 3]}
-                   ],
-                   "db" => [":key3"]
-                 }
-               ],
-               "need_reboot" => true
-             }
-
-      capture_log(fn ->
-        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
-      end) =~ "pleroma restarted"
-
-      configs =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      assert configs["need_reboot"] == false
-    end
-
-    test "saving config with nested merge", %{conn: conn} do
-      config =
-        insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: config.group,
-              key: config.key,
-              value: [
-                %{"tuple" => [":key3", 3]},
-                %{
-                  "tuple" => [
-                    ":key2",
-                    [
-                      %{"tuple" => [":k2", 1]},
-                      %{"tuple" => [":k3", 3]}
-                    ]
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => [
-                     %{"tuple" => [":key1", 1]},
-                     %{"tuple" => [":key3", 3]},
-                     %{
-                       "tuple" => [
-                         ":key2",
-                         [
-                           %{"tuple" => [":k1", 1]},
-                           %{"tuple" => [":k2", 1]},
-                           %{"tuple" => [":k3", 3]}
-                         ]
-                       ]
-                     }
-                   ],
-                   "db" => [":key1", ":key3", ":key2"]
-                 }
-               ]
-             }
-    end
-
-    test "saving special atoms", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          "configs" => [
-            %{
-              "group" => ":pleroma",
-              "key" => ":key1",
-              "value" => [
-                %{
-                  "tuple" => [
-                    ":ssl_options",
-                    [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => [
-                     %{
-                       "tuple" => [
-                         ":ssl_options",
-                         [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
-                       ]
-                     }
-                   ],
-                   "db" => [":ssl_options"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:pleroma, :key1) == [
-               ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
-             ]
-    end
-
-    test "saving full setting if value is in full_key_update list", %{conn: conn} do
-      backends = Application.get_env(:logger, :backends)
-      on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
-
-      config =
-        insert(:config,
-          group: ":logger",
-          key: ":backends",
-          value: :erlang.term_to_binary([])
-        )
-
-      Pleroma.Config.TransferTask.load_and_update_env([], false)
-
-      assert Application.get_env(:logger, :backends) == []
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: config.group,
-              key: config.key,
-              value: [":console"]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":logger",
-                   "key" => ":backends",
-                   "value" => [
-                     ":console"
-                   ],
-                   "db" => [":backends"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:logger, :backends) == [
-               :console
-             ]
-    end
-
-    test "saving full setting if value is not keyword", %{conn: conn} do
-      config =
-        insert(:config,
-          group: ":tesla",
-          key: ":adapter",
-          value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
-        )
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":tesla",
-                   "key" => ":adapter",
-                   "value" => "Tesla.Adapter.Httpc",
-                   "db" => [":adapter"]
-                 }
-               ]
-             }
-    end
-
-    test "update config setting & delete with fallback to default value", %{
-      conn: conn,
-      admin: admin,
-      token: token
-    } do
-      ueberauth = Application.get_env(:ueberauth, Ueberauth)
-      config1 = insert(:config, key: ":keyaa1")
-      config2 = insert(:config, key: ":keyaa2")
-
-      config3 =
-        insert(:config,
-          group: ":ueberauth",
-          key: "Ueberauth"
-        )
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{group: config1.group, key: config1.key, value: "another_value"},
-            %{group: config2.group, key: config2.key, value: "another_value"}
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => config1.key,
-                   "value" => "another_value",
-                   "db" => [":keyaa1"]
-                 },
-                 %{
-                   "group" => ":pleroma",
-                   "key" => config2.key,
-                   "value" => "another_value",
-                   "db" => [":keyaa2"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:pleroma, :keyaa1) == "another_value"
-      assert Application.get_env(:pleroma, :keyaa2) == "another_value"
-      assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
-
-      conn =
-        build_conn()
-        |> assign(:user, admin)
-        |> assign(:token, token)
-        |> post("/api/pleroma/admin/config", %{
-          configs: [
-            %{group: config2.group, key: config2.key, delete: true},
-            %{
-              group: ":ueberauth",
-              key: "Ueberauth",
-              delete: true
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => []
-             }
-
-      assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
-      refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
-    end
-
-    test "common config example", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":pleroma",
-              "key" => "Pleroma.Captcha.NotReal",
-              "value" => [
-                %{"tuple" => [":enabled", false]},
-                %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
-                %{"tuple" => [":seconds_valid", 60]},
-                %{"tuple" => [":path", ""]},
-                %{"tuple" => [":key1", nil]},
-                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
-                %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
-                %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
-                %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
-                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
-                %{"tuple" => [":name", "Pleroma"]}
-              ]
-            }
-          ]
-        })
-
-      assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => "Pleroma.Captcha.NotReal",
-                   "value" => [
-                     %{"tuple" => [":enabled", false]},
-                     %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
-                     %{"tuple" => [":seconds_valid", 60]},
-                     %{"tuple" => [":path", ""]},
-                     %{"tuple" => [":key1", nil]},
-                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
-                     %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
-                     %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
-                     %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
-                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
-                     %{"tuple" => [":name", "Pleroma"]}
-                   ],
-                   "db" => [
-                     ":enabled",
-                     ":method",
-                     ":seconds_valid",
-                     ":path",
-                     ":key1",
-                     ":partial_chain",
-                     ":regex1",
-                     ":regex2",
-                     ":regex3",
-                     ":regex4",
-                     ":name"
-                   ]
-                 }
-               ]
-             }
-    end
-
-    test "tuples with more than two values", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":pleroma",
-              "key" => "Pleroma.Web.Endpoint.NotReal",
-              "value" => [
-                %{
-                  "tuple" => [
-                    ":http",
-                    [
-                      %{
-                        "tuple" => [
-                          ":key2",
-                          [
-                            %{
-                              "tuple" => [
-                                ":_",
-                                [
-                                  %{
-                                    "tuple" => [
-                                      "/api/v1/streaming",
-                                      "Pleroma.Web.MastodonAPI.WebsocketHandler",
-                                      []
-                                    ]
-                                  },
-                                  %{
-                                    "tuple" => [
-                                      "/websocket",
-                                      "Phoenix.Endpoint.CowboyWebSocket",
-                                      %{
-                                        "tuple" => [
-                                          "Phoenix.Transports.WebSocket",
-                                          %{
-                                            "tuple" => [
-                                              "Pleroma.Web.Endpoint",
-                                              "Pleroma.Web.UserSocket",
-                                              []
-                                            ]
-                                          }
-                                        ]
-                                      }
-                                    ]
-                                  },
-                                  %{
-                                    "tuple" => [
-                                      ":_",
-                                      "Phoenix.Endpoint.Cowboy2Handler",
-                                      %{"tuple" => ["Pleroma.Web.Endpoint", []]}
-                                    ]
-                                  }
-                                ]
-                              ]
-                            }
-                          ]
-                        ]
-                      }
-                    ]
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => "Pleroma.Web.Endpoint.NotReal",
-                   "value" => [
-                     %{
-                       "tuple" => [
-                         ":http",
-                         [
-                           %{
-                             "tuple" => [
-                               ":key2",
-                               [
-                                 %{
-                                   "tuple" => [
-                                     ":_",
-                                     [
-                                       %{
-                                         "tuple" => [
-                                           "/api/v1/streaming",
-                                           "Pleroma.Web.MastodonAPI.WebsocketHandler",
-                                           []
-                                         ]
-                                       },
-                                       %{
-                                         "tuple" => [
-                                           "/websocket",
-                                           "Phoenix.Endpoint.CowboyWebSocket",
-                                           %{
-                                             "tuple" => [
-                                               "Phoenix.Transports.WebSocket",
-                                               %{
-                                                 "tuple" => [
-                                                   "Pleroma.Web.Endpoint",
-                                                   "Pleroma.Web.UserSocket",
-                                                   []
-                                                 ]
-                                               }
-                                             ]
-                                           }
-                                         ]
-                                       },
-                                       %{
-                                         "tuple" => [
-                                           ":_",
-                                           "Phoenix.Endpoint.Cowboy2Handler",
-                                           %{"tuple" => ["Pleroma.Web.Endpoint", []]}
-                                         ]
-                                       }
-                                     ]
-                                   ]
-                                 }
-                               ]
-                             ]
-                           }
-                         ]
-                       ]
-                     }
-                   ],
-                   "db" => [":http"]
-                 }
-               ]
-             }
-    end
-
-    test "settings with nesting map", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":pleroma",
-              "key" => ":key1",
-              "value" => [
-                %{"tuple" => [":key2", "some_val"]},
-                %{
-                  "tuple" => [
-                    ":key3",
-                    %{
-                      ":max_options" => 20,
-                      ":max_option_chars" => 200,
-                      ":min_expiration" => 0,
-                      ":max_expiration" => 31_536_000,
-                      "nested" => %{
-                        ":max_options" => 20,
-                        ":max_option_chars" => 200,
-                        ":min_expiration" => 0,
-                        ":max_expiration" => 31_536_000
-                      }
-                    }
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) ==
-               %{
-                 "configs" => [
-                   %{
-                     "group" => ":pleroma",
-                     "key" => ":key1",
-                     "value" => [
-                       %{"tuple" => [":key2", "some_val"]},
-                       %{
-                         "tuple" => [
-                           ":key3",
-                           %{
-                             ":max_expiration" => 31_536_000,
-                             ":max_option_chars" => 200,
-                             ":max_options" => 20,
-                             ":min_expiration" => 0,
-                             "nested" => %{
-                               ":max_expiration" => 31_536_000,
-                               ":max_option_chars" => 200,
-                               ":max_options" => 20,
-                               ":min_expiration" => 0
-                             }
-                           }
-                         ]
-                       }
-                     ],
-                     "db" => [":key2", ":key3"]
-                   }
-                 ]
-               }
-    end
-
-    test "value as map", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":pleroma",
-              "key" => ":key1",
-              "value" => %{"key" => "some_val"}
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) ==
-               %{
-                 "configs" => [
-                   %{
-                     "group" => ":pleroma",
-                     "key" => ":key1",
-                     "value" => %{"key" => "some_val"},
-                     "db" => [":key1"]
-                   }
-                 ]
-               }
-    end
-
-    test "queues key as atom", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":oban",
-              "key" => ":queues",
-              "value" => [
-                %{"tuple" => [":federator_incoming", 50]},
-                %{"tuple" => [":federator_outgoing", 50]},
-                %{"tuple" => [":web_push", 50]},
-                %{"tuple" => [":mailer", 10]},
-                %{"tuple" => [":transmogrifier", 20]},
-                %{"tuple" => [":scheduled_activities", 10]},
-                %{"tuple" => [":background", 5]}
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":oban",
-                   "key" => ":queues",
-                   "value" => [
-                     %{"tuple" => [":federator_incoming", 50]},
-                     %{"tuple" => [":federator_outgoing", 50]},
-                     %{"tuple" => [":web_push", 50]},
-                     %{"tuple" => [":mailer", 10]},
-                     %{"tuple" => [":transmogrifier", 20]},
-                     %{"tuple" => [":scheduled_activities", 10]},
-                     %{"tuple" => [":background", 5]}
-                   ],
-                   "db" => [
-                     ":federator_incoming",
-                     ":federator_outgoing",
-                     ":web_push",
-                     ":mailer",
-                     ":transmogrifier",
-                     ":scheduled_activities",
-                     ":background"
-                   ]
-                 }
-               ]
-             }
-    end
-
-    test "delete part of settings by atom subkeys", %{conn: conn} do
-      config =
-        insert(:config,
-          key: ":keyaa1",
-          value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
-        )
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: config.group,
-              key: config.key,
-              subkeys: [":subkey1", ":subkey3"],
-              delete: true
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":keyaa1",
-                   "value" => [%{"tuple" => [":subkey2", "val2"]}],
-                   "db" => [":subkey2"]
-                 }
-               ]
-             }
-    end
-
-    test "proxy tuple localhost", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: ":pleroma",
-              key: ":http",
-              value: [
-                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}
-              ]
-            }
-          ]
-        })
-
-      assert %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":http",
-                   "value" => value,
-                   "db" => db
-                 }
-               ]
-             } = json_response(conn, 200)
-
-      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value
-      assert ":proxy_url" in db
-    end
-
-    test "proxy tuple domain", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: ":pleroma",
-              key: ":http",
-              value: [
-                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}
-              ]
-            }
-          ]
-        })
-
-      assert %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":http",
-                   "value" => value,
-                   "db" => db
-                 }
-               ]
-             } = json_response(conn, 200)
-
-      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value
-      assert ":proxy_url" in db
-    end
-
-    test "proxy tuple ip", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: ":pleroma",
-              key: ":http",
-              value: [
-                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}
-              ]
-            }
-          ]
-        })
-
-      assert %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":http",
-                   "value" => value,
-                   "db" => db
-                 }
-               ]
-             } = json_response(conn, 200)
-
-      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value
-      assert ":proxy_url" in db
-    end
-
-    @tag capture_log: true
-    test "doesn't set keys not in the whitelist", %{conn: conn} do
-      clear_config(:database_config_whitelist, [
-        {:pleroma, :key1},
-        {:pleroma, :key2},
-        {:pleroma, Pleroma.Captcha.NotReal},
-        {:not_real}
-      ])
-
-      post(conn, "/api/pleroma/admin/config", %{
-        configs: [
-          %{group: ":pleroma", key: ":key1", value: "value1"},
-          %{group: ":pleroma", key: ":key2", value: "value2"},
-          %{group: ":pleroma", key: ":key3", value: "value3"},
-          %{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
-          %{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
-          %{group: ":not_real", key: ":anything", value: "value6"}
-        ]
-      })
-
-      assert Application.get_env(:pleroma, :key1) == "value1"
-      assert Application.get_env(:pleroma, :key2) == "value2"
-      assert Application.get_env(:pleroma, :key3) == nil
-      assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
-      assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
-      assert Application.get_env(:not_real, :anything) == "value6"
-    end
-  end
-
-  describe "GET /api/pleroma/admin/restart" do
-    setup do: clear_config(:configurable_from_database, true)
-
-    test "pleroma restarts", %{conn: conn} do
-      capture_log(fn ->
-        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
-      end) =~ "pleroma restarted"
-
-      refute Restarter.Pleroma.need_reboot?()
-    end
-  end
-
-  test "need_reboot flag", %{conn: conn} do
-    assert conn
-           |> get("/api/pleroma/admin/need_reboot")
-           |> json_response(200) == %{"need_reboot" => false}
-
-    Restarter.Pleroma.need_reboot()
-
-    assert conn
-           |> get("/api/pleroma/admin/need_reboot")
-           |> json_response(200) == %{"need_reboot" => true}
-
-    on_exit(fn -> Restarter.Pleroma.refresh() end)
-  end
-
-  describe "GET /api/pleroma/admin/users/:nickname/statuses" do
-    setup do
-      user = insert(:user)
-
-      date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
-      date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
-      date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
-
-      insert(:note_activity, user: user, published: date1)
-      insert(:note_activity, user: user, published: date2)
-      insert(:note_activity, user: user, published: date3)
-
-      %{user: user}
-    end
-
-    test "renders user's statuses", %{conn: conn, user: user} do
-      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
-
-      assert json_response(conn, 200) |> length() == 3
-    end
-
-    test "renders user's statuses with a limit", %{conn: conn, user: user} do
-      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2")
-
-      assert json_response(conn, 200) |> length() == 2
-    end
-
-    test "doesn't return private statuses by default", %{conn: conn, user: user} do
-      {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
-
-      {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
-
-      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
-
-      assert json_response(conn, 200) |> length() == 4
-    end
-
-    test "returns private statuses with godmode on", %{conn: conn, user: user} do
-      {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
-
-      {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
-
-      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
-
-      assert json_response(conn, 200) |> length() == 5
-    end
-
-    test "excludes reblogs by default", %{conn: conn, user: user} do
-      other_user = insert(:user)
-      {:ok, activity} = CommonAPI.post(user, %{status: "."})
-      {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user)
-
-      conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses")
-      assert json_response(conn_res, 200) |> length() == 0
-
-      conn_res =
-        get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true")
-
-      assert json_response(conn_res, 200) |> length() == 1
-    end
-  end
-
-  describe "GET /api/pleroma/admin/moderation_log" do
-    setup do
-      moderator = insert(:user, is_moderator: true)
-
-      %{moderator: moderator}
-    end
-
-    test "returns the log", %{conn: conn, admin: admin} do
-      Repo.insert(%ModerationLog{
-        data: %{
-          actor: %{
-            "id" => admin.id,
-            "nickname" => admin.nickname,
-            "type" => "user"
-          },
-          action: "relay_follow",
-          target: "https://example.org/relay"
-        },
-        inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
-      })
-
-      Repo.insert(%ModerationLog{
-        data: %{
-          actor: %{
-            "id" => admin.id,
-            "nickname" => admin.nickname,
-            "type" => "user"
-          },
-          action: "relay_unfollow",
-          target: "https://example.org/relay"
-        },
-        inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
-      })
-
-      conn = get(conn, "/api/pleroma/admin/moderation_log")
-
-      response = json_response(conn, 200)
-      [first_entry, second_entry] = response["items"]
-
-      assert response["total"] == 2
-      assert first_entry["data"]["action"] == "relay_unfollow"
-
-      assert first_entry["message"] ==
-               "@#{admin.nickname} unfollowed relay: https://example.org/relay"
-
-      assert second_entry["data"]["action"] == "relay_follow"
-
-      assert second_entry["message"] ==
-               "@#{admin.nickname} followed relay: https://example.org/relay"
-    end
-
-    test "returns the log with pagination", %{conn: conn, admin: admin} do
-      Repo.insert(%ModerationLog{
-        data: %{
-          actor: %{
-            "id" => admin.id,
-            "nickname" => admin.nickname,
-            "type" => "user"
-          },
-          action: "relay_follow",
-          target: "https://example.org/relay"
-        },
-        inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
-      })
-
-      Repo.insert(%ModerationLog{
-        data: %{
-          actor: %{
-            "id" => admin.id,
-            "nickname" => admin.nickname,
-            "type" => "user"
-          },
-          action: "relay_unfollow",
-          target: "https://example.org/relay"
-        },
-        inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
-      })
-
-      conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1")
-
-      response1 = json_response(conn1, 200)
-      [first_entry] = response1["items"]
-
-      assert response1["total"] == 2
-      assert response1["items"] |> length() == 1
-      assert first_entry["data"]["action"] == "relay_unfollow"
-
-      assert first_entry["message"] ==
-               "@#{admin.nickname} unfollowed relay: https://example.org/relay"
-
-      conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2")
-
-      response2 = json_response(conn2, 200)
-      [second_entry] = response2["items"]
-
-      assert response2["total"] == 2
-      assert response2["items"] |> length() == 1
-      assert second_entry["data"]["action"] == "relay_follow"
-
-      assert second_entry["message"] ==
-               "@#{admin.nickname} followed relay: https://example.org/relay"
-    end
-
-    test "filters log by date", %{conn: conn, admin: admin} do
-      first_date = "2017-08-15T15:47:06Z"
-      second_date = "2017-08-20T15:47:06Z"
+    test "filters log by date", %{conn: conn, admin: admin} do
+      first_date = "2017-08-15T15:47:06Z"
+      second_date = "2017-08-20T15:47:06Z"
 
       Repo.insert(%ModerationLog{
         data: %{
@@ -3281,57 +1604,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "relays" do
-    test "POST /relay", %{conn: conn, admin: admin} do
-      conn =
-        post(conn, "/api/pleroma/admin/relay", %{
-          relay_url: "http://mastodon.example.org/users/admin"
-        })
-
-      assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
-
-      log_entry = Repo.one(ModerationLog)
-
-      assert ModerationLog.get_log_entry_message(log_entry) ==
-               "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
-    end
-
-    test "GET /relay", %{conn: conn} do
-      relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
-
-      ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
-      |> Enum.each(fn ap_id ->
-        {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
-        User.follow(relay_user, user)
-      end)
-
-      conn = get(conn, "/api/pleroma/admin/relay")
-
-      assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == []
-    end
-
-    test "DELETE /relay", %{conn: conn, admin: admin} do
-      post(conn, "/api/pleroma/admin/relay", %{
-        relay_url: "http://mastodon.example.org/users/admin"
-      })
-
-      conn =
-        delete(conn, "/api/pleroma/admin/relay", %{
-          relay_url: "http://mastodon.example.org/users/admin"
-        })
-
-      assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
-
-      [log_entry_one, log_entry_two] = Repo.all(ModerationLog)
-
-      assert ModerationLog.get_log_entry_message(log_entry_one) ==
-               "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
-
-      assert ModerationLog.get_log_entry_message(log_entry_two) ==
-               "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
-    end
-  end
-
   describe "instances" do
     test "GET /instances/:instance/statuses", %{conn: conn} do
       user = insert(:user, local: false, nickname: "archaeme@archae.me")
@@ -3421,116 +1693,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "POST /reports/:id/notes" do
-    setup %{conn: conn, admin: admin} do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
-
-      {:ok, %{id: report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
-
-      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
-        content: "this is disgusting!"
-      })
-
-      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
-        content: "this is disgusting2!"
-      })
-
-      %{
-        admin_id: admin.id,
-        report_id: report_id
-      }
-    end
-
-    test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
-      [note, _] = Repo.all(ReportNote)
-
-      assert %{
-               activity_id: ^report_id,
-               content: "this is disgusting!",
-               user_id: ^admin_id
-             } = note
-    end
-
-    test "it returns reports with notes", %{conn: conn, admin: admin} do
-      conn = get(conn, "/api/pleroma/admin/reports")
-
-      response = json_response(conn, 200)
-      notes = hd(response["reports"])["notes"]
-      [note, _] = notes
-
-      assert note["user"]["nickname"] == admin.nickname
-      assert note["content"] == "this is disgusting!"
-      assert note["created_at"]
-      assert response["total"] == 1
-    end
-
-    test "it deletes the note", %{conn: conn, report_id: report_id} do
-      assert ReportNote |> Repo.all() |> length() == 2
-
-      [note, _] = Repo.all(ReportNote)
-
-      delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}")
-
-      assert ReportNote |> Repo.all() |> length() == 1
-    end
-  end
-
-  describe "GET /api/pleroma/admin/config/descriptions" do
-    test "structure", %{conn: conn} do
-      admin = insert(:user, is_admin: true)
-
-      conn =
-        assign(conn, :user, admin)
-        |> get("/api/pleroma/admin/config/descriptions")
-
-      assert [child | _others] = json_response(conn, 200)
-
-      assert child["children"]
-      assert child["key"]
-      assert String.starts_with?(child["group"], ":")
-      assert child["description"]
-    end
-
-    test "filters by database configuration whitelist", %{conn: conn} do
-      clear_config(:database_config_whitelist, [
-        {:pleroma, :instance},
-        {:pleroma, :activitypub},
-        {:pleroma, Pleroma.Upload},
-        {:esshd}
-      ])
-
-      admin = insert(:user, is_admin: true)
-
-      conn =
-        assign(conn, :user, admin)
-        |> get("/api/pleroma/admin/config/descriptions")
-
-      children = json_response(conn, 200)
-
-      assert length(children) == 4
-
-      assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
-
-      instance = Enum.find(children, fn c -> c["key"] == ":instance" end)
-      assert instance["children"]
-
-      activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end)
-      assert activitypub["children"]
-
-      web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
-      assert web_endpoint["children"]
-
-      esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
-      assert esshd["children"]
-    end
-  end
-
   describe "/api/pleroma/admin/stats" do
     test "status visibility count", %{conn: conn} do
       admin = insert(:user, is_admin: true)
@@ -3549,191 +1711,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                response["status_visibility"]
     end
   end
-
-  describe "POST /api/pleroma/admin/oauth_app" do
-    test "errors", %{conn: conn} do
-      response = conn |> post("/api/pleroma/admin/oauth_app", %{}) |> json_response(200)
-
-      assert response == %{"name" => "can't be blank", "redirect_uris" => "can't be blank"}
-    end
-
-    test "success", %{conn: conn} do
-      base_url = Web.base_url()
-      app_name = "Trusted app"
-
-      response =
-        conn
-        |> post("/api/pleroma/admin/oauth_app", %{
-          name: app_name,
-          redirect_uris: base_url
-        })
-        |> json_response(200)
-
-      assert %{
-               "client_id" => _,
-               "client_secret" => _,
-               "name" => ^app_name,
-               "redirect_uri" => ^base_url,
-               "trusted" => false
-             } = response
-    end
-
-    test "with trusted", %{conn: conn} do
-      base_url = Web.base_url()
-      app_name = "Trusted app"
-
-      response =
-        conn
-        |> post("/api/pleroma/admin/oauth_app", %{
-          name: app_name,
-          redirect_uris: base_url,
-          trusted: true
-        })
-        |> json_response(200)
-
-      assert %{
-               "client_id" => _,
-               "client_secret" => _,
-               "name" => ^app_name,
-               "redirect_uri" => ^base_url,
-               "trusted" => true
-             } = response
-    end
-  end
-
-  describe "GET /api/pleroma/admin/oauth_app" do
-    setup do
-      app = insert(:oauth_app)
-      {:ok, app: app}
-    end
-
-    test "list", %{conn: conn} do
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app")
-        |> json_response(200)
-
-      assert %{"apps" => apps, "count" => count, "page_size" => _} = response
-
-      assert length(apps) == count
-    end
-
-    test "with page size", %{conn: conn} do
-      insert(:oauth_app)
-      page_size = 1
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app", %{page_size: to_string(page_size)})
-        |> json_response(200)
-
-      assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response
-
-      assert length(apps) == page_size
-    end
-
-    test "search by client name", %{conn: conn, app: app} do
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app", %{name: app.client_name})
-        |> json_response(200)
-
-      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
-      assert returned["client_id"] == app.client_id
-      assert returned["name"] == app.client_name
-    end
-
-    test "search by client id", %{conn: conn, app: app} do
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app", %{client_id: app.client_id})
-        |> json_response(200)
-
-      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
-      assert returned["client_id"] == app.client_id
-      assert returned["name"] == app.client_name
-    end
-
-    test "only trusted", %{conn: conn} do
-      app = insert(:oauth_app, trusted: true)
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app", %{trusted: true})
-        |> json_response(200)
-
-      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
-      assert returned["client_id"] == app.client_id
-      assert returned["name"] == app.client_name
-    end
-  end
-
-  describe "DELETE /api/pleroma/admin/oauth_app/:id" do
-    test "with id", %{conn: conn} do
-      app = insert(:oauth_app)
-
-      response =
-        conn
-        |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id))
-        |> json_response(:no_content)
-
-      assert response == ""
-    end
-
-    test "with non existance id", %{conn: conn} do
-      response =
-        conn
-        |> delete("/api/pleroma/admin/oauth_app/0")
-        |> json_response(:bad_request)
-
-      assert response == ""
-    end
-  end
-
-  describe "PATCH /api/pleroma/admin/oauth_app/:id" do
-    test "with id", %{conn: conn} do
-      app = insert(:oauth_app)
-
-      name = "another name"
-      url = "https://example.com"
-      scopes = ["admin"]
-      id = app.id
-      website = "http://website.com"
-
-      response =
-        conn
-        |> patch("/api/pleroma/admin/oauth_app/" <> to_string(app.id), %{
-          name: name,
-          trusted: true,
-          redirect_uris: url,
-          scopes: scopes,
-          website: website
-        })
-        |> json_response(200)
-
-      assert %{
-               "client_id" => _,
-               "client_secret" => _,
-               "id" => ^id,
-               "name" => ^name,
-               "redirect_uri" => ^url,
-               "trusted" => true,
-               "website" => ^website
-             } = response
-    end
-
-    test "without id", %{conn: conn} do
-      response =
-        conn
-        |> patch("/api/pleroma/admin/oauth_app/0")
-        |> json_response(:bad_request)
-
-      assert response == ""
-    end
-  end
 end
 
 # Needed for testing
diff --git a/test/web/admin_api/controllers/config_controller_test.exs b/test/web/admin_api/controllers/config_controller_test.exs
new file mode 100644 (file)
index 0000000..780de8d
--- /dev/null
@@ -0,0 +1,1290 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
+  use Pleroma.Web.ConnCase, async: true
+
+  import ExUnit.CaptureLog
+  import Pleroma.Factory
+
+  alias Pleroma.Config
+  alias Pleroma.ConfigDB
+
+  setup do
+    admin = insert(:user, is_admin: true)
+    token = insert(:oauth_admin_token, user: admin)
+
+    conn =
+      build_conn()
+      |> assign(:user, admin)
+      |> assign(:token, token)
+
+    {:ok, %{admin: admin, token: token, conn: conn}}
+  end
+
+  describe "GET /api/pleroma/admin/config" do
+    setup do: clear_config(:configurable_from_database, true)
+
+    test "when configuration from database is off", %{conn: conn} do
+      Config.put(:configurable_from_database, false)
+      conn = get(conn, "/api/pleroma/admin/config")
+
+      assert json_response_and_validate_schema(conn, 400) ==
+               %{
+                 "error" => "To use this endpoint you need to enable configuration from database."
+               }
+    end
+
+    test "with settings only in db", %{conn: conn} do
+      config1 = insert(:config)
+      config2 = insert(:config)
+
+      conn = get(conn, "/api/pleroma/admin/config?only_db=true")
+
+      %{
+        "configs" => [
+          %{
+            "group" => ":pleroma",
+            "key" => key1,
+            "value" => _
+          },
+          %{
+            "group" => ":pleroma",
+            "key" => key2,
+            "value" => _
+          }
+        ]
+      } = json_response_and_validate_schema(conn, 200)
+
+      assert key1 == config1.key
+      assert key2 == config2.key
+    end
+
+    test "db is added to settings that are in db", %{conn: conn} do
+      _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response_and_validate_schema(200)
+
+      [instance_config] =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key == ":instance"
+        end)
+
+      assert instance_config["db"] == [":name"]
+    end
+
+    test "merged default setting with db settings", %{conn: conn} do
+      config1 = insert(:config)
+      config2 = insert(:config)
+
+      config3 =
+        insert(:config,
+          value: ConfigDB.to_binary(k1: :v1, k2: :v2)
+        )
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response_and_validate_schema(200)
+
+      assert length(configs) > 3
+
+      received_configs =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key in [config1.key, config2.key, config3.key]
+        end)
+
+      assert length(received_configs) == 3
+
+      db_keys =
+        config3.value
+        |> ConfigDB.from_binary()
+        |> Keyword.keys()
+        |> ConfigDB.convert()
+
+      Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
+        assert db in [[config1.key], [config2.key], db_keys]
+
+        assert value in [
+                 ConfigDB.from_binary_with_convert(config1.value),
+                 ConfigDB.from_binary_with_convert(config2.value),
+                 ConfigDB.from_binary_with_convert(config3.value)
+               ]
+      end)
+    end
+
+    test "subkeys with full update right merge", %{conn: conn} do
+      config1 =
+        insert(:config,
+          key: ":emoji",
+          value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
+        )
+
+      config2 =
+        insert(:config,
+          key: ":assets",
+          value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
+        )
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response_and_validate_schema(200)
+
+      vals =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key in [config1.key, config2.key]
+        end)
+
+      emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
+      assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
+
+      emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
+      assets_val = ConfigDB.transform_with_out_binary(assets["value"])
+
+      assert emoji_val[:groups] == [a: 1, b: 2]
+      assert assets_val[:mascots] == [a: 1, b: 2]
+    end
+  end
+
+  test "POST /api/pleroma/admin/config error", %{conn: conn} do
+    conn =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/pleroma/admin/config", %{"configs" => []})
+
+    assert json_response_and_validate_schema(conn, 400) ==
+             %{"error" => "To use this endpoint you need to enable configuration from database."}
+  end
+
+  describe "POST /api/pleroma/admin/config" do
+    setup do
+      http = Application.get_env(:pleroma, :http)
+
+      on_exit(fn ->
+        Application.delete_env(:pleroma, :key1)
+        Application.delete_env(:pleroma, :key2)
+        Application.delete_env(:pleroma, :key3)
+        Application.delete_env(:pleroma, :key4)
+        Application.delete_env(:pleroma, :keyaa1)
+        Application.delete_env(:pleroma, :keyaa2)
+        Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
+        Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
+        Application.put_env(:pleroma, :http, http)
+        Application.put_env(:tesla, :adapter, Tesla.Mock)
+        Restarter.Pleroma.refresh()
+      end)
+    end
+
+    setup do: clear_config(:configurable_from_database, true)
+
+    @tag capture_log: true
+    test "create new config setting in db", %{conn: conn} do
+      ueberauth = Application.get_env(:ueberauth, Ueberauth)
+      on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{group: ":pleroma", key: ":key1", value: "value1"},
+            %{
+              group: ":ueberauth",
+              key: "Ueberauth",
+              value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
+            },
+            %{
+              group: ":pleroma",
+              key: ":key2",
+              value: %{
+                ":nested_1" => "nested_value1",
+                ":nested_2" => [
+                  %{":nested_22" => "nested_value222"},
+                  %{":nested_33" => %{":nested_44" => "nested_444"}}
+                ]
+              }
+            },
+            %{
+              group: ":pleroma",
+              key: ":key3",
+              value: [
+                %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
+                %{"nested_4" => true}
+              ]
+            },
+            %{
+              group: ":pleroma",
+              key: ":key4",
+              value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
+            },
+            %{
+              group: ":idna",
+              key: ":key5",
+              value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => "value1",
+                   "db" => [":key1"]
+                 },
+                 %{
+                   "group" => ":ueberauth",
+                   "key" => "Ueberauth",
+                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
+                   "db" => [":consumer_secret"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key2",
+                   "value" => %{
+                     ":nested_1" => "nested_value1",
+                     ":nested_2" => [
+                       %{":nested_22" => "nested_value222"},
+                       %{":nested_33" => %{":nested_44" => "nested_444"}}
+                     ]
+                   },
+                   "db" => [":key2"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key3",
+                   "value" => [
+                     %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
+                     %{"nested_4" => true}
+                   ],
+                   "db" => [":key3"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key4",
+                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
+                   "db" => [":key4"]
+                 },
+                 %{
+                   "group" => ":idna",
+                   "key" => ":key5",
+                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
+                   "db" => [":key5"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :key1) == "value1"
+
+      assert Application.get_env(:pleroma, :key2) == %{
+               nested_1: "nested_value1",
+               nested_2: [
+                 %{nested_22: "nested_value222"},
+                 %{nested_33: %{nested_44: "nested_444"}}
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :key3) == [
+               %{"nested_3" => :nested_3, "nested_33" => "nested_33"},
+               %{"nested_4" => true}
+             ]
+
+      assert Application.get_env(:pleroma, :key4) == %{
+               "endpoint" => "https://example.com",
+               nested_5: :upload
+             }
+
+      assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
+    end
+
+    test "save configs setting without explicit key", %{conn: conn} do
+      level = Application.get_env(:quack, :level)
+      meta = Application.get_env(:quack, :meta)
+      webhook_url = Application.get_env(:quack, :webhook_url)
+
+      on_exit(fn ->
+        Application.put_env(:quack, :level, level)
+        Application.put_env(:quack, :meta, meta)
+        Application.put_env(:quack, :webhook_url, webhook_url)
+      end)
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":quack",
+              key: ":level",
+              value: ":info"
+            },
+            %{
+              group: ":quack",
+              key: ":meta",
+              value: [":none"]
+            },
+            %{
+              group: ":quack",
+              key: ":webhook_url",
+              value: "https://hooks.slack.com/services/KEY"
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":quack",
+                   "key" => ":level",
+                   "value" => ":info",
+                   "db" => [":level"]
+                 },
+                 %{
+                   "group" => ":quack",
+                   "key" => ":meta",
+                   "value" => [":none"],
+                   "db" => [":meta"]
+                 },
+                 %{
+                   "group" => ":quack",
+                   "key" => ":webhook_url",
+                   "value" => "https://hooks.slack.com/services/KEY",
+                   "db" => [":webhook_url"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:quack, :level) == :info
+      assert Application.get_env(:quack, :meta) == [:none]
+      assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
+    end
+
+    test "saving config with partial update", %{conn: conn} do
+      config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key1", 1]},
+                     %{"tuple" => [":key2", 2]},
+                     %{"tuple" => [":key3", 3]}
+                   ],
+                   "db" => [":key1", ":key2", ":key3"]
+                 }
+               ]
+             }
+    end
+
+    test "saving config which need pleroma reboot", %{conn: conn} do
+      chat = Config.get(:chat)
+      on_exit(fn -> Config.put(:chat, chat) end)
+
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post(
+               "/api/pleroma/admin/config",
+               %{
+                 configs: [
+                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                 ]
+               }
+             )
+             |> json_response_and_validate_schema(200) == %{
+               "configs" => [
+                 %{
+                   "db" => [":enabled"],
+                   "group" => ":pleroma",
+                   "key" => ":chat",
+                   "value" => [%{"tuple" => [":enabled", true]}]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response_and_validate_schema(200)
+
+      assert configs["need_reboot"]
+
+      capture_log(fn ->
+        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) ==
+                 %{}
+      end) =~ "pleroma restarted"
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response_and_validate_schema(200)
+
+      assert configs["need_reboot"] == false
+    end
+
+    test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
+      chat = Config.get(:chat)
+      on_exit(fn -> Config.put(:chat, chat) end)
+
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post(
+               "/api/pleroma/admin/config",
+               %{
+                 configs: [
+                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                 ]
+               }
+             )
+             |> json_response_and_validate_schema(200) == %{
+               "configs" => [
+                 %{
+                   "db" => [":enabled"],
+                   "group" => ":pleroma",
+                   "key" => ":chat",
+                   "value" => [%{"tuple" => [":enabled", true]}]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/pleroma/admin/config", %{
+               configs: [
+                 %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
+               ]
+             })
+             |> json_response_and_validate_schema(200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key3", 3]}
+                   ],
+                   "db" => [":key3"]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      capture_log(fn ->
+        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) ==
+                 %{}
+      end) =~ "pleroma restarted"
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response_and_validate_schema(200)
+
+      assert configs["need_reboot"] == false
+    end
+
+    test "saving config with nested merge", %{conn: conn} do
+      config =
+        insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: config.group,
+              key: config.key,
+              value: [
+                %{"tuple" => [":key3", 3]},
+                %{
+                  "tuple" => [
+                    ":key2",
+                    [
+                      %{"tuple" => [":k2", 1]},
+                      %{"tuple" => [":k3", 3]}
+                    ]
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key1", 1]},
+                     %{"tuple" => [":key3", 3]},
+                     %{
+                       "tuple" => [
+                         ":key2",
+                         [
+                           %{"tuple" => [":k1", 1]},
+                           %{"tuple" => [":k2", 1]},
+                           %{"tuple" => [":k3", 3]}
+                         ]
+                       ]
+                     }
+                   ],
+                   "db" => [":key1", ":key3", ":key2"]
+                 }
+               ]
+             }
+    end
+
+    test "saving special atoms", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          "configs" => [
+            %{
+              "group" => ":pleroma",
+              "key" => ":key1",
+              "value" => [
+                %{
+                  "tuple" => [
+                    ":ssl_options",
+                    [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{
+                       "tuple" => [
+                         ":ssl_options",
+                         [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
+                       ]
+                     }
+                   ],
+                   "db" => [":ssl_options"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :key1) == [
+               ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
+             ]
+    end
+
+    test "saving full setting if value is in full_key_update list", %{conn: conn} do
+      backends = Application.get_env(:logger, :backends)
+      on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
+
+      config =
+        insert(:config,
+          group: ":logger",
+          key: ":backends",
+          value: :erlang.term_to_binary([])
+        )
+
+      Pleroma.Config.TransferTask.load_and_update_env([], false)
+
+      assert Application.get_env(:logger, :backends) == []
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: config.group,
+              key: config.key,
+              value: [":console"]
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":logger",
+                   "key" => ":backends",
+                   "value" => [
+                     ":console"
+                   ],
+                   "db" => [":backends"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:logger, :backends) == [
+               :console
+             ]
+    end
+
+    test "saving full setting if value is not keyword", %{conn: conn} do
+      config =
+        insert(:config,
+          group: ":tesla",
+          key: ":adapter",
+          value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
+        )
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":tesla",
+                   "key" => ":adapter",
+                   "value" => "Tesla.Adapter.Httpc",
+                   "db" => [":adapter"]
+                 }
+               ]
+             }
+    end
+
+    test "update config setting & delete with fallback to default value", %{
+      conn: conn,
+      admin: admin,
+      token: token
+    } do
+      ueberauth = Application.get_env(:ueberauth, Ueberauth)
+      config1 = insert(:config, key: ":keyaa1")
+      config2 = insert(:config, key: ":keyaa2")
+
+      config3 =
+        insert(:config,
+          group: ":ueberauth",
+          key: "Ueberauth"
+        )
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config1.group, key: config1.key, value: "another_value"},
+            %{group: config2.group, key: config2.key, value: "another_value"}
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => config1.key,
+                   "value" => "another_value",
+                   "db" => [":keyaa1"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => config2.key,
+                   "value" => "another_value",
+                   "db" => [":keyaa2"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :keyaa1) == "another_value"
+      assert Application.get_env(:pleroma, :keyaa2) == "another_value"
+      assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+        |> assign(:token, token)
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config2.group, key: config2.key, delete: true},
+            %{
+              group: ":ueberauth",
+              key: "Ueberauth",
+              delete: true
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => []
+             }
+
+      assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
+      refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
+    end
+
+    test "common config example", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":pleroma",
+              "key" => "Pleroma.Captcha.NotReal",
+              "value" => [
+                %{"tuple" => [":enabled", false]},
+                %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
+                %{"tuple" => [":seconds_valid", 60]},
+                %{"tuple" => [":path", ""]},
+                %{"tuple" => [":key1", nil]},
+                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
+                %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
+                %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
+                %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
+                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
+                %{"tuple" => [":name", "Pleroma"]}
+              ]
+            }
+          ]
+        })
+
+      assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => "Pleroma.Captcha.NotReal",
+                   "value" => [
+                     %{"tuple" => [":enabled", false]},
+                     %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
+                     %{"tuple" => [":seconds_valid", 60]},
+                     %{"tuple" => [":path", ""]},
+                     %{"tuple" => [":key1", nil]},
+                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
+                     %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
+                     %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
+                     %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
+                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
+                     %{"tuple" => [":name", "Pleroma"]}
+                   ],
+                   "db" => [
+                     ":enabled",
+                     ":method",
+                     ":seconds_valid",
+                     ":path",
+                     ":key1",
+                     ":partial_chain",
+                     ":regex1",
+                     ":regex2",
+                     ":regex3",
+                     ":regex4",
+                     ":name"
+                   ]
+                 }
+               ]
+             }
+    end
+
+    test "tuples with more than two values", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":pleroma",
+              "key" => "Pleroma.Web.Endpoint.NotReal",
+              "value" => [
+                %{
+                  "tuple" => [
+                    ":http",
+                    [
+                      %{
+                        "tuple" => [
+                          ":key2",
+                          [
+                            %{
+                              "tuple" => [
+                                ":_",
+                                [
+                                  %{
+                                    "tuple" => [
+                                      "/api/v1/streaming",
+                                      "Pleroma.Web.MastodonAPI.WebsocketHandler",
+                                      []
+                                    ]
+                                  },
+                                  %{
+                                    "tuple" => [
+                                      "/websocket",
+                                      "Phoenix.Endpoint.CowboyWebSocket",
+                                      %{
+                                        "tuple" => [
+                                          "Phoenix.Transports.WebSocket",
+                                          %{
+                                            "tuple" => [
+                                              "Pleroma.Web.Endpoint",
+                                              "Pleroma.Web.UserSocket",
+                                              []
+                                            ]
+                                          }
+                                        ]
+                                      }
+                                    ]
+                                  },
+                                  %{
+                                    "tuple" => [
+                                      ":_",
+                                      "Phoenix.Endpoint.Cowboy2Handler",
+                                      %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+                                    ]
+                                  }
+                                ]
+                              ]
+                            }
+                          ]
+                        ]
+                      }
+                    ]
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => "Pleroma.Web.Endpoint.NotReal",
+                   "value" => [
+                     %{
+                       "tuple" => [
+                         ":http",
+                         [
+                           %{
+                             "tuple" => [
+                               ":key2",
+                               [
+                                 %{
+                                   "tuple" => [
+                                     ":_",
+                                     [
+                                       %{
+                                         "tuple" => [
+                                           "/api/v1/streaming",
+                                           "Pleroma.Web.MastodonAPI.WebsocketHandler",
+                                           []
+                                         ]
+                                       },
+                                       %{
+                                         "tuple" => [
+                                           "/websocket",
+                                           "Phoenix.Endpoint.CowboyWebSocket",
+                                           %{
+                                             "tuple" => [
+                                               "Phoenix.Transports.WebSocket",
+                                               %{
+                                                 "tuple" => [
+                                                   "Pleroma.Web.Endpoint",
+                                                   "Pleroma.Web.UserSocket",
+                                                   []
+                                                 ]
+                                               }
+                                             ]
+                                           }
+                                         ]
+                                       },
+                                       %{
+                                         "tuple" => [
+                                           ":_",
+                                           "Phoenix.Endpoint.Cowboy2Handler",
+                                           %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+                                         ]
+                                       }
+                                     ]
+                                   ]
+                                 }
+                               ]
+                             ]
+                           }
+                         ]
+                       ]
+                     }
+                   ],
+                   "db" => [":http"]
+                 }
+               ]
+             }
+    end
+
+    test "settings with nesting map", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":pleroma",
+              "key" => ":key1",
+              "value" => [
+                %{"tuple" => [":key2", "some_val"]},
+                %{
+                  "tuple" => [
+                    ":key3",
+                    %{
+                      ":max_options" => 20,
+                      ":max_option_chars" => 200,
+                      ":min_expiration" => 0,
+                      ":max_expiration" => 31_536_000,
+                      "nested" => %{
+                        ":max_options" => 20,
+                        ":max_option_chars" => 200,
+                        ":min_expiration" => 0,
+                        ":max_expiration" => 31_536_000
+                      }
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) ==
+               %{
+                 "configs" => [
+                   %{
+                     "group" => ":pleroma",
+                     "key" => ":key1",
+                     "value" => [
+                       %{"tuple" => [":key2", "some_val"]},
+                       %{
+                         "tuple" => [
+                           ":key3",
+                           %{
+                             ":max_expiration" => 31_536_000,
+                             ":max_option_chars" => 200,
+                             ":max_options" => 20,
+                             ":min_expiration" => 0,
+                             "nested" => %{
+                               ":max_expiration" => 31_536_000,
+                               ":max_option_chars" => 200,
+                               ":max_options" => 20,
+                               ":min_expiration" => 0
+                             }
+                           }
+                         ]
+                       }
+                     ],
+                     "db" => [":key2", ":key3"]
+                   }
+                 ]
+               }
+    end
+
+    test "value as map", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":pleroma",
+              "key" => ":key1",
+              "value" => %{"key" => "some_val"}
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) ==
+               %{
+                 "configs" => [
+                   %{
+                     "group" => ":pleroma",
+                     "key" => ":key1",
+                     "value" => %{"key" => "some_val"},
+                     "db" => [":key1"]
+                   }
+                 ]
+               }
+    end
+
+    test "queues key as atom", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":oban",
+              "key" => ":queues",
+              "value" => [
+                %{"tuple" => [":federator_incoming", 50]},
+                %{"tuple" => [":federator_outgoing", 50]},
+                %{"tuple" => [":web_push", 50]},
+                %{"tuple" => [":mailer", 10]},
+                %{"tuple" => [":transmogrifier", 20]},
+                %{"tuple" => [":scheduled_activities", 10]},
+                %{"tuple" => [":background", 5]}
+              ]
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":oban",
+                   "key" => ":queues",
+                   "value" => [
+                     %{"tuple" => [":federator_incoming", 50]},
+                     %{"tuple" => [":federator_outgoing", 50]},
+                     %{"tuple" => [":web_push", 50]},
+                     %{"tuple" => [":mailer", 10]},
+                     %{"tuple" => [":transmogrifier", 20]},
+                     %{"tuple" => [":scheduled_activities", 10]},
+                     %{"tuple" => [":background", 5]}
+                   ],
+                   "db" => [
+                     ":federator_incoming",
+                     ":federator_outgoing",
+                     ":web_push",
+                     ":mailer",
+                     ":transmogrifier",
+                     ":scheduled_activities",
+                     ":background"
+                   ]
+                 }
+               ]
+             }
+    end
+
+    test "delete part of settings by atom subkeys", %{conn: conn} do
+      config =
+        insert(:config,
+          key: ":keyaa1",
+          value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
+        )
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: config.group,
+              key: config.key,
+              subkeys: [":subkey1", ":subkey3"],
+              delete: true
+            }
+          ]
+        })
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":keyaa1",
+                   "value" => [%{"tuple" => [":subkey2", "val2"]}],
+                   "db" => [":subkey2"]
+                 }
+               ]
+             }
+    end
+
+    test "proxy tuple localhost", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}
+              ]
+            }
+          ]
+        })
+
+      assert %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => value,
+                   "db" => db
+                 }
+               ]
+             } = json_response_and_validate_schema(conn, 200)
+
+      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value
+      assert ":proxy_url" in db
+    end
+
+    test "proxy tuple domain", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}
+              ]
+            }
+          ]
+        })
+
+      assert %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => value,
+                   "db" => db
+                 }
+               ]
+             } = json_response_and_validate_schema(conn, 200)
+
+      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value
+      assert ":proxy_url" in db
+    end
+
+    test "proxy tuple ip", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}
+              ]
+            }
+          ]
+        })
+
+      assert %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => value,
+                   "db" => db
+                 }
+               ]
+             } = json_response_and_validate_schema(conn, 200)
+
+      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value
+      assert ":proxy_url" in db
+    end
+
+    @tag capture_log: true
+    test "doesn't set keys not in the whitelist", %{conn: conn} do
+      clear_config(:database_config_whitelist, [
+        {:pleroma, :key1},
+        {:pleroma, :key2},
+        {:pleroma, Pleroma.Captcha.NotReal},
+        {:not_real}
+      ])
+
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/pleroma/admin/config", %{
+        configs: [
+          %{group: ":pleroma", key: ":key1", value: "value1"},
+          %{group: ":pleroma", key: ":key2", value: "value2"},
+          %{group: ":pleroma", key: ":key3", value: "value3"},
+          %{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
+          %{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
+          %{group: ":not_real", key: ":anything", value: "value6"}
+        ]
+      })
+
+      assert Application.get_env(:pleroma, :key1) == "value1"
+      assert Application.get_env(:pleroma, :key2) == "value2"
+      assert Application.get_env(:pleroma, :key3) == nil
+      assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
+      assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
+      assert Application.get_env(:not_real, :anything) == "value6"
+    end
+  end
+
+  describe "GET /api/pleroma/admin/config/descriptions" do
+    test "structure", %{conn: conn} do
+      admin = insert(:user, is_admin: true)
+
+      conn =
+        assign(conn, :user, admin)
+        |> get("/api/pleroma/admin/config/descriptions")
+
+      assert [child | _others] = json_response_and_validate_schema(conn, 200)
+
+      assert child["children"]
+      assert child["key"]
+      assert String.starts_with?(child["group"], ":")
+      assert child["description"]
+    end
+
+    test "filters by database configuration whitelist", %{conn: conn} do
+      clear_config(:database_config_whitelist, [
+        {:pleroma, :instance},
+        {:pleroma, :activitypub},
+        {:pleroma, Pleroma.Upload},
+        {:esshd}
+      ])
+
+      admin = insert(:user, is_admin: true)
+
+      conn =
+        assign(conn, :user, admin)
+        |> get("/api/pleroma/admin/config/descriptions")
+
+      children = json_response_and_validate_schema(conn, 200)
+
+      assert length(children) == 4
+
+      assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
+
+      instance = Enum.find(children, fn c -> c["key"] == ":instance" end)
+      assert instance["children"]
+
+      activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end)
+      assert activitypub["children"]
+
+      web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
+      assert web_endpoint["children"]
+
+      esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
+      assert esshd["children"]
+    end
+  end
+end
diff --git a/test/web/admin_api/controllers/invite_controller_test.exs b/test/web/admin_api/controllers/invite_controller_test.exs
new file mode 100644 (file)
index 0000000..ab186c5
--- /dev/null
@@ -0,0 +1,281 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.InviteControllerTest do
+  use Pleroma.Web.ConnCase, async: true
+
+  import Pleroma.Factory
+
+  alias Pleroma.Config
+  alias Pleroma.Repo
+  alias Pleroma.UserInviteToken
+
+  setup do
+    admin = insert(:user, is_admin: true)
+    token = insert(:oauth_admin_token, user: admin)
+
+    conn =
+      build_conn()
+      |> assign(:user, admin)
+      |> assign(:token, token)
+
+    {:ok, %{admin: admin, token: token, conn: conn}}
+  end
+
+  describe "POST /api/pleroma/admin/users/email_invite, with valid config" do
+    setup do: clear_config([:instance, :registrations_open], false)
+    setup do: clear_config([:instance, :invites_enabled], true)
+
+    test "sends invitation and returns 204", %{admin: admin, conn: conn} do
+      recipient_email = "foo@bar.com"
+      recipient_name = "J. D."
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json;charset=utf-8")
+        |> post("/api/pleroma/admin/users/email_invite", %{
+          email: recipient_email,
+          name: recipient_name
+        })
+
+      assert json_response_and_validate_schema(conn, :no_content)
+
+      token_record = List.last(Repo.all(Pleroma.UserInviteToken))
+      assert token_record
+      refute token_record.used
+
+      notify_email = Config.get([:instance, :notify_email])
+      instance_name = Config.get([:instance, :name])
+
+      email =
+        Pleroma.Emails.UserEmail.user_invitation_email(
+          admin,
+          token_record,
+          recipient_email,
+          recipient_name
+        )
+
+      Swoosh.TestAssertions.assert_email_sent(
+        from: {instance_name, notify_email},
+        to: {recipient_name, recipient_email},
+        html_body: email.html_body
+      )
+    end
+
+    test "it returns 403 if requested by a non-admin" do
+      non_admin_user = insert(:user)
+      token = insert(:oauth_token, user: non_admin_user)
+
+      conn =
+        build_conn()
+        |> assign(:user, non_admin_user)
+        |> assign(:token, token)
+        |> put_req_header("content-type", "application/json;charset=utf-8")
+        |> post("/api/pleroma/admin/users/email_invite", %{
+          email: "foo@bar.com",
+          name: "JD"
+        })
+
+      assert json_response(conn, :forbidden)
+    end
+
+    test "email with +", %{conn: conn, admin: admin} do
+      recipient_email = "foo+bar@baz.com"
+
+      conn
+      |> put_req_header("content-type", "application/json;charset=utf-8")
+      |> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email})
+      |> json_response_and_validate_schema(:no_content)
+
+      token_record =
+        Pleroma.UserInviteToken
+        |> Repo.all()
+        |> List.last()
+
+      assert token_record
+      refute token_record.used
+
+      notify_email = Config.get([:instance, :notify_email])
+      instance_name = Config.get([:instance, :name])
+
+      email =
+        Pleroma.Emails.UserEmail.user_invitation_email(
+          admin,
+          token_record,
+          recipient_email
+        )
+
+      Swoosh.TestAssertions.assert_email_sent(
+        from: {instance_name, notify_email},
+        to: recipient_email,
+        html_body: email.html_body
+      )
+    end
+  end
+
+  describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
+    setup do: clear_config([:instance, :registrations_open])
+    setup do: clear_config([:instance, :invites_enabled])
+
+    test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do
+      Config.put([:instance, :registrations_open], false)
+      Config.put([:instance, :invites_enabled], false)
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/users/email_invite", %{
+          email: "foo@bar.com",
+          name: "JD"
+        })
+
+      assert json_response_and_validate_schema(conn, :bad_request) ==
+               %{
+                 "error" =>
+                   "To send invites you need to set the `invites_enabled` option to true."
+               }
+    end
+
+    test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
+      Config.put([:instance, :registrations_open], true)
+      Config.put([:instance, :invites_enabled], true)
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/users/email_invite", %{
+          email: "foo@bar.com",
+          name: "JD"
+        })
+
+      assert json_response_and_validate_schema(conn, :bad_request) ==
+               %{
+                 "error" =>
+                   "To send invites you need to set the `registrations_open` option to false."
+               }
+    end
+  end
+
+  describe "POST /api/pleroma/admin/users/invite_token" do
+    test "without options", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/users/invite_token")
+
+      invite_json = json_response_and_validate_schema(conn, 200)
+      invite = UserInviteToken.find_by_token!(invite_json["token"])
+      refute invite.used
+      refute invite.expires_at
+      refute invite.max_use
+      assert invite.invite_type == "one_time"
+    end
+
+    test "with expires_at", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/users/invite_token", %{
+          "expires_at" => Date.to_string(Date.utc_today())
+        })
+
+      invite_json = json_response_and_validate_schema(conn, 200)
+      invite = UserInviteToken.find_by_token!(invite_json["token"])
+
+      refute invite.used
+      assert invite.expires_at == Date.utc_today()
+      refute invite.max_use
+      assert invite.invite_type == "date_limited"
+    end
+
+    test "with max_use", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/users/invite_token", %{"max_use" => 150})
+
+      invite_json = json_response_and_validate_schema(conn, 200)
+      invite = UserInviteToken.find_by_token!(invite_json["token"])
+      refute invite.used
+      refute invite.expires_at
+      assert invite.max_use == 150
+      assert invite.invite_type == "reusable"
+    end
+
+    test "with max use and expires_at", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/users/invite_token", %{
+          "max_use" => 150,
+          "expires_at" => Date.to_string(Date.utc_today())
+        })
+
+      invite_json = json_response_and_validate_schema(conn, 200)
+      invite = UserInviteToken.find_by_token!(invite_json["token"])
+      refute invite.used
+      assert invite.expires_at == Date.utc_today()
+      assert invite.max_use == 150
+      assert invite.invite_type == "reusable_date_limited"
+    end
+  end
+
+  describe "GET /api/pleroma/admin/users/invites" do
+    test "no invites", %{conn: conn} do
+      conn = get(conn, "/api/pleroma/admin/users/invites")
+
+      assert json_response_and_validate_schema(conn, 200) == %{"invites" => []}
+    end
+
+    test "with invite", %{conn: conn} do
+      {:ok, invite} = UserInviteToken.create_invite()
+
+      conn = get(conn, "/api/pleroma/admin/users/invites")
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "invites" => [
+                 %{
+                   "expires_at" => nil,
+                   "id" => invite.id,
+                   "invite_type" => "one_time",
+                   "max_use" => nil,
+                   "token" => invite.token,
+                   "used" => false,
+                   "uses" => 0
+                 }
+               ]
+             }
+    end
+  end
+
+  describe "POST /api/pleroma/admin/users/revoke_invite" do
+    test "with token", %{conn: conn} do
+      {:ok, invite} = UserInviteToken.create_invite()
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token})
+
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "expires_at" => nil,
+               "id" => invite.id,
+               "invite_type" => "one_time",
+               "max_use" => nil,
+               "token" => invite.token,
+               "used" => true,
+               "uses" => 0
+             }
+    end
+
+    test "with invalid token", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"})
+
+      assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"}
+    end
+  end
+end
diff --git a/test/web/admin_api/controllers/oauth_app_controller_test.exs b/test/web/admin_api/controllers/oauth_app_controller_test.exs
new file mode 100644 (file)
index 0000000..ed7c417
--- /dev/null
@@ -0,0 +1,220 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do
+  use Pleroma.Web.ConnCase, async: true
+  use Oban.Testing, repo: Pleroma.Repo
+
+  import Pleroma.Factory
+
+  alias Pleroma.Config
+  alias Pleroma.Web
+
+  setup do
+    admin = insert(:user, is_admin: true)
+    token = insert(:oauth_admin_token, user: admin)
+
+    conn =
+      build_conn()
+      |> assign(:user, admin)
+      |> assign(:token, token)
+
+    {:ok, %{admin: admin, token: token, conn: conn}}
+  end
+
+  describe "POST /api/pleroma/admin/oauth_app" do
+    test "errors", %{conn: conn} do
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/oauth_app", %{})
+        |> json_response_and_validate_schema(400)
+
+      assert %{
+               "error" => "Missing field: name. Missing field: redirect_uris."
+             } = response
+    end
+
+    test "success", %{conn: conn} do
+      base_url = Web.base_url()
+      app_name = "Trusted app"
+
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/oauth_app", %{
+          name: app_name,
+          redirect_uris: base_url
+        })
+        |> json_response_and_validate_schema(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "name" => ^app_name,
+               "redirect_uri" => ^base_url,
+               "trusted" => false
+             } = response
+    end
+
+    test "with trusted", %{conn: conn} do
+      base_url = Web.base_url()
+      app_name = "Trusted app"
+
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/oauth_app", %{
+          name: app_name,
+          redirect_uris: base_url,
+          trusted: true
+        })
+        |> json_response_and_validate_schema(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "name" => ^app_name,
+               "redirect_uri" => ^base_url,
+               "trusted" => true
+             } = response
+    end
+  end
+
+  describe "GET /api/pleroma/admin/oauth_app" do
+    setup do
+      app = insert(:oauth_app)
+      {:ok, app: app}
+    end
+
+    test "list", %{conn: conn} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => apps, "count" => count, "page_size" => _} = response
+
+      assert length(apps) == count
+    end
+
+    test "with page size", %{conn: conn} do
+      insert(:oauth_app)
+      page_size = 1
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app?page_size=#{page_size}")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response
+
+      assert length(apps) == page_size
+    end
+
+    test "search by client name", %{conn: conn, app: app} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app?name=#{app.client_name}")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+
+    test "search by client id", %{conn: conn, app: app} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app?client_id=#{app.client_id}")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+
+    test "only trusted", %{conn: conn} do
+      app = insert(:oauth_app, trusted: true)
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app?trusted=true")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+  end
+
+  describe "DELETE /api/pleroma/admin/oauth_app/:id" do
+    test "with id", %{conn: conn} do
+      app = insert(:oauth_app)
+
+      response =
+        conn
+        |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id))
+        |> json_response_and_validate_schema(:no_content)
+
+      assert response == ""
+    end
+
+    test "with non existance id", %{conn: conn} do
+      response =
+        conn
+        |> delete("/api/pleroma/admin/oauth_app/0")
+        |> json_response_and_validate_schema(:bad_request)
+
+      assert response == ""
+    end
+  end
+
+  describe "PATCH /api/pleroma/admin/oauth_app/:id" do
+    test "with id", %{conn: conn} do
+      app = insert(:oauth_app)
+
+      name = "another name"
+      url = "https://example.com"
+      scopes = ["admin"]
+      id = app.id
+      website = "http://website.com"
+
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> patch("/api/pleroma/admin/oauth_app/#{id}", %{
+          name: name,
+          trusted: true,
+          redirect_uris: url,
+          scopes: scopes,
+          website: website
+        })
+        |> json_response_and_validate_schema(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "id" => ^id,
+               "name" => ^name,
+               "redirect_uri" => ^url,
+               "trusted" => true,
+               "website" => ^website
+             } = response
+    end
+
+    test "without id", %{conn: conn} do
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> patch("/api/pleroma/admin/oauth_app/0")
+        |> json_response_and_validate_schema(:bad_request)
+
+      assert response == ""
+    end
+  end
+end
diff --git a/test/web/admin_api/controllers/relay_controller_test.exs b/test/web/admin_api/controllers/relay_controller_test.exs
new file mode 100644 (file)
index 0000000..64086ad
--- /dev/null
@@ -0,0 +1,92 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.RelayControllerTest do
+  use Pleroma.Web.ConnCase
+
+  import Pleroma.Factory
+
+  alias Pleroma.Config
+  alias Pleroma.ModerationLog
+  alias Pleroma.Repo
+  alias Pleroma.User
+
+  setup_all do
+    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+    :ok
+  end
+
+  setup do
+    admin = insert(:user, is_admin: true)
+    token = insert(:oauth_admin_token, user: admin)
+
+    conn =
+      build_conn()
+      |> assign(:user, admin)
+      |> assign(:token, token)
+
+    {:ok, %{admin: admin, token: token, conn: conn}}
+  end
+
+  describe "relays" do
+    test "POST /relay", %{conn: conn, admin: admin} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/relay", %{
+          relay_url: "http://mastodon.example.org/users/admin"
+        })
+
+      assert json_response_and_validate_schema(conn, 200) ==
+               "http://mastodon.example.org/users/admin"
+
+      log_entry = Repo.one(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(log_entry) ==
+               "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
+    end
+
+    test "GET /relay", %{conn: conn} do
+      relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
+
+      ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
+      |> Enum.each(fn ap_id ->
+        {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
+        User.follow(relay_user, user)
+      end)
+
+      conn = get(conn, "/api/pleroma/admin/relay")
+
+      assert json_response_and_validate_schema(conn, 200)["relays"] --
+               ["mastodon.example.org", "mstdn.io"] == []
+    end
+
+    test "DELETE /relay", %{conn: conn, admin: admin} do
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/pleroma/admin/relay", %{
+        relay_url: "http://mastodon.example.org/users/admin"
+      })
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> delete("/api/pleroma/admin/relay", %{
+          relay_url: "http://mastodon.example.org/users/admin"
+        })
+
+      assert json_response_and_validate_schema(conn, 200) ==
+               "http://mastodon.example.org/users/admin"
+
+      [log_entry_one, log_entry_two] = Repo.all(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(log_entry_one) ==
+               "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
+
+      assert ModerationLog.get_log_entry_message(log_entry_two) ==
+               "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
+    end
+  end
+end
diff --git a/test/web/admin_api/controllers/report_controller_test.exs b/test/web/admin_api/controllers/report_controller_test.exs
new file mode 100644 (file)
index 0000000..940bce3
--- /dev/null
@@ -0,0 +1,374 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
+  use Pleroma.Web.ConnCase
+
+  import Pleroma.Factory
+
+  alias Pleroma.Activity
+  alias Pleroma.Config
+  alias Pleroma.ModerationLog
+  alias Pleroma.Repo
+  alias Pleroma.ReportNote
+  alias Pleroma.Web.CommonAPI
+
+  setup do
+    admin = insert(:user, is_admin: true)
+    token = insert(:oauth_admin_token, user: admin)
+
+    conn =
+      build_conn()
+      |> assign(:user, admin)
+      |> assign(:token, token)
+
+    {:ok, %{admin: admin, token: token, conn: conn}}
+  end
+
+  describe "GET /api/pleroma/admin/reports/:id" do
+    test "returns report by its id", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports/#{report_id}")
+        |> json_response_and_validate_schema(:ok)
+
+      assert response["id"] == report_id
+    end
+
+    test "returns 404 when report id is invalid", %{conn: conn} do
+      conn = get(conn, "/api/pleroma/admin/reports/test")
+
+      assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"}
+    end
+  end
+
+  describe "PATCH /api/pleroma/admin/reports" do
+    setup do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      {:ok, %{id: second_report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel very offended",
+          status_ids: [activity.id]
+        })
+
+      %{
+        id: report_id,
+        second_report_id: second_report_id
+      }
+    end
+
+    test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
+      read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+      write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
+
+      response =
+        conn
+        |> assign(:token, read_token)
+        |> put_req_header("content-type", "application/json")
+        |> patch("/api/pleroma/admin/reports", %{
+          "reports" => [%{"state" => "resolved", "id" => id}]
+        })
+        |> json_response_and_validate_schema(403)
+
+      assert response == %{
+               "error" => "Insufficient permissions: admin:write:reports."
+             }
+
+      conn
+      |> assign(:token, write_token)
+      |> put_req_header("content-type", "application/json")
+      |> patch("/api/pleroma/admin/reports", %{
+        "reports" => [%{"state" => "resolved", "id" => id}]
+      })
+      |> json_response_and_validate_schema(:no_content)
+    end
+
+    test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> patch("/api/pleroma/admin/reports", %{
+        "reports" => [
+          %{"state" => "resolved", "id" => id}
+        ]
+      })
+      |> json_response_and_validate_schema(:no_content)
+
+      activity = Activity.get_by_id(id)
+      assert activity.data["state"] == "resolved"
+
+      log_entry = Repo.one(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(log_entry) ==
+               "@#{admin.nickname} updated report ##{id} with 'resolved' state"
+    end
+
+    test "closes report", %{conn: conn, id: id, admin: admin} do
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> patch("/api/pleroma/admin/reports", %{
+        "reports" => [
+          %{"state" => "closed", "id" => id}
+        ]
+      })
+      |> json_response_and_validate_schema(:no_content)
+
+      activity = Activity.get_by_id(id)
+      assert activity.data["state"] == "closed"
+
+      log_entry = Repo.one(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(log_entry) ==
+               "@#{admin.nickname} updated report ##{id} with 'closed' state"
+    end
+
+    test "returns 400 when state is unknown", %{conn: conn, id: id} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> patch("/api/pleroma/admin/reports", %{
+          "reports" => [
+            %{"state" => "test", "id" => id}
+          ]
+        })
+
+      assert "Unsupported state" =
+               hd(json_response_and_validate_schema(conn, :bad_request))["error"]
+    end
+
+    test "returns 404 when report is not exist", %{conn: conn} do
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> patch("/api/pleroma/admin/reports", %{
+          "reports" => [
+            %{"state" => "closed", "id" => "test"}
+          ]
+        })
+
+      assert hd(json_response_and_validate_schema(conn, :bad_request))["error"] == "not_found"
+    end
+
+    test "updates state of multiple reports", %{
+      conn: conn,
+      id: id,
+      admin: admin,
+      second_report_id: second_report_id
+    } do
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> patch("/api/pleroma/admin/reports", %{
+        "reports" => [
+          %{"state" => "resolved", "id" => id},
+          %{"state" => "closed", "id" => second_report_id}
+        ]
+      })
+      |> json_response_and_validate_schema(:no_content)
+
+      activity = Activity.get_by_id(id)
+      second_activity = Activity.get_by_id(second_report_id)
+      assert activity.data["state"] == "resolved"
+      assert second_activity.data["state"] == "closed"
+
+      [first_log_entry, second_log_entry] = Repo.all(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(first_log_entry) ==
+               "@#{admin.nickname} updated report ##{id} with 'resolved' state"
+
+      assert ModerationLog.get_log_entry_message(second_log_entry) ==
+               "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state"
+    end
+  end
+
+  describe "GET /api/pleroma/admin/reports" do
+    test "returns empty response when no reports created", %{conn: conn} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports")
+        |> json_response_and_validate_schema(:ok)
+
+      assert Enum.empty?(response["reports"])
+      assert response["total"] == 0
+    end
+
+    test "returns reports", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports")
+        |> json_response_and_validate_schema(:ok)
+
+      [report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert report["id"] == report_id
+
+      assert response["total"] == 1
+    end
+
+    test "returns reports with specified state", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: first_report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      {:ok, %{id: second_report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I don't like this user"
+        })
+
+      CommonAPI.update_report_state(second_report_id, "closed")
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports?state=open")
+        |> json_response_and_validate_schema(:ok)
+
+      assert [open_report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert open_report["id"] == first_report_id
+
+      assert response["total"] == 1
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports?state=closed")
+        |> json_response_and_validate_schema(:ok)
+
+      assert [closed_report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert closed_report["id"] == second_report_id
+
+      assert response["total"] == 1
+
+      assert %{"total" => 0, "reports" => []} ==
+               conn
+               |> get("/api/pleroma/admin/reports?state=resolved", %{
+                 "" => ""
+               })
+               |> json_response_and_validate_schema(:ok)
+    end
+
+    test "returns 403 when requested by a non-admin" do
+      user = insert(:user)
+      token = insert(:oauth_token, user: user)
+
+      conn =
+        build_conn()
+        |> assign(:user, user)
+        |> assign(:token, token)
+        |> get("/api/pleroma/admin/reports")
+
+      assert json_response(conn, :forbidden) ==
+               %{"error" => "User is not an admin or OAuth admin scope is not granted."}
+    end
+
+    test "returns 403 when requested by anonymous" do
+      conn = get(build_conn(), "/api/pleroma/admin/reports")
+
+      assert json_response(conn, :forbidden) == %{
+               "error" => "Invalid credentials."
+             }
+    end
+  end
+
+  describe "POST /api/pleroma/admin/reports/:id/notes" do
+    setup %{conn: conn, admin: admin} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{
+        content: "this is disgusting!"
+      })
+
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{
+        content: "this is disgusting2!"
+      })
+
+      %{
+        admin_id: admin.id,
+        report_id: report_id
+      }
+    end
+
+    test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
+      assert [note, _] = Repo.all(ReportNote)
+
+      assert %{
+               activity_id: ^report_id,
+               content: "this is disgusting!",
+               user_id: ^admin_id
+             } = note
+    end
+
+    test "it returns reports with notes", %{conn: conn, admin: admin} do
+      conn = get(conn, "/api/pleroma/admin/reports")
+
+      response = json_response_and_validate_schema(conn, 200)
+      notes = hd(response["reports"])["notes"]
+      [note, _] = notes
+
+      assert note["user"]["nickname"] == admin.nickname
+      assert note["content"] == "this is disgusting!"
+      assert note["created_at"]
+      assert response["total"] == 1
+    end
+
+    test "it deletes the note", %{conn: conn, report_id: report_id} do
+      assert ReportNote |> Repo.all() |> length() == 2
+      assert [note, _] = Repo.all(ReportNote)
+
+      delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}")
+
+      assert ReportNote |> Repo.all() |> length() == 1
+    end
+  end
+end
index 124d8dc2ea7b3b8a81de38b9e0cdf72ccbbf9be1..eff78fb0a1f02e7294e5ad0b8bca671db8d8c968 100644 (file)
@@ -42,6 +42,14 @@ defmodule Pleroma.Web.AdminAPI.StatusControllerTest do
         |> json_response_and_validate_schema(200)
 
       assert response["id"] == activity.id
+
+      account = response["account"]
+      actor = User.get_by_ap_id(activity.actor)
+
+      assert account["id"] == actor.id
+      assert account["nickname"] == actor.nickname
+      assert account["deactivated"] == actor.deactivated
+      assert account["confirmation_pending"] == actor.confirmation_pending
     end
   end
 
index 69622820374ddd2b62974639495b4f3e03cbf930..7c420985d927992014316f43f5af380f543f4336 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
   use Pleroma.Web.ConnCase
 
+  import Mock
   import Pleroma.Factory
 
   setup do: clear_config([:instance, :max_account_fields])
@@ -52,24 +53,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
       user = Repo.get(User, user_data["id"])
 
-      res_conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{
-          "pleroma_settings_store" => %{
-            masto_fe: %{
-              theme: "blub"
+      clear_config([:instance, :federating], true)
+
+      with_mock Pleroma.Web.Federator,
+        publish: fn _activity -> :ok end do
+        res_conn =
+          conn
+          |> assign(:user, user)
+          |> patch("/api/v1/accounts/update_credentials", %{
+            "pleroma_settings_store" => %{
+              masto_fe: %{
+                theme: "blub"
+              }
             }
-          }
-        })
+          })
 
-      assert user_data = json_response_and_validate_schema(res_conn, 200)
+        assert user_data = json_response_and_validate_schema(res_conn, 200)
 
-      assert user_data["pleroma"]["settings_store"] ==
-               %{
-                 "pleroma_fe" => %{"theme" => "bla"},
-                 "masto_fe" => %{"theme" => "blub"}
-               }
+        assert user_data["pleroma"]["settings_store"] ==
+                 %{
+                   "pleroma_fe" => %{"theme" => "bla"},
+                   "masto_fe" => %{"theme" => "blub"}
+                 }
+
+        assert_called(Pleroma.Web.Federator.publish(:_))
+      end
     end
 
     test "updates the user's bio", %{conn: conn} do
index 7d0cafccc834144e0e8b084a7dc59ffbb0d36b22..84d46895edcb7b436cd57ed4022c0e597975211c 100644 (file)
@@ -71,10 +71,48 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         get(conn, "/api/v2/search?q=天子")
         |> json_response_and_validate_schema(200)
 
+      assert results["hashtags"] == [
+               %{"name" => "天子", "url" => "#{Web.base_url()}/tag/天子"}
+             ]
+
       [status] = results["statuses"]
       assert status["id"] == to_string(activity.id)
     end
 
+    test "constructs hashtags from search query", %{conn: conn} do
+      results =
+        conn
+        |> get("/api/v2/search?#{URI.encode_query(%{q: "some text with #explicit #hashtags"})}")
+        |> json_response_and_validate_schema(200)
+
+      assert results["hashtags"] == [
+               %{"name" => "explicit", "url" => "#{Web.base_url()}/tag/explicit"},
+               %{"name" => "hashtags", "url" => "#{Web.base_url()}/tag/hashtags"}
+             ]
+
+      results =
+        conn
+        |> get("/api/v2/search?#{URI.encode_query(%{q: "john doe JOHN DOE"})}")
+        |> json_response_and_validate_schema(200)
+
+      assert results["hashtags"] == [
+               %{"name" => "john", "url" => "#{Web.base_url()}/tag/john"},
+               %{"name" => "doe", "url" => "#{Web.base_url()}/tag/doe"},
+               %{"name" => "JohnDoe", "url" => "#{Web.base_url()}/tag/JohnDoe"}
+             ]
+
+      results =
+        conn
+        |> get("/api/v2/search?#{URI.encode_query(%{q: "accident-prone"})}")
+        |> json_response_and_validate_schema(200)
+
+      assert results["hashtags"] == [
+               %{"name" => "accident", "url" => "#{Web.base_url()}/tag/accident"},
+               %{"name" => "prone", "url" => "#{Web.base_url()}/tag/prone"},
+               %{"name" => "AccidentProne", "url" => "#{Web.base_url()}/tag/AccidentProne"}
+             ]
+    end
+
     test "excludes a blocked users from search results", %{conn: conn} do
       user = insert(:user)
       user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"})
@@ -179,7 +217,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
       [account | _] = results["accounts"]
       assert account["id"] == to_string(user_three.id)
 
-      assert results["hashtags"] == []
+      assert results["hashtags"] == ["2hu"]
 
       [status] = results["statuses"]
       assert status["id"] == to_string(activity.id)
index 2375ac8e8d88ec23d6df9d2ba153109d54b3f8c5..f069390c11aca746b8b0acf11898199cf72e9d68 100644 (file)
@@ -60,9 +60,9 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
   describe "public" do
     @tag capture_log: true
     test "the public timeline", %{conn: conn} do
-      following = insert(:user)
+      user = insert(:user)
 
-      {:ok, _activity} = CommonAPI.post(following, %{status: "test"})
+      {:ok, activity} = CommonAPI.post(user, %{status: "test"})
 
       _activity = insert(:note_activity, local: false)
 
@@ -77,6 +77,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       conn = get(build_conn(), "/api/v1/timelines/public?local=1")
 
       assert [%{"content" => "test"}] = json_response_and_validate_schema(conn, :ok)
+
+      # does not contain repeats
+      {:ok, _} = CommonAPI.repeat(activity.id, user)
+
+      conn = get(build_conn(), "/api/v1/timelines/public?local=true")
+
+      assert [_] = json_response_and_validate_schema(conn, :ok)
     end
 
     test "the public timeline includes only public statuses for an authenticated user" do
@@ -90,6 +97,49 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       res_conn = get(conn, "/api/v1/timelines/public")
       assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
+
+    test "doesn't return replies if follower is posting with blocked user" do
+      %{conn: conn, user: blocker} = oauth_access(["read:statuses"])
+      [blockee, friend] = insert_list(2, :user)
+      {:ok, blocker} = User.follow(blocker, friend)
+      {:ok, _} = User.block(blocker, blockee)
+
+      conn = assign(conn, :user, blocker)
+
+      {:ok, %{id: activity_id} = activity} = CommonAPI.post(friend, %{status: "hey!"})
+
+      {:ok, reply_from_blockee} =
+        CommonAPI.post(blockee, %{status: "heya", in_reply_to_status_id: activity})
+
+      {:ok, _reply_from_friend} =
+        CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee})
+
+      res_conn = get(conn, "/api/v1/timelines/public")
+      [%{"id" => ^activity_id}] = json_response_and_validate_schema(res_conn, 200)
+    end
+
+    test "doesn't return replies if follow is posting with users from blocked domain" do
+      %{conn: conn, user: blocker} = oauth_access(["read:statuses"])
+      friend = insert(:user)
+      blockee = insert(:user, ap_id: "https://example.com/users/blocked")
+      {:ok, blocker} = User.follow(blocker, friend)
+      {:ok, blocker} = User.block_domain(blocker, "example.com")
+
+      conn = assign(conn, :user, blocker)
+
+      {:ok, %{id: activity_id} = activity} = CommonAPI.post(friend, %{status: "hey!"})
+
+      {:ok, reply_from_blockee} =
+        CommonAPI.post(blockee, %{status: "heya", in_reply_to_status_id: activity})
+
+      {:ok, _reply_from_friend} =
+        CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee})
+
+      res_conn = get(conn, "/api/v1/timelines/public")
+
+      activities = json_response_and_validate_schema(res_conn, 200)
+      [%{"id" => ^activity_id}] = activities
+    end
   end
 
   defp local_and_remote_activities do