From d3cf7e19fbe089b3a6d62d6a26f3dfc866a6669d Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 17 Mar 2020 13:02:10 +0100
Subject: [PATCH 01/43] activity_pub_controller_test.exs: test posting with AP
 C2S uploaded media

---
 .../activity_pub_controller_test.exs          | 34 +++++++++++++++++--
 1 file changed, 32 insertions(+), 2 deletions(-)

diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index bd8e0b5cc..2bd494a37 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -1241,16 +1241,46 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
         filename: "an_image.jpg"
       }
 
-      conn =
+      object =
         conn
         |> assign(:user, user)
         |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
+        |> json_response(:created)
 
-      assert object = json_response(conn, :created)
       assert object["name"] == desc
       assert object["type"] == "Document"
       assert object["actor"] == user.ap_id
+      assert [%{"href" => object_href}] = object["url"]
 
+      activity_request = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "type" => "Create",
+        "object" => %{
+          "type" => "Note",
+          "content" => "AP C2S test, attachment",
+          "attachment" => [object]
+        },
+        "to" => "https://www.w3.org/ns/activitystreams#Public",
+        "cc" => []
+      }
+
+      activity_response =
+        conn
+        |> assign(:user, user)
+        |> post("/users/#{user.nickname}/outbox", activity_request)
+        |> json_response(:created)
+
+      assert activity_response["id"]
+      assert activity_response["object"]
+      assert activity_response["actor"] == user.ap_id
+
+      assert %Object{data: %{"attachment" => [attachment]}} = Object.normalize(activity_response["object"])
+      assert attachment["type"] == "Document"
+      assert attachment["name"] == desc
+      assert [%{"href" => attachment_href}] = attachment["url"]
+      assert attachment_href == object_href
+
+      # Fails if unauthenticated
       conn
       |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
       |> json_response(403)

From f9d622d25a744f58fbaf8370ad4435597bb15bf0 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 19 Mar 2020 15:08:49 +0100
Subject: [PATCH 02/43] WIP

---
 lib/pleroma/web/activity_pub/transmogrifier.ex | 15 ---------------
 .../activity_pub_controller_test.exs           | 18 ++++++++++++++----
 2 files changed, 14 insertions(+), 19 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 9cd3de705..db848f657 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -202,21 +202,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("conversation", context)
   end
 
-  def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
-    attachments =
-      Enum.map(attachment, fn data ->
-        media_type = data["mediaType"] || data["mimeType"]
-        href = data["url"] || data["href"]
-        url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
-
-        data
-        |> Map.put("mediaType", media_type)
-        |> Map.put("url", url)
-      end)
-
-    Map.put(object, "attachment", attachments)
-  end
-
   def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do
     object
     |> Map.put("attachment", [attachment])
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 2bd494a37..01c955c0a 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -1250,7 +1250,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert object["name"] == desc
       assert object["type"] == "Document"
       assert object["actor"] == user.ap_id
-      assert [%{"href" => object_href}] = object["url"]
+      assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
+      assert is_binary(object_href)
+      assert object_mediatype == "image/jpeg"
 
       activity_request = %{
         "@context" => "https://www.w3.org/ns/activitystreams",
@@ -1274,11 +1276,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert activity_response["object"]
       assert activity_response["actor"] == user.ap_id
 
-      assert %Object{data: %{"attachment" => [attachment]}} = Object.normalize(activity_response["object"])
+      assert %Object{data: %{"attachment" => [attachment]}} =
+               Object.normalize(activity_response["object"])
+
       assert attachment["type"] == "Document"
       assert attachment["name"] == desc
-      assert [%{"href" => attachment_href}] = attachment["url"]
-      assert attachment_href == object_href
+
+      assert [
+               %{
+                 "href" => ^object_href,
+                 "type" => "Link",
+                 "mediaType" => ^object_mediatype
+               }
+             ] = attachment["url"]
 
       # Fails if unauthenticated
       conn

From c1fd4f665335ba67336bd1b2fab2d9df5e247e08 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 19 Mar 2020 19:10:03 +0100
Subject: [PATCH 03/43] transmogrifier.ex: rework fix_attachment for better IR

---
 .../web/activity_pub/transmogrifier.ex        | 45 +++++++++++++++++++
 test/web/activity_pub/transmogrifier_test.exs | 30 +++----------
 2 files changed, 50 insertions(+), 25 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index db848f657..df5ca0239 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -202,6 +202,51 @@ 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 ->
+        url =
+          cond do
+            is_list(data["url"]) -> List.first(data["url"])
+            is_map(data["url"]) -> data["url"]
+            true -> nil
+          end
+
+        media_type =
+          cond do
+            is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"]
+            is_binary(data["mediaType"]) -> data["mediaType"]
+            is_binary(data["mimeType"]) -> data["mimeType"]
+            true -> nil
+          end
+
+        href =
+          cond do
+            is_map(url) && is_binary(url["href"]) -> url["href"]
+            is_binary(data["url"]) -> data["url"]
+            is_binary(data["href"]) -> data["href"]
+          end
+
+        attachment_url =
+          %{"href" => href}
+          |> add_if_present("mediaType", media_type)
+          |> add_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"])
+      end)
+
+    Map.put(object, "attachment", attachments)
+  end
+
   def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do
     object
     |> Map.put("attachment", [attachment])
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index efbca82f6..242d933e7 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -1228,19 +1228,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       attachment = %{
         "type" => "Link",
         "mediaType" => "video/mp4",
-        "href" =>
-          "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
-        "mimeType" => "video/mp4",
-        "size" => 5_015_880,
         "url" => [
           %{
             "href" =>
               "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
-            "mediaType" => "video/mp4",
-            "type" => "Link"
+            "mediaType" => "video/mp4"
           }
-        ],
-        "width" => 480
+        ]
       }
 
       assert object.data["url"] ==
@@ -2067,11 +2061,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
                  %{
                    "mediaType" => "video/mp4",
                    "url" => [
-                     %{
-                       "href" => "https://peertube.moe/stat-480.mp4",
-                       "mediaType" => "video/mp4",
-                       "type" => "Link"
-                     }
+                     %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
                    ]
                  }
                ]
@@ -2089,23 +2079,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
                  %{
                    "mediaType" => "video/mp4",
                    "url" => [
-                     %{
-                       "href" => "https://pe.er/stat-480.mp4",
-                       "mediaType" => "video/mp4",
-                       "type" => "Link"
-                     }
+                     %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
                    ]
                  },
                  %{
-                   "href" => "https://pe.er/stat-480.mp4",
                    "mediaType" => "video/mp4",
-                   "mimeType" => "video/mp4",
                    "url" => [
-                     %{
-                       "href" => "https://pe.er/stat-480.mp4",
-                       "mediaType" => "video/mp4",
-                       "type" => "Link"
-                     }
+                     %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
                    ]
                  }
                ]

From 4a2538967caf5b0f9970cc5f973c16ea5d776aa3 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Tue, 24 Mar 2020 20:18:27 +0400
Subject: [PATCH 04/43] Support pagination in conversations

---
 CHANGELOG.md                                    |  3 +++
 lib/pleroma/web/activity_pub/activity_pub.ex    |  2 +-
 .../controllers/pleroma_api_controller.ex       | 10 +++++-----
 .../controllers/pleroma_api_controller_test.exs | 17 +++++++++++++++++
 4 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15a073c64..905364d7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
 </details>
 
+### Fixed
+- Support pagination in conversations API
+
 ## [2.0.0] - 2019-03-08
 ### Security
 - Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 30e282840..351d1bdb8 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -696,7 +696,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  defp fetch_activities_for_context_query(context, opts) do
+  def fetch_activities_for_context_query(context, opts) do
     public = [Constants.as_public()]
 
     recipients =
diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
index dae7f0f2f..edb071baa 100644
--- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
@@ -110,12 +110,11 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
   end
 
   def conversation_statuses(
-        %{assigns: %{user: user}} = conn,
+        %{assigns: %{user: %{id: user_id} = user}} = conn,
         %{"id" => participation_id} = params
       ) do
-    with %Participation{} = participation <-
-           Participation.get(participation_id, preload: [:conversation]),
-         true <- user.id == participation.user_id do
+    with %Participation{user_id: ^user_id} = participation <-
+           Participation.get(participation_id, preload: [:conversation]) do
       params =
         params
         |> Map.put("blocking_user", user)
@@ -124,7 +123,8 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
 
       activities =
         participation.conversation.ap_id
-        |> ActivityPub.fetch_activities_for_context(params)
+        |> ActivityPub.fetch_activities_for_context_query(params)
+        |> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
         |> Enum.reverse()
 
       conn
diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
index 32250f06f..8bf7eb3be 100644
--- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
+++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
@@ -169,6 +169,23 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     id_one = activity.id
     id_two = activity_two.id
     assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result
+
+    {:ok, %{id: id_three}} =
+      CommonAPI.post(other_user, %{
+        "status" => "Bye!",
+        "in_reply_to_status_id" => activity.id,
+        "in_reply_to_conversation_id" => participation.id
+      })
+
+    assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
+             conn
+             |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
+             |> json_response(:ok)
+
+    assert [%{"id" => ^id_three}] =
+             conn
+             |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
+             |> json_response(:ok)
   end
 
   test "PATCH /api/v1/pleroma/conversations/:id" do

From be9d18461a5ed6bd835e2eba8d3b54ba61fc51fb Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sat, 28 Mar 2020 18:49:03 +0300
Subject: [PATCH 05/43] FollowingRelationship storage & performance
 optimizations (state turned `ecto_enum`-driven integer, reorganized indices
 etc.).

---
 lib/pleroma/ecto_enums.ex                     |  6 +++
 lib/pleroma/following_relationship.ex         | 43 ++++++++++++++++---
 lib/pleroma/user.ex                           | 18 +++++---
 lib/pleroma/user/query.ex                     |  6 +--
 lib/pleroma/web/activity_pub/mrf.ex           |  2 +-
 .../web/activity_pub/transmogrifier.ex        | 13 +++---
 lib/pleroma/web/common_api/common_api.ex      |  4 +-
 .../web/mastodon_api/views/account_view.ex    |  6 +--
 ...llowing_relationships_state_to_integer.exs | 29 +++++++++++++
 ...owing_relationships_following_id_index.exs | 11 +++++
 test/following_relationship_test.exs          |  8 ++--
 test/tasks/user_test.exs                      |  2 +-
 test/user_test.exs                            |  9 ++--
 test/web/activity_pub/transmogrifier_test.exs |  2 +-
 test/web/common_api/common_api_test.exs       |  4 +-
 .../follow_request_controller_test.exs        |  4 +-
 test/web/streamer/streamer_test.exs           |  6 +--
 17 files changed, 128 insertions(+), 45 deletions(-)
 create mode 100644 priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs
 create mode 100644 priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs

diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex
index d9b601223..b98ac4ba1 100644
--- a/lib/pleroma/ecto_enums.ex
+++ b/lib/pleroma/ecto_enums.ex
@@ -11,3 +11,9 @@ defenum(UserRelationshipTypeEnum,
   notification_mute: 4,
   inverse_subscription: 5
 )
+
+defenum(FollowingRelationshipStateEnum,
+  follow_pending: 1,
+  follow_accept: 2,
+  follow_reject: 3
+)
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
index a9538ea4e..a28da8bec 100644
--- a/lib/pleroma/following_relationship.ex
+++ b/lib/pleroma/following_relationship.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.FollowingRelationship do
   alias Pleroma.User
 
   schema "following_relationships" do
-    field(:state, :string, default: "accept")
+    field(:state, FollowingRelationshipStateEnum, default: :follow_pending)
 
     belongs_to(:follower, User, type: CompatType)
     belongs_to(:following, User, type: CompatType)
@@ -27,6 +27,19 @@ defmodule Pleroma.FollowingRelationship do
     |> put_assoc(:follower, attrs.follower)
     |> put_assoc(:following, attrs.following)
     |> validate_required([:state, :follower, :following])
+    |> unique_constraint(:follower_id,
+      name: :following_relationships_follower_id_following_id_index
+    )
+    |> validate_not_self_relationship()
+  end
+
+  def state_to_enum(state) when is_binary(state) do
+    case state do
+      "pending" -> :follow_pending
+      "accept" -> :follow_accept
+      "reject" -> :follow_reject
+      _ -> raise "State is not convertible to FollowingRelationshipStateEnum: #{state}"
+    end
   end
 
   def get(%User{} = follower, %User{} = following) do
@@ -35,7 +48,7 @@ defmodule Pleroma.FollowingRelationship do
     |> Repo.one()
   end
 
-  def update(follower, following, "reject"), do: unfollow(follower, following)
+  def update(follower, following, :follow_reject), do: unfollow(follower, following)
 
   def update(%User{} = follower, %User{} = following, state) do
     case get(follower, following) do
@@ -50,7 +63,7 @@ defmodule Pleroma.FollowingRelationship do
     end
   end
 
-  def follow(%User{} = follower, %User{} = following, state \\ "accept") do
+  def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
     %__MODULE__{}
     |> changeset(%{follower: follower, following: following, state: state})
     |> Repo.insert(on_conflict: :nothing)
@@ -80,7 +93,7 @@ defmodule Pleroma.FollowingRelationship do
   def get_follow_requests(%User{id: id}) do
     __MODULE__
     |> join(:inner, [r], f in assoc(r, :follower))
-    |> where([r], r.state == "pending")
+    |> where([r], r.state == ^:follow_pending)
     |> where([r], r.following_id == ^id)
     |> select([r, f], f)
     |> Repo.all()
@@ -88,7 +101,7 @@ defmodule Pleroma.FollowingRelationship do
 
   def following?(%User{id: follower_id}, %User{id: followed_id}) do
     __MODULE__
-    |> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
+    |> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept)
     |> Repo.exists?()
   end
 
@@ -97,7 +110,7 @@ defmodule Pleroma.FollowingRelationship do
       __MODULE__
       |> join(:inner, [r], u in User, on: r.following_id == u.id)
       |> where([r], r.follower_id == ^user.id)
-      |> where([r], r.state == "accept")
+      |> where([r], r.state == ^:follow_accept)
       |> select([r, u], u.follower_address)
       |> Repo.all()
 
@@ -157,4 +170,22 @@ defmodule Pleroma.FollowingRelationship do
       fr -> fr.follower_id == follower.id and fr.following_id == following.id
     end)
   end
