Merge remote-tracking branch 'origin/develop' into feature/account-export
authorEgor Kislitsyn <egor@kislitsyn.com>
Fri, 30 Oct 2020 15:34:02 +0000 (19:34 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Fri, 30 Oct 2020 15:34:02 +0000 (19:34 +0400)
1  2 
CHANGELOG.md
config/description.exs
lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
lib/pleroma/web/router.ex
test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs

diff --combined CHANGELOG.md
index 23754257cb209036fd987a721c4a19bbea5c8294,11820d313a050b4c48952693445d402c1083e9a3..fb07124cc85f79fedee9f8f18530cea3cde690b2
@@@ -12,8 -12,8 +12,9 @@@ The format is based on [Keep a Changelo
  - Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
  - Pleroma API: Importing the mutes users from CSV files.
  - Experimental websocket-based federation between Pleroma instances.
+ - Support pagination of blocks and mutes
  - App metrics: ability to restrict access to specified IP whitelist.
 +- Account backup
  - Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
  
  ### Changed
@@@ -36,6 -36,8 +37,8 @@@
  - Pleroma API: Importing the mutes users from CSV files.
  - Admin API: Importing emoji from a zip file
  - Pleroma API: Pagination for remote/local packs and emoji.
+ - Admin API: (`GET /api/pleroma/admin/users`) added filters user by `unconfirmed` status
+ - Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
  
  </details>
  
@@@ -53,6 -55,7 +56,7 @@@ switched to a new configuration mechani
  - Allow sending out emails again.
  - Allow sending chat messages to yourself.
  - Fix remote users with a whitespace name.
+ - OStatus / static FE endpoints: fixed inaccessibility for anonymous users on non-federating instances, switched to handling per `:restrict_unauthenticated` setting.
  
  ## Unreleased (Patch)
  
diff --combined config/description.exs
index 8861cddea27fc9b296528226b4ed8423fe30545e,798cbe2adcb43837633fd5dd32a3ca8c07e508b6..0b651696b4bb37fa0a76fef692606c3adefd07a5
@@@ -1757,28 -1757,37 +1757,37 @@@ config :pleroma, :config_description, 
      related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
      label: "MRF Keyword",
      type: :group,
-     description: "Reject or Word-Replace messages with a keyword or regex",
+     description:
+       "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
      children: [
        %{
          key: :reject,
          type: {:list, :string},
-         description:
-           "A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.",
+         description: """
+           A list of patterns which result in message being rejected.
+           Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+         """,
          suggestions: ["foo", ~r/foo/iu]
        },
        %{
          key: :federated_timeline_removal,
          type: {:list, :string},
-         description:
-           "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.",
+         description: """
+           A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).
+           Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+         """,
          suggestions: ["foo", ~r/foo/iu]
        },
        %{
          key: :replace,
          type: {:list, :tuple},
-         description:
-           "A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
-         suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
+         description: """
+           **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+           **Replacement**: a string. Leaving the field empty is permitted.
+         """
        }
      ]
    },
              description: "Activity expiration queue",
              suggestions: [10]
            },
 +          %{
 +            key: :backup,
 +            type: :integer,
 +            description: "Backup queue",
 +            suggestions: [1]
 +          },
            %{
              key: :attachments_cleanup,
              type: :integer,
        }
      ]
    },
 +  %{
 +    group: :pleroma,
 +    key: Pleroma.User.Backup,
 +    type: :group,
 +    description: "Account Backup",
 +    children: [
 +      %{
 +        key: :purge_after_days,
 +        type: :integer,
 +        description: "Remove backup achives after N days",
 +        suggestions: [30]
 +      },
 +      %{
 +        key: :limit_days,
 +        type: :integer,
 +        description: "Limit user to export not more often than once per N days",
 +        suggestions: [7]
 +      }
 +    ]
 +  },
    %{
      group: :prometheus,
      key: Pleroma.Web.Endpoint.MetricsExporter,
index 0a27c58611319ef86b9e68df7575e0783ca1d9bb,df5817cfa4f44f8f2988e86fb85a9db4faba3335..5c2c282b3f8be3804579549ea7f2aef4fad67627
@@@ -5,7 -5,8 +5,8 @@@
  defmodule Pleroma.Web.AdminAPI.AdminAPIController do
    use Pleroma.Web, :controller
  
-   import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+   import Pleroma.Web.ControllerHelper,
+     only: [json_response: 3, fetch_integer_param: 3]
  
    alias Pleroma.Config
    alias Pleroma.MFA
    alias Pleroma.Stats
    alias Pleroma.User
    alias Pleroma.Web.ActivityPub.ActivityPub
-   alias Pleroma.Web.ActivityPub.Builder
-   alias Pleroma.Web.ActivityPub.Pipeline
    alias Pleroma.Web.AdminAPI
    alias Pleroma.Web.AdminAPI.AccountView
    alias Pleroma.Web.AdminAPI.ModerationLogView
-   alias Pleroma.Web.AdminAPI.Search
    alias Pleroma.Web.Endpoint
    alias Pleroma.Web.Plugs.OAuthScopesPlug
    alias Pleroma.Web.Router
  
-   require Logger
    @users_page_size 50
  
    plug(
      OAuthScopesPlug,
      %{scopes: ["read:accounts"], admin: true}
-     when action in [:list_users, :user_show, :right_get, :show_user_credentials, :create_backup]
 -    when action in [:right_get, :show_user_credentials]
++    when action in [:right_get, :show_user_credentials, :create_backup]
    )
  
    plug(
      when action in [
             :get_password_reset,
             :force_password_reset,
-            :user_delete,
-            :users_create,
-            :user_toggle_activation,
-            :user_activate,
-            :user_deactivate,
-            :user_approve,
             :tag_users,
             :untag_users,
             :right_add,
           ]
    )
  
-   plug(
-     OAuthScopesPlug,
-     %{scopes: ["write:follows"], admin: true}
-     when action in [:user_follow, :user_unfollow]
-   )
    plug(
      OAuthScopesPlug,
      %{scopes: ["read:statuses"], admin: true}
  
    action_fallback(AdminAPI.FallbackController)
  
-   def user_delete(conn, %{"nickname" => nickname}) do
-     user_delete(conn, %{"nicknames" => [nickname]})
-   end
-   def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
-     users =
-       nicknames
-       |> Enum.map(&User.get_cached_by_nickname/1)
-     users
-     |> Enum.each(fn user ->
-       {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
-       Pipeline.common_pipeline(delete_data, local: true)
-     end)
-     ModerationLog.insert_log(%{
-       actor: admin,
-       subject: users,
-       action: "delete"
-     })
-     json(conn, nicknames)
-   end
-   def user_follow(%{assigns: %{user: admin}} = conn, %{
-         "follower" => follower_nick,
-         "followed" => followed_nick
-       }) do
-     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
-          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
-       User.follow(follower, followed)
-       ModerationLog.insert_log(%{
-         actor: admin,
-         followed: followed,
-         follower: follower,
-         action: "follow"
-       })
-     end
-     json(conn, "ok")
-   end
-   def user_unfollow(%{assigns: %{user: admin}} = conn, %{
-         "follower" => follower_nick,
-         "followed" => followed_nick
-       }) do
-     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
-          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
-       User.unfollow(follower, followed)
-       ModerationLog.insert_log(%{
-         actor: admin,
-         followed: followed,
-         follower: follower,
-         action: "unfollow"
-       })
-     end
-     json(conn, "ok")
-   end
-   def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
-     changesets =
-       Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
-         user_data = %{
-           nickname: nickname,
-           name: nickname,
-           email: email,
-           password: password,
-           password_confirmation: password,
-           bio: "."
-         }
-         User.register_changeset(%User{}, user_data, need_confirmation: false)
-       end)
-       |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
-         Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
-       end)
-     case Pleroma.Repo.transaction(changesets) do
-       {:ok, users} ->
-         res =
-           users
-           |> Map.values()
-           |> Enum.map(fn user ->
-             {:ok, user} = User.post_register_action(user)
-             user
-           end)
-           |> Enum.map(&AccountView.render("created.json", %{user: &1}))
-         ModerationLog.insert_log(%{
-           actor: admin,
-           subjects: Map.values(users),
-           action: "create"
-         })
-         json(conn, res)
-       {:error, id, changeset, _} ->
-         res =
-           Enum.map(changesets.operations, fn
-             {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
-               AccountView.render("create-error.json", %{changeset: changeset})
-             {_, {:changeset, current_changeset, _}} ->
-               AccountView.render("create-error.json", %{changeset: current_changeset})
-           end)
-         conn
-         |> put_status(:conflict)
-         |> json(res)
-     end
-   end
-   def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
-     with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
-       conn
-       |> put_view(AccountView)
-       |> render("show.json", %{user: user})
-     else
-       _ -> {:error, :not_found}
-     end
-   end
    def list_instance_statuses(conn, %{"instance" => instance} = params) do
      with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
      {page, page_size} = page_params(params)
      end
    end
  
-   def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
-     user = User.get_cached_by_nickname(nickname)
-     {:ok, updated_user} = User.deactivate(user, !user.deactivated)
-     action = if user.deactivated, do: "activate", else: "deactivate"
-     ModerationLog.insert_log(%{
-       actor: admin,
-       subject: [user],
-       action: action
-     })
-     conn
-     |> put_view(AccountView)
-     |> render("show.json", %{user: updated_user})
-   end
-   def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
-     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
-     {:ok, updated_users} = User.deactivate(users, false)
-     ModerationLog.insert_log(%{
-       actor: admin,
-       subject: users,
-       action: "activate"
-     })
-     conn
-     |> put_view(AccountView)
-     |> render("index.json", %{users: Keyword.values(updated_users)})
-   end
-   def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
-     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
-     {:ok, updated_users} = User.deactivate(users, true)
-     ModerationLog.insert_log(%{
-       actor: admin,
-       subject: users,
-       action: "deactivate"
-     })
-     conn
-     |> put_view(AccountView)
-     |> render("index.json", %{users: Keyword.values(updated_users)})
-   end
-   def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
-     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
-     {:ok, updated_users} = User.approve(users)
-     ModerationLog.insert_log(%{
-       actor: admin,
-       subject: users,
-       action: "approve"
-     })
-     conn
-     |> put_view(AccountView)
-     |> render("index.json", %{users: updated_users})
-   end
    def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
      with {:ok, _} <- User.tag(nicknames, tags) do
        ModerationLog.insert_log(%{
      end
    end
  
-   def list_users(conn, params) do
-     {page, page_size} = page_params(params)
-     filters = maybe_parse_filters(params["filters"])
-     search_params = %{
-       query: params["query"],
-       page: page,
-       page_size: page_size,
-       tags: params["tags"],
-       name: params["name"],
-       email: params["email"]
-     }
-     with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
-       json(
-         conn,
-         AccountView.render("index.json",
-           users: users,
-           count: count,
-           page_size: page_size
-         )
-       )
-     end
-   end
-   @filters ~w(local external active deactivated need_approval is_admin is_moderator)
-   @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
-   defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
-   defp maybe_parse_filters(filters) do
-     filters
-     |> String.split(",")
-     |> Enum.filter(&Enum.member?(@filters, &1))
-     |> Map.new(&{String.to_existing_atom(&1), true})
-   end
    def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
          "permission_group" => permission_group,
          "nicknames" => nicknames
      json(conn, %{"status_visibility" => counters})
    end
  
 +  def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
 +    with %User{} = user <- User.get_by_nickname(nickname),
 +         {:ok, _} <- Pleroma.User.Backup.create(user, admin.id) do
 +      ModerationLog.insert_log(%{actor: admin, subject: user, action: "create_backup"})
 +
 +      json(conn, "")
 +    end
 +  end
 +
    defp page_params(params) do
-     {get_page(params["page"]), get_page_size(params["page_size"])}
-   end
-   defp get_page(page_string) when is_nil(page_string), do: 1
-   defp get_page(page_string) do
-     case Integer.parse(page_string) do
-       {page, _} -> page
-       :error -> 1
-     end
-   end
-   defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
-   defp get_page_size(page_size_string) do
-     case Integer.parse(page_size_string) do
-       {page_size, _} -> page_size
-       :error -> @users_page_size
-     end
+     {
+       fetch_integer_param(params, "page", 1),
+       fetch_integer_param(params, "page_size", @users_page_size)
+     }
    end
  end
index 1126536a35b5ec81ec3ce6c931d3412ffa06e373,76ca2c9b51ab3a9be87b109dbba5a2e9a6fb5af2..9592d0f38cceffe41d8075d5b62de3e2dcd8fd91
@@@ -5,6 -5,26 +5,26 @@@
  defmodule Pleroma.Web.Router do
    use Pleroma.Web, :router
  
+   pipeline :accepts_html do
+     plug(:accepts, ["html"])
+   end
+   pipeline :accepts_html_xml do
+     plug(:accepts, ["html", "xml", "rss", "atom"])
+   end
+   pipeline :accepts_html_json do
+     plug(:accepts, ["html", "activity+json", "json"])
+   end
+   pipeline :accepts_html_xml_json do
+     plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
+   end
+   pipeline :accepts_xml_rss_atom do
+     plug(:accepts, ["xml", "rss", "atom"])
+   end
    pipeline :browser do
      plug(:accepts, ["html"])
      plug(:fetch_session)
  
    scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
      pipe_through(:admin_api)
-     post("/backups", AdminAPIController, :create_backup)
-     post("/users/follow", AdminAPIController, :user_follow)
-     post("/users/unfollow", AdminAPIController, :user_unfollow)
--
++    
      put("/users/disable_mfa", AdminAPIController, :disable_mfa)
-     delete("/users", AdminAPIController, :user_delete)
-     post("/users", AdminAPIController, :users_create)
-     patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
-     patch("/users/activate", AdminAPIController, :user_activate)
-     patch("/users/deactivate", AdminAPIController, :user_deactivate)
-     patch("/users/approve", AdminAPIController, :user_approve)
      put("/users/tag", AdminAPIController, :tag_users)
      delete("/users/tag", AdminAPIController, :untag_users)
  
        :right_delete_multiple
      )
  
+     post("/users/follow", UserController, :follow)
+     post("/users/unfollow", UserController, :unfollow)
+     delete("/users", UserController, :delete)
+     post("/users", UserController, :create)
+     patch("/users/:nickname/toggle_activation", UserController, :toggle_activation)
+     patch("/users/activate", UserController, :activate)
+     patch("/users/deactivate", UserController, :deactivate)
+     patch("/users/approve", UserController, :approve)
      get("/relay", RelayController, :index)
      post("/relay", RelayController, :follow)
      delete("/relay", RelayController, :unfollow)
      get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
      patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
  
-     get("/users", AdminAPIController, :list_users)
-     get("/users/:nickname", AdminAPIController, :user_show)
+     get("/users", UserController, :list)
+     get("/users/:nickname", UserController, :show)
      get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
      get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
  
      get("/chats/:id", ChatController, :show)
      get("/chats/:id/messages", ChatController, :messages)
      delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
++
++    post("/backups", AdminAPIController, :create_backup)
    end
  
    scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
        put("/mascot", MascotController, :update)
  
        post("/scrobble", ScrobbleController, :create)
 +
 +      get("/backups", BackupController, :index)
 +      post("/backups", BackupController, :create)
      end
  
      scope [] do
      )
    end
  
-   pipeline :ostatus do
-     plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
-     plug(Pleroma.Web.Plugs.StaticFEPlug)
-   end
-   pipeline :oembed do
-     plug(:accepts, ["json", "xml"])
-   end
    scope "/", Pleroma.Web do
-     pipe_through([:ostatus, :http_signature])
+     # Note: html format is supported only if static FE is enabled
+     # Note: http signature is only considered for json requests (no auth for non-json requests)
+     pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
  
      get("/objects/:uuid", OStatus.OStatusController, :object)
      get("/activities/:uuid", OStatus.OStatusController, :activity)
      get("/notice/:id", OStatus.OStatusController, :notice)
-     get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
  
      # Mastodon compatibility routes
      get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object)
      get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity)
