Merge branch 'feature/more-user-filters' into 'develop'
Add more user filters + move search to its own module See merge request pleroma/pleroma!982
This commit is contained in:
commit
926bf114b7
|
@ -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 doesn’t exist.
|
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`
|
- Method: `GET`
|
||||||
- Params: none
|
- Params: none
|
||||||
|
|
|
@ -768,52 +768,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, "@")
|
||||||
|
@ -1067,6 +1021,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(),
|
||||||
|
|
|
@ -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"],
|
||||||
admin: admin,
|
|
||||||
local: params["local_only"] == "true",
|
|
||||||
page: page,
|
page: page,
|
||||||
page_size: page_size
|
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)
|
||||||
|
|
54
lib/pleroma/web/admin_api/search.ex
Normal file
54
lib/pleroma/web/admin_api/search.ex
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
88
test/web/admin_api/search_test.exs
Normal file
88
test/web/admin_api/search_test.exs
Normal 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
|
Loading…
Reference in a new issue