1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
6 use Pleroma.Web, :controller
8 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
10 alias Pleroma.Activity
12 alias Pleroma.ConfigDB
13 alias Pleroma.ModerationLog
14 alias Pleroma.Plugs.OAuthScopesPlug
15 alias Pleroma.ReportNote
18 alias Pleroma.UserInviteToken
19 alias Pleroma.Web.ActivityPub.ActivityPub
20 alias Pleroma.Web.ActivityPub.Relay
21 alias Pleroma.Web.ActivityPub.Utils
22 alias Pleroma.Web.AdminAPI.AccountView
23 alias Pleroma.Web.AdminAPI.ConfigView
24 alias Pleroma.Web.AdminAPI.ModerationLogView
25 alias Pleroma.Web.AdminAPI.Report
26 alias Pleroma.Web.AdminAPI.ReportView
27 alias Pleroma.Web.AdminAPI.Search
28 alias Pleroma.Web.CommonAPI
29 alias Pleroma.Web.Endpoint
30 alias Pleroma.Web.MastodonAPI.AppView
31 alias Pleroma.Web.MastodonAPI.StatusView
32 alias Pleroma.Web.OAuth.App
33 alias Pleroma.Web.Router
37 @descriptions_json Pleroma.Docs.JSON.compile()
42 %{scopes: ["read:accounts"], admin: true}
43 when action in [:list_users, :user_show, :right_get]
48 %{scopes: ["write:accounts"], admin: true}
53 :user_toggle_activation,
63 plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
67 %{scopes: ["write:invites"], admin: true}
68 when action in [:create_invite_token, :revoke_invite, :email_invite]
73 %{scopes: ["write:follows"], admin: true}
74 when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
79 %{scopes: ["read:reports"], admin: true}
80 when action in [:list_reports, :report_show]
85 %{scopes: ["write:reports"], admin: true}
86 when action in [:reports_update]
91 %{scopes: ["read:statuses"], admin: true}
92 when action == :list_user_statuses
97 %{scopes: ["write:statuses"], admin: true}
98 when action in [:status_update, :status_delete]
103 %{scopes: ["read"], admin: true}
104 when action in [:config_show, :list_log, :stats]
109 %{scopes: ["write"], admin: true}
110 when action == :config_update
113 action_fallback(:errors)
115 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
116 user = User.get_cached_by_nickname(nickname)
119 ModerationLog.insert_log(%{
129 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
130 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
133 ModerationLog.insert_log(%{
143 def user_follow(%{assigns: %{user: admin}} = conn, %{
144 "follower" => follower_nick,
145 "followed" => followed_nick
147 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
148 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
149 User.follow(follower, followed)
151 ModerationLog.insert_log(%{
163 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
164 "follower" => follower_nick,
165 "followed" => followed_nick
167 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
168 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
169 User.unfollow(follower, followed)
171 ModerationLog.insert_log(%{
183 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
185 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
191 password_confirmation: password,
195 User.register_changeset(%User{}, user_data, need_confirmation: false)
197 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
198 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
201 case Pleroma.Repo.transaction(changesets) do
206 |> Enum.map(fn user ->
207 {:ok, user} = User.post_register_action(user)
211 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
213 ModerationLog.insert_log(%{
215 subjects: Map.values(users),
222 {:error, id, changeset, _} ->
224 Enum.map(changesets.operations, fn
225 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
226 AccountView.render("create-error.json", %{changeset: changeset})
228 {_, {:changeset, current_changeset, _}} ->
229 AccountView.render("create-error.json", %{changeset: current_changeset})
233 |> put_status(:conflict)
238 def user_show(conn, %{"nickname" => nickname}) do
239 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
241 |> put_view(AccountView)
242 |> render("show.json", %{user: user})
244 _ -> {:error, :not_found}
248 def list_instance_statuses(conn, %{"instance" => instance} = params) do
249 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
250 {page, page_size} = page_params(params)
253 ActivityPub.fetch_statuses(nil, %{
254 "instance" => instance,
255 "limit" => page_size,
256 "offset" => (page - 1) * page_size,
257 "exclude_reblogs" => !with_reblogs && "true"
261 |> put_view(Pleroma.Web.AdminAPI.StatusView)
262 |> render("index.json", %{activities: activities, as: :activity})
265 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
266 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
267 godmode = params["godmode"] == "true" || params["godmode"] == true
269 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
270 {_, page_size} = page_params(params)
273 ActivityPub.fetch_user_activities(user, nil, %{
274 "limit" => page_size,
275 "godmode" => godmode,
276 "exclude_reblogs" => !with_reblogs && "true"
280 |> put_view(StatusView)
281 |> render("index.json", %{activities: activities, as: :activity})
283 _ -> {:error, :not_found}
287 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
288 user = User.get_cached_by_nickname(nickname)
290 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
292 action = if user.deactivated, do: "activate", else: "deactivate"
294 ModerationLog.insert_log(%{
301 |> put_view(AccountView)
302 |> render("show.json", %{user: updated_user})
305 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
306 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
307 {:ok, updated_users} = User.deactivate(users, false)
309 ModerationLog.insert_log(%{
316 |> put_view(AccountView)
317 |> render("index.json", %{users: Keyword.values(updated_users)})
320 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
321 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
322 {:ok, updated_users} = User.deactivate(users, true)
324 ModerationLog.insert_log(%{
331 |> put_view(AccountView)
332 |> render("index.json", %{users: Keyword.values(updated_users)})
335 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
336 with {:ok, _} <- User.tag(nicknames, tags) do
337 ModerationLog.insert_log(%{
339 nicknames: nicknames,
344 json_response(conn, :no_content, "")
348 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
349 with {:ok, _} <- User.untag(nicknames, tags) do
350 ModerationLog.insert_log(%{
352 nicknames: nicknames,
357 json_response(conn, :no_content, "")
361 def list_users(conn, params) do
362 {page, page_size} = page_params(params)
363 filters = maybe_parse_filters(params["filters"])
366 query: params["query"],
368 page_size: page_size,
369 tags: params["tags"],
370 name: params["name"],
371 email: params["email"]
374 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
375 {:ok, users, count} <- filter_service_users(users, count),
379 AccountView.render("index.json",
387 defp filter_service_users(users, count) do
388 filtered_users = Enum.reject(users, &service_user?/1)
389 count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
391 {:ok, filtered_users, count}
394 defp service_user?(user) do
395 String.match?(user.ap_id, ~r/.*\/relay$/) or
396 String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
399 @filters ~w(local external active deactivated is_admin is_moderator)
401 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
402 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
404 defp maybe_parse_filters(filters) do
407 |> Enum.filter(&Enum.member?(@filters, &1))
408 |> Enum.map(&String.to_atom(&1))
409 |> Enum.into(%{}, &{&1, true})
412 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
413 "permission_group" => permission_group,
414 "nicknames" => nicknames
416 when permission_group in ["moderator", "admin"] do
417 update = %{:"is_#{permission_group}" => true}
419 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
421 for u <- users, do: User.admin_api_update(u, update)
423 ModerationLog.insert_log(%{
427 permission: permission_group
433 def right_add_multiple(conn, _) do
434 render_error(conn, :not_found, "No such permission_group")
437 def right_add(%{assigns: %{user: admin}} = conn, %{
438 "permission_group" => permission_group,
439 "nickname" => nickname
441 when permission_group in ["moderator", "admin"] do
442 fields = %{:"is_#{permission_group}" => true}
446 |> User.get_cached_by_nickname()
447 |> User.admin_api_update(fields)
449 ModerationLog.insert_log(%{
453 permission: permission_group
459 def right_add(conn, _) do
460 render_error(conn, :not_found, "No such permission_group")
463 def right_get(conn, %{"nickname" => nickname}) do
464 user = User.get_cached_by_nickname(nickname)
468 is_moderator: user.is_moderator,
469 is_admin: user.is_admin
473 def right_delete_multiple(
474 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
476 "permission_group" => permission_group,
477 "nicknames" => nicknames
480 when permission_group in ["moderator", "admin"] do
481 with false <- Enum.member?(nicknames, admin_nickname) do
482 update = %{:"is_#{permission_group}" => false}
484 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
486 for u <- users, do: User.admin_api_update(u, update)
488 ModerationLog.insert_log(%{
492 permission: permission_group
497 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
501 def right_delete_multiple(conn, _) do
502 render_error(conn, :not_found, "No such permission_group")
506 %{assigns: %{user: admin}} = conn,
508 "permission_group" => permission_group,
509 "nickname" => nickname
512 when permission_group in ["moderator", "admin"] do
513 fields = %{:"is_#{permission_group}" => false}
517 |> User.get_cached_by_nickname()
518 |> User.admin_api_update(fields)
520 ModerationLog.insert_log(%{
524 permission: permission_group
530 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
531 render_error(conn, :forbidden, "You can't revoke your own admin status.")
534 def relay_list(conn, _params) do
535 with {:ok, list} <- Relay.list() do
536 json(conn, %{relays: list})
544 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
545 with {:ok, _message} <- Relay.follow(target) do
546 ModerationLog.insert_log(%{
547 action: "relay_follow",
561 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
562 with {:ok, _message} <- Relay.unfollow(target) do
563 ModerationLog.insert_log(%{
564 action: "relay_unfollow",
578 @doc "Sends registration invite via email"
579 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
581 Config.get([:instance, :invites_enabled]) &&
582 !Config.get([:instance, :registrations_open]),
583 {:ok, invite_token} <- UserInviteToken.create_invite(),
585 Pleroma.Emails.UserEmail.user_invitation_email(
591 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
592 json_response(conn, :no_content, "")
596 @doc "Create an account registration invite token"
597 def create_invite_token(conn, params) do
601 if params["max_use"],
602 do: Map.put(opts, :max_use, params["max_use"]),
606 if params["expires_at"],
607 do: Map.put(opts, :expires_at, params["expires_at"]),
610 {:ok, invite} = UserInviteToken.create_invite(opts)
612 json(conn, AccountView.render("invite.json", %{invite: invite}))
615 @doc "Get list of created invites"
616 def invites(conn, _params) do
617 invites = UserInviteToken.list_invites()
620 |> put_view(AccountView)
621 |> render("invites.json", %{invites: invites})
624 @doc "Revokes invite by token"
625 def revoke_invite(conn, %{"token" => token}) do
626 with {:ok, invite} <- UserInviteToken.find_by_token(token),
627 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
629 |> put_view(AccountView)
630 |> render("invite.json", %{invite: updated_invite})
632 nil -> {:error, :not_found}
636 @doc "Get a password reset token (base64 string) for given nickname"
637 def get_password_reset(conn, %{"nickname" => nickname}) do
638 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
639 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
644 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
648 @doc "Force password reset for a given user"
649 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
650 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
652 Enum.each(users, &User.force_password_reset_async/1)
654 ModerationLog.insert_log(%{
657 action: "force_password_reset"
660 json_response(conn, :no_content, "")
663 def list_reports(conn, params) do
664 {page, page_size} = page_params(params)
666 reports = Utils.get_reports(params, page, page_size)
669 |> put_view(ReportView)
670 |> render("index.json", %{reports: reports})
673 def list_grouped_reports(conn, _params) do
674 statuses = Utils.get_reported_activities()
677 |> put_view(ReportView)
678 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses))
681 def report_show(conn, %{"id" => id}) do
682 with %Activity{} = report <- Activity.get_by_id(id) do
684 |> put_view(ReportView)
685 |> render("show.json", Report.extract_report_info(report))
687 _ -> {:error, :not_found}
691 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
694 |> Enum.map(fn report ->
695 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
696 ModerationLog.insert_log(%{
697 action: "report_update",
704 {:error, message} -> %{id: report["id"], error: message}
708 case Enum.any?(result, &Map.has_key?(&1, :error)) do
709 true -> json_response(conn, :bad_request, result)
710 false -> json_response(conn, :no_content, "")
714 def report_notes_create(%{assigns: %{user: user}} = conn, %{
718 with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
719 ModerationLog.insert_log(%{
720 action: "report_note",
722 subject: Activity.get_by_id(report_id),
726 json_response(conn, :no_content, "")
728 _ -> json_response(conn, :bad_request, "")
732 def report_notes_delete(%{assigns: %{user: user}} = conn, %{
734 "report_id" => report_id
736 with {:ok, note} <- ReportNote.destroy(note_id) do
737 ModerationLog.insert_log(%{
738 action: "report_note_delete",
740 subject: Activity.get_by_id(report_id),
744 json_response(conn, :no_content, "")
746 _ -> json_response(conn, :bad_request, "")
750 def list_statuses(%{assigns: %{user: _admin}} = conn, params) do
751 godmode = params["godmode"] == "true" || params["godmode"] == true
752 local_only = params["local_only"] == "true" || params["local_only"] == true
753 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
754 {page, page_size} = page_params(params)
757 ActivityPub.fetch_statuses(nil, %{
758 "godmode" => godmode,
759 "local_only" => local_only,
760 "limit" => page_size,
761 "offset" => (page - 1) * page_size,
762 "exclude_reblogs" => !with_reblogs && "true"
766 |> put_view(Pleroma.Web.AdminAPI.StatusView)
767 |> render("index.json", %{activities: activities, as: :activity})
770 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
771 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
772 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
774 ModerationLog.insert_log(%{
775 action: "status_update",
778 sensitive: sensitive,
779 visibility: params["visibility"]
783 |> put_view(StatusView)
784 |> render("show.json", %{activity: activity})
788 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
789 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
790 ModerationLog.insert_log(%{
791 action: "status_delete",
800 def list_log(conn, params) do
801 {page, page_size} = page_params(params)
804 ModerationLog.get_all(%{
806 page_size: page_size,
807 start_date: params["start_date"],
808 end_date: params["end_date"],
809 user_id: params["user_id"],
810 search: params["search"]
814 |> put_view(ModerationLogView)
815 |> render("index.json", %{log: log})
818 def config_descriptions(conn, _params) do
820 |> Plug.Conn.put_resp_content_type("application/json")
821 |> Plug.Conn.send_resp(200, @descriptions_json)
824 def config_show(conn, %{"only_db" => true}) do
825 with :ok <- configurable_from_database(conn) do
826 configs = Pleroma.Repo.all(ConfigDB)
829 |> put_view(ConfigView)
830 |> render("index.json", %{configs: configs})
834 def config_show(conn, _params) do
835 with :ok <- configurable_from_database(conn) do
836 configs = ConfigDB.get_all_as_keyword()
839 Config.Holder.default_config()
840 |> ConfigDB.merge(configs)
841 |> Enum.map(fn {group, values} ->
842 Enum.map(values, fn {key, value} ->
844 if configs[group][key] do
845 ConfigDB.get_db_keys(configs[group][key], key)
848 db_value = configs[group][key]
851 if !is_nil(db_value) and Keyword.keyword?(db_value) and
852 ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
853 ConfigDB.merge_group(group, key, value, db_value)
859 group: ConfigDB.convert(group),
860 key: ConfigDB.convert(key),
861 value: ConfigDB.convert(merged_value)
864 if db, do: Map.put(setting, :db, db), else: setting
869 response = %{configs: merged}
872 if Restarter.Pleroma.need_reboot?() do
873 Map.put(response, :need_reboot, true)
882 def config_update(conn, %{"configs" => configs}) do
883 with :ok <- configurable_from_database(conn) do
886 %{"group" => group, "key" => key, "delete" => true} = params ->
887 ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]})
889 %{"group" => group, "key" => key, "value" => value} ->
890 ConfigDB.update_or_create(%{group: group, key: key, value: value})
892 |> Enum.split_with(fn result -> elem(result, 0) == :error end)
896 |> Enum.map(fn {:ok, config} ->
897 Map.put(config, :db, ConfigDB.get_db_keys(config))
899 |> Enum.split_with(fn config ->
900 Ecto.get_meta(config, :state) == :deleted
903 Config.TransferTask.load_and_update_env(deleted, false)
906 Restarter.Pleroma.need_reboot?() ||
907 Enum.any?(updated, fn config ->
908 group = ConfigDB.from_string(config.group)
909 key = ConfigDB.from_string(config.key)
910 value = ConfigDB.from_binary(config.value)
911 Config.TransferTask.pleroma_need_restart?(group, key, value)
914 response = %{configs: updated}
918 Restarter.Pleroma.need_reboot()
919 Map.put(response, :need_reboot, need_reboot?)
925 |> put_view(ConfigView)
926 |> render("index.json", response)
930 def restart(conn, _params) do
931 with :ok <- configurable_from_database(conn) do
932 Restarter.Pleroma.restart(Config.get(:env), 50)
938 defp configurable_from_database(conn) do
939 if Config.get(:configurable_from_database) do
944 {:error, "To use this endpoint you need to enable configuration from database."}
949 def reload_emoji(conn, _params) do
950 Pleroma.Emoji.reload()
955 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
956 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
958 User.toggle_confirmation(users)
960 ModerationLog.insert_log(%{
963 action: "confirm_email"
969 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
970 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
972 User.try_send_confirmation_email(users)
974 ModerationLog.insert_log(%{
977 action: "resend_confirmation_email"
983 def oauth_app_create(conn, params) do
986 Map.put(params, "client_name", params["name"])
992 case App.create(params) do
994 AppView.render("show.json", %{app: app, admin: true})
996 {:error, changeset} ->
997 App.errors(changeset)
1003 def oauth_app_update(conn, params) do
1005 if params["name"] do
1006 Map.put(params, "client_name", params["name"])
1011 with {:ok, app} <- App.update(params) do
1012 json(conn, AppView.render("show.json", %{app: app, admin: true}))
1014 {:error, changeset} ->
1015 json(conn, App.errors(changeset))
1018 json_response(conn, :bad_request, "")
1022 def oauth_app_list(conn, params) do
1023 {page, page_size} = page_params(params)
1026 client_name: params["name"],
1027 client_id: params["client_id"],
1029 page_size: page_size
1033 if Map.has_key?(params, "trusted") do
1034 Map.put(search_params, :trusted, params["trusted"])
1039 with {:ok, apps, count} <- App.search(search_params) do
1042 AppView.render("index.json",
1045 page_size: page_size,
1052 def oauth_app_delete(conn, params) do
1053 with {:ok, _app} <- App.destroy(params["id"]) do
1054 json_response(conn, :no_content, "")
1056 _ -> json_response(conn, :bad_request, "")
1060 def stats(conn, _) do
1061 count = Stats.get_status_visibility_count()
1064 |> json(%{"status_visibility" => count})
1067 def errors(conn, {:error, :not_found}) do
1069 |> put_status(:not_found)
1070 |> json(dgettext("errors", "Not found"))
1073 def errors(conn, {:error, reason}) do
1075 |> put_status(:bad_request)
1079 def errors(conn, {:param_cast, _}) do
1081 |> put_status(:bad_request)
1082 |> json(dgettext("errors", "Invalid parameters"))
1085 def errors(conn, _) do
1087 |> put_status(:internal_server_error)
1088 |> json(dgettext("errors", "Something went wrong"))
1091 defp page_params(params) do
1092 {get_page(params["page"]), get_page_size(params["page_size"])}
1095 defp get_page(page_string) when is_nil(page_string), do: 1
1097 defp get_page(page_string) do
1098 case Integer.parse(page_string) do
1104 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
1106 defp get_page_size(page_size_string) do
1107 case Integer.parse(page_size_string) do
1108 {page_size, _} -> page_size
1109 :error -> @users_page_size