+   end
  
-     get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+   scope "/", Pleroma.Web do
+     # Note: html format is supported only if static FE is enabled
+     # Note: http signature is only considered for json requests (no auth for non-json requests)
+     pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+     # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
      get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
+   end
  
+   scope "/", Pleroma.Web do
+     # Note: html format is supported only if static FE is enabled
+     pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug])
+     get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+   end
+   scope "/", Pleroma.Web do
+     pipe_through(:accepts_html)
+     get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
+   end
+   scope "/", Pleroma.Web do
+     pipe_through(:accepts_xml_rss_atom)
      get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
    end
  
index 5efe8ef71286b974d6213118b2e50e653ab9e64e,34b26dddfe5c34c3ae38363e8d6bc60528fc7a77..84028bdde1d4532facc30cc10c849e3411b90080
@@@ -7,22 -7,17 +7,17 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI
    use Oban.Testing, repo: Pleroma.Repo
  
    import ExUnit.CaptureLog
-   import Mock
    import Pleroma.Factory
    import Swoosh.TestAssertions
  
    alias Pleroma.Activity
    alias Pleroma.Config
-   alias Pleroma.HTML
    alias Pleroma.MFA
    alias Pleroma.ModerationLog
    alias Pleroma.Repo
    alias Pleroma.Tests.ObanHelpers
    alias Pleroma.User
