Add more user filters + move search to its own module

This commit is contained in:
Maxim Filippov 2019-03-27 03:51:59 +05:00
parent 15aa94f40f
commit 3cf7539bca
7 changed files with 256 additions and 87 deletions

View file

@ -8,10 +8,15 @@ Authentication is required and the user must be an admin.
- Method `GET` - Method `GET`
- Query Params: - Query Params:
- `query`: **string** *optional* search term - *optional* `query`: **string** search term
- `local_only`: **bool** *optional* whether to return only local users - *optional* `filters`: **string** comma-separated string of filters:
- `page`: **integer** *optional* page number - `local`: only local users
- `page_size`: **integer** *optional* number of users per page (default is `50`) - `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: - Response:
```JSON ```JSON
@ -22,7 +27,13 @@ Authentication is required and the user must be an admin.
{ {
"deactivated": bool, "deactivated": bool,
"id": integer, "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 doesnt exist. Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesnt exist.
### Get user user permission groups membership ### Get user user permission groups membership per permission group
- Method: `GET` - Method: `GET`
- Params: none - Params: none

View file

@ -772,52 +772,6 @@ def get_recipients_from_activity(%Activity{recipients: to}) do
Repo.all(query) Repo.all(query)
end 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 def search(query, resolve \\ false, for_user \\ nil) do
# Strip the beginning @ off if there is a query # Strip the beginning @ off if there is a query
query = String.trim_leading(query, "@") query = String.trim_leading(query, "@")
@ -856,7 +810,7 @@ defp boost_search_rank_query(query, for_user) do
search_rank: search_rank:
fragment( fragment(
""" """
CASE WHEN (?) THEN (?) * 1.3 CASE WHEN (?) THEN (?) * 1.3
WHEN (?) THEN (?) * 1.2 WHEN (?) THEN (?) * 1.2
WHEN (?) THEN (?) * 1.1 WHEN (?) THEN (?) * 1.1
ELSE (?) END ELSE (?) END
@ -1071,6 +1025,42 @@ def local_user_query(query \\ User) do
) )
end 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 def active_local_user_query do
from( from(
u in local_user_query(), u in local_user_query(),

View file

@ -3,17 +3,18 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AdminAPIController do defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@users_page_size 50
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.AdminAPI.Search
import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]
require Logger require Logger
@users_page_size 50
action_fallback(:errors) action_fallback(:errors)
def user_delete(conn, %{"nickname" => nickname}) do def user_delete(conn, %{"nickname" => nickname}) do
@ -63,17 +64,17 @@ def untag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
do: json_response(conn, :no_content, "") do: json_response(conn, :no_content, "")
end end
def list_users(%{assigns: %{user: admin}} = conn, params) do def list_users(conn, params) do
{page, page_size} = page_params(params) {page, page_size} = page_params(params)
filters = maybe_parse_filters(params["filters"])
with {:ok, users, count} <- search_params = %{
User.search_for_admin(%{ query: params["query"],
query: params["query"], page: page,
admin: admin, page_size: page_size
local: params["local_only"] == "true", }
page: page,
page_size: page_size with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
}),
do: do:
conn conn
|> json( |> json(
@ -85,6 +86,19 @@ def list_users(%{assigns: %{user: admin}} = conn, params) do
) )
end 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}) def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
when permission_group in ["moderator", "admin"] do when permission_group in ["moderator", "admin"] do
user = User.get_by_nickname(nickname) user = User.get_by_nickname(nickname)

View file

@ -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

View file

@ -8,6 +8,7 @@ defmodule Pleroma.UserTest do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
@ -1107,21 +1108,4 @@ test "bookmarks" do
assert {:ok, user_state3} = User.bookmark(user, id2) assert {:ok, user_state3} = User.bookmark(user, id2)
assert user_state3.bookmarks == [id2] assert user_state3.bookmarks == [id2]
end 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 end

View file

@ -408,13 +408,13 @@ test "regular search" do
test "regular search with page size" do test "regular search with page size" do
admin = insert(:user, info: %{is_admin: true}) admin = insert(:user, info: %{is_admin: true})
user = insert(:user, nickname: "bob") user = insert(:user, nickname: "aalice")
user2 = insert(:user, nickname: "bo") user2 = insert(:user, nickname: "alice")
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> 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) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
@ -434,7 +434,7 @@ test "regular search with page size" do
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> 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) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
@ -461,7 +461,7 @@ test "only local users" do
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> 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) == %{ assert json_response(conn, 200) == %{
"count" => 1, "count" => 1,
@ -488,7 +488,7 @@ test "only local users with no query" do
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> assign(:user, admin)
|> get("/api/pleroma/admin/users?local_only=true") |> get("/api/pleroma/admin/users?filters=local")
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
@ -513,6 +513,34 @@ test "only local users with no query" do
] ]
} }
end 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 end
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do

View file

@ -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