Merge remote-tracking branch 'remotes/upstream/develop' into 1304-user-info-deprecation
authorIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 20 Oct 2019 17:43:18 +0000 (20:43 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 20 Oct 2019 17:43:18 +0000 (20:43 +0300)
# Conflicts:
# CHANGELOG.md
# lib/pleroma/notification.ex
# lib/pleroma/user.ex
# lib/pleroma/user/info.ex
# lib/pleroma/web/activity_pub/activity_pub.ex
# lib/pleroma/web/admin_api/admin_api_controller.ex
# lib/pleroma/web/ostatus/handlers/follow_handler.ex
# lib/pleroma/web/ostatus/ostatus.ex
# lib/pleroma/web/salmon/salmon.ex
# lib/pleroma/web/websub/websub.ex
# test/web/admin_api/admin_api_controller_test.exs
# test/web/federator_test.exs
# test/web/mastodon_api/controllers/conversation_controller_test.exs
# test/web/ostatus/ostatus_controller_test.exs
# test/web/ostatus/ostatus_test.exs
# test/web/salmon/salmon_test.exs
# test/web/websub/websub_test.exs

23 files changed:
1  2 
CHANGELOG.md
docs/API/admin_api.md
lib/pleroma/notification.ex
lib/pleroma/user.ex
lib/pleroma/user/search.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/publisher.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/account_view.ex
lib/pleroma/web/masto_fe_controller.ex
lib/pleroma/web/views/masto_fe_view.ex
priv/repo/migrations/20190711042024_copy_muted_to_muted_notifications.exs
test/moderation_log_test.exs
test/signature_test.exs
test/user_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/transmogrifier_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/federator_test.exs
test/web/mastodon_api/controllers/account_controller_test.exs
test/web/mastodon_api/controllers/conversation_controller_test.exs
test/web/mastodon_api/views/account_view_test.exs

diff --cc CHANGELOG.md
index 4ce9fa4b0ee5ebc34b7ef8e4afdf10dca08f2549,19cd1e53990f471c7bb9c6854f655f5db477eb50..cf16cf5df40de1140b7ad432b78bab5341165de2
@@@ -29,15 -63,37 +63,38 @@@ The format is based on [Keep a Changelo
  - MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities
  - OStatus: Extract RSS functionality
  - Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
+ - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
+ </details>
 +- Deprecated `User.Info` embedded schema (fields moved to `User`)
  
  ### Fixed
+ - Report emails now include functional links to profiles of remote user accounts
+ <details>
+   <summary>API Changes</summary>
  - Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
  - Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
- - Added `:instance, extended_nickname_format` setting to the default config
- - Report emails now include functional links to profiles of remote user accounts
+ </details>
+ ## [1.1.2] - 2019-10-18
+ ### Fixed
+ - `pleroma_ctl` trying to connect to a running instance when generating the config, which of course doesn't exist.
  
- ## [1.1.0] - 2019-??-??
+ ## [1.1.1] - 2019-10-18
+ ### Fixed
+ - One of the migrations between 1.0.0 and 1.1.0 wiping user info of the relay user because of unexpected behavior of postgresql's `jsonb_set`, resulting in inability to post in the default configuration. If you were affected, please run the following query in postgres console, the relay user will be recreated automatically:
+ ```
+ delete from users where ap_id = 'https://your.instance.hostname/relay';
+ ```
+ - Bad user search matches
+ ## [1.1.0] - 2019-10-14
+ **Breaking:** The stable branch has been changed from `master` to `stable`. If you want to keep using 1.0, the `release/1.0` branch will receive security updates for 6 months after 1.1 release.
+ **OTP Note:** `pleroma_ctl` in 1.0 defaults to `master` and doesn't support specifying arbitrary branches, making `./pleroma_ctl update` fail. To fix this, fetch a version of `pleroma_ctl` from 1.1 using the command below and proceed with the update normally:
+ ```
+ curl -Lo ./bin/pleroma_ctl 'https://git.pleroma.social/pleroma/pleroma/raw/develop/rel/files/bin/pleroma_ctl'
+ ```
  ### Security
  - Mastodon API: respect post privacy in `/api/v1/statuses/:id/{favourited,reblogged}_by`
  
index b56f53967c8879ed62e096317098f7aab8f26f8b,6adeda07e7d9ec3c9c2a85433c9694a3c62efee2..6faf1786be7cf804cbd2d35353fd273ebf335e6e
@@@ -159,19 -170,76 +170,76 @@@ Note: Available `:permission_group` is 
  - Params: none
  - Response:
    - On failure: `{"error": "…"}`
 -  - On success: JSON of the `user.info`
 +  - On success: JSON of the user
  
+ ## `POST /api/pleroma/admin/users/permission_group/:permission_group`
+ ### Add users to permission group
+ - Params:
+   - `nicknames`: nicknames array
+ - Response:
+   - On failure: `{"error": "…"}`
+   - On success: JSON of the `user.info`
+ ## DEPRECATED `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
  ### Remove user from permission group
  
- - Method: `DELETE`
  - Params: none
  - Response:
    - On failure: `{"error": "…"}`
 -  - On success: JSON of the `user.info`
 +  - On success: JSON of the user
  - Note: An admin cannot revoke their own admin status.
  
- ## `/api/pleroma/admin/users/:nickname/activation_status`
+ ## `DELETE /api/pleroma/admin/users/permission_group/:permission_group`
+ ### Remove users from permission group
+ - Params:
+   - `nicknames`: nicknames array
+ - Response:
+   - On failure: `{"error": "…"}`
+   - On success: JSON of the `user.info`
+ - Note: An admin cannot revoke their own admin status.
+ ## `PATCH /api/pleroma/admin/users/activate`
+ ### Activate user
+ - Params:
+   - `nicknames`: nicknames array
+ - Response:
+ ```json
+ {
+   users: [
+     {
+       // user object
+     }
+   ]
+ }
+ ```
+ ## `PATCH /api/pleroma/admin/users/deactivate`
+ ### Deactivate user
+ - Params:
+   - `nicknames`: nicknames array
+ - Response:
+ ```json
+ {
+   users: [
+     {
+       // user object
+     }
+   ]
+ }
+ ```
+ ## DEPRECATED `PATCH /api/pleroma/admin/users/:nickname/activation_status`
  
  ### Active or deactivate a user
  
index 6ded91f3c13271e4830b46d6a1ba1e70c1e7b6a7,d145f8d5b5a42c8ba3b685196754e45a1eaf682e..21b26a53d7b894369a446ebc1d0ee31a2a49d111
@@@ -34,40 -35,66 +35,66 @@@ defmodule Pleroma.Notification d
    end
  
    def for_user_query(user, opts \\ []) do
-     query =
-       Notification
-       |> where(user_id: ^user.id)
-       |> where(
-         [n, a],
+     Notification
+     |> where(user_id: ^user.id)
+     |> where(
+       [n, a],
+       fragment(
 -        "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
++        "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')",
+         a.actor
+       )
+     )
+     |> join(:inner, [n], activity in assoc(n, :activity))
+     |> join(:left, [n, a], object in Object,
+       on:
          fragment(
-           "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')",
-           a.actor
+           "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
+           object.data,
+           a.data
          )
-       )
-       |> join(:inner, [n], activity in assoc(n, :activity))
-       |> join(:left, [n, a], object in Object,
-         on:
-           fragment(
-             "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
-             object.data,
-             a.data
-           )
-       )
-       |> preload([n, a, o], activity: {a, object: o})
+     )
+     |> preload([n, a, o], activity: {a, object: o})
+     |> exclude_muted(user, opts)
+     |> exclude_visibility(opts)
+   end
+   defp exclude_muted(query, _, %{with_muted: true}) do
+     query
+   end
+   defp exclude_muted(query, user, _opts) do
+     query
 -    |> where([n, a], a.actor not in ^user.info.muted_notifications)
 -    |> where([n, a], a.actor not in ^user.info.blocks)
++    |> where([n, a], a.actor not in ^user.muted_notifications)
++    |> where([n, a], a.actor not in ^user.blocks)
+     |> where(
+       [n, a],
 -      fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
++      fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
+     )
+     |> join(:left, [n, a], tm in Pleroma.ThreadMute,
+       on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
+     )
+     |> where([n, a, o, tm], is_nil(tm.user_id))
+   end
+   @valid_visibilities ~w[direct unlisted public private]
  
-     if opts[:with_muted] do
+   defp exclude_visibility(query, %{exclude_visibilities: visibility})
+        when is_list(visibility) do
+     if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
        query
-     else
-       where(query, [n, a], a.actor not in ^user.muted_notifications)
-       |> where([n, a], a.actor not in ^user.blocks)
        |> where(
          [n, a],
-         fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
-       )
-       |> join(:left, [n, a], tm in Pleroma.ThreadMute,
-         on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
+         not fragment(
+           "activity_visibility(?, ?, ?) = ANY (?)",
+           a.actor,
+           a.recipients,
+           a.data,
+           ^visibility
+         )
        )
-       |> where([n, a, o, tm], is_nil(tm.user_id))
+     else
+       Logger.error("Could not exclude visibility to #{visibility}")
+       query
      end
    end
  
index 282cf69622e9ea943a589713bd81a1dd1ed28a53,ec705b8f6ca4c5702b1461f01a2fd0f7fd6639cd..e2ebb7d64b33790c63ce38be2e9a9e82d4feb0dd
@@@ -1209,8 -1048,16 +1198,16 @@@ defmodule Pleroma.User d
      BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
    end
  
-   def deactivate(%User{} = user, status \\ true) do
+   def deactivate(user, status \\ true)
+   def deactivate(users, status) when is_list(users) do
+     Repo.transaction(fn ->
+       for user <- users, do: deactivate(user, status)
+     end)
+   end
+   def deactivate(%User{} = user, status) do
 -    with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do
 +    with {:ok, user} <- set_activation_status(user, status) do
        Enum.each(get_followers(user), &invalidate_cache/1)
        Enum.each(get_friends(user), &update_follower_count/1)
  
      end
    end
  
 -  def update_notification_settings(%User{} = user, settings \\ %{}) do
 -    update_info(user, &User.Info.update_notification_settings(&1, settings))
 +  def update_notification_settings(%User{} = user, settings) do
 +    settings =
 +      settings
 +      |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
 +      |> Map.new()
 +
 +    notification_settings =
 +      user.notification_settings
 +      |> Map.merge(settings)
 +      |> Map.take(["followers", "follows", "non_follows", "non_followers"])
 +
 +    params = %{notification_settings: notification_settings}
 +
 +    user
 +    |> cast(params, [:notification_settings])
 +    |> validate_required([:notification_settings])
 +    |> update_and_set_cache()
    end
  
+   def delete(users) when is_list(users) do
+     for user <- users, do: delete(user)
+   end
    def delete(%User{} = user) do
      BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
    end
Simple merge
index b9e88ec703c9c28ad21868cf03579dfb988a03b5,94c467b69e72196df67da628f450b063c22b8d05..d4dac5dab3bf54cbe361a6f5dd685dac758cda65
@@@ -235,6 -247,9 +247,7 @@@ defmodule Pleroma.Web.ActivityPub.Activ
           {:fake, false, activity} <- {:fake, fake, activity},
           _ <- increase_replies_count_if_reply(create_data),
           _ <- increase_poll_votes_if_vote(create_data),
 -         # Changing note count prior to enqueuing federation task in order to avoid
 -         # race conditions on updating user.info
+          {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
           {:ok, _actor} <- increase_note_count_if_public(actor, activity),
           :ok <- maybe_federate(activity) do
        {:ok, activity}
index 3894b37425f63d7f2a5b08837a3e8b04472f6439,b6d3f79c8a0145123c28a3c55b3e025f5ea5b548..66b5276d4979aaf73a73d742c5c93bb82de3f0e9
@@@ -375,32 -474,23 +474,40 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI
        permission: permission_group
      })
  
 -    json(conn, info)
 +    json(conn, fields)
    end
  
-   def right_delete(conn, _) do
-     render_error(conn, :not_found, "No such permission_group")
+   def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
+     render_error(conn, :forbidden, "You can't revoke your own admin status.")
    end
  
-         "nickname" => nickname,
-         "status" => status
-       }) do
 +  def set_activation_status(%{assigns: %{user: admin}} = conn, %{
 -    end
 -  end
++    "nickname" => nickname,
++    "status" => status
++  }) do
 +    with {:ok, status} <- Ecto.Type.cast(:boolean, status),
 +         %User{} = user <- User.get_cached_by_nickname(nickname),
 +         {:ok, _} <- User.deactivate(user, !status) do
 +      action = if(user.deactivated, do: "activate", else: "deactivate")
 +
 +      ModerationLog.insert_log(%{
 +        actor: admin,
 +        subject: user,
 +        action: action
 +      })
 +
 +      json_response(conn, :no_content, "")
 +    end
 +  end
 +