-   alias Pleroma.Web
-   alias Pleroma.Web.ActivityPub.Relay
    alias Pleroma.Web.CommonAPI
-   alias Pleroma.Web.MediaProxy
  
    setup_all do
      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
      end
    end
  
-   describe "DELETE /api/pleroma/admin/users" do
-     test "single user", %{admin: admin, conn: conn} do
-       clear_config([:instance, :federating], true)
-       user =
-         insert(:user,
-           avatar: %{"url" => [%{"href" => "https://someurl"}]},
-           banner: %{"url" => [%{"href" => "https://somebanner"}]},
-           bio: "Hello world!",
-           name: "A guy"
-         )
-       # Create some activities to check they got deleted later
-       follower = insert(:user)
-       {:ok, _} = CommonAPI.post(user, %{status: "test"})
-       {:ok, _, _, _} = CommonAPI.follow(user, follower)
-       {:ok, _, _, _} = CommonAPI.follow(follower, user)
-       user = Repo.get(User, user.id)
-       assert user.note_count == 1
-       assert user.follower_count == 1
-       assert user.following_count == 1
-       refute user.deactivated
-       with_mock Pleroma.Web.Federator,
-         publish: fn _ -> nil end,
-         perform: fn _, _ -> nil end do
-         conn =
-           conn
-           |> put_req_header("accept", "application/json")
-           |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
-         ObanHelpers.perform_all()
-         assert User.get_by_nickname(user.nickname).deactivated
-         log_entry = Repo.one(ModerationLog)
-         assert ModerationLog.get_log_entry_message(log_entry) ==
-                  "@#{admin.nickname} deleted users: @#{user.nickname}"
-         assert json_response(conn, 200) == [user.nickname]
-         user = Repo.get(User, user.id)
-         assert user.deactivated
-         assert user.avatar == %{}
-         assert user.banner == %{}
-         assert user.note_count == 0
-         assert user.follower_count == 0
-         assert user.following_count == 0
-         assert user.bio == ""
-         assert user.name == nil
-         assert called(Pleroma.Web.Federator.publish(:_))
-       end
-     end
-     test "multiple users", %{admin: admin, conn: conn} do
-       user_one = insert(:user)
-       user_two = insert(:user)
-       conn =
-         conn
-         |> 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", %{conn: conn} do
-       conn =
-         conn
-         |> put_req_header("accept", "application/json")
-         |> post("/api/pleroma/admin/users", %{
-           "users" => [
-             %{
-               "nickname" => "lain",
-               "email" => "lain@example.org",
-               "password" => "test"
-             },
-             %{
-               "nickname" => "lain2",
-               "email" => "lain2@example.org",
-               "password" => "test"
-             }
-           ]
-         })
-       response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
-       assert response == ["success", "success"]
-       log_entry = Repo.one(ModerationLog)
-       assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == []
-     end
-     test "Cannot create user with existing email", %{conn: conn} do
-       user = insert(:user)
-       conn =
-         conn
-         |> put_req_header("accept", "application/json")
-         |> post("/api/pleroma/admin/users", %{
-           "users" => [
-             %{
-               "nickname" => "lain",
-               "email" => user.email,
-               "password" => "test"
-             }
-           ]
-         })
-       assert json_response(conn, 409) == [
-                %{
-                  "code" => 409,
-                  "data" => %{
-                    "email" => user.email,
-                    "nickname" => "lain"
-                  },
-                  "error" => "email has already been taken",
-                  "type" => "error"
-                }
-              ]
-     end
-     test "Cannot create user with existing nickname", %{conn: conn} do
-       user = insert(:user)
-       conn =
-         conn
-         |> put_req_header("accept", "application/json")
-         |> post("/api/pleroma/admin/users", %{
-           "users" => [
-             %{
-               "nickname" => user.nickname,
-               "email" => "someuser@plerama.social",
-               "password" => "test"
-             }
-           ]
-         })
-       assert json_response(conn, 409) == [
-                %{
-                  "code" => 409,
-                  "data" => %{
-                    "email" => "someuser@plerama.social",
-                    "nickname" => user.nickname
-                  },
-                  "error" => "nickname has already been taken",
-                  "type" => "error"
-                }
-              ]
-     end
-     test "Multiple user creation works in transaction", %{conn: conn} do
-       user = insert(:user)
-       conn =
-         conn
-         |> put_req_header("accept", "application/json")
-         |> post("/api/pleroma/admin/users", %{
-           "users" => [
-             %{
-               "nickname" => "newuser",
-               "email" => "newuser@pleroma.social",
-               "password" => "test"
-             },
-             %{
-               "nickname" => "lain",
-               "email" => user.email,
-               "password" => "test"
-             }
-           ]
-         })
-       assert json_response(conn, 409) == [
-                %{
-                  "code" => 409,
-                  "data" => %{
-                    "email" => user.email,
-                    "nickname" => "lain"
-                  },
-                  "error" => "email has already been taken",
-                  "type" => "error"
-                },
-                %{
-                  "code" => 409,
-                  "data" => %{
-                    "email" => "newuser@pleroma.social",
-                    "nickname" => "newuser"
-                  },
-                  "error" => "",
-                  "type" => "error"
-                }
-              ]
-       assert User.get_by_nickname("newuser") === nil
-     end
-   end
-   describe "/api/pleroma/admin/users/:nickname" do
-     test "Show", %{conn: conn} do
-       user = insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
-       expected = %{
-         "deactivated" => false,
-         "id" => to_string(user.id),
-         "local" => true,
-         "nickname" => user.nickname,
-         "roles" => %{"admin" => false, "moderator" => false},
-         "tags" => [],
-         "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-         "display_name" => HTML.strip_tags(user.name || user.nickname),
-         "confirmation_pending" => false,
-         "approval_pending" => false,
-         "url" => user.ap_id,
-         "registration_reason" => nil,
-         "actor_type" => "Person"
-       }
-       assert expected == json_response(conn, 200)
-     end
-     test "when the user doesn't exist", %{conn: conn} do
-       user = build(:user)
-       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
-       assert %{"error" => "Not found"} == json_response(conn, 404)
-     end
-   end
-   describe "/api/pleroma/admin/users/follow" do
-     test "allows to force-follow another user", %{admin: admin, conn: conn} do
-       user = insert(:user)
-       follower = insert(:user)
-       conn
-       |> put_req_header("accept", "application/json")
-       |> post("/api/pleroma/admin/users/follow", %{
-         "follower" => follower.nickname,
-         "followed" => user.nickname
-       })
-       user = User.get_cached_by_id(user.id)
-       follower = User.get_cached_by_id(follower.id)
-       assert User.following?(follower, user)
-       log_entry = Repo.one(ModerationLog)
-       assert ModerationLog.get_log_entry_message(log_entry) ==
-                "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}"
-     end
-   end
-   describe "/api/pleroma/admin/users/unfollow" do
-     test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
-       user = insert(:user)
-       follower = insert(:user)
-       User.follow(follower, user)
-       conn
-       |> put_req_header("accept", "application/json")
-       |> post("/api/pleroma/admin/users/unfollow", %{
-         "follower" => follower.nickname,
-         "followed" => user.nickname
-       })
-       user = User.get_cached_by_id(user.id)
-       follower = User.get_cached_by_id(follower.id)
-       refute User.following?(follower, user)
-       log_entry = Repo.one(ModerationLog)
-       assert ModerationLog.get_log_entry_message(log_entry) ==
-                "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}"
-     end
-   end
    describe "PUT /api/pleroma/admin/users/tag" do
      setup %{conn: conn} do
        user1 = insert(:user, %{tags: ["x"]})
      assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
    end
  
