logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma
commit: 6c6a2dbfdd7ae4043633c3201693c5c49538ef84
parent: b531e366dfffa6b34900dd249492707bff647401
Author: kaniini <nenolod@gmail.com>
Date:   Sun,  3 Mar 2019 15:59:15 +0000

Merge branch 'feature/add-pagination-to-users-admin-api' into 'develop'

Add pagination and search to users

See merge request pleroma/pleroma!873

Diffstat:

Mdocs/Admin-API.md58++++++++++++++++++++++++++++++++++++++++++++++------------
Mlib/pleroma/user.ex87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mlib/pleroma/web/admin_api/admin_api_controller.ex65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mlib/pleroma/web/mastodon_api/mastodon_api_controller.ex6+++---
Alib/pleroma/web/mastodon_api/views/admin/account_view.ex25+++++++++++++++++++++++++
Mlib/pleroma/web/router.ex1+
Mlib/pleroma/web/twitter_api/twitter_api_controller.ex2+-
Mlib/pleroma/web/twitter_api/views/user_view.ex14--------------
Mtest/user_test.exs5+++--
Mtest/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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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