Add more user filters + move search to its own module
authorMaxim Filippov <colixer@gmail.com>
Tue, 26 Mar 2019 22:51:59 +0000 (03:51 +0500)
committerMaxim Filippov <colixer@gmail.com>
Tue, 26 Mar 2019 22:51:59 +0000 (03:51 +0500)
docs/Admin-API.md
lib/pleroma/user.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/search.ex [new file with mode: 0644]
test/user_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/admin_api/search_test.exs [new file with mode: 0644]

index 2edb31f3c2e71c6ac4b4a63083e386960c3cc80f..84adca6ff335df38077ade0c73500bc0f6270df3 100644 (file)
@@ -8,10 +8,15 @@ Authentication is required and the user must be an admin.
 
 - Method `GET`
 - Query Params:
-  - `query`: **string** *optional* search term
-  - `local_only`: **bool** *optional* whether to return only local users
-  - `page`: **integer** *optional* page number
-  - `page_size`: **integer** *optional* number of users per page (default is `50`)
+  - *optional* `query`: **string** search term
+  - *optional* `filters`: **string** comma-separated string of filters:
+    - `local`: only local users
+    - `external`: only external users
+    - `active`: only active users
+    - `deactivated`: only deactivated users
+  - *optional* `page`: **integer** page number
+  - *optional* `page_size`: **integer** number of users per page (default is `50`)
+- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10`
 - Response:
 
 ```JSON
@@ -22,7 +27,13 @@ Authentication is required and the user must be an admin.
     {
       "deactivated": bool,
       "id": integer,
-      "nickname": string
+      "nickname": string,
+      "roles": {
+        "admin": bool,
+        "moderator": bool
+      },
+      "local": bool,
+      "tags": array
     },
     ...
   ]
@@ -99,7 +110,7 @@ Authentication is required and the user must be an admin.
 
 Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesn’t exist.
 
-### Get user user permission groups membership
+### Get user user permission groups membership per permission group
 
 - Method: `GET`
 - Params: none
index 41289b4d0591cb6cad4b07d768075726ba95d4b2..495f9bdf517b9c86288986bd60734ac74fca92d2 100644 (file)
@@ -772,52 +772,6 @@ defmodule Pleroma.User do
     Repo.all(query)
   end
 
-  @spec search_for_admin(%{
-          local: boolean(),
-          page: number(),
-          page_size: number()
-        }) :: {:ok, [Pleroma.User.t()], number()}
-  def search_for_admin(%{query: nil, local: local, page: page, page_size: page_size}) do
-    query =
-      from(u in User, order_by: u.nickname)
-      |> maybe_local_user_query(local)
-
-    paginated_query =
-      query
-      |> paginate(page, page_size)
-
-    count =
-      query
-      |> Repo.aggregate(:count, :id)
-
-    {:ok, Repo.all(paginated_query), count}
-  end
-
-  @spec search_for_admin(%{
-          query: binary(),
-          local: boolean(),
-          page: number(),
-          page_size: number()
-        }) :: {:ok, [Pleroma.User.t()], number()}
-  def search_for_admin(%{
-        query: term,
-        local: local,
-        page: page,
-        page_size: page_size
-      }) do
-    maybe_local_query = User |> maybe_local_user_query(local)
-
-    search_query = from(u in maybe_local_query, where: ilike(u.nickname, ^"%#{term}%"))
-    count = search_query |> Repo.aggregate(:count, :id)
-
-    results =
-      search_query
-      |> paginate(page, page_size)
-      |> Repo.all()
-
-    {:ok, results, count}
-  end
-
   def search(query, resolve \\ false, for_user \\ nil) do
     # Strip the beginning @ off if there is a query
     query = String.trim_leading(query, "@")