+   def relay_list(conn, _params) do
+     with {:ok, list} <- Relay.list() do
+       json(conn, %{relays: list})
+     else
+       _ ->
+         conn
+         |> put_status(500)
    def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
      with {:ok, _message} <- Relay.follow(target) do
        ModerationLog.insert_log(%{
index cf0fe91ce3af1b87d8173923d80c4648b2fa45cb,93b38e8f4c8cd7435caf2cc5c29152a41f380657..ca261ad6ed96a944822cc130e8bb6769a37ee407
@@@ -34,9 -34,15 +34,15 @@@ defmodule Pleroma.Web.MastoFEControlle
      end
    end
  
+   @doc "GET /web/manifest.json"
+   def manifest(conn, _params) do
+     conn
+     |> render("manifest.json")
+   end
    @doc "PUT /api/web/settings"
    def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
 -    with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
 +    with {:ok, _} <- User.mastodon_settings_update(user, settings) do
        json(conn, %{})
      else
        e ->
Simple merge
Simple merge
Simple merge
index a1027e2e25933e90c4438f21a9e831b18fbbbe36,9da4940be5132a2a0c44d8eb2e235094e852d5d0..58435d23cea519ce0bc69f5470a0960df4ac2579
@@@ -17,9 -17,15 +17,15 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI
    alias Pleroma.Web.MediaProxy
    import Pleroma.Factory
  
-   describe "/api/pleroma/admin/users" do
-     test "Delete" do
+   setup_all do
+     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+     :ok
+   end
+   describe "DELETE /api/pleroma/admin/users" do
+     test "single user" do
 -      admin = insert(:user, info: %{is_admin: true})
 +      admin = insert(:user, is_admin: true)
        user = insert(:user)
  
        conn =
        assert json_response(conn, 200) == user.nickname
      end
  
 -      admin = insert(:user, info: %{is_admin: true})
+     test "multiple users" do
++      admin = insert(:user, is_admin: true)
+       user_one = insert(:user)
+       user_two = insert(:user)
+       conn =
+         build_conn()
+         |> assign(:user, admin)
+         |> put_req_header("accept", "application/json")
+         |> delete("/api/pleroma/admin/users", %{
+           nicknames: [user_one.nickname, user_two.nickname]
+         })
+       log_entry = Repo.one(ModerationLog)
+       assert ModerationLog.get_log_entry_message(log_entry) ==
+                "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
+       response = json_response(conn, 200)
+       assert response -- [user_one.nickname, user_two.nickname] == []
+     end
+   end
+   describe "/api/pleroma/admin/users" do
      test "Create" do
 -      admin = insert(:user, info: %{is_admin: true})
 +      admin = insert(:user, is_admin: true)
  
        conn =
          build_conn()
                 "@#{admin.nickname} made @#{user.nickname} admin"
      end
  
-     test "/:right DELETE, can remove from a permission group" do
+     test "/:right POST, can add to a permission group (multiple)" do
 -      admin = insert(:user, info: %{is_admin: true})
 +      admin = insert(:user, is_admin: true)
-       user = insert(:user, is_admin: true)
+       user_one = insert(:user)
+       user_two = insert(:user)
  
        conn =
          build_conn()
        log_entry = Repo.one(ModerationLog)
  
        assert ModerationLog.get_log_entry_message(log_entry) ==
-                "@#{admin.nickname} revoked admin role from @#{user.nickname}"
+                "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin"
      end
-   end
  
-   describe "PUT /api/pleroma/admin/users/:nickname/activation_status" do
-     setup %{conn: conn} do
+     test "/:right DELETE, can remove from a permission group" do
 -      admin = insert(:user, info: %{is_admin: true})
 -      user = insert(:user, info: %{is_admin: true})
 +      admin = insert(:user, is_admin: true)
++      user = insert(:user, is_admin: true)
  
        conn =
-         conn
+         build_conn()
          |> assign(:user, admin)
          |> put_req_header("accept", "application/json")
+         |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
  
-       %{conn: conn, admin: admin}
-     end
-     test "deactivates the user", %{conn: conn, admin: admin} do
-       user = insert(:user)
-       conn =
-         conn
-         |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false})
-       user = User.get_cached_by_id(user.id)
-       assert user.deactivated == true
-       assert json_response(conn, :no_content)
+       assert json_response(conn, 200) == %{
+                "is_admin" => false
+              }
  
        log_entry = Repo.one(ModerationLog)
  
        assert ModerationLog.get_log_entry_message(log_entry) ==