+
+  defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
+    changeset
+    |> validate_change(:following_id, fn _, following_id ->
+      if following_id == get_field(changeset, :follower_id) do
+        [target_id: "can't be equal to follower_id"]
+      else
+        []
+      end
+    end)
+    |> validate_change(:follower_id, fn _, follower_id ->
+      if follower_id == get_field(changeset, :following_id) do
+        [source_id: "can't be equal to following_id"]
+      else
+        []
+      end
+    end)
+  end
 end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index d9aa54057..6ffb82045 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -697,7 +697,7 @@ defmodule Pleroma.User do
 
   @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
   def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
-    follow(follower, followed, "pending")
+    follow(follower, followed, :follow_pending)
   end
 
   def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
@@ -717,14 +717,14 @@ defmodule Pleroma.User do
   def follow_all(follower, followeds) do
     followeds
     |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
-    |> Enum.each(&follow(follower, &1, "accept"))
+    |> Enum.each(&follow(follower, &1, :follow_accept))
 
     set_cache(follower)
   end
 
   defdelegate following(user), to: FollowingRelationship
 
-  def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
+  def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
     deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
 
     cond do
@@ -751,7 +751,7 @@ defmodule Pleroma.User do
 
   def unfollow(%User{} = follower, %User{} = followed) do
     case get_follow_state(follower, followed) do
-      state when state in ["accept", "pending"] ->
+      state when state in [:follow_pending, :follow_accept] ->
         FollowingRelationship.unfollow(follower, followed)
         {:ok, followed} = update_follower_count(followed)
 
@@ -769,6 +769,7 @@ defmodule Pleroma.User do
 
   defdelegate following?(follower, followed), to: FollowingRelationship
 
+  @doc "Returns follow state as FollowingRelationshipStateEnum value"
   def get_follow_state(%User{} = follower, %User{} = following) do
     following_relationship = FollowingRelationship.get(follower, following)
     get_follow_state(follower, following, following_relationship)
@@ -782,8 +783,11 @@ defmodule Pleroma.User do
     case {following_relationship, following.local} do
       {nil, false} ->
         case Utils.fetch_latest_follow(follower, following) do
-          %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
-          _ -> nil
+          %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
+            FollowingRelationship.state_to_enum(state)
+
+          _ ->
+            nil
         end
 
       {%{state: state}, _} ->
@@ -1282,7 +1286,7 @@ defmodule Pleroma.User do
 
   def blocks?(%User{} = user, %User{} = target) do
     blocks_user?(user, target) ||
-      (!User.following?(user, target) && blocks_domain?(user, target))
+      (blocks_domain?(user, target) and not User.following?(user, target))
   end
 
   def blocks_user?(%User{} = user, %User{} = target) do
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 884e33039..ec88088cf 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -148,7 +148,7 @@ defmodule Pleroma.User.Query do
       as: :relationships,
       on: r.following_id == ^id and r.follower_id == u.id
     )
-    |> where([relationships: r], r.state == "accept")
+    |> where([relationships: r], r.state == ^:follow_accept)
   end
 
   defp compose_query({:friends, %User{id: id}}, query) do
@@ -158,7 +158,7 @@ defmodule Pleroma.User.Query do
       as: :relationships,
       on: r.following_id == u.id and r.follower_id == ^id
     )
-    |> where([relationships: r], r.state == "accept")
+    |> where([relationships: r], r.state == ^:follow_accept)
   end
 
   defp compose_query({:recipients_from_activity, to}, query) do
@@ -173,7 +173,7 @@ defmodule Pleroma.User.Query do
     )
     |> where(
       [u, following: f, relationships: r],
-      u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept")
+      u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept)
     )
     |> distinct(true)
   end
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index a0b3af432..f54647945 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
 
   @spec subdomain_match?([Regex.t()], String.t()) :: boolean()
   def subdomain_match?(domains, host) do