-   describe "GET /api/pleroma/admin/users" do
-     test "renders users array for the first page", %{conn: conn, admin: admin} do
-       user = insert(:user, local: false, tags: ["foo", "bar"])
-       user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude")
-       conn = get(conn, "/api/pleroma/admin/users?page=1")
-       users =
-         [
-           %{
-             "deactivated" => admin.deactivated,
-             "id" => admin.id,
-             "nickname" => admin.nickname,
-             "roles" => %{"admin" => true, "moderator" => false},
-             "local" => true,
-             "tags" => [],
-             "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(admin.name || admin.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => admin.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           },
-           %{
-             "deactivated" => user.deactivated,
-             "id" => user.id,
-             "nickname" => user.nickname,
-             "roles" => %{"admin" => false, "moderator" => false},
-             "local" => false,
-             "tags" => ["foo", "bar"],
-             "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(user.name || user.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => user.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           },
-           %{
-             "deactivated" => user2.deactivated,
-             "id" => user2.id,
-             "nickname" => user2.nickname,
-             "roles" => %{"admin" => false, "moderator" => false},
-             "local" => true,
-             "tags" => [],
-             "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(user2.name || user2.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => true,
-             "url" => user2.ap_id,
-             "registration_reason" => "I'm a chill dude",
-             "actor_type" => "Person"
-           }
-         ]
-         |> Enum.sort_by(& &1["nickname"])
-       assert json_response(conn, 200) == %{
-                "count" => 3,
-                "page_size" => 50,
-                "users" => users
-              }
-     end
-     test "pagination works correctly with service users", %{conn: conn} do
-       service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido")
-       insert_list(25, :user)
-       assert %{"count" => 26, "page_size" => 10, "users" => users1} =
-                conn
-                |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
-                |> json_response(200)
-       assert Enum.count(users1) == 10
-       assert service1 not in users1
-       assert %{"count" => 26, "page_size" => 10, "users" => users2} =
-                conn
-                |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
-                |> json_response(200)
-       assert Enum.count(users2) == 10
-       assert service1 not in users2
-       assert %{"count" => 26, "page_size" => 10, "users" => users3} =
-                conn
-                |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
-                |> json_response(200)
-       assert Enum.count(users3) == 6
-       assert service1 not in users3
-     end
-     test "renders empty array for the second page", %{conn: conn} do
-       insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users?page=2")
-       assert json_response(conn, 200) == %{
-                "count" => 2,
-                "page_size" => 50,
-                "users" => []
-              }
-     end
-     test "regular search", %{conn: conn} do
-       user = insert(:user, nickname: "bob")
-       conn = get(conn, "/api/pleroma/admin/users?query=bo")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => user.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user.name || user.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "search by domain", %{conn: conn} do
-       user = insert(:user, nickname: "nickname@domain.com")
-       insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => user.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user.name || user.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "search by full nickname", %{conn: conn} do
-       user = insert(:user, nickname: "nickname@domain.com")
-       insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => user.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user.name || user.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "search by display name", %{conn: conn} do
-       user = insert(:user, name: "Display name")
-       insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users?name=display")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => user.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user.name || user.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "search by email", %{conn: conn} do
-       user = insert(:user, email: "email@example.com")
-       insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => user.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user.name || user.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "regular search with page size", %{conn: conn} do
-       user = insert(:user, nickname: "aalice")
-       user2 = insert(:user, nickname: "alice")
-       conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
-       assert json_response(conn1, 200) == %{
-                "count" => 2,
-                "page_size" => 1,
-                "users" => [
-                  %{
-                    "deactivated" => user.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user.name || user.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-       conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
-       assert json_response(conn2, 200) == %{
-                "count" => 2,
-                "page_size" => 1,
-                "users" => [
-                  %{
-                    "deactivated" => user2.deactivated,
-                    "id" => user2.id,
-                    "nickname" => user2.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user2.name || user2.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user2.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "only local users" do
-       admin = insert(:user, is_admin: true, nickname: "john")
-       token = insert(:oauth_admin_token, user: admin)
-       user = insert(:user, nickname: "bob")
-       insert(:user, nickname: "bobb", local: false)
-       conn =
-         build_conn()
-         |> assign(:user, admin)
-         |> assign(:token, token)
-         |> get("/api/pleroma/admin/users?query=bo&filters=local")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => user.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user.name || user.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "only local users with no query", %{conn: conn, admin: old_admin} do
-       admin = insert(:user, is_admin: true, nickname: "john")
-       user = insert(:user, nickname: "bob")
-       insert(:user, nickname: "bobb", local: false)
-       conn = get(conn, "/api/pleroma/admin/users?filters=local")
-       users =
-         [
-           %{
-             "deactivated" => user.deactivated,
-             "id" => user.id,
-             "nickname" => user.nickname,
-             "roles" => %{"admin" => false, "moderator" => false},
-             "local" => true,
-             "tags" => [],
-             "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(user.name || user.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => user.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           },
-           %{
-             "deactivated" => admin.deactivated,
-             "id" => admin.id,
-             "nickname" => admin.nickname,
-             "roles" => %{"admin" => true, "moderator" => false},
-             "local" => true,
-             "tags" => [],
-             "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(admin.name || admin.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => admin.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           },
-           %{
-             "deactivated" => false,
-             "id" => old_admin.id,
-             "local" => true,
-             "nickname" => old_admin.nickname,
-             "roles" => %{"admin" => true, "moderator" => false},
-             "tags" => [],
-             "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => old_admin.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           }
-         ]
-         |> Enum.sort_by(& &1["nickname"])
-       assert json_response(conn, 200) == %{
-                "count" => 3,
-                "page_size" => 50,
-                "users" => users
-              }
-     end
-     test "only unapproved users", %{conn: conn} do
-       user =
-         insert(:user,
-           nickname: "sadboy",
-           approval_pending: true,
-           registration_reason: "Plz let me in!"
-         )
-       insert(:user, nickname: "happyboy", approval_pending: false)
-       conn = get(conn, "/api/pleroma/admin/users?filters=need_approval")
-       users =
-         [
-           %{
-             "deactivated" => user.deactivated,
-             "id" => user.id,
-             "nickname" => user.nickname,
-             "roles" => %{"admin" => false, "moderator" => false},
-             "local" => true,
-             "tags" => [],
-             "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(user.name || user.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => true,
-             "url" => user.ap_id,
-             "registration_reason" => "Plz let me in!",
-             "actor_type" => "Person"
-           }
-         ]
-         |> Enum.sort_by(& &1["nickname"])
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => users
-              }
-     end
-     test "load only admins", %{conn: conn, admin: admin} do
-       second_admin = insert(:user, is_admin: true)
-       insert(:user)
-       insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
-       users =
-         [
-           %{
-             "deactivated" => false,
-             "id" => admin.id,
-             "nickname" => admin.nickname,
-             "roles" => %{"admin" => true, "moderator" => false},
-             "local" => admin.local,
-             "tags" => [],
-             "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(admin.name || admin.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => admin.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           },
-           %{
-             "deactivated" => false,
-             "id" => second_admin.id,
-             "nickname" => second_admin.nickname,
-             "roles" => %{"admin" => true, "moderator" => false},
-             "local" => second_admin.local,
-             "tags" => [],
-             "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => second_admin.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           }
-         ]
-         |> Enum.sort_by(& &1["nickname"])
-       assert json_response(conn, 200) == %{
-                "count" => 2,
-                "page_size" => 50,
-                "users" => users
-              }
-     end
-     test "load only moderators", %{conn: conn} do
-       moderator = insert(:user, is_moderator: true)
-       insert(:user)
-       insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => false,
-                    "id" => moderator.id,
-                    "nickname" => moderator.nickname,
-                    "roles" => %{"admin" => false, "moderator" => true},
-                    "local" => moderator.local,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => moderator.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "load users with tags list", %{conn: conn} do
-       user1 = insert(:user, tags: ["first"])
-       user2 = insert(:user, tags: ["second"])
-       insert(:user)
-       insert(:user)
-       conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
-       users =
-         [
-           %{
-             "deactivated" => false,
-             "id" => user1.id,
-             "nickname" => user1.nickname,
-             "roles" => %{"admin" => false, "moderator" => false},
-             "local" => user1.local,
-             "tags" => ["first"],
-             "avatar" => User.avatar_url(user1) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(user1.name || user1.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => user1.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           },
-           %{
-             "deactivated" => false,
-             "id" => user2.id,
-             "nickname" => user2.nickname,
-             "roles" => %{"admin" => false, "moderator" => false},
-             "local" => user2.local,
-             "tags" => ["second"],
-             "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
-             "display_name" => HTML.strip_tags(user2.name || user2.nickname),
-             "confirmation_pending" => false,
-             "approval_pending" => false,
-             "url" => user2.ap_id,
-             "registration_reason" => nil,
-             "actor_type" => "Person"
-           }
-         ]
-         |> Enum.sort_by(& &1["nickname"])
-       assert json_response(conn, 200) == %{
-                "count" => 2,
-                "page_size" => 50,
-                "users" => users
-              }
-     end
-     test "`active` filters out users pending approval", %{token: token} do
-       insert(:user, approval_pending: true)
-       %{id: user_id} = insert(:user, approval_pending: false)
-       %{id: admin_id} = token.user
-       conn =
-         build_conn()
-         |> assign(:user, token.user)
-         |> assign(:token, token)
-         |> get("/api/pleroma/admin/users?filters=active")
-       assert %{
-                "count" => 2,
-                "page_size" => 50,
-                "users" => [
-                  %{"id" => ^admin_id},
-                  %{"id" => ^user_id}
-                ]
-              } = json_response(conn, 200)
-     end
-     test "it works with multiple filters" do
-       admin = insert(:user, nickname: "john", is_admin: true)
-       token = insert(:oauth_admin_token, user: admin)
-       user = insert(:user, nickname: "bob", local: false, deactivated: true)
-       insert(:user, nickname: "ken", local: true, deactivated: true)
-       insert(:user, nickname: "bobb", local: false, deactivated: false)
-       conn =
-         build_conn()
-         |> assign(:user, admin)
-         |> assign(:token, token)
-         |> get("/api/pleroma/admin/users?filters=deactivated,external")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => user.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => user.local,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(user.name || user.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => user.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-     test "it omits relay user", %{admin: admin, conn: conn} do
-       assert %User{} = Relay.get_actor()
-       conn = get(conn, "/api/pleroma/admin/users")
-       assert json_response(conn, 200) == %{
-                "count" => 1,
-                "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => admin.deactivated,
-                    "id" => admin.id,
-                    "nickname" => admin.nickname,
-                    "roles" => %{"admin" => true, "moderator" => false},
-                    "local" => true,
-                    "tags" => [],
-                    "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
-                    "display_name" => HTML.strip_tags(admin.name || admin.nickname),
-                    "confirmation_pending" => false,
-                    "approval_pending" => false,
-                    "url" => admin.ap_id,
-                    "registration_reason" => nil,
-                    "actor_type" => "Person"
-                  }
-                ]
-              }
-     end
-   end
-   test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
-     user_one = insert(:user, deactivated: true)
-     user_two = insert(:user, deactivated: true)
-     conn =
-       patch(
-         conn,
-         "/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", %{admin: admin, conn: conn} do
-     user_one = insert(:user, deactivated: false)
-     user_two = insert(:user, deactivated: false)
-     conn =
-       patch(
-         conn,
-         "/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/approve", %{admin: admin, conn: conn} do
-     user_one = insert(:user, approval_pending: true)
-     user_two = insert(:user, approval_pending: true)
-     conn =
-       patch(
-         conn,
-         "/api/pleroma/admin/users/approve",
-         %{nicknames: [user_one.nickname, user_two.nickname]}
-       )
-     response = json_response(conn, 200)
-     assert Enum.map(response["users"], & &1["approval_pending"]) == [false, false]
-     log_entry = Repo.one(ModerationLog)
-     assert ModerationLog.get_log_entry_message(log_entry) ==
-              "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}"
-   end
-   test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
-     user = insert(:user)
-     conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
-     assert json_response(conn, 200) ==
-              %{
-                "deactivated" => !user.deactivated,
-                "id" => user.id,
-                "nickname" => user.nickname,
-                "roles" => %{"admin" => false, "moderator" => false},
-                "local" => true,
-                "tags" => [],
-                "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                "display_name" => HTML.strip_tags(user.name || user.nickname),
-                "confirmation_pending" => false,
-                "approval_pending" => false,
-                "url" => user.ap_id,
-                "registration_reason" => nil,
-                "actor_type" => "Person"
-              }
-     log_entry = Repo.one(ModerationLog)
-     assert ModerationLog.get_log_entry_message(log_entry) ==
-              "@#{admin.nickname} deactivated users: @#{user.nickname}"
-   end
    describe "PUT disable_mfa" do
      test "returns 200 and disable 2fa", %{conn: conn} do
        user =
                 response["status_visibility"]
      end
    end
 +
 +  describe "/api/pleroma/backups" do
 +    test "it creates a backup", %{conn: conn} do
 +      admin = %{id: admin_id, nickname: admin_nickname} = insert(:user, is_admin: true)
 +      token = insert(:oauth_admin_token, user: admin)
 +      user = %{id: user_id, nickname: user_nickname} = insert(:user)
 +
 +      assert "" ==
 +               conn
 +               |> assign(:user, admin)
 +               |> assign(:token, token)
 +               |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
 +               |> json_response(200)
 +
 +      assert [backup] = Repo.all(Pleroma.User.Backup)
 +
 +      ObanHelpers.perform_all()
 +
 +      email = Pleroma.Emails.UserEmail.backup_is_ready_email(backup, admin.id)
 +
 +      assert String.contains?(email.html_body, "Admin @#{admin.nickname} requested a full backup")
 +      assert_email_sent(to: {user.name, user.email}, html_body: email.html_body)
 +
 +      log_message = "@#{admin_nickname} requested account backup for @#{user_nickname}"
 +
 +      assert [
 +               %{
 +                 data: %{
 +                   "action" => "create_backup",
 +                   "actor" => %{
 +                     "id" => ^admin_id,
 +                     "nickname" => ^admin_nickname
 +                   },
 +                   "message" => ^log_message,
 +                   "subject" => %{
 +                     "id" => ^user_id,
 +                     "nickname" => ^user_nickname
 +                   }
 +                 }
 +               }
 +             ] = Pleroma.ModerationLog |> Repo.all()
 +    end
 +
 +    test "it doesn't limit admins", %{conn: conn} do
 +      admin = insert(:user, is_admin: true)
 +      token = insert(:oauth_admin_token, user: admin)
 +      user = insert(:user)
 +
 +      assert "" ==
 +               conn
 +               |> assign(:user, admin)
 +               |> assign(:token, token)
 +               |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
 +               |> json_response(200)
 +
 +      assert [_backup] = Repo.all(Pleroma.User.Backup)
 +
 +      assert "" ==
 +               conn
 +               |> assign(:user, admin)
 +               |> assign(:token, token)
 +               |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
 +               |> json_response(200)
 +
 +      assert Repo.aggregate(Pleroma.User.Backup, :count) == 2
 +    end
 +  end
  end
  
  # Needed for testing