-                "@#{admin.nickname} deactivated user @#{user.nickname}"
+                "@#{admin.nickname} revoked admin role from @#{user.nickname}"
      end
  
-     test "activates the user", %{conn: conn, admin: admin} do
-       user = insert(:user, deactivated: true)
+     test "/:right DELETE, can remove from a permission group (multiple)" do
 -      admin = insert(:user, info: %{is_admin: true})
 -      user_one = insert(:user, info: %{is_admin: true})
 -      user_two = insert(:user, info: %{is_admin: true})
++      admin = insert(:user, is_admin: true)
++      user_one = insert(:user, is_admin: true)
++      user_two = insert(:user, is_admin: true)
  
        conn =
-         conn
-         |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: true})
+         build_conn()
+         |> assign(:user, admin)
+         |> put_req_header("accept", "application/json")
+         |> delete("/api/pleroma/admin/users/permission_group/admin", %{
+           nicknames: [user_one.nickname, user_two.nickname]
+         })
  
-       user = User.get_cached_by_id(user.id)
-       assert user.deactivated == false
-       assert json_response(conn, :no_content)
+       assert json_response(conn, 200) == %{
+                "is_admin" => false
+              }
  
        log_entry = Repo.one(ModerationLog)
  
      end
    end
  
 -    admin = insert(:user, info: %{is_admin: true})
 -    user_one = insert(:user, info: %{deactivated: true})
 -    user_two = insert(:user, info: %{deactivated: true})