-    Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
+    !!Enum.find(domains, fn domain -> Regex.match?(domain, host) end)
   end
 
   @callback describe() :: {:ok | :error, Map.t()}
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index d6549a932..37e485741 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -490,7 +490,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
            {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
            {_, {:ok, _}} <-
              {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
-           {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
+           {:ok, _relationship} <-
+             FollowingRelationship.update(follower, followed, :follow_accept) do
         ActivityPub.accept(%{
           to: [follower.ap_id],
           actor: followed,
@@ -500,7 +501,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       else
         {:user_blocked, true} ->
           {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
-          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
 
           ActivityPub.reject(%{
             to: [follower.ap_id],
@@ -511,7 +512,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
         {:follow, {:error, _}} ->
           {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
-          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
 
           ActivityPub.reject(%{
             to: [follower.ap_id],
@@ -521,7 +522,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           })
 
         {:user_locked, true} ->
-          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending")
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
           :noop
       end
 
@@ -541,7 +542,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
          %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
       ActivityPub.accept(%{
         to: follow_activity.data["to"],
         type: "Accept",
@@ -564,7 +565,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
          %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
          {:ok, activity} <-
            ActivityPub.reject(%{
              to: follow_activity.data["to"],
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 2646b9f7b..d530da42c 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -42,7 +42,7 @@ defmodule Pleroma.Web.CommonAPI do
     with {:ok, follower} <- User.follow(follower, followed),
          %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
          {:ok, _activity} <-
            ActivityPub.accept(%{
              to: [follower.ap_id],
@@ -57,7 +57,7 @@ defmodule Pleroma.Web.CommonAPI do
   def reject_follow_request(follower, followed) do
     with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
          {:ok, _activity} <-
            ActivityPub.reject(%{
              to: [follower.ap_id],
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 0efcabc01..f2dc2a9bd 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -71,7 +71,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     followed_by =
       if following_relationships do
         case FollowingRelationship.find(following_relationships, target, reading_user) do
-          %{state: "accept"} -> true
+          %{state: :follow_accept} -> true
           _ -> false
         end
       else
@@ -81,7 +81,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
     %{
       id: to_string(target.id),
-      following: follow_state == "accept",
+      following: follow_state == :follow_accept,
       followed_by: followed_by,
       blocking:
         UserRelationship.exists?(
@@ -123,7 +123,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
           reading_user,
           &User.subscribed_to?(&2, &1)
         ),
-      requested: follow_state == "pending",
+      requested: follow_state == :follow_pending,
       domain_blocking: User.blocks_domain?(reading_user, target),
       showing_reblogs:
         not UserRelationship.exists?(
diff --git a/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs
new file mode 100644
index 000000000..d5a431c00
--- /dev/null
+++ b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs
@@ -0,0 +1,29 @@
+defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do
+  use Ecto.Migration
+
+  @alter_apps_scopes "ALTER TABLE following_relationships ALTER COLUMN state"
+
+  def up do
+    execute("""
+    #{@alter_apps_scopes} TYPE integer USING
+    CASE
+      WHEN state = 'pending' THEN 1
+      WHEN state = 'accept' THEN 2
+      WHEN state = 'reject' THEN 3
+      ELSE 0
+    END;
+    """)
+  end
+
+  def down do
+    execute("""
+    #{@alter_apps_scopes} TYPE varchar(255) USING
+    CASE
+      WHEN state = 1 THEN 'pending'
+      WHEN state = 2 THEN 'accept'
+      WHEN state = 3 THEN 'reject'
+      ELSE ''
+    END;
+    """)
+  end
+end
diff --git a/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs
new file mode 100644
index 000000000..4c9faf48f
--- /dev/null
+++ b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs
@@ -0,0 +1,11 @@
+defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do
+  use Ecto.Migration
+
+  # [:follower_index] index is useless because of [:follower_id, :following_id] index
+  # [:following_id] index makes sense because of user's followers-targeted queries
+  def change do
+    drop_if_exists(index(:following_relationships, [:follower_id]))
+
+    create_if_not_exists(drop_if_exists(index(:following_relationships, [:following_id])))
+  end
+end
diff --git a/test/following_relationship_test.exs b/test/following_relationship_test.exs
index 865bb3838..17a468abb 100644
--- a/test/following_relationship_test.exs
+++ b/test/following_relationship_test.exs
@@ -15,28 +15,28 @@ defmodule Pleroma.FollowingRelationshipTest do
     test "returns following addresses without internal.fetch" do
       user = insert(:user)
       fetch_actor = InternalFetchActor.get_actor()
-      FollowingRelationship.follow(fetch_actor, user, "accept")
+      FollowingRelationship.follow(fetch_actor, user, :follow_accept)
       assert FollowingRelationship.following(fetch_actor) == [user.follower_address]
     end
 
     test "returns following addresses without relay" do
       user = insert(:user)
       relay_actor = Relay.get_actor()
-      FollowingRelationship.follow(relay_actor, user, "accept")
+      FollowingRelationship.follow(relay_actor, user, :follow_accept)
       assert FollowingRelationship.following(relay_actor) == [user.follower_address]
     end
 
     test "returns following addresses without remote user" do
       user = insert(:user)
       actor = insert(:user, local: false)
-      FollowingRelationship.follow(actor, user, "accept")
+      FollowingRelationship.follow(actor, user, :follow_accept)
       assert FollowingRelationship.following(actor) == [user.follower_address]
     end
 
     test "returns following addresses with local user" do
       user = insert(:user)
       actor = insert(:user, local: true)
-      FollowingRelationship.follow(actor, user, "accept")
+      FollowingRelationship.follow(actor, user, :follow_accept)
 
       assert FollowingRelationship.following(actor) == [
                actor.follower_address,
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index b45f37263..8df835b56 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -140,7 +140,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do
     test "user is unsubscribed" do
       followed = insert(:user)
       user = insert(:user)
-      User.follow(user, followed, "accept")
+      User.follow(user, followed, :follow_accept)
 
       Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])
 
diff --git a/test/user_test.exs b/test/user_test.exs
index 8055ebd08..e7dfc5980 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -194,7 +194,8 @@ defmodule Pleroma.UserTest do
     CommonAPI.follow(pending_follower, locked)
     CommonAPI.follow(pending_follower, locked)
     CommonAPI.follow(accepted_follower, locked)
-    Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept")
+
+    Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept)
 
     assert [^pending_follower] = User.get_follow_requests(locked)
   end
@@ -319,7 +320,7 @@ defmodule Pleroma.UserTest do
           following_address: "http://localhost:4001/users/fuser2/following"
         })
 
-      {:ok, user} = User.follow(user, followed, "accept")
+      {:ok, user} = User.follow(user, followed, :follow_accept)
 
       {:ok, user, _activity} = User.unfollow(user, followed)
 
@@ -332,7 +333,7 @@ defmodule Pleroma.UserTest do
       followed = insert(:user)
       user = insert(:user)
 
-      {:ok, user} = User.follow(user, followed, "accept")
+      {:ok, user} = User.follow(user, followed, :follow_accept)
 
       assert User.following(user) == [user.follower_address, followed.follower_address]
 
@@ -353,7 +354,7 @@ defmodule Pleroma.UserTest do
   test "test if a user is following another user" do
     followed = insert(:user)
     user = insert(:user)
-    User.follow(user, followed, "accept")
+    User.follow(user, followed, :follow_accept)
 
     assert User.following?(user, followed)
     refute User.following?(followed, user)
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index b2cabbd30..b998f0d78 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -1622,7 +1622,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
         })
 
       user_two = insert(:user)
-      Pleroma.FollowingRelationship.follow(user_two, user, "accept")
+      Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
 
       {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
       {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 0da0bd2e2..e53a7cedd 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -562,7 +562,7 @@ defmodule Pleroma.Web.CommonAPITest do
       assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
                CommonAPI.follow(follower, followed)
 
-      assert User.get_follow_state(follower, followed) == "pending"
+      assert User.get_follow_state(follower, followed) == :follow_pending
       assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
       assert User.get_follow_state(follower, followed) == nil
 
@@ -584,7 +584,7 @@ defmodule Pleroma.Web.CommonAPITest do
       assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
                CommonAPI.follow(follower, followed)
 
-      assert User.get_follow_state(follower, followed) == "pending"
+      assert User.get_follow_state(follower, followed) == :follow_pending
       assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
       assert User.get_follow_state(follower, followed) == nil
 
diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
index dd848821a..d8dbe4800 100644
--- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs
+++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
-      {:ok, other_user} = User.follow(other_user, user, "pending")
+      {:ok, other_user} = User.follow(other_user, user, :follow_pending)
 
       assert User.following?(other_user, user) == false
 
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
-      {:ok, other_user} = User.follow(other_user, user, "pending")
+      {:ok, other_user} = User.follow(other_user, user, :follow_pending)
 
       user = User.get_cached_by_id(user.id)
       other_user = User.get_cached_by_id(other_user.id)
diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs
index a5d6e8ecf..ad8ce030b 100644
--- a/test/web/streamer/streamer_test.exs
+++ b/test/web/streamer/streamer_test.exs
@@ -197,7 +197,7 @@ defmodule Pleroma.Web.StreamerTest do
       Pleroma.Config.put([:instance, :skip_thread_containment], false)
       author = insert(:user)
       user = insert(:user)
-      User.follow(user, author, "accept")
+      User.follow(user, author, :follow_accept)
 
       activity =
         insert(:note_activity,
@@ -220,7 +220,7 @@ defmodule Pleroma.Web.StreamerTest do
       Pleroma.Config.put([:instance, :skip_thread_containment], true)
       author = insert(:user)
       user = insert(:user)
-      User.follow(user, author, "accept")
+      User.follow(user, author, :follow_accept)
 
       activity =
         insert(:note_activity,
@@ -243,7 +243,7 @@ defmodule Pleroma.Web.StreamerTest do
       Pleroma.Config.put([:instance, :skip_thread_containment], false)
       author = insert(:user)
       user = insert(:user, skip_thread_containment: true)
-      User.follow(user, author, "accept")
+      User.follow(user, author, :follow_accept)
 
       activity =
         insert(:note_activity,

From 9c94b6a327118d8c7ea21355d6c378ef31c54321 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 30 Mar 2020 19:08:37 +0300
Subject: [PATCH 06/43] [#2332] Misc. fixes per code change requests.

---
 lib/pleroma/web/activity_pub/mrf.ex                             | 2 +-
 ...328130139_add_following_relationships_following_id_index.exs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index f54647945..a0b3af432 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
 
   @spec subdomain_match?([Regex.t()], String.t()) :: boolean()
   def subdomain_match?(domains, host) do
-    !!Enum.find(domains, fn domain -> Regex.match?(domain, host) end)
+    Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
   end
 
   @callback describe() :: {:ok | :error, Map.t()}
diff --git a/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs
index 4c9faf48f..884832f84 100644
--- a/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs
+++ b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs
@@ -6,6 +6,6 @@ defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do
   def change do
     drop_if_exists(index(:following_relationships, [:follower_id]))
 
-    create_if_not_exists(drop_if_exists(index(:following_relationships, [:following_id])))
+    create_if_not_exists(index(:following_relationships, [:following_id]))
   end
 end

From ea9c57b26ed463622e4489736fcddb8fca1b3341 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 31 Mar 2020 09:21:42 +0300
Subject: [PATCH 07/43] [#2332] Misc. improvements per code change requests.

---
 lib/pleroma/ecto_enums.ex                     |  4 +-
 lib/pleroma/following_relationship.ex         | 42 +++++++++++--------
 lib/pleroma/user.ex                           |  2 +-
 lib/pleroma/user_relationship.ex              | 33 +++++++++------
 ...llowing_relationships_state_to_integer.exs |  6 +--
 5 files changed, 52 insertions(+), 35 deletions(-)

diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex
index b98ac4ba1..6fc47620c 100644
--- a/lib/pleroma/ecto_enums.ex
+++ b/lib/pleroma/ecto_enums.ex
@@ -4,7 +4,7 @@
 
 import EctoEnum
 
-defenum(UserRelationshipTypeEnum,
+defenum(Pleroma.UserRelationship.Type,
   block: 1,
   mute: 2,
   reblog_mute: 3,
@@ -12,7 +12,7 @@ defenum(UserRelationshipTypeEnum,
   inverse_subscription: 5
 )
 
-defenum(FollowingRelationshipStateEnum,
+defenum(Pleroma.FollowingRelationship.State,
   follow_pending: 1,
   follow_accept: 2,
   follow_reject: 3
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
index a28da8bec..9ccf40495 100644
--- a/lib/pleroma/following_relationship.ex
+++ b/lib/pleroma/following_relationship.ex
@@ -8,12 +8,13 @@ defmodule Pleroma.FollowingRelationship do
   import Ecto.Changeset
   import Ecto.Query
 
+  alias Ecto.Changeset
   alias FlakeId.Ecto.CompatType
   alias Pleroma.Repo
   alias Pleroma.User
 
   schema "following_relationships" do
-    field(:state, FollowingRelationshipStateEnum, default: :follow_pending)
+    field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending)
 
     belongs_to(:follower, User, type: CompatType)
     belongs_to(:following, User, type: CompatType)
@@ -33,13 +34,12 @@ defmodule Pleroma.FollowingRelationship do
     |> validate_not_self_relationship()
   end
 
-  def state_to_enum(state) when is_binary(state) do
-    case state do
-      "pending" -> :follow_pending
-      "accept" -> :follow_accept
-      "reject" -> :follow_reject
-      _ -> raise "State is not convertible to FollowingRelationshipStateEnum: #{state}"
-    end
+  def state_to_enum(state) when state in ["pending", "accept", "reject"] do
+    String.to_existing_atom("follow_#{state}")
+  end
+
+  def state_to_enum(state) do
+    raise "State is not convertible to Pleroma.FollowingRelationship.State: #{state}"
   end
 
   def get(%User{} = follower, %User{} = following) do
@@ -171,16 +171,14 @@ defmodule Pleroma.FollowingRelationship do
     end)
   end
 
-  defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
+  defp validate_not_self_relationship(%Changeset{} = changeset) do
     changeset
-    |> validate_change(:following_id, fn _, following_id ->
-      if following_id == get_field(changeset, :follower_id) do
-        [target_id: "can't be equal to follower_id"]
-      else
-        []
-      end
-    end)
-    |> validate_change(:follower_id, fn _, follower_id ->
+    |> validate_follower_id_following_id_inequality()
+    |> validate_following_id_follower_id_inequality()
+  end
+
+  defp validate_follower_id_following_id_inequality(%Changeset{} = changeset) do
+    validate_change(changeset, :follower_id, fn _, follower_id ->
       if follower_id == get_field(changeset, :following_id) do
         [source_id: "can't be equal to following_id"]
       else
@@ -188,4 +186,14 @@ defmodule Pleroma.FollowingRelationship do
       end
     end)
   end
+
+  defp validate_following_id_follower_id_inequality(%Changeset{} = changeset) do
+    validate_change(changeset, :following_id, fn _, following_id ->
+      if following_id == get_field(changeset, :follower_id) do
+        [target_id: "can't be equal to follower_id"]
+      else
+        []
+      end
+    end)
+  end
 end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 6ffb82045..4f3abd7d5 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -769,7 +769,7 @@ defmodule Pleroma.User do
 
   defdelegate following?(follower, followed), to: FollowingRelationship
 
-  @doc "Returns follow state as FollowingRelationshipStateEnum value"
+  @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
   def get_follow_state(%User{} = follower, %User{} = following) do
     following_relationship = FollowingRelationship.get(follower, following)
     get_follow_state(follower, following, following_relationship)
diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex
index 18a5eec72..ad0d303b1 100644
--- a/lib/pleroma/user_relationship.ex
+++ b/lib/pleroma/user_relationship.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
   import Ecto.Changeset
   import Ecto.Query
 
+  alias Ecto.Changeset
   alias Pleroma.FollowingRelationship
   alias Pleroma.Repo
   alias Pleroma.User
@@ -16,12 +17,12 @@ defmodule Pleroma.UserRelationship do
   schema "user_relationships" do
     belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
     belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
-    field(:relationship_type, UserRelationshipTypeEnum)
+    field(:relationship_type, Pleroma.UserRelationship.Type)
 
     timestamps(updated_at: false)
   end
 
-  for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
+  for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
     # `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
     #   `def create_notification_mute/2`, `def create_inverse_subscription/2`
     def unquote(:"create_#{relationship_type}")(source, target),
@@ -40,7 +41,7 @@ defmodule Pleroma.UserRelationship do
 
   def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
 
-  def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__()
+  def user_relationship_mappings, do: Pleroma.UserRelationship.Type.__enum_map__()
 
   def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
     user_relationship
@@ -147,16 +148,14 @@ defmodule Pleroma.UserRelationship do
     %{user_relationships: user_relationships, following_relationships: following_relationships}
   end
 
-  defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
+  defp validate_not_self_relationship(%Changeset{} = changeset) do
     changeset
-    |> validate_change(:target_id, fn _, target_id ->
-      if target_id == get_field(changeset, :source_id) do
-        [target_id: "can't be equal to source_id"]
-      else
-        []
-      end
-    end)
-    |> validate_change(:source_id, fn _, source_id ->
+    |> validate_source_id_target_id_inequality()
+    |> validate_target_id_source_id_inequality()
+  end
+
+  defp validate_source_id_target_id_inequality(%Changeset{} = changeset) do
+    validate_change(changeset, :source_id, fn _, source_id ->
       if source_id == get_field(changeset, :target_id) do
         [source_id: "can't be equal to target_id"]
       else
@@ -164,4 +163,14 @@ defmodule Pleroma.UserRelationship do
       end
     end)
   end
+
+  defp validate_target_id_source_id_inequality(%Changeset{} = changeset) do
+    validate_change(changeset, :target_id, fn _, target_id ->
+      if target_id == get_field(changeset, :source_id) do
+        [target_id: "can't be equal to source_id"]
+      else
+        []
+      end
+    end)
+  end
 end
diff --git a/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs
index d5a431c00..2b0820f3f 100644
--- a/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs
+++ b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs
@@ -1,11 +1,11 @@
 defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do
   use Ecto.Migration
 
-  @alter_apps_scopes "ALTER TABLE following_relationships ALTER COLUMN state"
+  @alter_following_relationship_state "ALTER TABLE following_relationships ALTER COLUMN state"
 
   def up do
     execute("""
-    #{@alter_apps_scopes} TYPE integer USING
+    #{@alter_following_relationship_state} TYPE integer USING
     CASE
       WHEN state = 'pending' THEN 1
       WHEN state = 'accept' THEN 2
@@ -17,7 +17,7 @@ defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do
 
   def down do
     execute("""
-    #{@alter_apps_scopes} TYPE varchar(255) USING
+    #{@alter_following_relationship_state} TYPE varchar(255) USING
     CASE
       WHEN state = 1 THEN 'pending'
       WHEN state = 2 THEN 'accept'

From d191b0942f64a32a2bf450318fac85981aa17c83 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 31 Mar 2020 22:48:42 +0900
Subject: [PATCH 08/43] Remove no longer used function

---
 lib/pleroma/user.ex | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index d9aa54057..6644d6b66 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1983,17 +1983,6 @@ defmodule Pleroma.User do
 
   def fields(%{fields: fields}), do: fields
 
-  def sanitized_fields(%User{} = user) do
-    user
-    |> User.fields()
-    |> Enum.map(fn %{"name" => name, "value" => value} ->
-      %{
-        "name" => name,
-        "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
-      }
-    end)
-  end
-
   def validate_fields(changeset, remote? \\ false) do
     limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
     limit = Pleroma.Config.get([:instance, limit_name], 0)

From dbf9d719f98770056ac906b3087e7ed501cd64e6 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Wed, 1 Apr 2020 00:05:13 +0900
Subject: [PATCH 09/43] split test for update profile fields

---
 .../update_credentials_test.exs               | 98 ++++++++++---------
 1 file changed, 53 insertions(+), 45 deletions(-)

diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index b693c1a47..8687d7995 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -273,7 +273,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
     test "update fields", %{conn: conn} do
       fields = [
         %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"},
-        %{"name" => "link", "value" => "cofe.io"}
+        %{"name" => "link.io", "value" => "cofe.io"}
       ]
 
       account_data =
@@ -283,7 +283,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
       assert account_data["fields"] == [
                %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
-               %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
+               %{
+                 "name" => "link.io",
+                 "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)
+               }
              ]
 
       assert account_data["source"]["fields"] == [
@@ -291,14 +294,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
                  "name" => "<a href=\"http://google.com\">foo</a>",
                  "value" => "<script>bar</script>"
                },
-               %{"name" => "link", "value" => "cofe.io"}
+               %{"name" => "link.io", "value" => "cofe.io"}
              ]
+    end
 
+    test "update fields by urlencoded", %{conn: conn} do
       fields =
         [
           "fields_attributes[1][name]=link",
-          "fields_attributes[1][value]=cofe.io",
-          "fields_attributes[0][name]=<a href=\"http://google.com\">foo</a>",
+          "fields_attributes[1][value]=http://cofe.io",
+          "fields_attributes[0][name]=foo",
           "fields_attributes[0][value]=bar"
         ]
         |> Enum.join("&")
@@ -310,51 +315,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
         |> json_response(200)
 
       assert account["fields"] == [
-               %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
-               %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
+               %{"name" => "foo", "value" => "bar"},
+               %{
+                 "name" => "link",
+                 "value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>)
+               }
              ]
 
       assert account["source"]["fields"] == [
-               %{
-                 "name" => "<a href=\"http://google.com\">foo</a>",
-                 "value" => "bar"
-               },
-               %{"name" => "link", "value" => "cofe.io"}
+               %{"name" => "foo", "value" => "bar"},
+               %{"name" => "link", "value" => "http://cofe.io"}
              ]
+    end
 
-      name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
-      value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
-
-      long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
-
-      fields = [%{"name" => "<b>foo<b>", "value" => long_value}]
-
-      assert %{"error" => "Invalid request"} ==
-               conn
-               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
-
-      long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
-
-      fields = [%{"name" => long_name, "value" => "bar"}]
-
-      assert %{"error" => "Invalid request"} ==
-               conn
-               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
-
-      Pleroma.Config.put([:instance, :max_account_fields], 1)
-
-      fields = [
-        %{"name" => "<b>foo<b>", "value" => "<i>bar</i>"},
-        %{"name" => "link", "value" => "cofe.io"}
-      ]
-
-      assert %{"error" => "Invalid request"} ==
-               conn
-               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
-
+    test "update fields with empty name", %{conn: conn} do
       fields = [
         %{"name" => "foo", "value" => ""},
         %{"name" => "", "value" => "bar"}
@@ -369,5 +343,39 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
                %{"name" => "foo", "value" => ""}
              ]
     end
+
+    test "update fields when invalid request", %{conn: conn} do
+      name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
+      value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
+
+      long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
+      long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
+
+      fields = [%{"name" => "foo", "value" => long_value}]
+
+      assert %{"error" => "Invalid request"} ==
+               conn
+               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+               |> json_response(403)
+
+      fields = [%{"name" => long_name, "value" => "bar"}]
+
+      assert %{"error" => "Invalid request"} ==
+               conn
+               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+               |> json_response(403)
+
+      Pleroma.Config.put([:instance, :max_account_fields], 1)
+
+      fields = [
+        %{"name" => "foo", "value" => "bar"},
+        %{"name" => "link", "value" => "cofe.io"}
+      ]
+
+      assert %{"error" => "Invalid request"} ==
+               conn
+               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+               |> json_response(403)
+    end
   end
 end

From 7408f003a663c5f634cabad963c0446ba54810bf Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 31 Mar 2020 11:13:53 +0000
Subject: [PATCH 10/43] Use `Pleroma.Formatter.linkify` instead of
 `AutoLinker.link`

---
 lib/pleroma/user.ex | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 6644d6b66..c29935871 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -16,6 +16,7 @@ defmodule Pleroma.User do
   alias Pleroma.Conversation.Participation
   alias Pleroma.Delivery
   alias Pleroma.FollowingRelationship
+  alias Pleroma.Formatter
   alias Pleroma.HTML
   alias Pleroma.Keys
   alias Pleroma.Notification
@@ -456,7 +457,7 @@ defmodule Pleroma.User do
 
       fields =
         raw_fields
-        |> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
+        |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
 
       changeset
       |> put_change(:raw_fields, raw_fields)
@@ -466,6 +467,12 @@ defmodule Pleroma.User do
     end
   end
 
+  defp parse_fields(value) do
+    value
+    |> Formatter.linkify(mentions_format: :full)
+    |> elem(0)
+  end
+
   defp put_change_if_present(changeset, map_field, value_function) do
     if value = get_change(changeset, map_field) do
       with {:ok, new_value} <- value_function.(value) do

From 2f2bd7fe72f474b7177c751a2dc3af716622ba91 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Wed, 1 Apr 2020 19:49:09 +0300
Subject: [PATCH 11/43] Ability to control the output of
 account/pleroma/relationship in statuses in order to improve the rendering
 performance. See `[:extensions, output_relationships_in_statuses_by_default]`
 setting and `with_relationships` param.

---
 CHANGELOG.md                                  |  3 +-
 benchmarks/load_testing/fetcher.ex            | 21 +++++++---
 config/config.exs                             |  2 +
 config/description.exs                        | 16 ++++++++
 lib/mix/tasks/pleroma/benchmark.ex            |  3 +-
 lib/pleroma/user_relationship.ex              | 18 +++++++--
 .../web/admin_api/admin_api_controller.ex     |  6 +--
 .../web/admin_api/views/report_view.ex        |  7 +++-
 lib/pleroma/web/common_api/activity_draft.ex  |  2 +-
 lib/pleroma/web/controller_helper.ex          | 24 +++++++++--
 .../controllers/account_controller.ex         | 15 ++++++-
 .../controllers/notification_controller.ex    |  8 +++-
 .../controllers/search_controller.ex          | 24 ++++++++---
 .../controllers/status_controller.ex          | 26 +++++++++---
 .../controllers/timeline_controller.ex        | 40 +++++++++++++++----
 .../web/mastodon_api/views/account_view.ex    | 15 ++++---
 .../mastodon_api/views/notification_view.ex   | 30 ++++++++------
 .../web/mastodon_api/views/status_view.ex     | 11 +++--
 .../controllers/account_controller.ex         |  9 ++++-
 .../controllers/pleroma_api_controller.ex     | 17 ++++++--
 .../20190414125034_migrate_old_bookmarks.exs  |  1 -
 .../20190711042021_create_safe_jsonb_set.exs  |  1 -
 .../notification_controller_test.exs          | 20 ++++++++++
 .../controllers/status_controller_test.exs    |  6 ++-
 .../controllers/timeline_controller_test.exs  | 29 ++++++++++++--
 25 files changed, 278 insertions(+), 76 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 350e03894..a391bf1fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Added
 - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
 - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
+- Configuration: `:extensions/:output_relationships_in_statuses_by_default` option (if `false`, disables the output of account/pleroma/relationship for statuses and notifications by default, breaking the compatibility with older PleromaFE versions).
 <details>
   <summary>API Changes</summary>
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
@@ -20,7 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [2.0.0] - 2019-03-08
 ### Security
-- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
+- Mastodon API: Fix being able to request enormous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
 
 ### Removed
 - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
diff --git a/benchmarks/load_testing/fetcher.ex b/benchmarks/load_testing/fetcher.ex
index bd65ac84f..786929ace 100644
--- a/benchmarks/load_testing/fetcher.ex
+++ b/benchmarks/load_testing/fetcher.ex
@@ -386,47 +386,56 @@ defmodule Pleroma.LoadTesting.Fetcher do
 
     favourites = ActivityPub.fetch_favourites(user)
 
+    output_relationships =
+      !!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default])
+
     Benchee.run(
       %{
         "Rendering home timeline" => fn ->
           StatusView.render("index.json", %{
             activities: home_activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering direct timeline" => fn ->
           StatusView.render("index.json", %{
             activities: direct_activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering public timeline" => fn ->
           StatusView.render("index.json", %{
             activities: public_activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering tag timeline" => fn ->
           StatusView.render("index.json", %{
             activities: tag_activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering notifications" => fn ->
           Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
             notifications: notifications,
-            for: user
+            for: user,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering favourites timeline" => fn ->
           StatusView.render("index.json", %{
             activities: favourites,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end
       },
diff --git a/config/config.exs b/config/config.exs
index 2ab939107..73bf658fe 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -262,6 +262,8 @@ config :pleroma, :instance,
   extended_nickname_format: true,
   cleanup_attachments: false
 
+config :pleroma, :extensions, output_relationships_in_statuses_by_default: true
+
 config :pleroma, :feed,
   post_title: %{
     max_length: 100,
diff --git a/config/description.exs b/config/description.exs
index 9612adba7..d127f8f20 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -121,6 +121,22 @@ config :pleroma, :config_description, [
       }
     ]
   },
+  %{
+    group: :pleroma,
+    key: :extensions,
+    type: :group,
+    description: "Pleroma-specific extensions",
+    children: [
+      %{
+        key: :output_relationships_in_statuses_by_default,
+        type: :beeolean,
+        description:
+          "If `true`, outputs account/pleroma/relationship map for each rendered status / notification (for all clients). " <>
+            "If `false`, outputs the above only if `with_relationships` param is tru-ish " <>
+            "(that breaks compatibility with older PleromaFE versions which do not send this param but expect the output)."
+      }
+    ]
+  },
   %{
     group: :pleroma,
     key: Pleroma.Uploaders.Local,
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index a4885b70c..b2bbe40ac 100644
--- a/lib/mix/tasks/pleroma/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -67,7 +67,8 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
           Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
             activities: activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: true
           })
         end
       },
diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex
index 18a5eec72..d42dc250e 100644
--- a/lib/pleroma/user_relationship.ex
+++ b/lib/pleroma/user_relationship.ex
@@ -129,17 +129,27 @@ defmodule Pleroma.UserRelationship do
   end
 
   @doc ":relationships option for StatusView / AccountView / NotificationView"
-  def view_relationships_option(nil = _reading_user, _actors) do
+  def view_relationships_option(reading_user, actors, opts \\ [])
+
+  def view_relationships_option(nil = _reading_user, _actors, _opts) do
     %{user_relationships: [], following_relationships: []}
   end
 
-  def view_relationships_option(%User{} = reading_user, actors) do
+  def view_relationships_option(%User{} = reading_user, actors, opts) do
+    {source_to_target_rel_types, target_to_source_rel_types} =
+      if opts[:source_mutes_only] do
+        # This option is used for rendering statuses (FE needs `muted` flag for each one anyways)
+        {[:mute], []}
+      else
+        {[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
+      end
+
     user_relationships =
       UserRelationship.dictionary(
         [reading_user],
         actors,
-        [:block, :mute, :notification_mute, :reblog_mute],
-        [:block, :inverse_subscription]
+        source_to_target_rel_types,
+        target_to_source_rel_types
       )
 
     following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index ca5439920..747d97f80 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -258,7 +258,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
     conn
     |> put_view(Pleroma.Web.AdminAPI.StatusView)
-    |> render("index.json", %{activities: activities, as: :activity})
+    |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
   end
 
   def list_user_statuses(conn, %{"nickname" => nickname} = params) do
@@ -277,7 +277,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
       conn
       |> put_view(StatusView)
-      |> render("index.json", %{activities: activities, as: :activity})
+      |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
     else
       _ -> {:error, :not_found}
     end
@@ -801,7 +801,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
     conn
     |> put_view(Pleroma.Web.AdminAPI.StatusView)
-    |> render("index.json", %{activities: activities, as: :activity})
+    |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
   end
 
   def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
index ca0bcebc7..d50969b2a 100644
--- a/lib/pleroma/web/admin_api/views/report_view.ex
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -38,7 +38,12 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
       actor: merge_account_views(user),
       content: content,
       created_at: created_at,
-      statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
+      statuses:
+        StatusView.render("index.json", %{
+          activities: statuses,
+          as: :activity,
+          skip_relationships: false
+        }),
       state: report.data["state"],
       notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
     }
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index c4356f93b..c1cd15bb2 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -187,7 +187,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
   end
 
   defp preview?(draft) do
-    preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false
+    preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"])
     %__MODULE__{draft | preview?: preview?}
   end
 
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index b49523ec3..4780081b2 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -5,10 +5,18 @@
 defmodule Pleroma.Web.ControllerHelper do
   use Pleroma.Web, :controller
 
-  # As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
+  alias Pleroma.Config
+
+  # 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"]
-  def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
-  def truthy_param?(value), do: value not in @falsy_param_values
+
+  def explicitly_falsy_param?(value), do: value in @falsy_param_values
+
+  # Note: `nil` and `""` are considered falsy values in Pleroma
+  def falsy_param?(value),
+    do: explicitly_falsy_param?(value) or value in [nil, ""]
+
+  def truthy_param?(value), do: not falsy_param?(value)
 
   def json_response(conn, status, json) do
     conn
@@ -96,4 +104,14 @@ defmodule Pleroma.Web.ControllerHelper do
   def put_if_exist(map, _key, nil), do: map
 
   def put_if_exist(map, key, value), do: Map.put(map, key, value)
+
+  @doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications"
+  def skip_relationships?(params) do
+    if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do
+      false
+    else
+      # BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships.
+      not truthy_param?(params["with_relationships"])
+    end
+  end
 end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 21bc3d5a5..7da1a11f6 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
+    only: [
+      add_link_headers: 2,
+      truthy_param?: 1,
+      assign_account_by_id: 2,
+      json_response: 3,
+      skip_relationships?: 1
+    ]
 
   alias Pleroma.Plugs.OAuthScopesPlug
   alias Pleroma.Plugs.RateLimiter
@@ -237,7 +243,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       conn
       |> add_link_headers(activities)
       |> put_view(StatusView)
-      |> render("index.json", activities: activities, for: reading_user, as: :activity)
+      |> render("index.json",
+        activities: activities,
+        for: reading_user,
+        as: :activity,
+        skip_relationships: skip_relationships?(params)
+      )
     else
       _e -> render_error(conn, :not_found, "Can't find user")
     end
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
index 0c9218454..c7e808253 100644
--- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.MastodonAPI.NotificationController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
 
   alias Pleroma.Notification
   alias Pleroma.Plugs.OAuthScopesPlug
@@ -45,7 +45,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
 
     conn
     |> add_link_headers(notifications)
-    |> render("index.json", notifications: notifications, for: user)
+    |> render("index.json",
+      notifications: notifications,
+      for: user,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   # GET /api/v1/notifications/:id
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
index fcab4ef63..c258742dd 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -5,13 +5,14 @@
 defmodule Pleroma.Web.MastodonAPI.SearchController do
   use Pleroma.Web, :controller
 
+  import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1]
+
   alias Pleroma.Activity
   alias Pleroma.Plugs.OAuthScopesPlug
   alias Pleroma.Plugs.RateLimiter
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web
-  alias Pleroma.Web.ControllerHelper
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MastodonAPI.StatusView
 
@@ -66,10 +67,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
 
   defp search_options(params, user) do
     [
+      skip_relationships: skip_relationships?(params),
       resolve: params["resolve"] == "true",
       following: params["following"] == "true",
-      limit: ControllerHelper.fetch_integer_param(params, "limit"),
-      offset: ControllerHelper.fetch_integer_param(params, "offset"),
+      limit: fetch_integer_param(params, "limit"),
+      offset: fetch_integer_param(params, "offset"),
       type: params["type"],
       author: get_author(params),
       for_user: user
@@ -79,12 +81,24 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
 
   defp resource_search(_, "accounts", query, options) do
     accounts = with_fallback(fn -> User.search(query, options) end)
-    AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user)
+
+    AccountView.render("index.json",
+      users: accounts,
+      for: options[:for_user],
+      as: :user,
+      skip_relationships: false
+    )
   end
 
   defp resource_search(_, "statuses", query, options) do
     statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
-    StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
+
+    StatusView.render("index.json",
+      activities: statuses,
+      for: options[:for_user],
+      as: :activity,
+      skip_relationships: options[:skip_relationships]
+    )
   end
 
   defp resource_search(:v2, "hashtags", query, _options) do
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 37afe6949..eb3d90aeb 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -5,7 +5,8 @@
 defmodule Pleroma.Web.MastodonAPI.StatusController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2]
+  import Pleroma.Web.ControllerHelper,
+    only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
 
   require Ecto.Query
 
@@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
   `ids` query param is required
   """
-  def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
+  def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
     limit = 100
 
     activities =
@@ -110,7 +111,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
       |> Activity.all_by_ids_with_object()
       |> Enum.filter(&Visibility.visible_for_user?(&1, user))
 
-    render(conn, "index.json", activities: activities, for: user, as: :activity)
+    render(conn, "index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   @doc """
@@ -360,7 +366,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
     conn
     |> add_link_headers(activities)
-    |> render("index.json", activities: activities, for: user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   @doc "GET /api/v1/bookmarks"
@@ -378,6 +389,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
     conn
     |> add_link_headers(bookmarks)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 end
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 91f41416d..b3c58005e 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
+    only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]
 
   alias Pleroma.Pagination
   alias Pleroma.Plugs.OAuthScopesPlug
@@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
 
-  # TODO: Replace with a macro when there is a Phoenix release with
+  # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
   # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
-  # in it
 
   plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
   plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
@@ -49,7 +48,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
     conn
     |> add_link_headers(activities)
-    |> render("index.json", activities: activities, for: user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   # GET /api/v1/timelines/direct
@@ -68,7 +72,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
     conn
     |> add_link_headers(activities)
-    |> render("index.json", activities: activities, for: user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   # GET /api/v1/timelines/public
@@ -95,7 +104,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
       conn
       |> add_link_headers(activities, %{"local" => local_only})
-      |> render("index.json", activities: activities, for: user, as: :activity)
+      |> render("index.json",
+        activities: activities,
+        for: user,
+        as: :activity,
+        skip_relationships: skip_relationships?(params)
+      )
     else
       render_error(conn, :unauthorized, "authorization required for timeline view")
     end
@@ -140,7 +154,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
     conn
     |> add_link_headers(activities, %{"local" => local_only})
-    |> render("index.json", activities: activities, for: user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   # GET /api/v1/timelines/list/:list_id
@@ -164,7 +183,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
         |> ActivityPub.fetch_activities_bounded(following, params)
         |> Enum.reverse()
 
-      render(conn, "index.json", activities: activities, for: user, as: :activity)
+      render(conn, "index.json",
+        activities: activities,
+        for: user,
+        as: :activity,
+        skip_relationships: skip_relationships?(params)
+      )
     else
       _e -> render_error(conn, :forbidden, "Error.")
     end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index c482bba64..b20a00a89 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
   alias Pleroma.Web.MediaProxy
 
   def render("index.json", %{users: users} = opts) do
+    # Note: :skip_relationships option is currently intentionally not supported for accounts
     relationships_opt =
       cond do
         Map.has_key?(opts, :relationships) ->
@@ -190,11 +191,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       end)
 
     relationship =
-      render("relationship.json", %{
-        user: opts[:for],
-        target: user,
-        relationships: opts[:relationships]
-      })
+      if opts[:skip_relationships] do
+        %{}
+      else
+        render("relationship.json", %{
+          user: opts[:for],
+          target: user,
+          relationships: opts[:relationships]
+        })
+      end
 
     %{
       id: to_string(user.id),
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
index 89f5734ff..78d187f9a 100644
--- a/lib/pleroma/web/mastodon_api/views/notification_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -51,14 +51,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
             |> Enum.filter(& &1)
             |> Kernel.++(move_activities_targets)
 
-          UserRelationship.view_relationships_option(reading_user, actors)
+          UserRelationship.view_relationships_option(reading_user, actors,
+            source_mutes_only: opts[:skip_relationships]
+          )
       end
 
-    opts = %{
-      for: reading_user,
-      parent_activities: parent_activities,
-      relationships: relationships_opt
-    }
+    opts =
+      opts
+      |> Map.put(:parent_activities, parent_activities)
+      |> Map.put(:relationships, relationships_opt)
 
     safe_render_many(notifications, NotificationView, "show.json", opts)
   end
@@ -82,12 +83,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
 
     mastodon_type = Activity.mastodon_notification_type(activity)
 
+    render_opts = %{
+      relationships: opts[:relationships],
+      skip_relationships: opts[:skip_relationships]
+    }
+
     with %{id: _} = account <-
-           AccountView.render("show.json", %{
-             user: actor,
-             for: reading_user,
-             relationships: opts[:relationships]
-           }) do
+           AccountView.render(
+             "show.json",
+             Map.merge(render_opts, %{user: actor, for: reading_user})
+           ) do
       response = %{
         id: to_string(notification.id),
         type: mastodon_type,
@@ -98,8 +103,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
         }
       }
 
-      render_opts = %{relationships: opts[:relationships]}
-
       case mastodon_type do
         "mention" ->
           put_status(response, activity, reading_user, render_opts)
@@ -111,6 +114,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
           put_status(response, parent_activity_fn.(), reading_user, render_opts)
 
         "move" ->
+          # Note: :skip_relationships option being applied to _account_ rendering (here)
           put_target(response, activity, reading_user, render_opts)
 
         "follow" ->
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 82326986c..9cbd31878 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -97,7 +97,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         true ->
           actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
 
-          UserRelationship.view_relationships_option(opts[:for], actors)
+          UserRelationship.view_relationships_option(opts[:for], actors,
+            source_mutes_only: opts[:skip_relationships]
+          )
       end
 
     opts =
@@ -151,7 +153,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         AccountView.render("show.json", %{
           user: user,
           for: opts[:for],
-          relationships: opts[:relationships]
+          relationships: opts[:relationships],
+          skip_relationships: opts[:skip_relationships]
         }),
       in_reply_to_id: nil,
       in_reply_to_account_id: nil,
@@ -299,6 +302,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         _ -> []
       end
 
+    # Status muted state (would do 1 request per status unless user mutes are preloaded)
     muted =
       thread_muted? ||
         UserRelationship.exists?(
@@ -317,7 +321,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         AccountView.render("show.json", %{
           user: user,
           for: opts[:for],
-          relationships: opts[:relationships]
+          relationships: opts[:relationships],
+          skip_relationships: opts[:skip_relationships]
         }),
       in_reply_to_id: reply_to && to_string(reply_to.id),
       in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index dcba67d03..9d0b3b1e4 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
+    only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1]
 
   alias Ecto.Changeset
   alias Pleroma.Plugs.OAuthScopesPlug
@@ -139,7 +139,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
     conn
     |> add_link_headers(activities)
     |> put_view(StatusView)
-    |> render("index.json", activities: activities, for: for_user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: for_user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   @doc "POST /api/v1/pleroma/accounts/:id/subscribe"
diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
index dae7f0f2f..83983b576 100644
--- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
 
   alias Pleroma.Activity
   alias Pleroma.Conversation.Participation
@@ -130,7 +130,12 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
       conn
       |> add_link_headers(activities)
       |> put_view(StatusView)
-      |> render("index.json", %{activities: activities, for: user, as: :activity})
+      |> render("index.json",
+        activities: activities,
+        for: user,
+        as: :activity,
+        skip_relationships: skip_relationships?(params)
+      )
     else
       _error ->
         conn
@@ -184,13 +189,17 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
     end
   end
 
-  def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
+  def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
     with notifications <- Notification.set_read_up_to(user, max_id) do
       notifications = Enum.take(notifications, 80)
 
       conn
       |> put_view(NotificationView)
-      |> render("index.json", %{notifications: notifications, for: user})
+      |> render("index.json",
+        notifications: notifications,
+        for: user,
+        skip_relationships: skip_relationships?(params)
+      )
     end
   end
 end
diff --git a/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs b/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs
index c618ea381..b6f0ac66b 100644
--- a/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs
+++ b/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs
@@ -3,7 +3,6 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
   import Ecto.Query
   alias Pleroma.Activity
   alias Pleroma.Bookmark
-  alias Pleroma.User
   alias Pleroma.Repo
 
   def up do
diff --git a/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs b/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs
index 2f336a5e8..43d616705 100644
--- a/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs
+++ b/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs
@@ -1,6 +1,5 @@
 defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do
   use Ecto.Migration
-  alias Pleroma.User
 
   def change do
     execute("""
diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs
index 7a0011646..42a311f99 100644
--- a/test/web/mastodon_api/controllers/notification_controller_test.exs
+++ b/test/web/mastodon_api/controllers/notification_controller_test.exs
@@ -12,6 +12,26 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
 
   import Pleroma.Factory
 
+  test "does NOT render account/pleroma/relationship if this is disabled by default" do
+    clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
+
+    %{user: user, conn: conn} = oauth_access(["read:notifications"])
+    other_user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+    {:ok, [_notification]} = Notification.create_notifications(activity)
+
+    response =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/notifications")
+      |> json_response(200)
+
+    assert Enum.all?(response, fn n ->
+             get_in(n, ["account", "pleroma", "relationship"]) == %{}
+           end)
+  end
+
   test "list of notifications" do
     %{user: user, conn: conn} = oauth_access(["read:notifications"])
     other_user = insert(:user)
diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs
index d59974d50..6b126217a 100644
--- a/test/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/web/mastodon_api/controllers/status_controller_test.exs
@@ -1043,6 +1043,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
   end
 
   test "bookmarks" do
+    bookmarks_uri = "/api/v1/bookmarks?with_relationships=true"
+
     %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
     author = insert(:user)
 
@@ -1064,7 +1066,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
 
     assert json_response(response2, 200)["bookmarked"] == true
 
-    bookmarks = get(conn, "/api/v1/bookmarks")
+    bookmarks = get(conn, bookmarks_uri)
 
     assert [json_response(response2, 200), json_response(response1, 200)] ==
              json_response(bookmarks, 200)
@@ -1073,7 +1075,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
 
     assert json_response(response1, 200)["bookmarked"] == false
 
-    bookmarks = get(conn, "/api/v1/bookmarks")
+    bookmarks = get(conn, bookmarks_uri)
 
     assert [json_response(response2, 200)] == json_response(bookmarks, 200)
   end
diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs
index 97b1c3e66..06efdc901 100644
--- a/test/web/mastodon_api/controllers/timeline_controller_test.exs
+++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs
@@ -20,7 +20,30 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
   describe "home" do
     setup do: oauth_access(["read:statuses"])
 
+    test "does NOT render account/pleroma/relationship if this is disabled by default", %{
+      user: user,
+      conn: conn
+    } do
+      clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
+
+      other_user = insert(:user)
+
+      {:ok, _} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> get("/api/v1/timelines/home")
+        |> json_response(200)
+
+      assert Enum.all?(response, fn n ->
+               get_in(n, ["account", "pleroma", "relationship"]) == %{}
+             end)
+    end
+
     test "the home timeline", %{user: user, conn: conn} do
+      uri = "/api/v1/timelines/home?with_relationships=true"
+
       following = insert(:user, nickname: "followed")
       third_user = insert(:user, nickname: "repeated")
 
@@ -28,13 +51,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       {:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})
       {:ok, _, _} = CommonAPI.repeat(activity.id, following)
 
-      ret_conn = get(conn, "/api/v1/timelines/home")
+      ret_conn = get(conn, uri)
 
       assert Enum.empty?(json_response(ret_conn, :ok))
 
       {:ok, _user} = User.follow(user, following)
 
-      ret_conn = get(conn, "/api/v1/timelines/home")
+      ret_conn = get(conn, uri)
 
       assert [
                %{
@@ -59,7 +82,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
 
       {:ok, _user} = User.follow(third_user, user)
 
-      ret_conn = get(conn, "/api/v1/timelines/home")
+      ret_conn = get(conn, uri)
 
       assert [
                %{

From 2d64500a9dee8bc53c988719bde1c1f4f41575b7 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Wed, 1 Apr 2020 20:26:33 +0300
Subject: [PATCH 12/43] error improvement for email_invite endpoint

---
 docs/API/admin_api.md                         | 13 +++++++
 .../web/admin_api/admin_api_controller.ex     | 17 ++++++--
 .../admin_api/admin_api_controller_test.exs   | 39 ++++++++++++++++++-
 3 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md
index edcf73e14..179d8c451 100644
--- a/docs/API/admin_api.md
+++ b/docs/API/admin_api.md
@@ -392,6 +392,19 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
   - `email`
   - `name`, optional
 
+- Response:
+  - On success: `204`, empty response
+  - On failure:
+    - 400 Bad Request, JSON:
+
+    ```json
+      [
+        {
+          `error` // error message
+        }
+      ]
+    ```
+
 ## `GET /api/pleroma/admin/users/:nickname/password_reset`
 
 ### Get a password reset token for a given nickname
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index ca5439920..7b442f6e1 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -576,9 +576,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
   @doc "Sends registration invite via email"
   def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
-    with true <-
-           Config.get([:instance, :invites_enabled]) &&
-             !Config.get([:instance, :registrations_open]),
+    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(
@@ -589,6 +588,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
            ),
          {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
       json_response(conn, :no_content, "")
+    else
+      {:registrations_open, _} ->
+        errors(
+          conn,
+          {:error, "To send invites you need set `registrations_open` option to false."}
+        )
+
+      {:invites_enabled, _} ->
+        errors(
+          conn,
+          {:error, "To send invites you need set `invites_enabled` option to true."}
+        )
     end
   end
 
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index ea0c92502..32fe69d19 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -625,6 +625,39 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       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
@@ -637,7 +670,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
-      assert json_response(conn, :internal_server_error)
+      assert json_response(conn, :bad_request) ==
+               "To send invites you need set `invites_enabled` option to true."
     end
 
     test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
@@ -646,7 +680,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
-      assert json_response(conn, :internal_server_error)
+      assert json_response(conn, :bad_request) ==
+               "To send invites you need set `registrations_open` option to false."
     end
   end
 

From 23219e6fb3163bfac07fb5fb1b2602dcd27e47c2 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 1 Apr 2020 23:00:59 +0400
Subject: [PATCH 13/43] Add OpenAPI

---
 lib/pleroma/web/api_spec.ex                   | 30 ++++++
 .../web/api_spec/operations/app_operation.ex  | 94 +++++++++++++++++++
 .../api_spec/schemas/app_create_request.ex    | 33 +++++++
 .../api_spec/schemas/app_create_response.ex   | 33 +++++++
 .../controllers/app_controller.ex             |  9 +-
 lib/pleroma/web/oauth/scopes.ex               |  7 +-
 lib/pleroma/web/router.ex                     | 11 +++
 mix.exs                                       |  3 +-
 mix.lock                                      |  1 +
 test/web/api_spec/app_operation_test.exs      | 45 +++++++++
 .../controllers/account_controller_test.exs   |  4 +-
 .../controllers/app_controller_test.exs       |  4 +-
 12 files changed, 266 insertions(+), 8 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec.ex
 create mode 100644 lib/pleroma/web/api_spec/operations/app_operation.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/app_create_request.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/app_create_response.ex
 create mode 100644 test/web/api_spec/app_operation_test.exs

diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
new file mode 100644
index 000000000..22f76d4bf
--- /dev/null
+++ b/lib/pleroma/web/api_spec.ex
@@ -0,0 +1,30 @@
+# 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 do
+  alias OpenApiSpex.OpenApi
+  alias Pleroma.Web.Endpoint
+  alias Pleroma.Web.Router
+
+  @behaviour OpenApi
+
+  @impl OpenApi
+  def spec do
+    %OpenApi{
+      servers: [
+        # Populate the Server info from a phoenix endpoint
+        OpenApiSpex.Server.from_endpoint(Endpoint)
+      ],
+      info: %OpenApiSpex.Info{
+        title: "Pleroma",
+        description: Application.spec(:pleroma, :description) |> to_string(),
+        version: Application.spec(:pleroma, :vsn) |> to_string()
+      },
+      # populate the paths from a phoenix router
+      paths: OpenApiSpex.Paths.from_router(Router)
+    }
+    # discover request/response schemas from path specs
+    |> OpenApiSpex.resolve_schema_modules()
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex
new file mode 100644
index 000000000..2a4958acf
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/app_operation.ex
@@ -0,0 +1,94 @@
+# 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.AppOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
+  alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
+
+  @spec open_api_operation(atom) :: Operation.t()
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  @spec create_operation() :: Operation.t()
+  def create_operation do
+    %Operation{
+      tags: ["apps"],
+      summary: "Create an application",
+      description: "Create a new application to obtain OAuth2 credentials",
+      operationId: "AppController.create",
+      requestBody:
+        Operation.request_body("Parameters", "application/json", AppCreateRequest, required: true),
+      responses: %{
+        200 => Operation.response("App", "application/json", AppCreateResponse),
+        422 =>
+          Operation.response(
+            "Unprocessable Entity",
+            "application/json",
+            %Schema{
+              type: :object,
+              description:
+                "If a required parameter is missing or improperly formatted, the request will fail.",
+              properties: %{
+                error: %Schema{type: :string}
+              },
+              example: %{
+                "error" => "Validation failed: Redirect URI must be an absolute URI."
+              }
+            }
+          )
+      }
+    }
+  end
+
+  def verify_credentials_operation do
+    %Operation{
+      tags: ["apps"],
+      summary: "Verify your app works",
+      description: "Confirm that the app's OAuth2 credentials work.",
+      operationId: "AppController.verify_credentials",
+      parameters: [
+        Operation.parameter(:authorization, :header, :string, "Bearer <app token>", required: true)
+      ],
+      responses: %{
+        200 =>
+          Operation.response("App", "application/json", %Schema{
+            type: :object,
+            description:
+              "If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.",
+            properties: %{
+              name: %Schema{type: :string},
+              vapid_key: %Schema{type: :string},
+              website: %Schema{type: :string, nullable: true}
+            },
+            example: %{
+              "name" => "My App",
+              "vapid_key" =>
+                "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
+              "website" => "https://myapp.com/"
+            }
+          }),
+        422 =>
+          Operation.response(
+            "Unauthorized",
+            "application/json",
+            %Schema{
+              type: :object,
+              description:
+                "If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.",
+              properties: %{
+                error: %Schema{type: :string}
+              },
+              example: %{
+                "error" => "The access token is invalid."
+              }
+            }
+          )
+      }
+    }
+  end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_request.ex b/lib/pleroma/web/api_spec/schemas/app_create_request.ex
new file mode 100644
index 000000000..8a83abef3
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/app_create_request.ex
@@ -0,0 +1,33 @@
+# 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.Schemas.AppCreateRequest do
+  alias OpenApiSpex.Schema
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AppCreateRequest",
+    description: "POST body for creating an app",
+    type: :object,
+    properties: %{
+      client_name: %Schema{type: :string, description: "A name for your application."},
+      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."
+      },
+      scopes: %Schema{
+        type: :string,
+        description: "Space separated list of scopes. If none is provided, defaults to `read`."
+      },
+      website: %Schema{type: :string, description: "A URL to the homepage of your app"}
+    },
+    required: [:client_name, :redirect_uris],
+    example: %{
+      "client_name" => "My App",
+      "redirect_uris" => "https://myapp.com/auth/callback",
+      "website" => "https://myapp.com/"
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_response.ex b/lib/pleroma/web/api_spec/schemas/app_create_response.ex
new file mode 100644
index 000000000..f290fb031
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/app_create_response.ex
@@ -0,0 +1,33 @@
+# 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.Schemas.AppCreateResponse do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AppCreateResponse",
+    description: "Response schema for an app",
+    type: :object,
+    properties: %{
+      id: %Schema{type: :string},
+      name: %Schema{type: :string},
+      client_id: %Schema{type: :string},
+      client_secret: %Schema{type: :string},
+      redirect_uri: %Schema{type: :string},
+      vapid_key: %Schema{type: :string},
+      website: %Schema{type: :string, nullable: true}
+    },
+    example: %{
+      "id" => "123",
+      "name" => "My App",
+      "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
+      "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
+      "vapid_key" =>
+        "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
+      "website" => "https://myapp.com/"
+    }
+  })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
index 5e2871f18..005c60444 100644
--- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
@@ -14,17 +14,20 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
   plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
+  plug(OpenApiSpex.Plug.CastAndValidate)
 
   @local_mastodon_name "Mastodon-Local"
 
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
+
   @doc "POST /api/v1/apps"
-  def create(conn, params) do
+  def create(%{body_params: params} = conn, _params) do
     scopes = Scopes.fetch_scopes(params, ["read"])
 
     app_attrs =
       params
-      |> Map.drop(["scope", "scopes"])
-      |> Map.put("scopes", scopes)
+      |> Map.take([:client_name, :redirect_uris, :website])
+      |> Map.put(:scopes, scopes)
 
     with cs <- App.register_changeset(%App{}, app_attrs),
          false <- cs.changes[:client_name] == @local_mastodon_name,
diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex
index 8ecf901f3..1023f16d4 100644
--- a/lib/pleroma/web/oauth/scopes.ex
+++ b/lib/pleroma/web/oauth/scopes.ex
@@ -15,7 +15,12 @@ defmodule Pleroma.Web.OAuth.Scopes do
   Note: `scopes` is used by Mastodon — supporting it but sticking to
   OAuth's standard `scope` wherever we control it
   """
-  @spec fetch_scopes(map(), list()) :: list()
+  @spec fetch_scopes(map() | struct(), list()) :: list()
+
+  def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do
+    parse_scopes(scopes, default)
+  end
+
   def fetch_scopes(params, default) do
     parse_scopes(params["scope"] || params["scopes"], default)
   end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 5a0902739..3ecd59cd1 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -29,6 +29,7 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Plugs.SetUserSessionIdPlug)
     plug(Pleroma.Plugs.EnsureUserKeyPlug)
     plug(Pleroma.Plugs.IdempotencyPlug)
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :authenticated_api do
@@ -44,6 +45,7 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Plugs.SetUserSessionIdPlug)
     plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
     plug(Pleroma.Plugs.IdempotencyPlug)
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :admin_api do
@@ -61,6 +63,7 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
     plug(Pleroma.Plugs.UserIsAdminPlug)
     plug(Pleroma.Plugs.IdempotencyPlug)
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :mastodon_html do
@@ -94,10 +97,12 @@ defmodule Pleroma.Web.Router do
 
   pipeline :config do
     plug(:accepts, ["json", "xml"])
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :pleroma_api do
     plug(:accepts, ["html", "json"])
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :mailbox_preview do
@@ -500,6 +505,12 @@ defmodule Pleroma.Web.Router do
     )
   end
 
+  scope "/api" do
+    pipe_through(:api)
+
+    get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
+  end
+
   scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
     pipe_through(:authenticated_api)
 
diff --git a/mix.exs b/mix.exs
index 890979f8b..ebd4a5ea6 100644
--- a/mix.exs
+++ b/mix.exs
@@ -171,7 +171,8 @@ defmodule Pleroma.Mixfile do
        git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
        ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
       {:mox, "~> 0.5", only: :test},
-      {:restarter, path: "./restarter"}
+      {:restarter, path: "./restarter"},
+      {:open_api_spex, "~> 3.6"}
     ] ++ oauth_deps()
   end
 
diff --git a/mix.lock b/mix.lock
index 62e14924a..fd26ca01b 100644
--- a/mix.lock
+++ b/mix.lock
@@ -72,6 +72,7 @@
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
   "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
   "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"},
+  "open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},
   "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
diff --git a/test/web/api_spec/app_operation_test.exs b/test/web/api_spec/app_operation_test.exs
new file mode 100644
index 000000000..5b96abb44
--- /dev/null
+++ b/test/web/api_spec/app_operation_test.exs
@@ -0,0 +1,45 @@
+# 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.AppOperationTest do
+  use Pleroma.Web.ConnCase, async: true
+
+  alias Pleroma.Web.ApiSpec
+  alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
+  alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
+
+  import OpenApiSpex.TestAssertions
+  import Pleroma.Factory
+
+  test "AppCreateRequest example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = AppCreateRequest.schema()
+    assert_schema(schema.example, "AppCreateRequest", api_spec)
+  end
+
+  test "AppCreateResponse example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = AppCreateResponse.schema()
+    assert_schema(schema.example, "AppCreateResponse", api_spec)
+  end
+
+  test "AppController produces a AppCreateResponse", %{conn: conn} do
+    api_spec = ApiSpec.spec()
+    app_attrs = build(:oauth_app)
+
+    json =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post(
+        "/api/v1/apps",
+        Jason.encode!(%{
+          client_name: app_attrs.client_name,
+          redirect_uris: app_attrs.redirect_uris
+        })
+      )
+      |> json_response(200)
+
+    assert_schema(json, "AppCreateResponse", api_spec)
+  end
+end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index a9fa0ce48..a450a732c 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -794,7 +794,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "Account registration via Application", %{conn: conn} do
       conn =
-        post(conn, "/api/v1/apps", %{
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/v1/apps", %{
           client_name: "client_name",
           redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
           scopes: "read, write, follow"
diff --git a/test/web/mastodon_api/controllers/app_controller_test.exs b/test/web/mastodon_api/controllers/app_controller_test.exs
index 77d234d67..e7b11d14e 100644
--- a/test/web/mastodon_api/controllers/app_controller_test.exs
+++ b/test/web/mastodon_api/controllers/app_controller_test.exs
@@ -16,8 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
 
     conn =
       conn
-      |> assign(:user, token.user)
-      |> assign(:token, token)
+      |> put_req_header("authorization", "Bearer #{token.token}")
       |> get("/api/v1/apps/verify_credentials")
 
     app = Repo.preload(token, :app).app
@@ -37,6 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
 
     conn =
       conn
+      |> put_req_header("content-type", "application/json")
       |> assign(:user, user)
       |> post("/api/v1/apps", %{
         client_name: app_attrs.client_name,

From 591f7015d91b383dae1ee29576d13c0fad65cad6 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Thu, 2 Apr 2020 09:34:11 +0300
Subject: [PATCH 14/43] update Oban package

---
 mix.exs                                               |  2 +-
 mix.lock                                              |  6 +++---
 .../20200402063221_update_oban_jobs_table.exs         | 11 +++++++++++
 3 files changed, 15 insertions(+), 4 deletions(-)
 create mode 100644 priv/repo/migrations/20200402063221_update_oban_jobs_table.exs

diff --git a/mix.exs b/mix.exs
index 87c025d89..375bc67c1 100644
--- a/mix.exs
+++ b/mix.exs
@@ -108,7 +108,7 @@ defmodule Pleroma.Mixfile do
       {:ecto_enum, "~> 1.4"},
       {:ecto_sql, "~> 3.3.2"},
       {:postgrex, ">= 0.13.5"},
-      {:oban, "~> 0.12.1"},
+      {:oban, "~> 1.2"},
       {:gettext, "~> 0.15"},
       {:comeonin, "~> 4.1.1"},
       {:pbkdf2_elixir, "~> 0.12.3"},
diff --git a/mix.lock b/mix.lock
index 6cca578d6..50be45a4d 100644
--- a/mix.lock
+++ b/mix.lock
@@ -26,7 +26,7 @@
   "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
   "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
-  "ecto": {:hex, :ecto, "3.3.3", "0830bf3aebcbf3d8c1a1811cd581773b6866886c012f52c0f027031fa96a0b53", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "12e368e3c2a2938d7776defaabdae40e82900fc4d8d66120ec1e01dfd8b93c3a"},
+  "ecto": {:hex, :ecto, "3.4.0", "a7a83ab8359bf816ce729e5e65981ce25b9fc5adfc89c2ea3980f4fed0bfd7c1", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5eed18252f5b5bbadec56a24112b531343507dbe046273133176b12190ce19cc"},
   "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
   "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
   "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
@@ -55,7 +55,7 @@
   "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
   "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
   "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
-  "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
+  "jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"},
   "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
   "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
   "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
@@ -73,7 +73,7 @@
   "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
   "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
-  "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"},
+  "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},
   "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
diff --git a/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs
new file mode 100644
index 000000000..c8ee12192
--- /dev/null
+++ b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs
@@ -0,0 +1,11 @@
+defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do
+  use Ecto.Migration
+
+  def up do
+    Oban.Migrations.up()
+  end
+
+  def down do
+    Oban.Migrations.down(version: 1)
+  end
+end

From 0aa24a150bbb153f55ca92dfb595385b4fe3839c Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 2 Apr 2020 17:33:23 +0400
Subject: [PATCH 15/43] Add oAuth

---
 lib/pleroma/web/api_spec.ex                      | 16 +++++++++++++++-
 .../web/api_spec/operations/app_operation.ex     |  6 ++++--
 2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index 22f76d4bf..41e48a085 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -22,7 +22,21 @@ defmodule Pleroma.Web.ApiSpec do
         version: Application.spec(:pleroma, :vsn) |> to_string()
       },
       # populate the paths from a phoenix router
-      paths: OpenApiSpex.Paths.from_router(Router)
+      paths: OpenApiSpex.Paths.from_router(Router),
+      components: %OpenApiSpex.Components{
+        securitySchemes: %{
+          "oAuth" => %OpenApiSpex.SecurityScheme{
+            type: "oauth2",
+            flows: %OpenApiSpex.OAuthFlows{
+              password: %OpenApiSpex.OAuthFlow{
+                authorizationUrl: "/oauth/authorize",
+                tokenUrl: "/oauth/token",
+                scopes: %{"read" => "read"}
+              }
+            }
+          }
+        }
+      }
     }
     # discover request/response schemas from path specs
     |> OpenApiSpex.resolve_schema_modules()
diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex
index 2a4958acf..41d56693a 100644
--- a/lib/pleroma/web/api_spec/operations/app_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/app_operation.ex
@@ -51,8 +51,10 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
       summary: "Verify your app works",
       description: "Confirm that the app's OAuth2 credentials work.",
       operationId: "AppController.verify_credentials",
-      parameters: [
-        Operation.parameter(:authorization, :header, :string, "Bearer <app token>", required: true)
+      security: [
+        %{
+          "oAuth" => ["read"]
+        }
       ],
       responses: %{
         200 =>

From 03eebabe8e5b2e3f96f6ffe51a6f063a42f6a5d2 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Fri, 3 Apr 2020 22:52:25 +0400
Subject: [PATCH 16/43] Add Pleroma.Web.ApiSpec.Helpers

---
 lib/pleroma/web/api_spec/helpers.ex           | 27 +++++++++++++++++++
 .../web/api_spec/operations/app_operation.ex  |  4 +--
 2 files changed, 29 insertions(+), 2 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/helpers.ex

diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
new file mode 100644
index 000000000..35cf4c0d8
--- /dev/null
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -0,0 +1,27 @@
+# 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.Helpers do
+  def request_body(description, schema_ref, opts \\ []) do
+    media_types = ["application/json", "multipart/form-data"]
+
+    content =
+      media_types
+      |> Enum.map(fn type ->
+        {type,
+         %OpenApiSpex.MediaType{
+           schema: schema_ref,
+           example: opts[:example],
+           examples: opts[:examples]
+         }}
+      end)
+      |> Enum.into(%{})
+
+    %OpenApiSpex.RequestBody{
+      description: description,
+      content: content,
+      required: opts[:required] || false
+    }
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex
index 41d56693a..26d8dbd42 100644
--- a/lib/pleroma/web/api_spec/operations/app_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/app_operation.ex
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.ApiSpec.AppOperation do
   alias OpenApiSpex.Operation
   alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Helpers
   alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
 
@@ -21,8 +22,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
       summary: "Create an application",
       description: "Create a new application to obtain OAuth2 credentials",
       operationId: "AppController.create",
-      requestBody:
-        Operation.request_body("Parameters", "application/json", AppCreateRequest, required: true),
+      requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
       responses: %{
         200 => Operation.response("App", "application/json", AppCreateResponse),
         422 =>

From 06471940e0cb917bb362cbcb9d872ab1336a04cf Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 7 Apr 2020 08:44:53 +0000
Subject: [PATCH 17/43] Apply suggestion to
 test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs

---
 .../controllers/account_controller/update_credentials_test.exs  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index 8687d7995..d78fbc5a1 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -298,7 +298,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
              ]
     end
 
-    test "update fields by urlencoded", %{conn: conn} do
+    test "update fields via x-www-form-urlencoded", %{conn: conn} do
       fields =
         [
           "fields_attributes[1][name]=link",

From 5739c498c029914c446656244cdd213a3e358fec Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Wed, 8 Apr 2020 18:46:01 +0300
Subject: [PATCH 18/43] fix for gun connections pool

---
 CHANGELOG.md            | 3 +++
 lib/pleroma/gun/conn.ex | 4 +++-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6e5d807c..92d1abc4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
 </details>
 
+### Fixed
+- Gun connections pool `max_connections` option.
+
 ## [2.0.0] - 2019-03-08
 ### Security
 - Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex
index 20823a765..cd25a2e74 100644
--- a/lib/pleroma/gun/conn.ex
+++ b/lib/pleroma/gun/conn.ex
@@ -49,8 +49,10 @@ defmodule Pleroma.Gun.Conn do
 
     key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
 
+    max_connections = pool_opts[:max_connections] || 250
+
     conn_pid =
-      if Connections.count(name) < opts[:max_connection] do
+      if Connections.count(name) < max_connections do
         do_open(uri, opts)
       else
         close_least_used_and_do_open(name, uri, opts)

From d067eaa7b3bb76e7fc5ae019d6e00510b657171d Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Wed, 8 Apr 2020 22:58:31 +0300
Subject: [PATCH 19/43] formatter.ex: Use Phoenix.HTML for mention/hashtag
 generation

Unlike concatenating strings, this makes sure everything is escaped.
Tests had to be changed because Phoenix.HTML runs attributes through
Enum.sort before generation for whatever reason.
---
 lib/pleroma/formatter.ex                      | 26 ++++++++++++++++---
 test/formatter_test.exs                       | 24 +++++++----------
 test/user_test.exs                            |  2 +-
 test/web/common_api/common_api_utils_test.exs |  6 ++---
 .../update_credentials_test.exs               |  4 +--
 .../notification_controller_test.exs          |  4 +--
 test/web/twitter_api/twitter_api_test.exs     |  2 +-
 7 files changed, 41 insertions(+), 27 deletions(-)

diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index e2a658cb3..c44e7fc8b 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -35,9 +35,19 @@ defmodule Pleroma.Formatter do
         nickname_text = get_nickname_text(nickname, opts)
 
         link =
-          ~s(<span class="h-card"><a data-user="#{id}" class="u-url mention" href="#{ap_id}" rel="ugc">@<span>#{
-            nickname_text
-          }</span></a></span>)
+          Phoenix.HTML.Tag.content_tag(
+            :span,
+            Phoenix.HTML.Tag.content_tag(
+              :a,
+              ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
+              "data-user": id,
+              class: "u-url mention",
+              href: ap_id,
+              rel: "ugc"
+            ),
+            class: "h-card"
+          )
+          |> Phoenix.HTML.safe_to_string()
 
         {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
 
@@ -49,7 +59,15 @@ defmodule Pleroma.Formatter do
   def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
     tag = String.downcase(tag)
     url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
-    link = ~s(<a class="hashtag" data-tag="#{tag}" href="#{url}" rel="tag ugc">#{tag_text}</a>)
+
+    link =
+      Phoenix.HTML.Tag.content_tag(:a, tag_text,
+        class: "hashtag",
+        "data-tag": tag,
+        href: url,
+        rel: "tag ugc"
+      )
+      |> Phoenix.HTML.safe_to_string()
 
     {link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
   end
diff --git a/test/formatter_test.exs b/test/formatter_test.exs
index cf8441cf6..93fd8eab7 100644
--- a/test/formatter_test.exs
+++ b/test/formatter_test.exs
@@ -150,13 +150,13 @@ defmodule Pleroma.FormatterTest do
       assert length(mentions) == 3
 
       expected_text =
-        ~s(<span class="h-card"><a data-user="#{gsimg.id}" class="u-url mention" href="#{
+        ~s(<span class="h-card"><a class="u-url mention" data-user="#{gsimg.id}" href="#{
           gsimg.ap_id
-        }" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a data-user="#{
+        }" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a class="u-url mention" data-user="#{
           archaeme.id
-        }" class="u-url mention" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a data-user="#{
+        }" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a class="u-url mention" data-user="#{
           archaeme_remote.id
-        }" class="u-url mention" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)
+        }" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)
 
       assert expected_text == text
     end
@@ -171,7 +171,7 @@ defmodule Pleroma.FormatterTest do
       assert length(mentions) == 1
 
       expected_text =
-        ~s(<span class="h-card"><a data-user="#{mike.id}" class="u-url mention" href="#{
+        ~s(<span class="h-card"><a class="u-url mention" data-user="#{mike.id}" href="#{
           mike.ap_id
         }" rel="ugc">@<span>mike</span></a></span> test)
 
@@ -187,7 +187,7 @@ defmodule Pleroma.FormatterTest do
       assert length(mentions) == 1
 
       expected_text =
-        ~s(<span class="h-card"><a data-user="#{o.id}" class="u-url mention" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)
+        ~s(<span class="h-card"><a class="u-url mention" data-user="#{o.id}" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)
 
       assert expected_text == text
     end
@@ -209,17 +209,13 @@ defmodule Pleroma.FormatterTest do
       assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]
 
       assert expected_text ==
-               ~s(<span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{
+               ~s(<span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="#{
                  user.ap_id
-               }" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a data-user="#{
+               }" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
                  other_user.id
-               }" class="u-url mention" href="#{other_user.ap_id}" rel="ugc">@<span>#{
-                 other_user.nickname
-               }</span></a></span> hey dudes i hate <span class="h-card"><a data-user="#{
+               }" href="#{other_user.ap_id}" rel="ugc">@<span>#{other_user.nickname}</span></a></span> hey dudes i hate <span class="h-card"><a class="u-url mention" data-user="#{
                  third_user.id
-               }" class="u-url mention" href="#{third_user.ap_id}" rel="ugc">@<span>#{
-                 third_user.nickname
-               }</span></a></span>)
+               }" href="#{third_user.ap_id}" rel="ugc">@<span>#{third_user.nickname}</span></a></span>)
     end
 
     test "given the 'safe_mention' option, it will still work without any mention" do
diff --git a/test/user_test.exs b/test/user_test.exs
index 0479f294d..d39787f35 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1404,7 +1404,7 @@ defmodule Pleroma.UserTest do
       bio = "A.k.a. @nick@domain.com"
 
       expected_text =
-        ~s(A.k.a. <span class="h-card"><a data-user="#{remote_user.id}" class="u-url mention" href="#{
+        ~s(A.k.a. <span class="h-card"><a class="u-url mention" data-user="#{remote_user.id}" href="#{
           remote_user.ap_id
         }" rel="ugc">@<span>nick@domain.com</span></a></span>)
 
diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs
index d383d1714..98cf02d49 100644
--- a/test/web/common_api/common_api_utils_test.exs
+++ b/test/web/common_api/common_api_utils_test.exs
@@ -159,11 +159,11 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
       {output, _, _} = Utils.format_input(text, "text/markdown")
 
       assert output ==
-               ~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a data-user="#{
+               ~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a class="u-url mention" data-user="#{
                  user.id
-               }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a data-user="#{
+               }" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a class="u-url mention" data-user="#{
                  user.id
-               }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
+               }" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
     end
   end
 
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index d78fbc5a1..2d256f63c 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -82,9 +82,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert user_data = json_response(conn, 200)
 
       assert user_data["note"] ==
-               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{
+               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
                  user2.id
-               }" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
+               }" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
     end
 
     test "updates the user's locking status", %{conn: conn} do
diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs
index 344eabb4a..6f1fab069 100644
--- a/test/web/mastodon_api/controllers/notification_controller_test.exs
+++ b/test/web/mastodon_api/controllers/notification_controller_test.exs
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
       |> get("/api/v1/notifications")
 
     expected_response =
-      "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
+      "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
         user.ap_id
       }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
 
@@ -45,7 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     conn = get(conn, "/api/v1/notifications/#{notification.id}")
 
     expected_response =
-      "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
+      "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
         user.ap_id
       }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
 
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index 92f9aa0f5..f6e13b661 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -109,7 +109,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     {:ok, user2} = TwitterAPI.register_user(data2)
 
     expected_text =
-      ~s(<span class="h-card"><a data-user="#{user1.id}" class="u-url mention" href="#{
+      ~s(<span class="h-card"><a class="u-url mention" data-user="#{user1.id}" href="#{
         user1.ap_id
       }" rel="ugc">@<span>john</span></a></span> test)
 

From c401b00c7885823744183dbd077db9239585d20d Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 9 Apr 2020 04:36:39 +0200
Subject: [PATCH 20/43] ObjectValidators.Types.ObjectID: Fix when URI.parse
 returns %URL{host: ""}

---
 .../object_validators/types/object_id.ex             | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
index ee10be0b0..f6e749b33 100644
--- a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
@@ -6,14 +6,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
   def cast(object) when is_binary(object) do
     # Host has to be present and scheme has to be an http scheme (for now)
     case URI.parse(object) do
-      %URI{host: nil} ->
-        :error
-
-      %URI{scheme: scheme} when scheme in ["https", "http"] ->
-        {:ok, object}
-
-      _ ->
-        :error
+      %URI{host: nil} -> :error
+      %URI{host: ""} -> :error
+      %URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object}
+      _ -> :error
     end
   end
 

From 73134e248a031613151df87fdd406580d16dc6b9 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 9 Apr 2020 08:03:21 +0300
Subject: [PATCH 21/43] no changelog entry - bug fixed only in develop

---
 CHANGELOG.md | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92d1abc4e..b6e5d807c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,9 +20,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
 </details>
 
-### Fixed
-- Gun connections pool `max_connections` option.
-
 ## [2.0.0] - 2019-03-08
 ### Security
 - Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.

From c8bfbf511eeca2045267ad4792c35648625788cf Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 9 Apr 2020 10:17:24 +0000
Subject: [PATCH 22/43] Apply suggestion to docs/API/admin_api.md

---
 docs/API/admin_api.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md
index 179d8c451..b3cf89818 100644
--- a/docs/API/admin_api.md
+++ b/docs/API/admin_api.md
@@ -400,7 +400,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
     ```json
       [
         {
-          `error` // error message
+          "error": "Appropriate error message here"
         }
       ]
     ```

From 4c60fdcbb1ab06183b8e300cbbb84d70ecd3e25b Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 9 Apr 2020 10:17:31 +0000
Subject: [PATCH 23/43] Apply suggestion to
 lib/pleroma/web/admin_api/admin_api_controller.ex

---
 lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 7b442f6e1..a66db68f3 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -592,7 +592,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       {:registrations_open, _} ->
         errors(
           conn,
-          {:error, "To send invites you need set `registrations_open` option to false."}
+          {:error, "To send invites you need to set the `registrations_open` option to false."}
         )
 
       {:invites_enabled, _} ->

From 1cf0d5ab0d579ee4a1a779c308fedb0ab8ec3884 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 9 Apr 2020 10:17:36 +0000
Subject: [PATCH 24/43] Apply suggestion to
 lib/pleroma/web/admin_api/admin_api_controller.ex

---
 lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index a66db68f3..09959b3bf 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -598,7 +598,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       {:invites_enabled, _} ->
         errors(
           conn,
-          {:error, "To send invites you need set `invites_enabled` option to true."}
+          {:error, "To send invites you need set to set the `invites_enabled` option to true."}
         )
     end
   end

From 365c34a7a96a9cbd5acb30eb6eedf195eeaff131 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 9 Apr 2020 10:17:44 +0000
Subject: [PATCH 25/43] Apply suggestion to
 test/web/admin_api/admin_api_controller_test.exs

---
 test/web/admin_api/admin_api_controller_test.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 32fe69d19..afd894269 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -671,7 +671,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
       assert json_response(conn, :bad_request) ==
-               "To send invites you need set `invites_enabled` option to true."
+               "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

From 9795ff5b016e74c0e7b94ac2ea28023208d1f8ee Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 9 Apr 2020 10:17:50 +0000
Subject: [PATCH 26/43] Apply suggestion to
 test/web/admin_api/admin_api_controller_test.exs

---
 test/web/admin_api/admin_api_controller_test.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index afd894269..e8d11b88c 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -681,7 +681,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
       assert json_response(conn, :bad_request) ==
-               "To send invites you need set `registrations_open` option to false."
+               "To send invites you need to set the `registrations_open` option to false."
     end
   end
 

From f20a19de853e8834f7774ee0098a14213bc7427f Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 9 Apr 2020 13:28:54 +0300
Subject: [PATCH 27/43] typo fix

---
 lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 09959b3bf..fdbd24acb 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -598,7 +598,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       {:invites_enabled, _} ->
         errors(
           conn,
-          {:error, "To send invites you need set to set the `invites_enabled` option to true."}
+          {:error, "To send invites you need to set the `invites_enabled` option to true."}
         )
     end
   end

From d37a102933dbfbb0996546b4d148bbe36fbd4220 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Thu, 9 Apr 2020 21:16:29 +0900
Subject: [PATCH 28/43] Fix OTP_VERSION file in docker

---
 Dockerfile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Dockerfile b/Dockerfile
index 29931a5e3..c2f3ad98c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,6 +12,8 @@ RUN apk add git gcc g++ musl-dev make &&\
 	mkdir release &&\
 	mix release --path release
 
+RUN echo "${OTP_VERSION}" > release/OTP_VERSION
+
 FROM alpine:3.11
 
 ARG BUILD_DATE

From d545b883eb3c5b79b89a49ccaf9256c31b401145 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 9 Apr 2020 17:08:43 +0400
Subject: [PATCH 29/43] Add `/api/v1/notifications/:id/dismiss` endpoint

---
 .../controllers/notification_controller.ex     |  3 ++-
 lib/pleroma/web/router.ex                      |  4 +++-
 .../notification_controller_test.exs           | 18 +++++++++++++++++-
 3 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
index 0c9218454..a6b4096ec 100644
--- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -66,7 +66,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
     json(conn, %{})
   end
 
-  # POST /api/v1/notifications/dismiss
+  # POST /api/v1/notifications/:id/dismiss
+  # POST /api/v1/notifications/dismiss (deprecated)
   def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
     with {:ok, _notif} <- Notification.dismiss(user, id) do
       json(conn, %{})
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 3ecd59cd1..5f5ec1c81 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -352,9 +352,11 @@ defmodule Pleroma.Web.Router do
 
     get("/notifications", NotificationController, :index)
     get("/notifications/:id", NotificationController, :show)
+    post("/notifications/:id/dismiss", NotificationController, :dismiss)
     post("/notifications/clear", NotificationController, :clear)
-    post("/notifications/dismiss", NotificationController, :dismiss)
     delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
+    # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
+    post("/notifications/dismiss", NotificationController, :dismiss)
 
     get("/scheduled_statuses", ScheduledActivityController, :index)
     get("/scheduled_statuses/:id", ScheduledActivityController, :show)
diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs
index 6f1fab069..1557937d8 100644
--- a/test/web/mastodon_api/controllers/notification_controller_test.exs
+++ b/test/web/mastodon_api/controllers/notification_controller_test.exs
@@ -53,7 +53,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     assert response == expected_response
   end
 
-  test "dismissing a single notification" do
+  test "dismissing a single notification (deprecated endpoint)" do
     %{user: user, conn: conn} = oauth_access(["write:notifications"])
     other_user = insert(:user)
 
@@ -69,6 +69,22 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     assert %{} = json_response(conn, 200)
   end
 
+  test "dismissing a single notification" do
+    %{user: user, conn: conn} = oauth_access(["write:notifications"])
+    other_user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+    {:ok, [notification]} = Notification.create_notifications(activity)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> post("/api/v1/notifications/#{notification.id}/dismiss")
+
+    assert %{} = json_response(conn, 200)
+  end
+
   test "clearing all notifications" do
     %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])
     other_user = insert(:user)

From 0e8f6d24b87812664d3bb021d17f120686cf2401 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Fri, 10 Apr 2020 00:19:09 +0900
Subject: [PATCH 30/43] Create OTP_VERSION file by `mix release`

---
 Dockerfile |  2 --
 mix.exs    | 11 ++++++++++-
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index c2f3ad98c..29931a5e3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,8 +12,6 @@ RUN apk add git gcc g++ musl-dev make &&\
 	mkdir release &&\
 	mix release --path release
 
-RUN echo "${OTP_VERSION}" > release/OTP_VERSION
-
 FROM alpine:3.11
 
 ARG BUILD_DATE
diff --git a/mix.exs b/mix.exs
index 3e4c7cbd8..ad2029518 100644
--- a/mix.exs
+++ b/mix.exs
@@ -37,12 +37,21 @@ defmodule Pleroma.Mixfile do
         pleroma: [
           include_executables_for: [:unix],
           applications: [ex_syslogger: :load, syslog: :load],
-          steps: [:assemble, &copy_files/1, &copy_nginx_config/1]
+          steps: [:assemble, &put_files/1, &copy_files/1, &copy_nginx_config/1]
         ]
       ]
     ]
   end
 
+  def put_files(%{path: target_path} = release) do
+    File.write!(
+      Path.join([target_path, "OTP_VERSION"]),
+      Pleroma.OTPVersion.version()
+    )
+
+    release
+  end
+
   def copy_files(%{path: target_path} = release) do
     File.cp_r!("./rel/files", target_path)
     release

From c826d5195f1746449eb369e86a730f14de9fa267 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 9 Apr 2020 23:36:17 +0400
Subject: [PATCH 31/43] Update CHANGELOG

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6e5d807c..2f5d8f612 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 <details>
   <summary>API Changes</summary>
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
+- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
 </details>
 
 ## [2.0.0] - 2019-03-08

From 781ac28859596fce5f2fd24ffe1cdf24caaaa2fc Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Sun, 15 Mar 2020 17:26:58 +0300
Subject: [PATCH 32/43] changelog.md: add 2.0.1 entry

---
 CHANGELOG.md | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f5d8f612..15f0463b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,9 +3,19 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
-## [unreleased]
+## [2.0.1] - 2020-03-15
+### Fixed
+- 500 errors when no `Accept` header is present if Static-FE is enabled
+- Instance panel not being updated immediately due to wrong `Cache-Control` headers
+- Statuses posted with BBCode/Markdown having unncessary newlines in Pleroma-FE
+- OTP: Fix some settings not being migrated to in-database config properly
+- No `Cache-Control` headers on attachment/media proxy requests
+- Character limit enforcement being off by 1
+- Mastodon Streaming API: hashtag timelines not working
+
 ### Changed
-- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
+- BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
+- Mastodon API: Allow registration without email if email verification is not enabled
 
 ### Removed
 - **Breaking:** removed `with_move` parameter from notifications timeline.

From 2a08f44b026bae611064b6ac459e7df16e4a36f9 Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Mon, 16 Mar 2020 00:50:03 +0300
Subject: [PATCH 33/43] CHANGELOG.md: Add upgrade notes for 2.0.1

---
 CHANGELOG.md | 35 +++++++++++++++++++++++------------
 1 file changed, 23 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15f0463b2..8c976228c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## [unreleased]
+### Removed
+- **Breaking:** removed `with_move` parameter from notifications timeline.
+
+### Added
+- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
+- NodeInfo: `pleroma_emoji_reactions` to the `features` list.
+- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
+- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
+<details>
+  <summary>API Changes</summary>
+- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
+- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
+</details>
+
 ## [2.0.1] - 2020-03-15
 ### Fixed
 - 500 errors when no `Accept` header is present if Static-FE is enabled
@@ -17,19 +32,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
 - Mastodon API: Allow registration without email if email verification is not enabled
 
-### Removed
-- **Breaking:** removed `with_move` parameter from notifications timeline.
+### Upgrade notes
+#### Nginx only
+1. Remove `proxy_ignore_headers Cache-Control;` and `proxy_hide_header  Cache-Control;` from your config.
 
-### Added
-- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
-- NodeInfo: `pleroma_emoji_reactions` to the `features` list.
-- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
-- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
-<details>
-  <summary>API Changes</summary>
-- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
-- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
-</details>
+#### Everyone
+1. Run database migrations (inside Pleroma directory):
+  - OTP: `./bin/pleroma_ctl migrate`
+  - From Source: `mix ecto.migrate`
+2. Restart Pleroma
 
 ## [2.0.0] - 2019-03-08
 ### Security

From 7306d2d06942f7912fd42809b1feb9ac43089012 Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Tue, 31 Mar 2020 13:59:26 +0300
Subject: [PATCH 34/43] CHANGELOG.md: Add 2.0.2 entry

---
 CHANGELOG.md | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c976228c..6942ad0bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
 </details>
 
+## [2.0.2] - 2020-03-31
+### Fixed
+- Blocked/muted users still generating push notifications
+- Input textbox for bio ignoring newlines
+- OTP: Inability to use PostgreSQL databases with SSL
+- `user delete_activities` breaking when trying to delete already deleted posts
+
+### Added
+- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials`
+
 ## [2.0.1] - 2020-03-15
+### Security
+- Static-FE: Fix remote posts not being sanitized
+
 ### Fixed
 - 500 errors when no `Accept` header is present if Static-FE is enabled
 - Instance panel not being updated immediately due to wrong `Cache-Control` headers

From 0b8f9a66aefdf4c9e2b7c1fa931e19cd724b6b4b Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Thu, 2 Apr 2020 23:37:14 +0300
Subject: [PATCH 35/43] CHANGELOG.md: add entries for funkwhale-related changes

---
 CHANGELOG.md | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6942ad0bf..8eed9cf7d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,14 +19,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 </details>
 
 ## [2.0.2] - 2020-03-31
+### Added
+- Support for Funkwhale's `Audio` activity
+- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials`
+
 ### Fixed
 - Blocked/muted users still generating push notifications
 - Input textbox for bio ignoring newlines
 - OTP: Inability to use PostgreSQL databases with SSL
 - `user delete_activities` breaking when trying to delete already deleted posts
-
-### Added
-- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials`
+- Incorrect URL for Funkwhale channels
 
 ## [2.0.1] - 2020-03-15
 ### Security

From adeb82e4966a505e9ac65743e6336db27558e38f Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Wed, 8 Apr 2020 00:38:48 +0300
Subject: [PATCH 36/43] CHANGELOG.md: add 2.0.2 update notes

---
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8eed9cf7d..408b932b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - `user delete_activities` breaking when trying to delete already deleted posts
 - Incorrect URL for Funkwhale channels
 
+### Upgrade notes
+1. Restart Pleroma
+
 ## [2.0.1] - 2020-03-15
 ### Security
 - Static-FE: Fix remote posts not being sanitized

From 9abf13abe05f3f53bdf21d4d97242e571b1767c6 Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Wed, 8 Apr 2020 00:39:55 +0300
Subject: [PATCH 37/43] CHANGELOG.md: update 2.0.2 release date

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 408b932b8..bac69ad6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
 </details>
 
-## [2.0.2] - 2020-03-31
+## [2.0.2] - 2020-04-08
 ### Added
 - Support for Funkwhale's `Audio` activity
 - Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials`

From c2aad36aa86694d4131adb2ed47441beca2ab2e8 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Thu, 9 Apr 2020 23:19:41 +0000
Subject: [PATCH 38/43] Rename function

---
 mix.exs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mix.exs b/mix.exs
index ad2029518..a1fcde564 100644
--- a/mix.exs
+++ b/mix.exs
@@ -37,13 +37,13 @@ defmodule Pleroma.Mixfile do
         pleroma: [
           include_executables_for: [:unix],
           applications: [ex_syslogger: :load, syslog: :load],
-          steps: [:assemble, &put_files/1, &copy_files/1, &copy_nginx_config/1]
+          steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1]
         ]
       ]
     ]
   end
 
-  def put_files(%{path: target_path} = release) do
+  def put_otp_version(%{path: target_path} = release) do
     File.write!(
       Path.join([target_path, "OTP_VERSION"]),
       Pleroma.OTPVersion.version()

From 6ff8812ea3403a2f4a31206a96a58fad93fff51f Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Fri, 10 Apr 2020 11:37:02 -0500
Subject: [PATCH 39/43] Add a section for changelog entries that pertain to the
 next patch release.

This will make it easier to keep changelogs synced between develop and stable branches.
---
 CHANGELOG.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd5d5f800..36897503a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Fixed
 - Support pagination in conversations API
 
+## [unreleased-patch]
+
 ## [2.0.2] - 2020-04-08
 ### Added
 - Support for Funkwhale's `Audio` activity

From ad92cef844d4f4211a65fd37b08f8bd8abea8dda Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Fri, 10 Apr 2020 21:27:50 +0300
Subject: [PATCH 40/43] fix Oban migration

---
 .../repo/migrations/20200402063221_update_oban_jobs_table.exs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs
index c8ee12192..e7ff04008 100644
--- a/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs
+++ b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs
@@ -2,10 +2,10 @@ defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do
   use Ecto.Migration
 
   def up do
-    Oban.Migrations.up()
+    Oban.Migrations.up(version: 8)
   end
 
   def down do
-    Oban.Migrations.down(version: 1)
+    Oban.Migrations.down(version: 7)
   end
 end

From 2ba754ffe11b98305e0c0607fec7ca4d510aa67f Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Sun, 12 Apr 2020 18:49:31 +0300
Subject: [PATCH 41/43] Fix mix tasks failing on OTP releases

No idea why this was even added.
Closes #1678
---
 lib/mix/pleroma.ex | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 4dfcc32e7..3ad6edbfb 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -5,7 +5,6 @@
 defmodule Mix.Pleroma do
   @doc "Common functions to be reused in mix tasks"
   def start_pleroma do
-    Mix.Task.run("app.start")
     Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
 
     if Pleroma.Config.get(:env) != :test do

From dc2637c18880160286f50505b1140a58fdfdf7d1 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 13 Apr 2020 09:16:35 +0300
Subject: [PATCH 42/43] [#2342] Removed changelog entry for temporary
 configuration option.

---
 CHANGELOG.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b41502a27..7d9b10b28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
 - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
 - New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
-- Configuration: `:extensions/:output_relationships_in_statuses_by_default` option (if `false`, disables the output of account/pleroma/relationship for statuses and notifications by default, breaking the compatibility with older PleromaFE versions).
 <details>
   <summary>API Changes</summary>
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.

From 5c76afb06c731557b537f928296e0b5c259f8d5e Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 13 Apr 2020 15:38:50 +0300
Subject: [PATCH 43/43] [#2342] Removed description.exs entry for temporary
 configuration option.

---
 config/description.exs | 16 ----------------
 1 file changed, 16 deletions(-)

diff --git a/config/description.exs b/config/description.exs
index 1b450db58..642f1a3ce 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -121,22 +121,6 @@ config :pleroma, :config_description, [
       }
     ]
   },
-  %{
-    group: :pleroma,
-    key: :extensions,
-    type: :group,
-    description: "Pleroma-specific extensions",
-    children: [
-      %{
-        key: :output_relationships_in_statuses_by_default,
-        type: :beeolean,
-        description:
-          "If `true`, outputs account/pleroma/relationship map for each rendered status / notification (for all clients). " <>
-            "If `false`, outputs the above only if `with_relationships` param is tru-ish " <>
-            "(that breaks compatibility with older PleromaFE versions which do not send this param but expect the output)."
-      }
-    ]
-  },
   %{
     group: :pleroma,
     key: Pleroma.Uploaders.Local,