@@ -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 | |||
@@ -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(), | |||
@@ -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) | |||
@@ -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.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 |
@@ -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 | |||
@@ -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 |