+   test "PATCH /api/pleroma/admin/users/activate" do
 -    admin = insert(:user, info: %{is_admin: true})
 -    user_one = insert(:user, info: %{deactivated: false})
 -    user_two = insert(:user, info: %{deactivated: false})
++    admin = insert(:user, is_admin: true)
++    user_one = insert(:user, deactivated: true)
++    user_two = insert(:user, deactivated: true)
+     conn =
+       build_conn()
+       |> assign(:user, admin)
+       |> patch(
+         "/api/pleroma/admin/users/activate",
+         %{nicknames: [user_one.nickname, user_two.nickname]}
+       )
+     response = json_response(conn, 200)
+     assert Enum.map(response["users"], & &1["deactivated"]) == [false, false]
+     log_entry = Repo.one(ModerationLog)
+     assert ModerationLog.get_log_entry_message(log_entry) ==
+              "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
+   end
+   test "PATCH /api/pleroma/admin/users/deactivate" do
++    admin = insert(:user, is_admin: true)
++    user_one = insert(:user, deactivated: false)
++    user_two = insert(:user, deactivated: false)
+     conn =
+       build_conn()
+       |> assign(:user, admin)
+       |> patch(
+         "/api/pleroma/admin/users/deactivate",
+         %{nicknames: [user_one.nickname, user_two.nickname]}
+       )
+     response = json_response(conn, 200)
+     assert Enum.map(response["users"], & &1["deactivated"]) == [true, true]
+     log_entry = Repo.one(ModerationLog)
+     assert ModerationLog.get_log_entry_message(log_entry) ==
+              "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
+   end
    test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do
 -    admin = insert(:user, info: %{is_admin: true})
 +    admin = insert(:user, is_admin: true)
      user = insert(:user)
  
      conn =
  
        ObanHelpers.perform_all()
  
 -      assert User.get_by_id(user.id).info.password_reset_pending == true
 +      assert User.get_by_id(user.id).password_reset_pending == true
      end
    end
 -      admin = insert(:user, info: %{is_admin: true})
