summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/Admin-API.md58
-rw-r--r--lib/pleroma/user.ex87
-rw-r--r--lib/pleroma/web/admin_api/admin_api_controller.ex65
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/admin/account_view.ex25
-rw-r--r--lib/pleroma/web/router.ex1
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api_controller.ex2
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex14
-rw-r--r--test/user_test.exs5
-rw-r--r--test/web/admin_api/admin_api_controller_test.exs141
10 files changed, 329 insertions, 75 deletions
diff --git a/docs/Admin-API.md b/docs/Admin-API.md
index 508981d38..407647645 100644
--- a/docs/Admin-API.md
+++ b/docs/Admin-API.md
@@ -7,17 +7,51 @@ Authentication is required and the user must be an admin.
### List users
- Method `GET`
+- Params:
+ - `page`: **integer** page number
+ - `page_size`: **integer** number of users per page (default is `50`)
- Response:
```JSON
-[
+{
+ "page_size": integer,
+ "count": integer,
+ "users": [
{
- "deactivated": bool,
- "id": integer,
- "nickname": string
+ "deactivated": bool,
+ "id": integer,
+ "nickname": string
},
...
-]
+ ]
+}
+```
+
+## `/api/pleroma/admin/users/search?query={query}&local={local}&page={page}&page_size={page_size}`
+
+### Search users by name or nickname
+
+- Method `GET`
+- Params:
+ - `query`: **string** search term
+ - `local`: **bool** whether to return only local users
+ - `page`: **integer** page number
+ - `page_size`: **integer** number of users per page (default is `50`)
+- Response:
+
+```JSON
+{
+ "page_size": integer,
+ "count": integer,
+ "users": [
+ {
+ "deactivated": bool,
+ "id": integer,
+ "nickname": string
+ },
+ ...
+ ]
+}
```
## `/api/pleroma/admin/user`
@@ -49,9 +83,9 @@ Authentication is required and the user must be an admin.
```JSON
{
- "deactivated": bool,
- "id": integer,
- "nickname": string
+ "deactivated": bool,
+ "id": integer,
+ "nickname": string
}
```
@@ -81,8 +115,8 @@ Authentication is required and the user must be an admin.
```JSON
{
- "is_moderator": bool,
- "is_admin": bool
+ "is_moderator": bool,
+ "is_admin": bool
}
```
@@ -98,8 +132,8 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
```JSON
{
- "is_moderator": bool,
- "is_admin": bool
+ "is_moderator": bool,
+ "is_admin": bool
}
```
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index d69ca094e..78543b426 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -547,11 +547,8 @@ defmodule Pleroma.User do
end
def get_followers_query(user, page) do
- from(
- u in get_followers_query(user, nil),
- limit: 20,
- offset: ^((page - 1) * 20)
- )
+ from(u in get_followers_query(user, nil))
+ |> paginate(page, 20)
end
def get_followers_query(user), do: get_followers_query(user, nil)
@@ -577,11 +574,8 @@ defmodule Pleroma.User do
end
def get_friends_query(user, page) do
- from(
- u in get_friends_query(user, nil),
- limit: 20,
- offset: ^((page - 1) * 20)
- )
+ from(u in get_friends_query(user, nil))
+ |> paginate(page, 20)
end
def get_friends_query(user), do: get_friends_query(user, nil)
@@ -754,6 +748,46 @@ defmodule Pleroma.User do
Repo.all(query)
end
+ @spec search_for_admin(binary(), %{
+ admin: Pleroma.User.t(),
+ local: boolean(),
+ page: number(),
+ page_size: number()
+ }) :: {:ok, [Pleroma.User.t()], number()}
+ def search_for_admin(term, %{admin: admin, local: local, page: page, page_size: page_size}) do
+ term = String.trim_leading(term, "@")
+
+ local_paginated_query =
+ User
+ |> maybe_local_user_query(local)
+ |> paginate(page, page_size)
+
+ search_query = fts_search_subquery(term, local_paginated_query)
+
+ count =
+ term
+ |> fts_search_subquery()
+ |> maybe_local_user_query(local)
+ |> Repo.aggregate(:count, :id)
+
+ {:ok, do_search(search_query, admin), count}
+ end
+
+ @spec all_for_admin(number(), number()) :: {:ok, [Pleroma.User.t()], number()}
+ def all_for_admin(page, page_size) do
+ query = from(u in User, order_by: u.id)
+
+ paginated_query =
+ query
+ |> paginate(page, page_size)
+
+ count =
+ query
+ |> Repo.aggregate(:count, :id)
+
+ {:ok, Repo.all(paginated_query), 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, "@")
@@ -771,12 +805,6 @@ defmodule Pleroma.User do
Enum.uniq_by(fts_results ++ trigram_results, & &1.id)
end
- def all_except_one(user) do
- query = from(u in User, where: u.id != ^user.id)
-
- Repo.all(query)
- end
-
defp do_search(subquery, for_user, options \\ []) do
q =
from(
@@ -793,9 +821,9 @@ defmodule Pleroma.User do
boost_search_results(results, for_user)
end
- defp fts_search_subquery(query) do
+ defp fts_search_subquery(term, query \\ User) do
processed_query =
- query
+ term
|> String.replace(~r/\W+/, " ")
|> String.trim()
|> String.split()
@@ -803,7 +831,7 @@ defmodule Pleroma.User do
|> Enum.join(" | ")
from(
- u in User,
+ u in query,
select_merge: %{
search_rank:
fragment(
@@ -833,19 +861,19 @@ defmodule Pleroma.User do
)
end
- defp trigram_search_subquery(query) do
+ defp trigram_search_subquery(term) do
from(
u in User,
select_merge: %{
search_rank:
fragment(
"similarity(?, trim(? || ' ' || coalesce(?, '')))",
- ^query,
+ ^term,
u.nickname,
u.name
)
},
- where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^query)
+ where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
)
end
@@ -1003,9 +1031,13 @@ defmodule Pleroma.User do
update_and_set_cache(cng)
end
- def local_user_query do
+ def maybe_local_user_query(query, local) do
+ if local, do: local_user_query(query), else: query
+ end
+
+ def local_user_query(query \\ User) do
from(
- u in User,
+ u in query,
where: u.local == true,
where: not is_nil(u.nickname)
)
@@ -1297,4 +1329,11 @@ defmodule Pleroma.User do
)
|> Repo.all()
end
+
+ defp paginate(query, page, page_size) do
+ from(u in query,
+ limit: ^page_size,
+ offset: ^((page - 1) * page_size)
+ )
+ end
end
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index ef72509fe..aae02cab8 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -3,10 +3,12 @@
# 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.TwitterAPI.UserView
+ alias Pleroma.Web.MastodonAPI.Admin.AccountView
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
@@ -48,7 +50,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
{:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
conn
- |> json(UserView.render("show_for_admin.json", %{user: updated_user}))
+ |> json(AccountView.render("show.json", %{user: updated_user}))
end
def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
@@ -61,11 +63,40 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
do: json_response(conn, :no_content, "")
end
- def list_users(%{assigns: %{user: admin}} = conn, _data) do
- users = User.all_except_one(admin)
+ def list_users(conn, params) do
+ {page, page_size} = page_params(params)
+
+ with {:ok, users, count} <- User.all_for_admin(page, page_size),
+ do:
+ conn
+ |> json(
+ AccountView.render("index.json",
+ users: users,
+ count: count,
+ page_size: page_size
+ )
+ )
+ end
- conn
- |> json(UserView.render("index_for_admin.json", %{users: users}))
+ def search_users(%{assigns: %{user: admin}} = conn, %{"query" => query} = params) do
+ {page, page_size} = page_params(params)
+
+ with {:ok, users, count} <-
+ User.search_for_admin(query, %{
+ admin: admin,
+ local: params["local"] == "true",
+ page: page,
+ page_size: page_size
+ }),
+ do:
+ conn
+ |> json(
+ AccountView.render("index.json",
+ users: users,
+ count: count,
+ page_size: page_size
+ )
+ )
end
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
@@ -211,4 +242,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> put_status(500)
|> json("Something went wrong")
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
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 12987442a..056be49b0 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -894,7 +894,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- accounts = User.search(query, params["resolve"] == "true", user)
+ accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
statuses = status_search(user, query)
@@ -919,7 +919,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- accounts = User.search(query, params["resolve"] == "true", user)
+ accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
statuses = status_search(user, query)
@@ -941,7 +941,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- accounts = User.search(query, params["resolve"] == "true", user)
+ accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
diff --git a/lib/pleroma/web/mastodon_api/views/admin/account_view.ex b/lib/pleroma/web/mastodon_api/views/admin/account_view.ex
new file mode 100644
index 000000000..74ca13564
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/admin/account_view.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.Admin.AccountView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.Web.MastodonAPI.Admin.AccountView
+
+ def render("index.json", %{users: users, count: count, page_size: page_size}) do
+ %{
+ users: render_many(users, AccountView, "show.json", as: :user),
+ count: count,
+ page_size: page_size
+ }
+ end
+
+ def render("show.json", %{user: user}) do
+ %{
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "deactivated" => user.info.deactivated
+ }
+ end
+end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 3b1fd46a5..6fcb46878 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -140,6 +140,7 @@ defmodule Pleroma.Web.Router do
pipe_through([:admin_api, :oauth_write])
get("/users", AdminAPIController, :list_users)
+ get("/users/search", AdminAPIController, :search_users)
delete("/user", AdminAPIController, :user_delete)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
post("/user", AdminAPIController, :user_create)
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 41e3acc60..de7b9f24c 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -703,7 +703,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
- users = User.search(query, true, user)
+ users = User.search(query, resolve: true, for_user: user)
conn
|> put_view(UserView)
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index e72ce977c..0791ed760 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -9,7 +9,6 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
- alias Pleroma.Web.TwitterAPI.UserView
def render("show.json", %{user: user = %User{}} = assigns) do
render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns)
@@ -27,19 +26,6 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
else: %{}
end
- def render("index_for_admin.json", %{users: users} = opts) do
- users
- |> render_many(UserView, "show_for_admin.json", opts)
- end
-
- def render("show_for_admin.json", %{user: user}) do
- %{
- "id" => user.id,
- "nickname" => user.nickname,
- "deactivated" => user.info.deactivated
- }
- end
-
def render("short.json", %{
user: %User{
nickname: nickname,
diff --git a/test/user_test.exs b/test/user_test.exs
index b8d41ecfd..390e3ef13 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -929,7 +929,8 @@ defmodule Pleroma.UserTest do
{:ok, follower} = User.follow(follower, u1)
{:ok, u1} = User.follow(u1, friend)
- assert [friend.id, follower.id, u2.id] == Enum.map(User.search("doe", false, u1), & &1.id)
+ assert [friend.id, follower.id, u2.id] --
+ Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
end
test "finds a user whose name is nil" do
@@ -951,7 +952,7 @@ defmodule Pleroma.UserTest do
end
test "works with URIs" do
- results = User.search("http://mastodon.example.org/users/admin", true)
+ results = User.search("http://mastodon.example.org/users/admin", resolve: true)
result = results |> List.first()
user = User.get_by_ap_id("http://mastodon.example.org/users/admin")
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index f6ae16844..42e0daf8e 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -331,22 +331,49 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
assert conn.status == 200
end
- test "GET /api/pleroma/admin/users" do
- admin = insert(:user, info: %{is_admin: true})
- user = insert(:user)
+ describe "GET /api/pleroma/admin/users" do
+ test "renders users array for the first page" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user)
- conn =
- build_conn()
- |> assign(:user, admin)
- |> get("/api/pleroma/admin/users")
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?page=1")
- assert json_response(conn, 200) == [
- %{
- "deactivated" => user.info.deactivated,
- "id" => user.id,
- "nickname" => user.nickname
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => admin.info.deactivated,
+ "id" => admin.id,
+ "nickname" => admin.nickname
+ },
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname
+ }
+ ]
+ }
+ end
+
+ test "renders empty array for the second page" do
+ admin = insert(:user, info: %{is_admin: true})
+ insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users?page=2")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => []
}
- ]
+ end
end
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do
@@ -365,4 +392,92 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"nickname" => user.nickname
}
end
+
+ describe "GET /api/pleroma/admin/users/search" do
+ test "regular search" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user, nickname: "bob")
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users/search?query=bo")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname
+ }
+ ]
+ }
+ end
+
+ test "regular search with page size" do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user, nickname: "bob")
+ user2 = insert(:user, nickname: "bo")
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users/search?query=bo&page_size=1&page=1")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 1,
+ "users" => [
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname
+ }
+ ]
+ }
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users/search?query=bo&page_size=1&page=2")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 1,
+ "users" => [
+ %{
+ "deactivated" => user2.info.deactivated,
+ "id" => user2.id,
+ "nickname" => user2.nickname
+ }
+ ]
+ }
+ end
+
+ test "only local users" do
+ admin = insert(:user, info: %{is_admin: true}, nickname: "john")
+ user = insert(:user, nickname: "bob")
+
+ insert(:user, nickname: "bobb", local: false)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users/search?query=bo&local=true")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.info.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname
+ }
+ ]
+ }
+ end
+ end
end