@@ -856,7 +810,7 @@ defmodule Pleroma.User do
         search_rank:
           fragment(
             """
-             CASE WHEN (?) THEN (?) * 1.3 
+             CASE WHEN (?) THEN (?) * 1.3
              WHEN (?) THEN (?) * 1.2
              WHEN (?) THEN (?) * 1.1
              ELSE (?) END
@@ -1071,6 +1025,42 @@ defmodule Pleroma.User do
     )
   end
 
+  def maybe_external_user_query(query, external) do
+    if external, do: external_user_query(query), else: query
+  end
+
+  def external_user_query(query \\ User) do
+    from(
+      u in query,
+      where: u.local == false,
+      where: not is_nil(u.nickname)
+    )
+  end
+
+  def maybe_active_user_query(query, active) do
+    if active, do: active_user_query(query), else: query
+  end
+
+  def active_user_query(query \\ User) do
+    from(
+      u in query,
+      where: fragment("not (?->'deactivated' @> 'true')", u.info),
+      where: not is_nil(u.nickname)
+    )
+  end
+
+  def maybe_deactivated_user_query(query, deactivated) do
+    if deactivated, do: deactivated_user_query(query), else: query
+  end
+
+  def deactivated_user_query(query \\ User) do
+    from(
+      u in query,
+      where: fragment("(?->'deactivated' @> 'true')", u.info),
+      where: not is_nil(u.nickname)
+    )
+  end
+
   def active_local_user_query do
     from(
       u in local_user_query(),
index 6d9bf289502f617ea40adffc3a3278fa43e616cc..3fa9c69097a8549b572e4f2e5b0e4fb90f8cb607 100644 (file)
@@ -3,17 +3,18 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
-  @users_page_size 50
-
   use Pleroma.Web, :controller
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.AdminAPI.AccountView
+  alias Pleroma.Web.AdminAPI.Search
 
   import Pleroma.Web.ControllerHelper, only: [json_response: 3]
 
   require Logger
 
+  @users_page_size 50
+
   action_fallback(:errors)
 
   def user_delete(conn, %{"nickname" => nickname}) do
@@ -63,17 +64,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
          do: json_response(conn, :no_content, "")
   end
 
-  def list_users(%{assigns: %{user: admin}} = conn, params) do
+  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
+    }
 
-    with {:ok, users, count} <-
-           User.search_for_admin(%{
-             query: params["query"],
-             admin: admin,
-             local: params["local_only"] == "true",
-             page: page,
-             page_size: page_size
-           }),
+    with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
          do:
            conn
            |> json(
@@ -85,6 +86,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
            )
   end
 
+  @filters ~w(local external active deactivated)
+
+  defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
+
+  @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
+  defp maybe_parse_filters(filters) do
+    filters
+    |> String.split(",")
+    |> Enum.filter(&Enum.member?(@filters, &1))
+    |> Enum.map(&String.to_atom(&1))
+    |> Enum.into(%{}, &{&1, true})
+  end
+
   def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
       when permission_group in ["moderator", "admin"] do
     user = User.get_by_nickname(nickname)
diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex
new file mode 100644 (file)
index 0000000..9a8e41c
--- /dev/null
@@ -0,0 +1,54 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.Search do
+  import Ecto.Query
+
+  alias Pleroma.Repo
+  alias Pleroma.User
+
+  @page_size 50
+
+  def user(%{query: term} = params) when is_nil(term) or term == "" do
+    query = maybe_filtered_query(params)
+
+    paginated_query =
+      maybe_filtered_query(params)
+      |> paginate(params[:page] || 1, params[:page_size] || @page_size)
+
+    count = query |> Repo.aggregate(:count, :id)
+
+    results = Repo.all(paginated_query)
+
+    {:ok, results, count}
+  end
+
+  def user(%{query: term} = params) when is_binary(term) do
+    search_query = from(u in maybe_filtered_query(params), where: ilike(u.nickname, ^"%#{term}%"))
+
+    count = search_query |> Repo.aggregate(:count, :id)
+
+    results =
+      search_query
+      |> paginate(params[:page] || 1, params[:page_size] || @page_size)
+      |> Repo.all()
+
+    {:ok, results, count}
+  end
+
+  defp maybe_filtered_query(params) do
+    from(u in User, order_by: u.nickname)
+    |> User.maybe_local_user_query(params[:local])
+    |> User.maybe_external_user_query(params[:external])
+    |> User.maybe_active_user_query(params[:active])
+    |> User.maybe_deactivated_user_query(params[:deactivated])
+  end
+
+  defp paginate(query, page, page_size) do
+    from(u in query,
+      limit: ^page_size,
+      offset: ^((page - 1) * page_size)
+    )
+  end
+end
index 442599910bff55b2bbc27ad7c33ed26f985e3741..8cf2ba6ab1d5251c6b71ccb783853e9d7f43564d 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.UserTest do
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
+
   use Pleroma.DataCase
 
   import Pleroma.Factory
@@ -1107,21 +1108,4 @@ defmodule Pleroma.UserTest do
     assert {:ok, user_state3} = User.bookmark(user, id2)
     assert user_state3.bookmarks == [id2]
   end
-
-  describe "search for admin" do
-    test "it ignores case" do
-      insert(:user, nickname: "papercoach")
-      insert(:user, nickname: "CanadaPaperCoach")
-
-      {:ok, _results, count} =
-        User.search_for_admin(%{
-          query: "paper",
-          local: false,
-          page: 1,
-          page_size: 50
-        })
-
-      assert count == 2
-    end
-  end
 end
index 0aab7f26257490fd612317491323587458c77a26..7da237ecac99068ee1087e50c2cde17f7e008d87 100644 (file)
@@ -408,13 +408,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
     test "regular search with page size" do
       admin = insert(:user, info: %{is_admin: true})
-      user = insert(:user, nickname: "bob")
-      user2 = insert(:user, nickname: "bo")
+      user = insert(:user, nickname: "aalice")
+      user2 = insert(:user, nickname: "alice")
 
       conn =
         build_conn()
         |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?query=bo&page_size=1&page=1")
+        |> get("/api/pleroma/admin/users?query=a&page_size=1&page=1")
 
       assert json_response(conn, 200) == %{
                "count" => 2,
@@ -434,7 +434,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn =
         build_conn()
         |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?query=bo&page_size=1&page=2")
+        |> get("/api/pleroma/admin/users?query=a&page_size=1&page=2")
 
       assert json_response(conn, 200) == %{
                "count" => 2,
@@ -461,7 +461,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn =
         build_conn()
         |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?query=bo&local_only=true")
+        |> get("/api/pleroma/admin/users?query=bo&filters=local")
 
       assert json_response(conn, 200) == %{
                "count" => 1,
@@ -488,7 +488,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn =
         build_conn()
         |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?local_only=true")
+        |> get("/api/pleroma/admin/users?filters=local")
 
       assert json_response(conn, 200) == %{
                "count" => 2,
@@ -513,6 +513,34 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                ]
              }
     end
+
+    test "it works with multiple filters" do
+      admin = insert(:user, nickname: "john", info: %{is_admin: true})
+      user = insert(:user, nickname: "bob", local: false, info: %{deactivated: true})
+
+      insert(:user, nickname: "ken", local: true, info: %{deactivated: true})
+      insert(:user, nickname: "bobb", local: false, info: %{deactivated: false})
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+        |> get("/api/pleroma/admin/users?filters=deactivated,external")
+
+      assert json_response(conn, 200) == %{
+               "count" => 1,
+               "page_size" => 50,
+               "users" => [
+                 %{
+                   "deactivated" => user.info.deactivated,
+                   "id" => user.id,
+                   "nickname" => user.nickname,
+                   "roles" => %{"admin" => false, "moderator" => false},
+                   "local" => user.local,
+                   "tags" => []
+                 }
+               ]
+             }
+    end
   end
 
   test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do
diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs
new file mode 100644 (file)
index 0000000..3950996
--- /dev/null
@@ -0,0 +1,88 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.SearchTest do
+  use Pleroma.Web.ConnCase
+
+  alias Pleroma.Web.AdminAPI.Search
+
+  import Pleroma.Factory
+
+  describe "search for admin" do
+    test "it ignores case" do
+      insert(:user, nickname: "papercoach")
+      insert(:user, nickname: "CanadaPaperCoach")
+
+      {:ok, _results, count} =
+        Search.user(%{
+          query: "paper",
+          local: false,
+          page: 1,
+          page_size: 50
+        })
+
+      assert count == 2
+    end
+
+    test "it returns local/external users" do
+      insert(:user, local: true)
+      insert(:user, local: false)
+      insert(:user, local: false)
+
+      {:ok, _results, local_count} =
+        Search.user(%{
+          query: "",
+          local: true
+        })
+
+      {:ok, _results, external_count} =
+        Search.user(%{
+          query: "",
+          external: true
+        })
+
+      assert local_count == 1
+      assert external_count == 2
+    end
+
+    test "it returns active/deactivated users" do
+      insert(:user, info: %{deactivated: true})
+      insert(:user, info: %{deactivated: true})
+      insert(:user, info: %{deactivated: false})
+
+      {:ok, _results, active_count} =
+        Search.user(%{
+          query: "",
+          active: true
+        })
+
+      {:ok, _results, deactivated_count} =
+        Search.user(%{
+          query: "",
+          deactivated: true
+        })
+
+      assert active_count == 1
+      assert deactivated_count == 2
+    end
+
+    test "it returns specific user" do
+      insert(:user)
+      insert(:user)
+      insert(:user, nickname: "bob", local: true, info: %{deactivated: false})
+
+      {:ok, _results, total_count} = Search.user(%{query: ""})
+
+      {:ok, _results, count} =
+        Search.user(%{
+          query: "Bo",
+          active: true,
+          local: true
+        })
+
+      assert total_count == 3
+      assert count == 1
+    end
+  end
+end