+   describe "relays" do
+     setup %{conn: conn} do
++      admin = insert(:user, is_admin: true)
+       %{conn: assign(conn, :user, admin), admin: admin}
+     end
+     test "POST /relay", %{admin: admin} do
+       conn =
+         build_conn()
+         |> assign(:user, admin)
+         |> post("/api/pleroma/admin/relay", %{
+           relay_url: "http://mastodon.example.org/users/admin"
+         })
+       assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
+       log_entry = Repo.one(ModerationLog)
+       assert ModerationLog.get_log_entry_message(log_entry) ==
+                "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
+     end
+     test "GET /relay", %{admin: admin} do
+       Pleroma.Web.ActivityPub.Relay.get_actor()
+       |> Ecto.Changeset.change(
+         following: [
+           "http://test-app.com/user/test1",
+           "http://test-app.com/user/test1",
+           "http://test-app-42.com/user/test1"
+         ]
+       )
+       |> Pleroma.User.update_and_set_cache()
+       conn =
+         build_conn()
+         |> assign(:user, admin)
+         |> get("/api/pleroma/admin/relay")
+       assert json_response(conn, 200)["relays"] -- ["test-app.com", "test-app-42.com"] == []
+     end
+     test "DELETE /relay", %{admin: admin} do
+       build_conn()
+       |> assign(:user, admin)
+       |> post("/api/pleroma/admin/relay", %{
+         relay_url: "http://mastodon.example.org/users/admin"
+       })
+       conn =
+         build_conn()
+         |> assign(:user, admin)
+         |> delete("/api/pleroma/admin/relay", %{
+           relay_url: "http://mastodon.example.org/users/admin"
+         })
+       assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
+       [log_entry_one, log_entry_two] = Repo.all(ModerationLog)
+       assert ModerationLog.get_log_entry_message(log_entry_one) ==
+                "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
+       assert ModerationLog.get_log_entry_message(log_entry_two) ==
+                "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
+     end
+   end
  end
  
  # Needed for testing
Simple merge
index 34b434f3f013cebf8a16893c7f4f71af247d64f7,d89a8717930083787f166365b6f30e68d68983e8..b92da665b7f90922d77b1f37c1ef46d91f19a00b
@@@ -54,9 -54,9 +54,9 @@@ defmodule Pleroma.Web.MastodonAPI.Conve
      assert user_two.id in account_ids
      assert user_three.id in account_ids
      assert is_binary(res_id)
-     assert unread == true
+     assert unread == false
      assert res_last_status["id"] == direct.id
-     assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
 -    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0
++    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
    end
  
    test "updates the last_status on reply", %{conn: conn} do
        |> post("/api/v1/conversations/#{direct_conversation_id}/read")
        |> json_response(200)
  
 -    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0
 -    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
 +    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
++    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
  
      # The conversation is marked as unread on reply
      {:ok, _} =
        |> get("/api/v1/conversations")
        |> json_response(200)
  
 -    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1
 -    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
 +    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
++    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
  
      # A reply doesn't increment the user's unread_conversation_count if the conversation is unread
      {:ok, _} =
          "in_reply_to_status_id" => direct.id
        })
  
 -    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1
 -    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
 +    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
++    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
    end
  
    test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do